Autopsy  4.9.1
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-2018 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.BlackboardArtifact;
41 import org.sleuthkit.datamodel.BlackboardAttribute;
42 import org.sleuthkit.datamodel.TskDataException;
43 
51 
52  private static final Logger logger = Logger.getLogger(DataSourceIntegrityIngestModule.class.getName());
53  private static final long DEFAULT_CHUNK_SIZE = 32 * 1024;
55 
56  private final boolean computeHashes;
57  private final boolean verifyHashes;
58 
59  private final List<HashData> hashDataList = new ArrayList<>();
60 
62 
63  DataSourceIntegrityIngestModule(DataSourceIntegrityIngestSettings settings) {
64  computeHashes = settings.shouldComputeHashes();
65  verifyHashes = settings.shouldVerifyHashes();
66  }
67 
68  @NbBundle.Messages({
69  "DataSourceIntegrityIngestModule.startup.noCheckboxesSelected=At least one of the checkboxes must be selected"
70  })
71  @Override
72  public void startUp(IngestJobContext context) throws IngestModuleException {
73  this.context = context;
74 
75  // It's an error if the module is run without either option selected
76  if (!(computeHashes || verifyHashes)) {
77  throw new IngestModuleException(Bundle.DataSourceIntegrityIngestModule_startup_noCheckboxesSelected());
78  }
79  }
80 
81  @NbBundle.Messages({
82  "# {0} - imageName",
83  "DataSourceIntegrityIngestModule.process.skipCompute=Not computing new hashes for {0} since the option was disabled",
84  "# {0} - imageName",
85  "DataSourceIntegrityIngestModule.process.skipVerify=Not verifying existing hashes for {0} since the option was disabled",
86  "# {0} - hashName",
87  "DataSourceIntegrityIngestModule.process.hashAlgorithmError=Error creating message digest for {0} algorithm",
88  "# {0} - hashName",
89  "DataSourceIntegrityIngestModule.process.hashMatch=<li>{0} hash verified </li>",
90  "# {0} - hashName",
91  "DataSourceIntegrityIngestModule.process.hashNonMatch=<li>{0} hash not verified </li>",
92  "# {0} - calculatedHashValue",
93  "# {1} - storedHashValue",
94  "DataSourceIntegrityIngestModule.process.hashList=<ul><li>Calculated hash: {0} </li><li>Stored hash: {1} </li></ul>",
95  "# {0} - hashName",
96  "# {1} - calculatedHashValue",
97  "DataSourceIntegrityIngestModule.process.calcHashWithType=<li>Calculated {0} hash: {1} </li>",
98  "# {0} - imageName",
99  "DataSourceIntegrityIngestModule.process.calculateHashDone=<p>Data Source Hash Calculation Results for {0} </p>",
100  "DataSourceIntegrityIngestModule.process.hashesCalculated= hashes calculated",
101  "# {0} - imageName",
102  "DataSourceIntegrityIngestModule.process.errorSavingHashes= Error saving hashes for image {0} to the database",
103  "# {0} - imageName",
104  "DataSourceIntegrityIngestModule.process.errorLoadingHashes= Error loading hashes for image {0} from the database",
105  "# {0} - hashAlgorithm",
106  "# {1} - calculatedHashValue",
107  "# {2} - storedHashValue",
108  "DataSourceIntegrityIngestModule.process.hashFailedForArtifact={0} hash verification failed:\n Calculated hash: {1}\n Stored hash: {2}\n",
109  "# {0} - imageName",
110  "DataSourceIntegrityIngestModule.process.verificationSuccess=Integrity of {0} verified",
111  "# {0} - imageName",
112  "DataSourceIntegrityIngestModule.process.verificationFailure={0} failed integrity verification",
113  })
114  @Override
115  public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
116  String imgName = dataSource.getName();
117 
118  // Skip non-images
119  if (!(dataSource instanceof Image)) {
120  logger.log(Level.INFO, "Skipping non-image {0}", imgName); //NON-NLS
122  NbBundle.getMessage(this.getClass(),
123  "DataSourceIntegrityIngestModule.process.skipNonEwf",
124  imgName)));
125  return ProcessResult.OK;
126  }
127  Image img = (Image) dataSource;
128 
129  // Make sure the image size we have is not zero
130  long size = img.getSize();
131  if (size == 0) {
132  logger.log(Level.WARNING, "Size of image {0} was 0 when queried.", imgName); //NON-NLS
134  NbBundle.getMessage(this.getClass(),
135  "DataSourceIntegrityIngestModule.process.errGetSizeOfImg",
136  imgName)));
137  }
138 
139  // Determine which mode we're in.
140  // - If there are any preset hashes, then we'll verify them (assuming the verify checkbox is selected)
141  // - Otherwise we'll calculate and store all three hashes (assuming the compute checkbox is selected)
142 
143  // First get a list of all stored hash types
144  try {
145  if (img.getMd5() != null && ! img.getMd5().isEmpty()) {
146  hashDataList.add(new HashData(HashType.MD5, img.getMd5()));
147  }
148  if (img.getSha1() != null && ! img.getSha1().isEmpty()) {
149  hashDataList.add(new HashData(HashType.SHA1, img.getSha1()));
150  }
151  if (img.getSha256() != null && ! img.getSha256().isEmpty()) {
152  hashDataList.add(new HashData(HashType.SHA256, img.getSha256()));
153  }
154  } catch (TskCoreException ex) {
155  String msg = Bundle.DataSourceIntegrityIngestModule_process_errorLoadingHashes(imgName);
157  logger.log(Level.SEVERE, msg, ex);
158  return ProcessResult.ERROR;
159  }
160 
161  // Figure out which mode we should be in
162  Mode mode;
163  if (hashDataList.isEmpty()) {
164  mode = Mode.COMPUTE;
165  } else {
166  mode = Mode.VERIFY;
167  }
168 
169  // If that mode was not enabled by the user, exit
170  if (mode.equals(Mode.COMPUTE) && ! this.computeHashes) {
171  logger.log(Level.INFO, "Not computing hashes for {0} since the option was disabled", imgName); //NON-NLS
173  Bundle.DataSourceIntegrityIngestModule_process_skipCompute(imgName)));
174  return ProcessResult.OK;
175  } else if (mode.equals(Mode.VERIFY) && ! this.verifyHashes) {
176  logger.log(Level.INFO, "Not verifying hashes for {0} since the option was disabled", imgName); //NON-NLS
178  Bundle.DataSourceIntegrityIngestModule_process_skipVerify(imgName)));
179  return ProcessResult.OK;
180  }
181 
182  // If we're in compute mode (i.e., the hash list is empty), add all hash algorithms
183  // to the list.
184  if (mode.equals(Mode.COMPUTE)) {
185  for(HashType type : HashType.values()) {
186  hashDataList.add(new HashData(type, ""));
187  }
188  }
189 
190  // Set up the digests
191  for (HashData hashData:hashDataList) {
192  try {
193  hashData.digest = MessageDigest.getInstance(hashData.type.getName());
194  } catch (NoSuchAlgorithmException ex) {
195  String msg = Bundle.DataSourceIntegrityIngestModule_process_hashAlgorithmError(hashData.type.getName());
197  logger.log(Level.SEVERE, msg, ex);
198  return ProcessResult.ERROR;
199  }
200  }
201 
202  // Libewf uses a chunk size of 64 times the sector size, which is the
203  // motivation for using it here. For other images it shouldn't matter,
204  // so they can use this chunk size as well.
205  long chunkSize = 64 * img.getSsize();
206  chunkSize = (chunkSize == 0) ? DEFAULT_CHUNK_SIZE : chunkSize;
207 
208  // Casting to double to capture decimals
209  int totalChunks = (int) Math.ceil((double) size / (double) chunkSize);
210  logger.log(Level.INFO, "Total chunks = {0}", totalChunks); //NON-NLS
211 
212  if (mode.equals(Mode.VERIFY)) {
213  logger.log(Level.INFO, "Starting hash verification of {0}", img.getName()); //NON-NLS
214  } else {
215  logger.log(Level.INFO, "Starting hash calculation for {0}", img.getName()); //NON-NLS
216  }
218  NbBundle.getMessage(this.getClass(),
219  "DataSourceIntegrityIngestModule.process.startingImg",
220  imgName)));
221 
222  // Set up the progress bar
223  statusHelper.switchToDeterminate(totalChunks);
224 
225  // Read in byte size chunks and update the hash value with the data.
226  byte[] data = new byte[(int) chunkSize];
227  int read;
228  for (int i = 0; i < totalChunks; i++) {
229  if (context.dataSourceIngestIsCancelled()) {
230  return ProcessResult.OK;
231  }
232  try {
233  read = img.read(data, i * chunkSize, chunkSize);
234  } catch (TskCoreException ex) {
235  String msg = NbBundle.getMessage(this.getClass(),
236  "DataSourceIntegrityIngestModule.process.errReadImgAtChunk", imgName, i);
238  logger.log(Level.SEVERE, msg, ex);
239  return ProcessResult.ERROR;
240  }
241 
242  // Only update with the read bytes.
243  if (read == chunkSize) {
244  for (HashData struct:hashDataList) {
245  struct.digest.update(data);
246  }
247  } else {
248  byte[] subData = Arrays.copyOfRange(data, 0, read);
249  for (HashData struct:hashDataList) {
250  struct.digest.update(subData);
251  }
252  }
253  statusHelper.progress(i);
254  }
255 
256  // Produce the final hashes
257  for(HashData hashData:hashDataList) {
258  hashData.calculatedHash = DatatypeConverter.printHexBinary(hashData.digest.digest()).toLowerCase();
259  logger.log(Level.INFO, "Hash calculated from {0}: {1}", new Object[]{imgName, hashData.calculatedHash}); //NON-NLS
260  }
261 
262  if (mode.equals(Mode.VERIFY)) {
263  // Check that each hash matches
264  boolean verified = true;
265  String detailedResults = NbBundle
266  .getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verifyResultsHeader", imgName);
267  String hashResults = "";
268  String artifactComment = "";
269 
270  for (HashData hashData:hashDataList) {
271  if (hashData.storedHash.equals(hashData.calculatedHash)) {
272  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashMatch(hashData.type.name) + " ";
273  } else {
274  verified = false;
275  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashNonMatch(hashData.type.name) + " ";
276  artifactComment += Bundle.DataSourceIntegrityIngestModule_process_hashFailedForArtifact(hashData.type.name,
277  hashData.calculatedHash, hashData.storedHash) + " ";
278  }
279  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashList(hashData.calculatedHash, hashData.storedHash);
280  }
281 
282  String verificationResultStr;
283  String messageResultStr;
284  MessageType messageType;
285  if (verified) {
286  messageType = MessageType.INFO;
287  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verified");
288  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationSuccess(imgName);
289  } else {
290  messageType = MessageType.WARNING;
291  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.notVerified");
292  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationFailure(imgName);
293  }
294 
295  detailedResults += NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.resultLi", verificationResultStr);
296  detailedResults += hashResults;
297 
298  if (!verified) {
299  try {
300  BlackboardArtifact verificationFailedArtifact = Case.getCurrentCase().getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED, img.getId());
301  verificationFailedArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
302  DataSourceIntegrityModuleFactory.getModuleName(), artifactComment));
303  } catch (TskCoreException ex) {
304  logger.log(Level.SEVERE, "Error creating 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 }
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-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.