diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleEntityParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleEntityParser.java index cdd84fa06e..9b34afc445 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleEntityParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleEntityParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,9 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -38,7 +40,7 @@ abstract class AbstractSingleEntityParser implements XRYFileParser { protected static final String PARSER_NAME = "XRY DSP"; @Override - public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + public void parse(XRYFileReader reader, Content parent, SleuthkitCase currentCase) throws IOException, TskCoreException, BlackboardException { Path reportPath = reader.getReportPath(); logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); @@ -94,7 +96,7 @@ abstract class AbstractSingleEntityParser implements XRYFileParser { } if(!keyValuePairs.isEmpty()) { - makeArtifact(keyValuePairs, parent); + makeArtifact(keyValuePairs, parent, currentCase); } } } @@ -122,9 +124,9 @@ abstract class AbstractSingleEntityParser implements XRYFileParser { */ abstract boolean isNamespace(String nameSpace); - /** +/** * Makes an artifact from the parsed key value pairs. */ - abstract void makeArtifact(List keyValuePairs, Content parent) throws TskCoreException; + abstract void makeArtifact(List keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, BlackboardException; -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index ef31f5f417..da599ea7d7 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,24 +18,22 @@ */ package org.sleuthkit.autopsy.datasourceprocessors.xry; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CallMediaType; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationDirection; /** * Parses XRY Calls files and creates artifacts. @@ -44,27 +42,19 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser { private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName()); - //Pattern is in reverse due to a Java 8 bug, see calculateSecondsSinceEpoch() - //function for more details. - private static final DateTimeFormatter DATE_TIME_PARSER - = DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y"); - - private static final String DEVICE_LOCALE = "(device)"; - private static final String NETWORK_LOCALE = "(network)"; - /** * All of the known XRY keys for call reports and their corresponding * blackboard attribute types, if any. */ private enum XryKey { NAME_MATCHED("name (matched)", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME), - TIME("time", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME), - DIRECTION("direction", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION), + TIME("time", null), + DIRECTION("direction", null), CALL_TYPE("call type", null), NUMBER("number", null), TEL("tel", null), - TO("to", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO), - FROM("from", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM), + TO("to", null), + FROM("from", null), DELETED("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED), DURATION("duration", null), STORAGE("storage", null), @@ -175,159 +165,157 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser { } @Override - void makeArtifact(List keyValuePairs, Content parent) throws TskCoreException { - List attributes = new ArrayList<>(); - for(XRYKeyValuePair pair : keyValuePairs) { - Optional attribute = getBlackboardAttribute(pair); - if(attribute.isPresent()) { - attributes.add(attribute.get()); - } - } - if(!attributes.isEmpty()) { - BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); - artifact.addAttributes(attributes); - } - } - - /** - * Creates the appropriate blackboard attribute given a single XRY Key Value - * pair, if any. Most XRY keys are mapped to an attribute type in the enum above. - */ - private Optional getBlackboardAttribute(XRYKeyValuePair pair) { - XryKey xryKey = XryKey.fromDisplayName(pair.getKey()); - XryNamespace xryNamespace = XryNamespace.NONE; - if (XryNamespace.contains(pair.getNamespace())) { - xryNamespace = XryNamespace.fromDisplayName(pair.getNamespace()); - } + void makeArtifact(List keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, BlackboardException { + // Transform all the data from XRY land into the appropriate CommHelper + // data types. + String callerId = null; + final Collection calleeList = new ArrayList<>(); + CommunicationDirection direction = CommunicationDirection.UNKNOWN; + long startTime = 0L; + final long endTime = 0L; + final CallMediaType callType = CallMediaType.UNKNOWN; + final Collection otherAttributes = new ArrayList<>(); - switch (xryKey) { - case TEL: - case NUMBER: - //Apply the namespace - switch (xryNamespace) { - case FROM: - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, - PARSER_NAME, pair.getValue())); - case TO: - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, - PARSER_NAME, pair.getValue())); - default: - return Optional.of(new BlackboardAttribute( + for (XRYKeyValuePair pair : keyValuePairs) { + XryKey xryKey = XryKey.fromDisplayName(pair.getKey()); + XryNamespace xryNamespace = XryNamespace.NONE; + if (XryNamespace.contains(pair.getNamespace())) { + xryNamespace = XryNamespace.fromDisplayName(pair.getNamespace()); + } + + switch (xryKey) { + case TEL: + case NUMBER: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + // Apply namespace or direction + if (xryNamespace == XryNamespace.FROM || direction == CommunicationDirection.INCOMING) { + callerId = pair.getValue(); + } else if (xryNamespace == XryNamespace.TO || direction == CommunicationDirection.OUTGOING) { + calleeList.add(pair.getValue()); + } else { + otherAttributes.add(new BlackboardAttribute( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, pair.getValue())); - } - case TIME: - try { - //Tranform value to seconds since epoch - long dateTimeSinceEpoch = calculateSecondsSinceEpoch(pair.getValue()); - return Optional.of(new BlackboardAttribute(xryKey.getType(), - PARSER_NAME, dateTimeSinceEpoch)); - } catch (DateTimeParseException ex) { - logger.log(Level.WARNING, String.format("[XRY DSP] Assumption" - + " about the date time formatting of call logs is " - + "not right. Here is the value [ %s ]", pair.getValue()), ex); - return Optional.empty(); - } - default: - //Otherwise, the XryKey enum contains the correct BlackboardAttribute - //type. - if (xryKey.getType() != null) { - return Optional.of(new BlackboardAttribute(xryKey.getType(), - PARSER_NAME, pair.getValue())); - } + } + break; + // Although confusing, as these are also 'name spaces', it appears + // later versions of XRY just made these standardized lines. + case TO: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + calleeList.add(pair.getValue()); + break; + case FROM: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + callerId = pair.getValue(); + break; + case TIME: + try { + //Tranform value to seconds since epoch + long dateTimeSinceEpoch = XRYUtils.calculateSecondsSinceEpoch(pair.getValue()); + startTime = dateTimeSinceEpoch; + } catch (DateTimeParseException ex) { + logger.log(Level.WARNING, String.format("[XRY DSP] Assumption" + + " about the date time formatting of call logs is " + + "not right. Here is the value [ %s ]", pair.getValue()), ex); + } + break; + case DIRECTION: + String directionString = pair.getValue().toLowerCase(); + if (directionString.equals("incoming")) { + direction = CommunicationDirection.INCOMING; + } else { + direction = CommunicationDirection.OUTGOING; + } + break; + case TYPE: + String typeString = pair.getValue(); + if (typeString.equalsIgnoreCase("received")) { + direction = CommunicationDirection.INCOMING; + } else if (typeString.equalsIgnoreCase("dialed")) { + direction = CommunicationDirection.OUTGOING; + } + break; + default: + //Otherwise, the XryKey enum contains the correct BlackboardAttribute + //type. + if (xryKey.getType() != null) { + otherAttributes.add(new BlackboardAttribute(xryKey.getType(), + PARSER_NAME, pair.getValue())); + } - logger.log(Level.INFO, String.format("[XRY DSP] Key value pair " - + "(in brackets) [ %s ] was recognized but " - + "more data or time is needed to finish implementation. Discarding... ", - pair)); - return Optional.empty(); + logger.log(Level.INFO, String.format("[XRY DSP] Key value pair " + + "(in brackets) [ %s ] was recognized but " + + "more data or time is needed to finish implementation. Discarding... ", + pair)); + } } - } - /** - * Removes the locale from the date time value. - * - * Locale in this case being (Device) or (Network). - * - * @param dateTime XRY datetime value to be sanitized. - * @return A purer date time value. - */ - private String removeDateTimeLocale(String dateTime) { - String result = dateTime; - int deviceIndex = result.toLowerCase().indexOf(DEVICE_LOCALE); - if (deviceIndex != -1) { - result = result.substring(0, deviceIndex); - } - int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE); - if (networkIndex != -1) { - result = result.substring(0, networkIndex); - } - return result; - } + // Make sure we have the required fields, otherwise the CommHelper will + // complain about illegal arguments. + + // These are all the invalid combinations. + if (callerId == null && calleeList.isEmpty() + || direction == CommunicationDirection.INCOMING && callerId == null + || direction == CommunicationDirection.OUTGOING && calleeList.isEmpty()) { - /** - * Parses the date time value and calculates seconds since epoch. - * - * @param dateTime - * @return - */ - private long calculateSecondsSinceEpoch(String dateTime) { - String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim(); - /** - * The format of time in XRY Messages reports is of the form: - * - * 1/3/1990 1:23:54 AM UTC+4 - * - * In our current version of Java (openjdk-1.8.0.222), there is a bug - * with having the timezone offset (UTC+4 or GMT-7) at the end of the - * date time input. This is fixed in later versions of the JDK (9 and - * beyond). https://bugs.openjdk.java.net/browse/JDK-8154050 Rather than - * update the JDK to accommodate this, the components of the date time - * string are reversed: - * - * UTC+4 AM 1:23:54 1/3/1990 - * - * The java time package will correctly parse this date time format. - */ - String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale); - /** - * Furthermore, the DateTimeFormatter's timezone offset letter ('O') - * does not recognize UTC but recognizes GMT. According to - * https://en.wikipedia.org/wiki/Coordinated_Universal_Time, GMT only - * differs from UTC by at most 1 second and so substitution will only - * introduce a trivial amount of error. - */ - String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT"); - TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT, - ZonedDateTime::from, - LocalDateTime::from, - OffsetDateTime::from); - //Query for the ZoneID - if (result.query(TemporalQueries.zoneId()) == null) { - //If none, assumed GMT+0. - return ZonedDateTime.of(LocalDateTime.from(result), - ZoneId.of("GMT")).toEpochSecond(); + // If the combo is invalid, just make an artifact with what we've got. + if (direction != CommunicationDirection.UNKNOWN) { + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, + PARSER_NAME, direction.getDisplayName())); + } + + if (startTime > 0L) { + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, + PARSER_NAME, startTime)); + } + + // If the DIRECTION check failed, just manually create accounts + // for these phones. Note, there is no need to create relationships. + // If both callerId and calleeList were non-null/non-empty, then + // it would have been a valid combination. + if (callerId != null) { + currentCase.getCommunicationsManager().createAccountFileInstance( + Account.Type.PHONE, callerId, PARSER_NAME, parent); + + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, + PARSER_NAME, callerId)); + } + + for (String phone : calleeList) { + currentCase.getCommunicationsManager().createAccountFileInstance( + Account.Type.PHONE, phone, PARSER_NAME, parent); + + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, + PARSER_NAME, phone)); + } + + if (!otherAttributes.isEmpty()) { + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); + artifact.addAttributes(otherAttributes); + + currentCase.getBlackboard().postArtifact(artifact, PARSER_NAME); + } } else { - return Instant.from(result).getEpochSecond(); - } - } - /** - * Reverses the order of the date time components. - * - * Example: 1/3/1990 1:23:54 AM UTC+4 becomes UTC+4 AM 1:23:54 1/3/1990 - * - * @param dateTime - * @return - */ - private String reverseOrderOfDateTimeComponents(String dateTime) { - StringBuilder reversedDateTime = new StringBuilder(dateTime.length()); - String[] dateTimeComponents = dateTime.split(" "); - for (String component : dateTimeComponents) { - reversedDateTime.insert(0, " ").insert(0, component); + // Otherwise we can safely use the helper. + CommunicationArtifactsHelper helper = new CommunicationArtifactsHelper( + currentCase, PARSER_NAME, parent, Account.Type.PHONE); + + helper.addCalllog(direction, callerId, calleeList, startTime, + endTime, callType, otherAttributes); } - return reversedDateTime.toString().trim(); } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index b2dae939e3..5f00a3e6e4 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,49 +19,30 @@ package org.sleuthkit.autopsy.datasourceprocessors.xry; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.autopsy.datasourceprocessors.xry.AbstractSingleEntityParser.PARSER_NAME; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper; /** * Parses XRY Contacts-Contacts files and creates artifacts. */ final class XRYContactsFileParser extends AbstractSingleEntityParser { - + private static final Logger logger = Logger.getLogger(XRYContactsFileParser.class.getName()); - //All of the known XRY keys for contacts. - private static final Map XRY_KEYS = - new HashMap() {{ - put("name", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - put("tel", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER); - put("mobile", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE); - put("home", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME); - put("related application", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - put("address home", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - put("email home", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_HOME); - put("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED); - - //Ignoring or need more information to decide. - put("storage", null); - put("other", null); - put("picture", null); - put("index", null); - put("account name", null); - - }}; - @Override boolean canProcess(XRYKeyValuePair pair) { - String normalizedKey = pair.getKey().toLowerCase(); - return XRY_KEYS.containsKey(normalizedKey); + return XryKey.contains(pair.getKey()); } @Override @@ -70,36 +51,168 @@ final class XRYContactsFileParser extends AbstractSingleEntityParser { return false; } - /** - * Creates the appropriate blackboard attribute given a single XRY Key Value - * pair. - */ - private Optional getBlackboardAttribute(XRYKeyValuePair pair) { - String normalizedKey = pair.getKey().toLowerCase(); - BlackboardAttribute.ATTRIBUTE_TYPE attrType = XRY_KEYS.get(normalizedKey); - if(attrType != null) { - return Optional.of(new BlackboardAttribute(attrType, PARSER_NAME, pair.getValue())); - } - - logger.log(Level.INFO, String.format("[XRY DSP] Key value pair " - + "(in brackets) [ %s ] was recognized but we need " - + "more data or time to finish implementation. Discarding... ", - pair)); - return Optional.empty(); - } - @Override - void makeArtifact(List keyValuePairs, Content parent) throws TskCoreException { - List attributes = new ArrayList<>(); - for(XRYKeyValuePair pair : keyValuePairs) { - Optional attribute = getBlackboardAttribute(pair); - if(attribute.isPresent()) { - attributes.add(attribute.get()); + void makeArtifact(List keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, Blackboard.BlackboardException { + // Transform all the data from XRY land into the appropriate CommHelper + // data types. + String contactName = null; + String phoneNumber = null; + String homePhoneNumber = null; + String mobilePhoneNumber = null; + String emailAddr = null; + boolean hasAnEmail = false; + final Collection additionalAttributes = new ArrayList<>(); + + for (XRYKeyValuePair pair : keyValuePairs) { + XryKey xryKey = XryKey.fromDisplayName(pair.getKey()); + switch (xryKey) { + case NAME: + if (contactName != null) { + additionalAttributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, PARSER_NAME, pair.getValue())); + } else { + contactName = pair.getValue(); + } + break; + case TEL: + if (!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + if (phoneNumber != null) { + additionalAttributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, pair.getValue())); + } else { + phoneNumber = pair.getValue(); + } + break; + case MOBILE: + if (!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + if (mobilePhoneNumber != null) { + additionalAttributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE, PARSER_NAME, pair.getValue())); + } else { + mobilePhoneNumber = pair.getValue(); + } + break; + case HOME: + if (!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + if (homePhoneNumber != null) { + additionalAttributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME, PARSER_NAME, pair.getValue())); + } else { + homePhoneNumber = pair.getValue(); + } + break; + case EMAIL_HOME: + if (!XRYUtils.isEmailValid(pair.getValue())) { + continue; + } + + hasAnEmail = true; + additionalAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_HOME, + PARSER_NAME, pair.getValue())); + break; + default: + //Otherwise, the XryKey enum contains the correct BlackboardAttribute + //type. + if (xryKey.getType() != null) { + additionalAttributes.add(new BlackboardAttribute(xryKey.getType(), + PARSER_NAME, pair.getValue())); + } + + logger.log(Level.INFO, String.format("[XRY DSP] Key value pair " + + "(in brackets) [ %s ] was recognized but " + + "more data or time is needed to finish implementation. Discarding... ", + pair)); } } - if(!attributes.isEmpty()) { - BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - artifact.addAttributes(attributes); + + // Make sure we have the required fields, otherwise the CommHelper will + // complain about illegal arguments. + if (phoneNumber != null || homePhoneNumber != null || mobilePhoneNumber != null || hasAnEmail) { + CommunicationArtifactsHelper helper = new CommunicationArtifactsHelper( + currentCase, PARSER_NAME, parent, Account.Type.DEVICE); + + helper.addContact(contactName, phoneNumber, homePhoneNumber, + mobilePhoneNumber, emailAddr, additionalAttributes); + } else { + // Just create an artifact with the attributes that we do have. + if (!additionalAttributes.isEmpty()) { + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + artifact.addAttributes(additionalAttributes); + + currentCase.getBlackboard().postArtifact(artifact, PARSER_NAME); + } + } + } + + /** + * Enum containing all known keys for contacts and their corresponding + * blackboard attribute. Some keys are intentionally null, because they are + * handled as special cases in makeArtifact(). Some are null because there's + * not an appropriate attribute type. + */ + private enum XryKey { + NAME("name", null), + TEL("tel", null), + MOBILE("mobile", null), + HOME("home", null), + RELATED_APPLICATION("related application", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME), + ADDRESS_HOME("address home", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION), + EMAIL_HOME("email home", null), + DELETED("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED), + //Ignoring or need more information to decide. + STORAGE("storage", null), + OTHER("other", null), + PICTURE("picture", null), + INDEX("index", null), + ACCOUNT_NAME("account name", null); + + private final String name; + private final BlackboardAttribute.ATTRIBUTE_TYPE type; + + XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type) { + this.name = name; + this.type = type; + } + + BlackboardAttribute.ATTRIBUTE_TYPE getType() { + return type; + } + + /** + * Indicates if the display name of the XRY key is a recognized type. + */ + static boolean contains(String key) { + try { + XryKey.fromDisplayName(key); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } + + /** + * Matches the display name of the xry key to the appropriate enum type. + * + * It is assumed that XRY key string is recognized. Otherwise, an + * IllegalArgumentException is thrown. Test all membership with + * contains() before hand. + */ + static XryKey fromDisplayName(String key) { + String normalizedKey = key.trim().toLowerCase(); + for (XryKey keyChoice : XryKey.values()) { + if (normalizedKey.equals(keyChoice.name)) { + return keyChoice; + } + } + + throw new IllegalArgumentException(String.format("Key [%s] was not found." + + " All keys should be tested with contains.", key)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index 83ddbc1f5b..217f7e9823 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,6 +48,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; @@ -204,12 +205,11 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa try { XRYFolder xryFolder = new XRYFolder(selectedPath); - FileManager fileManager = Case.getCurrentCaseThrows() - .getServices().getFileManager(); + Case currentCase = Case.getCurrentCaseThrows(); String uniqueUUID = UUID.randomUUID().toString(); //Move heavy lifting to a background task. swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, - callback, fileManager, uniqueUUID); + callback, currentCase, uniqueUUID); swingWorker.execute(); } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex); @@ -238,11 +238,10 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa try { XRYFolder xryFolder = new XRYFolder(dataSourcePath); - FileManager fileManager = Case.getCurrentCaseThrows() - .getServices().getFileManager(); + Case currentCase = Case.getCurrentCaseThrows(); //Move heavy lifting to a background task. swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, - callBack, fileManager, deviceId); + callBack, currentCase, deviceId); swingWorker.execute(); } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex); @@ -273,20 +272,19 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; - private final FileManager fileManager; + private final Case currentCase; private final XRYFolder xryFolder; private final String uniqueUUID; public XRYReportProcessorSwingWorker(XRYFolder folder, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback, - FileManager fileManager, - String uniqueUUID) { + Case currentCase, String uniqueUUID) { this.xryFolder = folder; this.progressMonitor = progressMonitor; this.callback = callback; - this.fileManager = fileManager; + this.currentCase = currentCase; this.uniqueUUID = uniqueUUID; } @@ -296,7 +294,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa "XRYDataSourceProcessor.processingFiles=Processing all XRY files..." }) protected LocalFilesDataSource doInBackground() throws TskCoreException, - TskDataException, IOException { + TskDataException, IOException, BlackboardException { progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_preppingFiles()); List nonXRYFiles = xryFolder.getNonXRYFiles(); @@ -304,7 +302,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa //Map paths to string representations. .map(Path::toString) .collect(Collectors.toList()); - LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource( + LocalFilesDataSource dataSource = currentCase.getServices().getFileManager().addLocalFilesDataSource( uniqueUUID, "XRY Text Export", //Name "", //Timezone @@ -313,7 +311,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa //Process the report files. progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_processingFiles()); - XRYReportProcessor.process(xryFolder, dataSource); + XRYReportProcessor.process(xryFolder, dataSource, currentCase.getSleuthkitCase()); return dataSource; } @@ -360,4 +358,4 @@ public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDa } } } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index 14d8d54660..ee2552c8a0 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +25,11 @@ import java.util.Map; import java.util.Optional; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -89,7 +91,7 @@ final class XRYDeviceGenInfoFileParser extends AbstractSingleEntityParser { } @Override - void makeArtifact(List keyValuePairs, Content parent) throws TskCoreException { + void makeArtifact(List keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, Blackboard.BlackboardException { List attributes = new ArrayList<>(); for(int i = 0; i < keyValuePairs.size(); i+=2) { Optional attribute; @@ -168,4 +170,4 @@ final class XRYDeviceGenInfoFileParser extends AbstractSingleEntityParser { return Optional.of(new BlackboardAttribute(attrType, PARSER_NAME, dataValue)); } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java index 1787641e78..837ee345f6 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,9 @@ package org.sleuthkit.autopsy.datasourceprocessors.xry; import java.io.IOException; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -40,7 +42,7 @@ interface XRYFileParser { * @throws IOException If an I/O error occurs during reading. * @throws TskCoreException If an error occurs during artifact creation. */ - void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException; + void parse(XRYFileReader reader, Content parent, SleuthkitCase currentCase) throws IOException, TskCoreException, BlackboardException; } - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index 48b542615f..b3cd172f4a 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,16 +20,9 @@ package org.sleuthkit.autopsy.datasourceprocessors.xry; import java.io.IOException; import java.nio.file.Path; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalQueries; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -37,10 +30,15 @@ import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationDirection; +import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.MessageReadStatus; /** * Parses Messages-SMS files and creates artifacts. @@ -52,31 +50,20 @@ final class XRYMessagesFileParser implements XRYFileParser { private static final String PARSER_NAME = "XRY DSP"; - //Pattern is in reverse due to a Java 8 bug, see calculateSecondsSinceEpoch() - //function for more details. - private static final DateTimeFormatter DATE_TIME_PARSER - = DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y"); - - private static final String DEVICE_LOCALE = "(device)"; - private static final String NETWORK_LOCALE = "(network)"; - - private static final int READ = 1; - private static final int UNREAD = 0; - /** * All of the known XRY keys for message reports and their corresponding * blackboard attribute types, if any. */ private enum XryKey { DELETED("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED), - DIRECTION("direction", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION), - MESSAGE("message", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT), + DIRECTION("direction", null), + MESSAGE("message", null), NAME_MATCHED("name (matched)", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON), - TEXT("text", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT), - TIME("time", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME), - SERVICE_CENTER("service center", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER), - FROM("from", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM), - TO("to", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO), + TEXT("text", null), + TIME("time", null), + SERVICE_CENTER("service center", null), + FROM("from", null), + TO("to", null), //The following keys either need special processing or more time and data to find a type. STORAGE("storage", null), NUMBER("number", null), @@ -272,7 +259,7 @@ final class XRYMessagesFileParser implements XRYFileParser { * encountered. */ @Override - public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + public void parse(XRYFileReader reader, Content parent, SleuthkitCase currentCase) throws IOException, TskCoreException, BlackboardException { Path reportPath = reader.getReportPath(); logger.log(Level.INFO, String.format("[XRY DSP] Processing report at" + " [ %s ]", reportPath.toString())); @@ -282,26 +269,178 @@ final class XRYMessagesFileParser implements XRYFileParser { while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); - List attributes = getBlackboardAttributes(xryEntity, reader, referenceNumbersSeen); - //Only create artifacts with non-empty attributes. - if (!attributes.isEmpty()) { - BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); - artifact.addAttributes(attributes); + + // This call will combine all segmented text into a single key value pair + List pairs = getXRYKeyValuePairs(xryEntity, reader, referenceNumbersSeen); + + // Transform all the data from XRY land into the appropriate CommHelper + // data types. + final String messageType = PARSER_NAME; + CommunicationDirection direction = CommunicationDirection.UNKNOWN; + String senderId = null; + final List recipientIdsList = new ArrayList<>(); + long dateTime = 0L; + MessageReadStatus readStatus = MessageReadStatus.UNKNOWN; + final String subject = null; + String text = null; + final String threadId = null; + final Collection otherAttributes = new ArrayList<>(); + + for(XRYKeyValuePair pair : pairs) { + XryNamespace namespace = XryNamespace.NONE; + if (XryNamespace.contains(pair.getNamespace())) { + namespace = XryNamespace.fromDisplayName(pair.getNamespace()); + } + XryKey key = XryKey.fromDisplayName(pair.getKey()); + String normalizedValue = pair.getValue().toLowerCase().trim(); + + switch (key) { + case TEL: + case NUMBER: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + // Apply namespace or direction + if(namespace == XryNamespace.FROM || direction == CommunicationDirection.INCOMING) { + senderId = pair.getValue(); + } else if(namespace == XryNamespace.TO || direction == CommunicationDirection.OUTGOING) { + recipientIdsList.add(pair.getValue()); + } else { + currentCase.getCommunicationsManager().createAccountFileInstance( + Account.Type.PHONE, pair.getValue(), PARSER_NAME, parent); + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, + PARSER_NAME, pair.getValue())); + } + break; + // Although confusing, as these are also 'name spaces', it appears + // later versions of XRY just made these standardized lines. + case FROM: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + senderId = pair.getValue(); + break; + case TO: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + recipientIdsList.add(pair.getValue()); + break; + case TIME: + try { + //Tranform value to seconds since epoch + long dateTimeSinceInEpoch = XRYUtils.calculateSecondsSinceEpoch(pair.getValue()); + dateTime = dateTimeSinceInEpoch; + } catch (DateTimeParseException ex) { + logger.log(Level.WARNING, String.format("[%s] Assumption" + + " about the date time formatting of messages is " + + "not right. Here is the pair [ %s ]", PARSER_NAME, pair), ex); + } + break; + case TYPE: + switch (normalizedValue) { + case "incoming": + direction = CommunicationDirection.INCOMING; + break; + case "outgoing": + direction = CommunicationDirection.OUTGOING; + break; + case "deliver": + case "submit": + case "status report": + //Ignore for now. + break; + default: + logger.log(Level.WARNING, String.format("[%s] Unrecognized " + + " value for key pair [ %s ].", PARSER_NAME, pair)); + } + break; + case STATUS: + switch (normalizedValue) { + case "read": + readStatus = MessageReadStatus.READ; + break; + case "unread": + readStatus = MessageReadStatus.UNREAD; + break; + case "deleted": + otherAttributes.add(new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED, + PARSER_NAME, pair.getValue())); + break; + case "sending failed": + case "unsent": + case "sent": + //Ignoring for now. + break; + default: + logger.log(Level.WARNING, String.format("[%s] Unrecognized " + + " value for key pair [ %s ].", PARSER_NAME, pair)); + } + break; + case TEXT: + case MESSAGE: + text = pair.getValue(); + break; + case DIRECTION: + switch (normalizedValue) { + case "incoming": + direction = CommunicationDirection.INCOMING; + break; + case "outgoing": + direction = CommunicationDirection.OUTGOING; + break; + default: + direction = CommunicationDirection.UNKNOWN; + break; + } + break; + case SERVICE_CENTER: + if(!XRYUtils.isPhoneValid(pair.getValue())) { + continue; + } + + otherAttributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, + PARSER_NAME, pair.getValue())); + break; + default: + //Otherwise, the XryKey enum contains the correct BlackboardAttribute + //type. + if (key.getType() != null) { + otherAttributes.add(new BlackboardAttribute(key.getType(), + PARSER_NAME, pair.getValue())); + } else { + logger.log(Level.INFO, String.format("[%s] Key value pair " + + "(in brackets) [ %s ] was recognized but " + + "more data or time is needed to finish implementation. Discarding... ", + PARSER_NAME, pair)); + } + } } + + CommunicationArtifactsHelper helper = new CommunicationArtifactsHelper( + currentCase, PARSER_NAME, parent, Account.Type.PHONE); + + helper.addMessage(messageType, direction, senderId, recipientIdsList, + dateTime, readStatus, subject, text, threadId, otherAttributes); } } /** - * Extracts all blackboard attributes from the XRY Entity. This function will - * unify any segmented text, if need be. + * Extracts all pairs from the XRY Entity. This function + * will unify any segmented text, if need be. */ - private List getBlackboardAttributes(String xryEntity, + private List getXRYKeyValuePairs(String xryEntity, XRYFileReader reader, Set referenceValues) throws IOException { String[] xryLines = xryEntity.split("\n"); //First line of the entity is the title, each XRY entity is non-empty. logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); - List attributes = new ArrayList<>(); + List pairs = new ArrayList<>(); //Count the key value pairs in the XRY entity. int keyCount = getCountOfKeyValuePairs(xryLines); @@ -339,14 +478,10 @@ final class XRYMessagesFileParser implements XRYFileParser { pair.getNamespace()); } - //Get the corresponding blackboard attribute, if any. - Optional attribute = getBlackboardAttribute(pair); - if (attribute.isPresent()) { - attributes.add(attribute.get()); - } + pairs.add(pair); } - return attributes; + return pairs; } /** @@ -364,7 +499,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } /** - * Builds up segmented message entities so that the text is unified for a + * Builds up segmented message entities so that the text is unified for a * single artifact. * * @param reader File reader that is producing XRY entities. @@ -461,7 +596,7 @@ final class XRYMessagesFileParser implements XRYFileParser { /** * Extracts the value of the XRY meta key, if any. - * + * * @param xryLines XRY entity to extract from. * @param metaKey The key type to extract. * @return @@ -486,10 +621,10 @@ final class XRYMessagesFileParser implements XRYFileParser { } /** - * Extracts the ith XRY Key Value pair in the XRY Entity. - * + * Extracts the ith XRY Key Value pair in the XRY Entity. + * * The total number of pairs can be determined via getCountOfKeyValuePairs(). - * + * * @param xryLines XRY entity. * @param index The requested Key Value pair. * @return @@ -531,191 +666,4 @@ final class XRYMessagesFileParser implements XRYFileParser { return Optional.empty(); } - - /** - * Creates an attribute from the extracted key value pair. - * - * @param nameSpace The namespace of this key value pair. It will have been - * verified beforehand, otherwise it will be NONE. - * @param key The recognized XRY key. - * @param value The value associated with that key. - * @return Corresponding blackboard attribute, if any. - */ - private Optional getBlackboardAttribute(XRYKeyValuePair pair) { - XryNamespace namespace = XryNamespace.NONE; - if (XryNamespace.contains(pair.getNamespace())) { - namespace = XryNamespace.fromDisplayName(pair.getNamespace()); - } - XryKey key = XryKey.fromDisplayName(pair.getKey()); - String normalizedValue = pair.getValue().toLowerCase().trim(); - - switch (key) { - case TEL: - case NUMBER: - switch (namespace) { - case FROM: - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, - PARSER_NAME, pair.getValue())); - case TO: - case PARTICIPANT: - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, - PARSER_NAME, pair.getValue())); - default: - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, - PARSER_NAME, pair.getValue())); - } - case TIME: - try { - //Tranform value to seconds since epoch - long dateTimeSinceInEpoch = calculateSecondsSinceEpoch(pair.getValue()); - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, - PARSER_NAME, dateTimeSinceInEpoch)); - } catch (DateTimeParseException ex) { - logger.log(Level.WARNING, String.format("[XRY DSP] Assumption" - + " about the date time formatting of messages is " - + "not right. Here is the pair [ %s ]", pair), ex); - return Optional.empty(); - } - case TYPE: - switch (normalizedValue) { - case "incoming": - case "outgoing": - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, - PARSER_NAME, pair.getValue())); - case "deliver": - case "submit": - case "status report": - //Ignore for now. - return Optional.empty(); - default: - logger.log(Level.WARNING, String.format("[XRY DSP] Unrecognized " - + " value for key pair [ %s ].", pair)); - return Optional.empty(); - } - case STATUS: - switch (normalizedValue) { - case "read": - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, - PARSER_NAME, READ)); - case "unread": - return Optional.of(new BlackboardAttribute( - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, - PARSER_NAME, UNREAD)); - case "sending failed": - case "deleted": - case "unsent": - case "sent": - //Ignore for now. - return Optional.empty(); - default: - logger.log(Level.WARNING, String.format("[XRY DSP] Unrecognized " - + " value for key pair [ %s ].", pair)); - return Optional.empty(); - } - default: - //Otherwise, the XryKey enum contains the correct BlackboardAttribute - //type. - if (key.getType() != null) { - return Optional.of(new BlackboardAttribute(key.getType(), - PARSER_NAME, pair.getValue())); - } - - logger.log(Level.INFO, String.format("[XRY DSP] Key value pair " - + "(in brackets) [ %s ] was recognized but " - + "more data or time is needed to finish implementation. Discarding... ", pair)); - - return Optional.empty(); - } - } - - /** - * Removes the locale from the date time value. - * - * Locale in this case being (Device) or (Network). - * - * @param dateTime XRY datetime value to be sanitized. - * @return A purer date time value. - */ - private String removeDateTimeLocale(String dateTime) { - String result = dateTime; - int deviceIndex = result.toLowerCase().indexOf(DEVICE_LOCALE); - if (deviceIndex != -1) { - result = result.substring(0, deviceIndex); - } - int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE); - if (networkIndex != -1) { - result = result.substring(0, networkIndex); - } - return result; - } - - /** - * Parses the date time value and calculates seconds since epoch. - * - * @param dateTime - * @return - */ - private long calculateSecondsSinceEpoch(String dateTime) { - String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim(); - /** - * The format of time in XRY Messages reports is of the form: - * - * 1/3/1990 1:23:54 AM UTC+4 - * - * In our current version of Java (openjdk-1.8.0.222), there is a bug - * with having the timezone offset (UTC+4 or GMT-7) at the end of the - * date time input. This is fixed in later versions of the JDK (9 and - * beyond). https://bugs.openjdk.java.net/browse/JDK-8154050 Rather than - * update the JDK to accommodate this, the components of the date time - * string are reversed: - * - * UTC+4 AM 1:23:54 1/3/1990 - * - * The java time package will correctly parse this date time format. - */ - String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale); - /** - * Furthermore, the DateTimeFormatter's timezone offset letter ('O') - * does not recognize UTC but recognizes GMT. According to - * https://en.wikipedia.org/wiki/Coordinated_Universal_Time, GMT only - * differs from UTC by at most 1 second and so substitution will only - * introduce a trivial amount of error. - */ - String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT"); - TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT, - ZonedDateTime::from, - LocalDateTime::from, - OffsetDateTime::from); - //Query for the ZoneID - if (result.query(TemporalQueries.zoneId()) == null) { - //If none, assumed GMT+0. - return ZonedDateTime.of(LocalDateTime.from(result), - ZoneId.of("GMT")).toEpochSecond(); - } else { - return Instant.from(result).getEpochSecond(); - } - } - - /** - * Reverses the order of the date time components. - * - * Example: 1/3/1990 1:23:54 AM UTC+4 becomes UTC+4 AM 1:23:54 1/3/1990 - * - * @param dateTime - * @return - */ - private String reverseOrderOfDateTimeComponents(String dateTime) { - StringBuilder reversedDateTime = new StringBuilder(dateTime.length()); - String[] dateTimeComponents = dateTime.split(" "); - for (String component : dateTimeComponents) { - reversedDateTime.insert(0, " ").insert(0, component); - } - return reversedDateTime.toString().trim(); - } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index a67ce772f4..6eafd221c1 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,9 @@ import java.io.IOException; import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -43,7 +45,7 @@ final class XRYReportProcessor { * @throws IOException If an I/O exception occurs. * @throws TskCoreException If an error occurs adding artifacts. */ - static void process(XRYFolder folder, Content parent) throws IOException, TskCoreException { + static void process(XRYFolder folder, Content parent, SleuthkitCase currentCase) throws IOException, TskCoreException, BlackboardException { //Get all XRY file readers from this folder. List xryFileReaders = folder.getXRYFileReaders(); @@ -52,7 +54,7 @@ final class XRYReportProcessor { String reportType = xryFileReader.getReportType(); if (XRYFileParserFactory.supports(reportType)) { XRYFileParser parser = XRYFileParserFactory.get(reportType); - parser.parse(xryFileReader, parent); + parser.parse(xryFileReader, parent, currentCase); } else { logger.log(Level.WARNING, String.format("[XRY DSP] XRY File (in brackets) " + "[ %s ] was found, but no parser to support its report type exists. " @@ -76,4 +78,4 @@ final class XRYReportProcessor { private XRYReportProcessor() { } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java new file mode 100755 index 0000000000..b244c88966 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java @@ -0,0 +1,151 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit 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.datasourceprocessors.xry; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; +import org.sleuthkit.datamodel.CommunicationsUtils; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Common utility methods shared among all XRY parser implementations. + */ +final class XRYUtils { + + // Pattern is in reverse due to a Java 8 bug, see calculateSecondsSinceEpoch() + // function for more details. + private static final DateTimeFormatter DATE_TIME_PARSER + = DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y"); + + private static final String DEVICE_LOCALE = "(device)"; + private static final String NETWORK_LOCALE = "(network)"; + + public static boolean isPhoneValid(String phoneNumber) { + try { + CommunicationsUtils.normalizePhoneNum(phoneNumber); + return true; + } catch (TskCoreException ex) { + return false; + } + } + + public static boolean isEmailValid(String email) { + try { + CommunicationsUtils.normalizeEmailAddress(email); + return true; + } catch (TskCoreException ex) { + return false; + } + } + + /** + * Parses the date time value and calculates seconds since epoch. + * + * @param dateTime + * @return + */ + public static long calculateSecondsSinceEpoch(String dateTime) { + String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim(); + /** + * The format of time in XRY reports is of the form: + * + * 1/3/1990 1:23:54 AM UTC+4 + * + * In our current version of Java (openjdk-1.8.0.222), there is a bug + * with having the timezone offset (UTC+4 or GMT-7, for example) at the + * end of the date time input. This is fixed in later versions of the + * JDK (9 and beyond). https://bugs.openjdk.java.net/browse/JDK-8154050 + * Rather than update the JDK to accommodate this, the components of the + * date time string are reversed: + * + * UTC+4 AM 1:23:54 1/3/1990 + * + * The java time package will correctly parse this date time format. + */ + String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale); + /** + * Furthermore, the DateTimeFormatter's timezone offset letter ('O') + * does not recognize UTC but recognizes GMT. According to + * https://en.wikipedia.org/wiki/Coordinated_Universal_Time, GMT only + * differs from UTC by at most 1 second and so substitution will only + * introduce a trivial amount of error. + */ + String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT"); + TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT, + ZonedDateTime::from, + LocalDateTime::from, + OffsetDateTime::from); + //Query for the ZoneID + if (result.query(TemporalQueries.zoneId()) == null) { + //If none, assumed GMT+0. + return ZonedDateTime.of(LocalDateTime.from(result), + ZoneId.of("GMT")).toEpochSecond(); + } else { + return Instant.from(result).getEpochSecond(); + } + } + + /** + * Reverses the order of the date time components. + * + * Example: 1/3/1990 1:23:54 AM UTC+4 becomes UTC+4 AM 1:23:54 1/3/1990 + * + * @param dateTime + * @return + */ + private static String reverseOrderOfDateTimeComponents(String dateTime) { + StringBuilder reversedDateTime = new StringBuilder(dateTime.length()); + String[] dateTimeComponents = dateTime.split(" "); + for (String component : dateTimeComponents) { + reversedDateTime.insert(0, " ").insert(0, component); + } + return reversedDateTime.toString().trim(); + } + + /** + * Removes the locale from the date time value. + * + * Locale in this case being (Device) or (Network). + * + * @param dateTime XRY datetime value to be sanitized. + * @return A purer date time value. + */ + private static String removeDateTimeLocale(String dateTime) { + String result = dateTime; + int deviceIndex = result.toLowerCase().indexOf(DEVICE_LOCALE); + if (deviceIndex != -1) { + result = result.substring(0, deviceIndex); + } + int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE); + if (networkIndex != -1) { + result = result.substring(0, networkIndex); + } + return result; + } + + private XRYUtils() { + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index 9678db0274..d37e9e7de2 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,11 @@ import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.Optional; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -67,7 +69,7 @@ final class XRYWebBookmarksFileParser extends AbstractSingleEntityParser { } @Override - void makeArtifact(List keyValuePairs, Content parent) throws TskCoreException { + void makeArtifact(List keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, BlackboardException { List attributes = new ArrayList<>(); for(XRYKeyValuePair pair : keyValuePairs) { Optional attribute = getBlackboardAttribute(pair); @@ -80,4 +82,4 @@ final class XRYWebBookmarksFileParser extends AbstractSingleEntityParser { artifact.addAttributes(attributes); } } -} +} \ No newline at end of file diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java index 5b86b3b9b0..2c7c5dfcb0 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * * Copyright 2012 42six Solutions. * @@ -46,7 +46,7 @@ import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -55,6 +55,7 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper; /** * Chrome recent activity extraction @@ -731,8 +732,13 @@ class Chrome extends Extract { // get form autofill artifacts bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X)); - // get form address atifacts - bbartifacts.addAll(getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X)); + try { + // get form address atifacts + getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X); + } catch (NoCurrentCaseException | TskCoreException | Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, String.format("Error adding artifacts to the case database " + + "for chrome file %s [objId=%d]", webDataFile.getName(), webDataFile.getId()), ex); + } dbFile.delete(); } @@ -808,17 +814,23 @@ class Chrome extends Extract { * * @return collection of TSK_WEB_FORM_ADDRESS artifacts */ - private Collection getFormAddressArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) { - Collection bbartifacts = new ArrayList<>(); + private void getFormAddressArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) throws NoCurrentCaseException, + TskCoreException, Blackboard.BlackboardException { String webformAddressQuery = (isSchemaV8X) ? WEBFORM_ADDRESS_QUERY_V8X : WEBFORM_ADDRESS_QUERY; + // Helper to create web form address artifacts. + WebBrowserArtifactsHelper helper = new WebBrowserArtifactsHelper( + Case.getCurrentCaseThrows().getSleuthkitCase(), + NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), + webDataFile + ); + // Get Web form addresses List> addresses = this.dbConnect(dbFilePath, webformAddressQuery); logger.log(Level.INFO, "{0}- Now getting Web form addresses from {1} with {2}artifacts identified.", new Object[]{moduleName, dbFilePath, addresses.size()}); //NON-NLS for (HashMap result : addresses) { - Collection bbattributes = new ArrayList<>(); // get name fields String first_name = result.get("first_name").toString() != null ? result.get("first_name").toString() : ""; @@ -853,73 +865,26 @@ class Chrome extends Extract { String address_line_2 = result.get("address_line_2").toString() != null ? result.get("address_line_2").toString() : ""; street_address = String.join(" ", address_line_1, address_line_2); } - - // If an email address is found, create an account instance for it - if (email_Addr != null && !email_Addr.isEmpty()) { - try { - Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email_Addr, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile); - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating email account instance for '%s' from Chrome WebData file '%s' .", - email_Addr, webDataFile.getName()), ex); //NON-NLS - } - } - // If a phone number is found, create an account instance for it - if (phone_number != null && !phone_number.isEmpty()) { - try { - Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, phone_number, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile); - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating phone account instance for '%s' from Chrome WebData file '%s' .", - phone_number, webDataFile.getName()), ex); //NON-NLS - } - } // Create atrributes from extracted fields if (full_name == null || full_name.isEmpty()) { full_name = String.join(" ", first_name, middle_name, last_name); } - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON, - RecentActivityExtracterModuleFactory.getModuleName(), - full_name)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL, - RecentActivityExtracterModuleFactory.getModuleName(), - email_Addr)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, - RecentActivityExtracterModuleFactory.getModuleName(), - phone_number)); //NON-NLS String locationAddress = String.join(", ", street_address, city, state, zipcode, country_code); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION, - RecentActivityExtracterModuleFactory.getModuleName(), - locationAddress)); //NON-NLS + List otherAttributes = new ArrayList<>(); if (date_modified > 0) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED, + otherAttributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED, RecentActivityExtracterModuleFactory.getModuleName(), date_modified)); //NON-NLS } - if (use_count > 0 ){ - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, - RecentActivityExtracterModuleFactory.getModuleName(), - use_count)); //NON-NLS - } - - if (use_date > 0) { - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, - RecentActivityExtracterModuleFactory.getModuleName(), - use_date)); //NON-NLS - } - - // Create artifact - BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS, webDataFile, bbattributes); - if (bbart != null) { - bbartifacts.add(bbart); - } + helper.addWebFormAddress( + full_name, email_Addr, phone_number, + locationAddress, 0, use_date, + use_count, otherAttributes); } - // return all extracted artifacts - return bbartifacts; } private boolean isChromePreVersion30(String temps) { @@ -933,4 +898,4 @@ class Chrome extends Extract { return false; } -} +} \ No newline at end of file diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 5341bda1e0..82b4a05b07 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -69,11 +69,14 @@ import java.util.HashSet; import static java.util.Locale.US; import static java.util.TimeZone.getTimeZone; import org.openide.util.Lookup; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.recentactivity.ShellBagParser.ShellBag; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -944,7 +947,7 @@ class ExtractRegistry extends Extract { Map userInfo = userInfoMap.remove(userID); //if the existing user id matches a user id which we parsed information for check if that information exists and if it doesn't add it if (userInfo != null) { - osAccount.addAttributes(getAttributesForAccount(userInfo, groupMap.get(userID), true)); + osAccount.addAttributes(getAttributesForAccount(userInfo, groupMap.get(userID), true, regAbstractFile)); } } } @@ -953,7 +956,7 @@ class ExtractRegistry extends Extract { //add remaining userinfos as accounts; for (Map userInfo : userInfoMap.values()) { BlackboardArtifact bbart = regAbstractFile.newArtifact(ARTIFACT_TYPE.TSK_OS_ACCOUNT); - bbart.addAttributes(getAttributesForAccount(userInfo, groupMap.get(userInfo.get(SID_KEY)), false)); + bbart.addAttributes(getAttributesForAccount(userInfo, groupMap.get(userInfo.get(SID_KEY)), false, regAbstractFile)); // index the artifact for keyword search newArtifacts.add(bbart); } @@ -983,7 +986,7 @@ class ExtractRegistry extends Extract { * * @throws ParseException */ - Collection getAttributesForAccount(Map userInfo, List groupList, boolean existingUser) throws ParseException { + Collection getAttributesForAccount(Map userInfo, List groupList, boolean existingUser, AbstractFile regAbstractFile) throws ParseException { Collection bbattributes = new ArrayList<>(); SimpleDateFormat regRipperTimeFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'"); @@ -1035,6 +1038,20 @@ class ExtractRegistry extends Extract { value = userInfo.get(INTERNET_NAME_KEY); if (value != null && !value.isEmpty()) { + try { + // Create an account for this email, if it doesn't already exist. + Case.getCurrentCaseThrows() + .getSleuthkitCase() + .getCommunicationsManager() + .createAccountFileInstance(Account.Type.EMAIL, + value, getRAModuleName(), regAbstractFile); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, + String.format("Error adding email account with value " + + "%s, to the case database for file %s [objId=%d]", + value, regAbstractFile.getName(), regAbstractFile.getId()), ex); + } + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL, getRAModuleName(), value)); } @@ -1802,4 +1819,4 @@ class ExtractRegistry extends Extract { public String autopsyPlugins = ""; public String fullPlugins = ""; } -} +} \ No newline at end of file diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 7fddeb0aa8..e0bf5c10b8 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -54,7 +54,7 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -62,6 +62,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper; @Messages({ "Progress_Message_Firefox_History=Firefox History", @@ -858,7 +859,6 @@ class Firefox extends Extract { } dataFound = true; - Collection bbartifacts = new ArrayList<>(); int j = 0; while (j < autofillProfilesFiles.size()) { @@ -916,6 +916,19 @@ class Firefox extends Extract { continue; } + WebBrowserArtifactsHelper helper; + try { + // Helper to create web form address artifacts. + helper = new WebBrowserArtifactsHelper( + Case.getCurrentCaseThrows().getSleuthkitCase(), + NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"), + profileFile + ); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "No case open, bailing.", ex); //NON-NLS + return; + } + for (JsonElement result : jAddressesArray) { JsonObject address = result.getAsJsonObject(); if (address == null) { @@ -959,63 +972,9 @@ class Firefox extends Extract { String mailingAddress = makeFullAddress(addressLine1, addressLine2, addressLine3, postalCode, country ); try { - Collection bbattributes = new ArrayList<>(); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON, - RecentActivityExtracterModuleFactory.getModuleName(), - name)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL, - RecentActivityExtracterModuleFactory.getModuleName(), - email)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, - RecentActivityExtracterModuleFactory.getModuleName(), - phoneNumber)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION, - RecentActivityExtracterModuleFactory.getModuleName(), - mailingAddress)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, - RecentActivityExtracterModuleFactory.getModuleName(), - datetimeCreated)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, - RecentActivityExtracterModuleFactory.getModuleName(), - datetimeLastUsed)); //NON-NLS - - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, - RecentActivityExtracterModuleFactory.getModuleName(), - timesUsed)); //NON-NLS - - BlackboardArtifact bbart = profileFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS); - - if (bbart != null) { - bbart.addAttributes(bbattributes); - bbartifacts.add(bbart); - } - - // If an email address is found, create an account instance for it - if (email != null && !email.isEmpty()) { - try { - Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"), profileFile); - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating email account instance for '%s' from Firefox profiles file '%s' .", - email, profileFile.getName()), ex); //NON-NLS - } - } - - // If a phone number is found, create an account instance for it - if (phoneNumber != null && !phoneNumber.isEmpty()) { - try { - Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, phoneNumber, NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"), profileFile); - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating phone number account instance for '%s' from Chrome profiles file '%s' .", - phoneNumber, profileFile.getName()), ex); //NON-NLS - } - } - - } catch (TskCoreException ex) { + helper.addWebFormAddress(name, email, phoneNumber, + mailingAddress, datetimeCreated, datetimeLastUsed, timesUsed); + } catch (TskCoreException | Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Error while trying to insert Firefox Autofill profile artifact{0}", ex); //NON-NLS this.addErrorMessage( NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzingFile4", @@ -1024,8 +983,6 @@ class Firefox extends Extract { } dbFile.delete(); } - - postArtifacts(bbartifacts); } /** @@ -1118,4 +1075,4 @@ class Firefox extends Extract { return updatedAddress; } -} +} \ No newline at end of file