Autopsy  4.20.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-2021 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 java.util.Arrays;
27 import org.apache.commons.codec.binary.Hex;
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.Score;
44 import org.sleuthkit.datamodel.TskDataException;
45 
53 
54  private static final Logger logger = Logger.getLogger(DataSourceIntegrityIngestModule.class.getName());
55  private static final long DEFAULT_CHUNK_SIZE = 32 * 1024;
57 
58  private final boolean computeHashes;
59  private final boolean verifyHashes;
60 
61  private final List<HashData> hashDataList = new ArrayList<>();
62 
64 
65  DataSourceIntegrityIngestModule(DataSourceIntegrityIngestSettings settings) {
66  computeHashes = settings.shouldComputeHashes();
67  verifyHashes = settings.shouldVerifyHashes();
68  }
69 
70  @NbBundle.Messages({
71  "DataSourceIntegrityIngestModule.startup.noCheckboxesSelected=At least one of the checkboxes must be selected"
72  })
73  @Override
74  public void startUp(IngestJobContext context) throws IngestModuleException {
75  this.context = context;
76 
77  // It's an error if the module is run without either option selected
78  if (!(computeHashes || verifyHashes)) {
79  throw new IngestModuleException(Bundle.DataSourceIntegrityIngestModule_startup_noCheckboxesSelected());
80  }
81  }
82 
83  @NbBundle.Messages({
84  "# {0} - imageName",
85  "DataSourceIntegrityIngestModule.process.skipCompute=Not computing new hashes for {0} since the option was disabled",
86  "# {0} - imageName",
87  "DataSourceIntegrityIngestModule.process.skipVerify=Not verifying existing hashes for {0} since the option was disabled",
88  "# {0} - hashName",
89  "DataSourceIntegrityIngestModule.process.hashAlgorithmError=Error creating message digest for {0} algorithm",
90  "# {0} - hashName",
91  "DataSourceIntegrityIngestModule.process.hashMatch=<li>{0} hash verified </li>",
92  "# {0} - hashName",
93  "DataSourceIntegrityIngestModule.process.hashNonMatch=<li>{0} hash not verified </li>",
94  "# {0} - calculatedHashValue",
95  "# {1} - storedHashValue",
96  "DataSourceIntegrityIngestModule.process.hashList=<ul><li>Calculated hash: {0} </li><li>Stored hash: {1} </li></ul>",
97  "# {0} - hashName",
98  "# {1} - calculatedHashValue",
99  "DataSourceIntegrityIngestModule.process.calcHashWithType=<li>Calculated {0} hash: {1} </li>",
100  "# {0} - imageName",
101  "DataSourceIntegrityIngestModule.process.calculateHashDone=<p>Data Source Hash Calculation Results for {0} </p>",
102  "DataSourceIntegrityIngestModule.process.hashesCalculated= hashes calculated",
103  "# {0} - imageName",
104  "DataSourceIntegrityIngestModule.process.errorSavingHashes= Error saving hashes for image {0} to the database",
105  "# {0} - imageName",
106  "DataSourceIntegrityIngestModule.process.errorLoadingHashes= Error loading hashes for image {0} from the database",
107  "# {0} - hashAlgorithm",
108  "# {1} - calculatedHashValue",
109  "# {2} - storedHashValue",
110  "DataSourceIntegrityIngestModule.process.hashFailedForArtifact={0} hash verification failed:\n Calculated hash: {1}\n Stored hash: {2}\n",
111  "# {0} - imageName",
112  "DataSourceIntegrityIngestModule.process.verificationSuccess=Integrity of {0} verified",
113  "# {0} - imageName",
114  "DataSourceIntegrityIngestModule.process.verificationFailure={0} failed integrity verification",})
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  // First get a list of all stored hash types
140  try {
141  if (img.getMd5() != null && !img.getMd5().isEmpty()) {
142  hashDataList.add(new HashData(HashType.MD5, img.getMd5()));
143  }
144  if (img.getSha1() != null && !img.getSha1().isEmpty()) {
145  hashDataList.add(new HashData(HashType.SHA1, img.getSha1()));
146  }
147  if (img.getSha256() != null && !img.getSha256().isEmpty()) {
148  hashDataList.add(new HashData(HashType.SHA256, img.getSha256()));
149  }
150  } catch (TskCoreException ex) {
151  String msg = Bundle.DataSourceIntegrityIngestModule_process_errorLoadingHashes(imgName);
153  logger.log(Level.SEVERE, msg, ex);
154  return ProcessResult.ERROR;
155  }
156 
157  // Figure out which mode we should be in
158  Mode mode;
159  if (hashDataList.isEmpty()) {
160  mode = Mode.COMPUTE;
161  } else {
162  mode = Mode.VERIFY;
163  }
164 
165  // If that mode was not enabled by the user, exit
166  if (mode.equals(Mode.COMPUTE) && !this.computeHashes) {
167  logger.log(Level.INFO, "Not computing hashes for {0} since the option was disabled", imgName); //NON-NLS
169  Bundle.DataSourceIntegrityIngestModule_process_skipCompute(imgName)));
170  return ProcessResult.OK;
171  } else if (mode.equals(Mode.VERIFY) && !this.verifyHashes) {
172  logger.log(Level.INFO, "Not verifying hashes for {0} since the option was disabled", imgName); //NON-NLS
174  Bundle.DataSourceIntegrityIngestModule_process_skipVerify(imgName)));
175  return ProcessResult.OK;
176  }
177 
178  // If we're in compute mode (i.e., the hash list is empty), add all hash algorithms
179  // to the list.
180  if (mode.equals(Mode.COMPUTE)) {
181  for (HashType type : HashType.values()) {
182  hashDataList.add(new HashData(type, ""));
183  }
184  }
185 
186  // Set up the digests
187  for (HashData hashData : hashDataList) {
188  try {
189  hashData.digest = MessageDigest.getInstance(hashData.type.getName());
190  } catch (NoSuchAlgorithmException ex) {
191  String msg = Bundle.DataSourceIntegrityIngestModule_process_hashAlgorithmError(hashData.type.getName());
193  logger.log(Level.SEVERE, msg, ex);
194  return ProcessResult.ERROR;
195  }
196  }
197 
198  // Libewf uses a chunk size of 64 times the sector size, which is the
199  // motivation for using it here. For other images it shouldn't matter,
200  // so they can use this chunk size as well.
201  long chunkSize = 64 * img.getSsize();
202  chunkSize = (chunkSize == 0) ? DEFAULT_CHUNK_SIZE : chunkSize;
203 
204  // Casting to double to capture decimals
205  int totalChunks = (int) Math.ceil((double) size / (double) chunkSize);
206  logger.log(Level.INFO, "Total chunks = {0}", totalChunks); //NON-NLS
207 
208  if (mode.equals(Mode.VERIFY)) {
209  logger.log(Level.INFO, "Starting hash verification of {0}", img.getName()); //NON-NLS
210  } else {
211  logger.log(Level.INFO, "Starting hash calculation for {0}", img.getName()); //NON-NLS
212  }
214  NbBundle.getMessage(this.getClass(),
215  "DataSourceIntegrityIngestModule.process.startingImg",
216  imgName)));
217 
218  // Set up the progress bar
219  statusHelper.switchToDeterminate(totalChunks);
220 
221  // Read in byte size chunks and update the hash value with the data.
222  byte[] data = new byte[(int) chunkSize];
223  int read;
224  for (int i = 0; i < totalChunks; i++) {
225  if (context.dataSourceIngestIsCancelled()) {
226  return ProcessResult.OK;
227  }
228  try {
229  read = img.read(data, i * chunkSize, chunkSize);
230  } catch (TskCoreException ex) {
231  String msg = NbBundle.getMessage(this.getClass(),
232  "DataSourceIntegrityIngestModule.process.errReadImgAtChunk", imgName, i);
234  logger.log(Level.SEVERE, msg, ex);
235  return ProcessResult.ERROR;
236  }
237 
238  // Only update with the read bytes.
239  if (read == chunkSize) {
240  for (HashData struct : hashDataList) {
241  struct.digest.update(data);
242  }
243  } else {
244  byte[] subData = Arrays.copyOfRange(data, 0, read);
245  for (HashData struct : hashDataList) {
246  struct.digest.update(subData);
247  }
248  }
249  statusHelper.progress(i);
250  }
251 
252  // Produce the final hashes
253  for(HashData hashData: hashDataList) {
254  hashData.calculatedHash = Hex.encodeHexString(hashData.digest.digest()).toLowerCase();
255  logger.log(Level.INFO, "Hash calculated from {0}: {1}", new Object[]{imgName, hashData.calculatedHash}); //NON-NLS
256  }
257 
258  if (mode.equals(Mode.VERIFY)) {
259  // Check that each hash matches
260  boolean verified = true;
261  String detailedResults = NbBundle
262  .getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verifyResultsHeader", imgName);
263  String hashResults = "";
264  String artifactComment = "";
265 
266  for (HashData hashData : hashDataList) {
267  if (hashData.storedHash.equals(hashData.calculatedHash)) {
268  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashMatch(hashData.type.name) + " ";
269  } else {
270  verified = false;
271  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashNonMatch(hashData.type.name) + " ";
272  artifactComment += Bundle.DataSourceIntegrityIngestModule_process_hashFailedForArtifact(hashData.type.name,
273  hashData.calculatedHash, hashData.storedHash) + " ";
274  }
275  hashResults += Bundle.DataSourceIntegrityIngestModule_process_hashList(hashData.calculatedHash, hashData.storedHash);
276  }
277 
278  String verificationResultStr;
279  String messageResultStr;
280  MessageType messageType;
281  if (verified) {
282  messageType = MessageType.INFO;
283  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.verified");
284  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationSuccess(imgName);
285  } else {
286  messageType = MessageType.WARNING;
287  verificationResultStr = NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.notVerified");
288  messageResultStr = Bundle.DataSourceIntegrityIngestModule_process_verificationFailure(imgName);
289  }
290 
291  detailedResults += NbBundle.getMessage(this.getClass(), "DataSourceIntegrityIngestModule.shutDown.resultLi", verificationResultStr);
292  detailedResults += hashResults;
293 
294  if (!verified) {
295  try {
296  BlackboardArtifact verificationFailedArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboard().newAnalysisResult(
297  BlackboardArtifact.Type.TSK_VERIFICATION_FAILED,
298  img.getId(), img.getId(),
299  Score.SCORE_NOTABLE,
300  null, null, artifactComment,
301  Arrays.asList(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
302  DataSourceIntegrityModuleFactory.getModuleName(), artifactComment)))
303  .getAnalysisResult();
304 
306  .postArtifact(verificationFailedArtifact, DataSourceIntegrityModuleFactory.getModuleName(), context.getJobId());
307  } catch (TskCoreException ex) {
308  logger.log(Level.SEVERE, "Error creating verification failed artifact", ex);
309  } catch (Blackboard.BlackboardException ex) {
310  logger.log(Level.SEVERE, "Error posting verification failed artifact", ex);
311  }
312  }
313 
314  services.postMessage(IngestMessage.createMessage(messageType, DataSourceIntegrityModuleFactory.getModuleName(),
315  messageResultStr, detailedResults));
316 
317  } else {
318  // Store the hashes in the database and update the image
319  try {
320  String results = Bundle.DataSourceIntegrityIngestModule_process_calculateHashDone(imgName);
321 
322  for (HashData hashData : hashDataList) {
323  switch (hashData.type) {
324  case MD5:
325  try {
326  img.setMD5(hashData.calculatedHash);
327  } catch (TskDataException ex) {
328  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
329  }
330  break;
331  case SHA1:
332  try {
333  img.setSha1(hashData.calculatedHash);
334  } catch (TskDataException ex) {
335  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
336  }
337  break;
338  case SHA256:
339  try {
340  img.setSha256(hashData.calculatedHash);
341  } catch (TskDataException ex) {
342  logger.log(Level.SEVERE, "Error setting calculated hash", ex);
343  }
344  break;
345  default:
346  break;
347  }
348  results += Bundle.DataSourceIntegrityIngestModule_process_calcHashWithType(hashData.type.name, hashData.calculatedHash);
349  }
350 
351  // Write the inbox message
353  imgName + Bundle.DataSourceIntegrityIngestModule_process_hashesCalculated(), results));
354 
355  } catch (TskCoreException ex) {
356  String msg = Bundle.DataSourceIntegrityIngestModule_process_errorSavingHashes(imgName);
358  logger.log(Level.SEVERE, "Error saving hash for image " + imgName + " to database", ex);
359  return ProcessResult.ERROR;
360  }
361  }
362 
363  return ProcessResult.OK;
364  }
365 
369  private enum Mode {
372  }
373 
378  private enum HashType {
379  MD5("MD5"),
380  SHA1("SHA-1"),
381  SHA256("SHA-256");
382 
383  private final String name; // This should be the string expected by MessageDigest
384 
385  HashType(String name) {
386  this.name = name;
387  }
388 
389  String getName() {
390  return name;
391  }
392  }
393 
397  private class HashData {
398 
399  private HashType type;
400  private MessageDigest digest;
401  private String storedHash;
402  private String calculatedHash;
403 
404  HashData(HashType type, String storedHash) {
405  this.type = type;
406  this.storedHash = storedHash;
407  }
408  }
409 }
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-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.