Autopsy 4.22.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-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 */
19package org.sleuthkit.autopsy.modules.dataSourceIntegrity;
20
21import java.security.MessageDigest;
22import java.security.NoSuchAlgorithmException;
23import java.util.ArrayList;
24import java.util.List;
25import java.util.logging.Level;
26import java.util.Arrays;
27import org.apache.commons.codec.binary.Hex;
28import org.sleuthkit.autopsy.coreutils.Logger;
29import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
30import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
31import org.sleuthkit.autopsy.ingest.IngestJobContext;
32import org.sleuthkit.autopsy.ingest.IngestMessage;
33import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
34import org.sleuthkit.autopsy.ingest.IngestServices;
35import org.sleuthkit.datamodel.Content;
36import org.sleuthkit.datamodel.Image;
37import org.sleuthkit.datamodel.TskCoreException;
38import org.openide.util.NbBundle;
39import org.sleuthkit.autopsy.casemodule.Case;
40import org.sleuthkit.datamodel.Blackboard;
41import org.sleuthkit.datamodel.BlackboardArtifact;
42import org.sleuthkit.datamodel.BlackboardAttribute;
43import org.sleuthkit.datamodel.Score;
44import org.sleuthkit.datamodel.TskDataException;
45
52public class DataSourceIntegrityIngestModule implements DataSourceIngestModule {
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
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
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
static synchronized IngestServices getInstance()
ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.