/*
* Autopsy Forensic Browser
*
* Copyright 2013-2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.e01verify;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import javax.xml.bind.DatatypeConverter;
import org.openide.util.NbBundle;
import org.python.bouncycastle.util.Arrays;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.openide.util.NbBundle;
/**
* Data source ingest module that verifies the integrity of an Expert Witness
* Format (EWF) E01 image file by generating a hash of the file and comparing it
* to the value stored in the image.
*/
@NbBundle.Messages({
"UnableToCalculateHashes=Unable to calculate MD5 hashes."
})
public class E01VerifyIngestModule implements DataSourceIngestModule {
private static final Logger logger = Logger.getLogger(E01VerifyIngestModule.class.getName());
private static final long DEFAULT_CHUNK_SIZE = 32 * 1024;
private static final IngestServices services = IngestServices.getInstance();
private MessageDigest messageDigest;
private boolean verified = false;
private String calculatedHash = "";
private String storedHash = "";
private IngestJobContext context;
E01VerifyIngestModule() {
}
@Override
public void startUp(IngestJobContext context) throws IngestModuleException {
this.context = context;
verified = false;
storedHash = "";
calculatedHash = "";
try {
messageDigest = MessageDigest.getInstance("MD5"); //NON-NLS
} catch (NoSuchAlgorithmException ex) {
throw new IngestModuleException(Bundle.UnableToCalculateHashes(), ex);
}
}
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
String imgName = dataSource.getName();
// Skip non-images
if (!(dataSource instanceof Image)) {
logger.log(Level.INFO, "Skipping non-image {0}", imgName); //NON-NLS
services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.skipNonEwf",
imgName)));
return ProcessResult.OK;
}
Image img = (Image) dataSource;
// Skip images that are not E01
if (img.getType() != TskData.TSK_IMG_TYPE_ENUM.TSK_IMG_TYPE_EWF_EWF) {
logger.log(Level.INFO, "Skipping non-ewf image {0}", imgName); //NON-NLS
services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.skipNonEwf",
imgName)));
return ProcessResult.OK;
}
// Report an error for null or empty MD5
if ((img.getMd5() == null) || img.getMd5().isEmpty()) {
services.postMessage(IngestMessage.createMessage(MessageType.ERROR, E01VerifierModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.noStoredHash",
imgName)));
return ProcessResult.ERROR;
}
storedHash = img.getMd5().toLowerCase();
logger.log(Level.INFO, "Hash value stored in {0}: {1}", new Object[]{imgName, storedHash}); //NON-NLS
logger.log(Level.INFO, "Starting hash verification of {0}", img.getName()); //NON-NLS
services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.startingImg",
imgName)));
long size = img.getSize();
if (size == 0) {
logger.log(Level.WARNING, "Size of image {0} was 0 when queried.", imgName); //NON-NLS
services.postMessage(IngestMessage.createMessage(MessageType.ERROR, E01VerifierModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.errGetSizeOfImg",
imgName)));
}
// Libewf uses a sector size of 64 times the sector size, which is the
// motivation for using it here.
long chunkSize = 64 * img.getSsize();
chunkSize = (chunkSize == 0) ? DEFAULT_CHUNK_SIZE : chunkSize;
// Casting to double to capture decimals
int totalChunks = (int) Math.ceil((double) size / (double) chunkSize);
logger.log(Level.INFO, "Total chunks = {0}", totalChunks); //NON-NLS
int read;
byte[] data = new byte[(int) chunkSize];
statusHelper.switchToDeterminate(totalChunks);
// Read in byte size chunks and update the hash value with the data.
for (int i = 0; i < totalChunks; i++) {
if (context.dataSourceIngestIsCancelled()) {
return ProcessResult.OK;
}
try {
read = img.read(data, i * chunkSize, chunkSize);
} catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(),
"EwfVerifyIngestModule.process.errReadImgAtChunk", imgName, i);
services.postMessage(IngestMessage.createMessage(MessageType.ERROR, E01VerifierModuleFactory.getModuleName(), msg));
logger.log(Level.SEVERE, msg, ex);
return ProcessResult.ERROR;
}
// Only update with the read bytes.
if (read == chunkSize) {
messageDigest.update(data);
} else {
byte[] subData = Arrays.copyOfRange(data, 0, read);
messageDigest.update(subData);
}
statusHelper.progress(i);
}
// Finish generating the hash and get it as a string value
calculatedHash = DatatypeConverter.printHexBinary(messageDigest.digest()).toLowerCase();
verified = calculatedHash.equals(storedHash);
logger.log(Level.INFO, "Hash calculated from {0}: {1}", new Object[]{imgName, calculatedHash}); //NON-NLS
logger.log(Level.INFO, "complete() {0}", E01VerifierModuleFactory.getModuleName()); //NON-NLS
String msg;
if (verified) {
msg = NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.verified");
} else {
msg = NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.notVerified");
}
String extra = NbBundle
.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.verifyResultsHeader", imgName);
extra += NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.resultLi", msg);
extra += NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.calcHashLi", calculatedHash);
extra += NbBundle.getMessage(this.getClass(), "EwfVerifyIngestModule.shutDown.storedHashLi", storedHash);
services.postMessage(IngestMessage.createMessage(MessageType.INFO, E01VerifierModuleFactory.getModuleName(), imgName + msg, extra));
logger.log(Level.INFO, "{0}{1}", new Object[]{imgName, msg});
return ProcessResult.OK;
}
}