Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DataSourceIntegrityIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2019 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.modules.dataSourceIntegrity;
20 
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.logging.Level;
26 import javax.xml.bind.DatatypeConverter;
27 import java.util.Arrays;
35 import org.sleuthkit.datamodel.Content;
36 import org.sleuthkit.datamodel.Image;
37 import org.sleuthkit.datamodel.TskCoreException;
38 import org.openide.util.NbBundle;
40 import org.sleuthkit.datamodel.Blackboard;
41 import org.sleuthkit.datamodel.BlackboardArtifact;
42 import org.sleuthkit.datamodel.BlackboardAttribute;
43 import org.sleuthkit.datamodel.TskDataException;
44 
52 
53  private static final Logger logger = Logger.getLogger(DataSourceIntegrityIngestModule.class.getName());
54  private static final long DEFAULT_CHUNK_SIZE = 32 * 1024;
56 
57  private final boolean computeHashes;
58  private final boolean verifyHashes;
59 
60  private final List<HashData> hashDataList = new ArrayList<>();
61 
63 
64  DataSourceIntegrityIngestModule(DataSourceIntegrityIngestSettings settings) {
65  computeHashes = settings.shouldComputeHashes();
66  verifyHashes = settings.shouldVerifyHashes();
67  }
68 
69  @NbBundle.Messages({
70  "DataSourceIntegrityIngestModule.startup.noCheckboxesSelected=At least one of the checkboxes must be selected"
71  })
72  @Override
73  public void startUp(IngestJobContext context) throws IngestModuleException {
74  this.context = context;
75 
76  // It's an error if the module is run without either option selected
77  if (!(computeHashes || verifyHashes)) {
78  throw new IngestModuleException(Bundle.DataSourceIntegrityIngestModule_startup_noCheckboxesSelected());
79  }
80  }
81 
82  @NbBundle.Messages({
83  "# {0} - imageName",
84  "DataSourceIntegrityIngestModule.process.skipCompute=Not computing new hashes for {0} since the option was disabled",
85  "# {0} - imageName",
86  "DataSourceIntegrityIngestModule.process.skipVerify=Not verifying existing hashes for {0} since the option was disabled",
87  "# {0} - hashName",
88  "DataSourceIntegrityIngestModule.process.hashAlgorithmError=Error creating message digest for {0} algorithm",
89  "# {0} - hashName",
90  "DataSourceIntegrityIngestModule.process.hashMatch=<li>{0} hash verified </li>",
91  "# {0} - hashName",
92  "DataSourceIntegrityIngestModule.process.hashNonMatch=<li>{0} hash not verified </li>",
93  "# {0} - calculatedHashValue",
94  "# {1} - storedHashValue",
95  "DataSourceIntegrityIngestModule.process.hashList=<ul><li>Calculated hash: {0} </li><li>Stored hash: {1} </li></ul>",
96  "# {0} - hashName",
97  "# {1} - calculatedHashValue",
98  "DataSourceIntegrityIngestModule.process.calcHashWithType=<li>Calculated {0} hash: {1} </li>",
99  "# {0} - imageName",
100  "DataSourceIntegrityIngestModule.process.calculateHashDone=<p>Data Source Hash Calculation Results for {0} </p>",
101  "DataSourceIntegrityIngestModule.process.hashesCalculated= hashes calculated",
102  "# {0} - imageName",
103  "DataSourceIntegrityIngestModule.process.errorSavingHashes= Error saving hashes for image {0} to the database",
104  "# {0} - imageName",
105  "DataSourceIntegrityIngestModule.process.errorLoadingHashes= Error loading hashes for image {0} from the database",
106  "# {0} - hashAlgorithm",
107  "# {1} - calculatedHashValue",
108  "# {2} - storedHashValue",
109  "DataSourceIntegrityIngestModule.process.hashFailedForArtifact={0} hash verification failed:\n Calculated hash: {1}\n Stored hash: {2}\n",
110  "# {0} - imageName",
111  "DataSourceIntegrityIngestModule.process.verificationSuccess=Integrity of {0} verified",
112  "# {0} - imageName",
113  "DataSourceIntegrityIngestModule.process.verificationFailure={0} failed integrity verification",
114  })
115  @Override
116  public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
117  String imgName = dataSource.getName();
118 
119  // Skip non-images
120  if (!(dataSource instanceof Image)) {
121  logger.log(Level.INFO, "Skipping non-image {0}", imgName); //NON-NLS
123  NbBundle.getMessage(this.getClass(),
124  "DataSourceIntegrityIngestModule.process.skipNonEwf",
125  imgName)));
126  return ProcessResult.OK;
127  }
128  Image img = (Image) dataSource;
129 
130  // Get the image size. Log a warning if it is zero.
131  long size = img.getSize();
132  if (size == 0) {
133  logger.log(Level.WARNING, "Size of image {0} was 0 when queried.", imgName); //NON-NLS
134  }
135 
136  // Determine which mode we're in.
137  // - If there are any preset hashes, then we'll verify them (assuming the verify checkbox is selected)
138  // - Otherwise we'll calculate and store all three hashes (assuming the compute checkbox is selected)
139 
140  // First get a list of all stored hash types
141  try {
142  if (img.getMd5() != null && ! img.getMd5().isEmpty()) {
143  hashDataList.add(new HashData(HashType.MD5, img.getMd5()));
144  }
145  if (img.getSha1() != null && ! img.getSha1().isEmpty()) {
146  hashDataList.add(new HashData(HashType.SHA1, img.getSha1()));
147  }
148  if (img.getSha256() != null && ! img.getSha256().isEmpty()) {
149  hashDataList.add(new HashData(HashType.SHA256, img.getSha256()));
150  }
151  } catch (TskCoreException ex) {
152  String msg = Bundle.DataSourceIntegrityIngestModule_process_errorLoadingHashes(imgName);
154  logger.log(Level.SEVERE, msg, ex);
155  return ProcessResult.ERROR;
156  }
157 
158  // Figure out which mode we should be in
159  Mode mode;
160  if (hashDataList.isEmpty()) {
161  mode = Mode.COMPUTE;
162  } else {
163  mode = Mode.VERIFY;
164  }
165 
166  // If that mode was not enabled by the user, exit
167  if (mode.equals(Mode.COMPUTE) && ! this.computeHashes) {
168  logger.log(Level.INFO, "Not computing hashes for {0} since the option was disabled", imgName); //NON-NLS
170  Bundle.DataSourceIntegrityIngestModule_process_skipCompute(imgName)));
171  return ProcessResult.OK;
172  } else if (mode.equals(Mode.VERIFY) && ! this.verifyHashes) {
173  logger.log(Level.INFO, "Not verifying hashes for {0} since the option was disabled", imgName); //NON-NLS
175  Bundle.DataSourceIntegrityIngestModule_process_skipVerify(imgName)));
176  return ProcessResult.OK;
177  }
178 
179  // If we're in compute mode (i.e., the hash list is empty), add all hash algorithms
180  // to the list.
181  if (mode.equals(Mode.COMPUTE)) {
182  for(HashType type : HashType.values()) {
183  hashDataList.add(new HashData(type, ""));
184  }
185  }
186 
187  // Set up the digests
188  for (HashData hashData:hashDataList) {
189  try {
190  hashData.digest = MessageDigest.getInstance(hashData.type.getName());
191  } catch (NoSuchAlgorithmException ex) {
192  String msg = Bundle.DataSourceIntegrityIngestModule_process_hashAlgorithmError(hashData.type.getName());
194  logger.log(Level.SEVERE, msg, ex);
195  return ProcessResult.ERROR;
196  }
197  }
198 
199  // Libewf uses a chunk size of 64 times the sector size, which is the
200  // motivation for using it here. For other images it shouldn't matter,
201  // so they can use this chunk size as well.
202  long chunkSize = 64 * img.getSsize();
203  chunkSize = (chunkSize == 0) ? DEFAULT_CHUNK_SIZE : chunkSize;
204 
205  // Casting to double to capture decimals
206  int totalChunks = (int) Math.ceil((double) size / (double) chunkSize);
207  logger.log(Level.INFO, "Total chunks = {0}", totalChunks); //NON-NLS
208 
209  if (mode.equals(Mode.VERIFY)) {
210  logger.log(Level.INFO, "Starting hash verification of {0}", img.getName()); //NON-NLS
211  } else {
212  logger.log(Level.INFO, "Starting hash calculation for {0}", img.getName()); //NON-NLS
213  }
215  NbBundle.getMessage(this.getClass(),
216  "DataSourceIntegrityIngestModule.process.startingImg",
217  imgName)));
218 
219  // Set up the progress bar
220  statusHelper.switchToDeterminate(totalChunks);
221 
222  // Read in byte size chunks and update the hash value with the data.
223  byte[] data = new byte[(int) chunkSize];
224  int read;
225  for (int i = 0; i < totalChunks; i++) {
226  if (context.dataSourceIngestIsCancelled()) {
227  return ProcessResult.OK;
228  }
229  try {
230  read = img.read(data, i * chunkSize, chunkSize);
231  } catch (TskCoreException ex) {
232  String msg = NbBundle.getMessage(this.getClass(),
233  "DataSourceIntegrityIngestModule.process.errReadImgAtChunk", imgName, i);
235  logger.log(Level.SEVERE, msg, ex);
236  return ProcessResult.ERROR;
237  }
238 
239  // Only update with the read bytes.
240  if (read == chunkSize) {
241  for (HashData struct:hashDataList) {
242  struct.digest.update(data);
243  }
244  } else {
245  byte[] subData = Arrays.copyOfRange(data, 0, read);
246  for (HashData struct:hashDataList) {
247  struct.digest.update(subData);
248  }
249  }
250  statusHelper.progress(i);
251  }
252 
253  // Produce the final hashes
254  for(HashData hashData:hashDataList) {
255  hashData.calculatedHash = DatatypeConverter.printHexBinary(hashData.digest.digest()).toLowerCase();
256  logger.log(Level.INFO, "Hash calculated from {0}: {1}", new Object[]{imgName, hashData.calculatedHash}); //NON-NLS
257  }
258 
259  if (mode.equals(Mode.VERIFY)) {
260  // Check that each hash matches
261  boolean verified = true;
262  String detailedResults = NbBundle
263  .getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verifyResultsHeader", imgName);
264  String hashResults = "";
265  String artifactComment = "";
266 
267  for (HashData hashData:hashDataList) {
268  if (hashData.storedHash.equals(hashData.calculatedHash)) {
269  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashMatch(hashData.type.name) + " ";
270  } else {
271  verified = false;
272  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashNonMatch(hashData.type.name) + " ";
273  artifactComment += Bundle.DataSourceIntegrityIngestModule_process_hashFailedForArtifact(hashData.type.name,
274  hashData.calculatedHash, hashData.storedHash) + " ";
275  }
276  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashList(hashData.calculatedHash, hashData.storedHash);
277  }
278 
279  String verificationResultStr;
280  String messageResultStr;
281  MessageType messageType;
282  if (verified) {
283  messageType = MessageType.INFO;
284  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verified");
285  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationSuccess(imgName);
286  } else {
287  messageType = MessageType.WARNING;
288  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.notVerified");
289  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationFailure(imgName);
290  }
291 
292  detailedResults += NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.resultLi", verificationResultStr);
293  detailedResults += hashResults;
294 
295  if (!verified) {
296  try {
297  BlackboardArtifact verificationFailedArtifact = Case.getCurrentCase().getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED, img.getId());
298  verificationFailedArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
299  DataSourceIntegrityModuleFactory.getModuleName(), artifactComment));
300  Case.getCurrentCase().getServices().getArtifactsBlackboard().postArtifact(verificationFailedArtifact, DataSourceIntegrityModuleFactory.getModuleName());
301  } catch (TskCoreException ex) {
302  logger.log(Level.SEVERE, "Error creating verification failed artifact", ex);
303  } catch (Blackboard.BlackboardException ex) {
304  logger.log(Level.SEVERE, "Error posting verification failed artifact", ex);
305  }
306  }
307 
308  services.postMessage(IngestMessage.createMessage(messageType, DataSourceIntegrityModuleFactory.getModuleName(),
309  messageResultStr, detailedResults));
310 
311  } else {
312  // Store the hashes in the database and update the image
313  try {
314  String results = Bundle.DataSourceIntegrityIngestModule_process_calculateHashDone(imgName);
315 
316  for (HashData hashData:hashDataList) {
317  switch (hashData.type) {
318  case MD5:
319  try {
320  img.setMD5(hashData.calculatedHash);
321  } catch (TskDataException ex) {
322  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
323  }
324  break;
325  case SHA1:
326  try {
327  img.setSha1(hashData.calculatedHash);
328  } catch (TskDataException ex) {
329  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
330  }
331  break;
332  case SHA256:
333  try {
334  img.setSha256(hashData.calculatedHash);
335  } catch (TskDataException ex) {
336  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
337  }
338  break;
339  default:
340  break;
341  }
342  results += Bundle.DataSourceIntegrityIngestModule_process_calcHashWithType(hashData.type.name, hashData.calculatedHash);
343  }
344 
345  // Write the inbox message
347  imgName + Bundle.DataSourceIntegrityIngestModule_process_hashesCalculated(), results));
348 
349  } catch (TskCoreException ex) {
350  String msg = Bundle.DataSourceIntegrityIngestModule_process_errorSavingHashes(imgName);
352  logger.log(Level.SEVERE, "Error saving hash for image " + imgName + " to database", ex);
353  return ProcessResult.ERROR;
354  }
355  }
356 
357  return ProcessResult.OK;
358  }
359 
363  private enum Mode {
366  }
367 
372  private enum HashType {
373  MD5("MD5"),
374  SHA1("SHA-1"),
375  SHA256("SHA-256");
376 
377  private final String name; // This should be the string expected by MessageDigest
378 
379  HashType(String name) {
380  this.name = name;
381  }
382 
383  String getName() {
384  return name;
385  }
386  }
387 
391  private class HashData {
392  private HashType type;
393  private MessageDigest digest;
394  private String storedHash;
395  private String calculatedHash;
396 
397  HashData(HashType type, String storedHash) {
398  this.type = type;
399  this.storedHash = storedHash;
400  }
401  }
402 }
org.sleuthkit.datamodel.Blackboard getArtifactsBlackboard()
Definition: Services.java:86
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
void postMessage(final IngestMessage message)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper)
static synchronized IngestServices getInstance()

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.