2013-10-29 18:43:27 -04:00

373 lines
12 KiB
Java

/*
* Autopsy Forensic Browser
*
* Copyright 2011 - 2013 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.hashdatabase;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitJNI;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
/**
* Hash database representation of NSRL and Known Bad hash databases
* with indexing capability
*
*/
public class HashDb implements Comparable<HashDb> {
enum EVENT {INDEXING_DONE };
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public enum DBType{
NSRL("NSRL"), KNOWN_BAD("Known Bad");
private String displayName;
private DBType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return this.displayName;
}
}
// Suffix added to the end of a database name to get its index file
private static final String INDEX_SUFFIX = ".kdb";
private static final String INDEX_SUFFIX_OLD = "-md5.idx";
private String name;
private List<String> databasePaths; // TODO: Only need a single path, may only need to store handle
private boolean useForIngest;
private boolean showInboxMessages;
private boolean indexing;
private DBType type;
private int handle;
static public HashDb openHashDatabase(String name, String databasePath, boolean useForIngest, boolean showInboxMessages, DBType type) throws TskCoreException {
HashDb database = new HashDb(SleuthkitJNI.openHashDatabase(databasePath), name, Collections.singletonList(databasePath), useForIngest, showInboxMessages, type);
addToXMLFile(database);
return database;
}
static public HashDb createHashDatabase(String name, String databasePath, boolean useForIngest, boolean showInboxMessages, DBType type) throws TskCoreException {
HashDb database = new HashDb(SleuthkitJNI.newHashDatabase(databasePath), name, Collections.singletonList(databasePath), useForIngest, showInboxMessages, type);
addToXMLFile(database);
return database;
}
static private void addToXMLFile(HashDb database) {
HashDbXML xmlFileManager = HashDbXML.getCurrent();
if (database.getDbType() == HashDb.DBType.NSRL) {
xmlFileManager.setNSRLSet(database);
}
else {
xmlFileManager.addKnownBadSet(database);
}
xmlFileManager.save();
}
static public List<HashDb> getUpdateableHashDatabases() {
ArrayList<HashDb> updateableDbs = new ArrayList<>();
List<HashDb> candidateDbs = HashDbXML.getCurrent().getKnownBadSets();
for (HashDb db : candidateDbs) {
if (db.isUpdateable()) {
updateableDbs.add(db);
}
}
return updateableDbs;
}
private HashDb(int handle, String name, List<String> databasePaths, boolean useForIngest, boolean showInboxMessages, DBType type) {
this.handle = handle;
this.name = name;
this.databasePaths = databasePaths;
this.useForIngest = useForIngest;
this.showInboxMessages = showInboxMessages;
this.type = type;
this.indexing = false;
}
public boolean isUpdateable() {
// RJCTODO: Complete this
return true;
}
public void addContentHash(Content content) throws TskCoreException {
// @@@ This only works for AbstractFiles at present.
if (content instanceof AbstractFile) {
AbstractFile file = (AbstractFile)content;
if (null != file.getMd5Hash()) {
SleuthkitJNI.addToHashDatabase(file.getName(), file.getMd5Hash(), "", "", handle);
}
}
}
void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
boolean getUseForIngest() {
return useForIngest;
}
boolean getShowInboxMessages() {
return showInboxMessages;
}
DBType getDbType() {
return type;
}
String getName() {
return name;
}
List<String> getDatabasePaths() {
return databasePaths;
}
void setUseForIngest(boolean useForIngest) {
this.useForIngest = useForIngest;
}
void setShowInboxMessages(boolean showInboxMessages) {
this.showInboxMessages = showInboxMessages;
}
void setName(String name) {
this.name = name;
}
void setDatabasePaths(List<String> databasePaths) {
this.databasePaths = databasePaths;
}
void setDbType(DBType type) {
this.type = type;
}
/**
* Checks if the database exists.
* @return true if a file exists at the database path, else false
*/
boolean databaseExists() {
return databaseFile().exists();
}
/**
* Checks if Sleuth Kit can open the index for the database path.
* @return true if the index was found and opened successfully, else false
*/
boolean indexExists() {
try {
return hasIndex(databasePaths.get(0)); // TODO: support multiple paths
} catch (TskException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Error checking if index exists.", ex);
return false;
}
}
/**
* Gets the database file.
* @return a File initialized with the database path
*/
File databaseFile() {
return new File(databasePaths.get(0)); // TODO: don't support multiple paths
}
/**
* Gets the index file
* @return a File initialized with an index path derived from the database
* path
*/
File indexFile() {
return new File(toIndexPath(databasePaths.get(0))); // TODO: don't support multiple paths
}
/**
* Checks if the index file is older than the database file
* @return true if there is are files at the index path and the database
* path, and the index file has an older modified-time than the database
* file, else false
*/
boolean isOutdated() {
File i = indexFile();
File db = databaseFile();
return i.exists() && db.exists() && isOlderThan(i, db);
}
/**
* Checks if the database is being indexed
*/
boolean isIndexing() {
return indexing;
}
/**
* Returns the status of the HashDb as determined from indexExists(),
* databaseExists(), and isOutdated()
* @return IndexStatus enum according to their definitions
*/
IndexStatus status() {
boolean i = this.indexExists();
boolean db = this.databaseExists();
if(indexing)
return IndexStatus.INDEXING;
if (i) {
if (db) {
return this.isOutdated() ? IndexStatus.INDEX_OUTDATED : IndexStatus.INDEX_CURRENT;
} else {
return IndexStatus.NO_DB;
}
} else {
return db ? IndexStatus.NO_INDEX : IndexStatus.NONE;
}
}
/**
* Tries to index the database (overwrites any existing index)
* @throws TskException if an error occurs in the SleuthKit bindings
*/
void createIndex() throws TskException {
indexing = true;
CreateIndex creator = new CreateIndex();
creator.execute();
}
/**
* Checks if one file is older than an other
* @param a first file
* @param b second file
* @return true if the first file's last modified data is before the second
* file's last modified date
*/
private static boolean isOlderThan(File a, File b) {
return a.lastModified() < b.lastModified();
}
/**
* Determines if a path points to an index by checking the suffix
* @param path
* @return true if index
*/
static boolean isIndexPath(String path) {
return (path.endsWith(INDEX_SUFFIX) || path.endsWith(INDEX_SUFFIX_OLD));
}
/**
* Derives database path from an image path by removing the suffix.
* @param indexPath
* @return
*/
static String toDatabasePath(String indexPath) {
if (indexPath.endsWith(INDEX_SUFFIX_OLD)) {
return indexPath.substring(0, indexPath.lastIndexOf(INDEX_SUFFIX_OLD));
} else {
return indexPath.substring(0, indexPath.lastIndexOf(INDEX_SUFFIX));
}
}
/**
* Derives index path from an database path by appending the suffix.
* @param databasePath
* @return
*/
static String toIndexPath(String databasePath) {
return databasePath.concat(INDEX_SUFFIX);
}
/**
* Derives old-format index path from an database path by appending the suffix.
* @param databasePath
* @return
*/
static String toOldIndexPath(String databasePath) {
return databasePath.concat(INDEX_SUFFIX_OLD);
}
/**
* Calls Sleuth Kit method via JNI to determine whether there is an
* index for the given path
* @param databasePath path Path for the database the index is of
* (database doesn't have to actually exist)'
* @return true if index exists
* @throws TskException if there is an error in the JNI call
*/
static boolean hasIndex(String databasePath) throws TskException {
return SleuthkitJNI.lookupIndexExists(databasePath);
}
@Override
public int compareTo(HashDb o) {
return this.name.compareTo(o.name);
}
/* Thread that creates a database's index */
private class CreateIndex extends SwingWorker<Object,Void> {
private ProgressHandle progress;
CreateIndex(){};
@Override
protected Object doInBackground() throws Exception {
progress = ProgressHandleFactory.createHandle("Indexing " + name);
/** We need proper cancel support in TSK to make the task cancellable
new Cancellable() {
Override
public boolean cancel() {
return CreateIndex.this.cancel(true);
}
});
*/
progress.start();
progress.switchToIndeterminate();
SleuthkitJNI.createLookupIndex(databasePaths.get(0));
return null;
}
/* clean up or start the worker threads */
@Override
protected void done() {
indexing = false;
progress.finish();
pcs.firePropertyChange(EVENT.INDEXING_DONE.toString(), null, name);
}
}
}