def testRuleDeviceConnection(self): """Tests the device_connection tagging rule.""" event = events.EventObject() event.timestamp = self._TEST_TIMESTAMP event.timestamp_desc = definitions.TIME_DESCRIPTION_UNKNOWN event_data = ipod.IPodPlistEventData() storage_writer = self._TagEvent(event, event_data) self.assertEqual(storage_writer.number_of_event_tags, 1) self._CheckLabels(storage_writer, ['device_connection']) event_data = plist_event.PlistTimeEventData() event_data.plugin = 'bogus' storage_writer = self._TagEvent(event, event_data) self.assertEqual(storage_writer.number_of_event_tags, 0) self._CheckLabels(storage_writer, []) event_data = plist_event.PlistTimeEventData() event_data.plugin = 'plist_airport' storage_writer = self._TagEvent(event, event_data) self.assertEqual(storage_writer.number_of_event_tags, 1) self._CheckLabels(storage_writer, ['device_connection'])
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant Volume Configuration Spotlight entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ stores = match.get('Stores', {}) for volume_name, volume in iter(stores.items()): datetime_value = volume.get('CreationDate', None) if not datetime_value: continue partial_path = volume['PartialPath'] event_data = plist_event.PlistTimeEventData() event_data.desc = 'Spotlight Volume {0:s} ({1:s}) activated.'.format( volume_name, partial_path) event_data.key = '' event_data.root = '/Stores' event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant Airport entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ if 'RememberedNetworks' not in match: return for wifi in match['RememberedNetworks']: ssid = wifi.get('SSIDString', 'UNKNOWN_SSID') security_type = wifi.get('SecurityType', 'UNKNOWN_SECURITY_TYPE') event_data = plist_event.PlistTimeEventData() event_data.desc = ( '[WiFi] Connected to network: <{0:s}> using security {1:s}' ).format(ssid, security_type) event_data.key = 'item' event_data.root = '/RememberedNetworks' datetime_value = wifi.get('LastConnected', None) if datetime_value: event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) else: date_time = dfdatetime_semantic_time.SemanticTime('Not set') event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_NOT_A_TIME) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant TimeMachine entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ destinations = match.get('Destinations', []) for destination in destinations: destination_identifier = (destination.get('DestinationID', None) or 'Unknown device') alias = destination.get('BackupAlias', '<ALIAS>') try: alias = self.TM_BACKUP_ALIAS.parse(alias).value alias = codecs.decode(alias, 'utf-8') except construct.FieldError: alias = 'Unknown alias' event_data = plist_event.PlistTimeEventData() event_data.desc = 'TimeMachine Backup in {0:s} ({1:s})'.format( alias, destination_identifier) event_data.key = 'item/SnapshotDates' event_data.root = '/Destinations' snapshot_dates = destination.get('SnapshotDates', []) for datetime_value in snapshot_dates: event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant Spotlight entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ shortcuts = match.get('UserShortcuts', {}) for search_text, data in iter(shortcuts.items()): datetime_value = data.get('LAST_USED', None) if not datetime_value: continue display_name = data.get('DISPLAY_NAME', '<DISPLAY_NAME>') path = data.get('PATH', '<PATH>') event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Spotlight term searched "{0:s}" associate to {1:s} ({2:s})' ).format(search_text, display_name, path) event_data.key = search_text event_data.root = '/UserShortcuts' timestamp = timelib.Timestamp.FromPythonDatetime(datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, top_level=None, **unused_kwargs): """Extracts relevant install history entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. top_level (dict[str, object]): plist top-level key. """ for entry in top_level: datetime_value = entry.get('date', None) package_identifiers = entry.get('packageIdentifiers', []) if not datetime_value or not package_identifiers: continue display_name = entry.get('displayName', '<UNKNOWN>') display_version = entry.get('displayVersion', '<DISPLAY_VERSION>') process_name = entry.get('processName', '<PROCESS_NAME>') package_identifiers = ', '.join(package_identifiers) event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Installation of [{0:s} {1:s}] using [{2:s}]. Packages: ' '{3:s}.').format(display_name, display_version, process_name, package_identifiers) event_data.key = '' event_data.root = '/item' event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, top_level=None, **unused_kwargs): """Extracts launchd information from the plist. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. top_level (Optional[dict[str, object]]): plist top-level item. """ label = top_level.get('Label') command = top_level.get('Program', '') program_arguments = top_level.get('ProgramArguments') for argument in program_arguments: command += " %s" % argument user_name = top_level.get('UserName') group_name = top_level.get('GroupName') event_data = plist_event.PlistTimeEventData() event_data.desc = ('Launchd service config {0:s} points to {1:s} with ' 'user:{2:s} group:{3:s}').format(label, command, user_name, group_name) event_data.key = 'launchdServiceConfig' event_data.root = '/' date_time = dfdatetime_semantic_time.NotSet() event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_NOT_A_TIME) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant Apple Account entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ accounts = match.get('Accounts', {}) for name_account, account in iter(accounts.items()): first_name = account.get('FirstName', '<FirstName>') last_name = account.get('LastName', '<LastName>') general_description = '{0:s} ({1:s} {2:s})'.format( name_account, first_name, last_name) event_data = plist_event.PlistTimeEventData() event_data.key = name_account event_data.root = '/Accounts' datetime_value = account.get('CreationDate', None) if datetime_value: event_data.desc = 'Configured Apple account {0:s}'.format( general_description) timestamp = timelib.Timestamp.FromPythonDatetime( datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) datetime_value = account.get('LastSuccessfulConnect', None) if datetime_value: event_data.desc = 'Connected Apple account {0:s}'.format( general_description) timestamp = timelib.Timestamp.FromPythonDatetime( datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) datetime_value = account.get('ValidationDate', None) if datetime_value: event_data.desc = 'Last validation Apple account {0:s}'.format( general_description) timestamp = timelib.Timestamp.FromPythonDatetime( datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant BT entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ device_cache = match.get('DeviceCache', {}) for device, value in iter(device_cache.items()): name = value.get('Name', '') if name: name = ''.join(('Name:', name)) event_data = plist_event.PlistTimeEventData() event_data.root = '/DeviceCache' datetime_value = value.get('LastInquiryUpdate', None) if datetime_value: event_data.desc = ' '.join( filter(None, ('Bluetooth Discovery', name))) event_data.key = '{0:s}/LastInquiryUpdate'.format(device) event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) if device in match.get('PairedDevices', []): event_data.desc = 'Paired:True {0:s}'.format(name) event_data.key = device event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData( event, event_data) datetime_value = value.get('LastNameUpdate', None) if datetime_value: event_data.desc = ' '.join( filter(None, ('Device Name Set', name))) event_data.key = '{0:s}/LastNameUpdate'.format(device) event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) datetime_value = value.get('LastServicesUpdate', None) if datetime_value: event_data.desc = ' '.join( filter(None, ('Services Updated', name))) event_data.key = '{0:s}/LastServicesUpdate'.format(device) event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def testGetAttributeNames(self): """Tests the GetAttributeNames function.""" attribute_container = plist_event.PlistTimeEventData() expected_attribute_names = [ 'data_type', 'desc', 'hostname', 'key', 'offset', 'query', 'root', 'username'] attribute_names = sorted(attribute_container.GetAttributeNames()) self.assertEqual(attribute_names, expected_attribute_names)
def testRuleApplicationInstall(self): """Tests the application_install tagging rule.""" event = events.EventObject() event.timestamp = self._TEST_TIMESTAMP event.timestamp_desc = definitions.TIME_DESCRIPTION_UNKNOWN event_data = plist_event.PlistTimeEventData() event_data.plugin = 'bogus' storage_writer = self._TagEvent(event, event_data) self.assertEqual(storage_writer.number_of_event_tags, 0) self._CheckLabels(storage_writer, []) event_data = plist_event.PlistTimeEventData() event_data.plugin = 'plist_install_history' storage_writer = self._TagEvent(event, event_data) self.assertEqual(storage_writer.number_of_event_tags, 1) self._CheckLabels(storage_writer, ['application_install'])
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant Mac OS X update entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ version = match.get(u'LastAttemptSystemVersion', u'N/A') pending = match.get(u'LastUpdatesAvailable', None) event_data = plist_event.PlistTimeEventData() event_data.desc = u'Last Mac OS X {0:s} full update.'.format(version) event_data.key = u'' event_data.root = u'/' datetime_value = match.get(u'LastFullSuccessfulDate', None) if datetime_value: timestamp = timelib.Timestamp.FromPythonDatetime(datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) datetime_value = match.get(u'LastSuccessfulDate', None) if datetime_value and pending: software = [] for update in match.get(u'RecommendedUpdates', []): identifier = update.get(u'Identifier', u'<IDENTIFIER>') product_key = update.get(u'Product Key', u'<PRODUCT_KEY>') software.append(u'{0:s}({1:s})'.format(identifier, product_key)) if not software: return software = u','.join(software) event_data.desc = ( u'Last Mac OS {0!s} partially update, pending {1!s}: ' u'{2:s}.').format(version, pending, software) timestamp = timelib.Timestamp.FromPythonDatetime(datetime_value) date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, **unused_kwargs): """Extracts entries for testing. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. """ event_data = plist_event.PlistTimeEventData() event_data.key = 'LastInquiryUpdate' event_data.root = '/DeviceCache/44-00-00-00-00-00' date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=1351827808261762) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, top_level=None, **unused_kwargs): """Simple method to exact date values from a Plist. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. top_level (dict[str, object]): plist top-level key. """ for root, key, datetime_value in interface.RecurseKey(top_level): if not isinstance(datetime_value, datetime.datetime): continue event_data = plist_event.PlistTimeEventData() event_data.key = key event_data.root = root event = time_events.PythonDatetimeEvent( datetime_value, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant TimeMachine entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ backup_alias_map = self._GetDataTypeMap('timemachine_backup_alias') destinations = match.get('Destinations', []) for destination in destinations: backup_alias_data = destination.get('BackupAlias', b'') try: backup_alias = self._ReadStructureFromByteStream( backup_alias_data, 0, backup_alias_map) alias = backup_alias.string except (ValueError, errors.ParseError) as exception: parser_mediator.ProduceExtractionWarning( 'unable to parse backup alias value with error: {0!s}'. format(exception)) alias = 'Unknown alias' destination_identifier = (destination.get('DestinationID', None) or 'Unknown device') event_data = plist_event.PlistTimeEventData() event_data.desc = 'TimeMachine Backup in {0:s} ({1:s})'.format( alias, destination_identifier) event_data.key = 'item/SnapshotDates' event_data.root = '/Destinations' snapshot_dates = destination.get('SnapshotDates', []) for datetime_value in snapshot_dates: date_time = dfdatetime_time_elements.TimeElementsInMicroseconds( ) date_time.CopyFromDatetime(datetime_value) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def _ParsePlist(self, parser_mediator, top_level=None, **unused_kwargs): """Extracts events from the values of entries within a plist. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. top_level (Optional[dict[str, object]]): plist top-level item. """ for root, key, datetime_value in self._RecurseKey(top_level): if not isinstance(datetime_value, datetime.datetime): continue event_data = plist_event.PlistTimeEventData() event_data.key = key event_data.root = root date_time = dfdatetime_time_elements.TimeElementsInMicroseconds() date_time.CopyFromDatetime(datetime_value) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant user timestamp entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ if 'name' not in match or 'uid' not in match: return account = match['name'][0] uid = match['uid'][0] for policy in match.get('passwordpolicyoptions', []): try: xml_policy = ElementTree.fromstring(policy) except (ElementTree.ParseError, LookupError) as exception: logger.error(( 'Unable to parse XML structure for an user policy, account: ' '{0:s} and uid: {1!s}, with error: {2!s}').format( account, uid, exception)) continue for dict_elements in xml_policy.iterfind('dict'): key_values = [value.text for value in iter(dict_elements)] # Taking a list and converting it to a dict, using every other item # as the key and the other one as the value. policy_dict = dict(zip(key_values[0::2], key_values[1::2])) time_string = policy_dict.get('passwordLastSetTime', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse password last set time string: {0:s}'. format(time_string)) shadow_hash_data = match.get('ShadowHashData', None) if date_time and isinstance(shadow_hash_data, (list, tuple)): # Extract the hash password information. # It is store in the attribute ShadowHasData which is # a binary plist data; However biplist only extracts one # level of binary plist, then it returns this information # as a string. # TODO: change this into a DataRange instead. For this we # need the file offset and size of the ShadowHashData value data. shadow_hash_data = shadow_hash_data[0] resolver_context = context.Context() fake_file = fake_file_io.FakeFile(resolver_context, shadow_hash_data) shadow_hash_data_path_spec = fake_path_spec.FakePathSpec( location='ShadowHashData') fake_file.open(path_spec=shadow_hash_data_path_spec) try: plist_file = biplist.readPlist(fake_file) except biplist.InvalidPlistException: plist_file = {} salted_hash = plist_file.get('SALTED-SHA512-PBKDF2', None) if salted_hash: salt_hex_bytes = codecs.encode(salted_hash['salt'], 'hex') salt_string = codecs.decode(salt_hex_bytes, 'ascii') entropy_hex_bytes = codecs.encode( salted_hash['entropy'], 'hex') entropy_string = codecs.decode(entropy_hex_bytes, 'ascii') password_hash = '$ml${0:d}${1:s}${2:s}'.format( salted_hash['iterations'], salt_string, entropy_string) else: password_hash = 'N/A' event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Last time {0:s} ({1!s}) changed the password: {2!s}' ).format(account, uid, password_hash) event_data.key = 'passwordLastSetTime' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData( event, event_data) time_string = policy_dict.get('lastLoginTimestamp', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse last login time string: {0:s}'.format( time_string)) if date_time: event_data = plist_event.PlistTimeEventData() event_data.desc = 'Last login from {0:s} ({1!s})'.format( account, uid) event_data.key = 'lastLoginTimestamp' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData( event, event_data) time_string = policy_dict.get('failedLoginTimestamp', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse failed login time string: {0:s}'. format(time_string)) if date_time: event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Last failed login from {0:s} ({1!s}) ({2!s} times)' ).format(account, uid, policy_dict.get('failedLoginCount', 0)) event_data.key = 'failedLoginTimestamp' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData( event, event_data)
def _ParsePlist(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant user timestamp entries. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. match (Optional[dict[str: object]]): keys extracted from PLIST_KEYS. """ if 'name' not in match or 'uid' not in match: return account = match['name'][0] uid = match['uid'][0] for policy in match.get('passwordpolicyoptions', []): try: xml_policy = ElementTree.fromstring(policy) except (LookupError, ElementTree.ParseError, expat.ExpatError) as exception: logger.error(( 'Unable to parse XML structure for an user policy, account: ' '{0:s} and uid: {1!s}, with error: {2!s}').format( account, uid, exception)) continue for dict_elements in xml_policy.iterfind('dict'): key_values = [value.text for value in dict_elements] # Taking a list and converting it to a dict, using every other item # as the key and the other one as the value. policy_dict = dict(zip(key_values[0::2], key_values[1::2])) time_string = policy_dict.get('passwordLastSetTime', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse password last set time string: {0:s}'.format( time_string)) shadow_hash_data = match.get('ShadowHashData', None) if date_time and isinstance(shadow_hash_data, (list, tuple)): # Extract the hash password information, which is stored in # the attribute ShadowHashData which is a binary plist data. try: property_list = plistlib.loads(shadow_hash_data[0]) except plistlib.InvalidFileException as exception: parser_mediator.ProduceExtractionWarning( 'unable to parse ShadowHashData with error: {0!s}'.format( exception)) property_list = {} password_hash = 'N/A' salted_hash = property_list.get('SALTED-SHA512-PBKDF2', None) if salted_hash: salt_hex_bytes = codecs.encode(salted_hash['salt'], 'hex') salt_string = codecs.decode(salt_hex_bytes, 'ascii') entropy_hex_bytes = codecs.encode(salted_hash['entropy'], 'hex') entropy_string = codecs.decode(entropy_hex_bytes, 'ascii') password_hash = '$ml${0:d}${1:s}${2:s}'.format( salted_hash['iterations'], salt_string, entropy_string) event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Last time {0:s} ({1!s}) changed the password: {2!s}').format( account, uid, password_hash) event_data.key = 'passwordLastSetTime' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) time_string = policy_dict.get('lastLoginTimestamp', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse last login time string: {0:s}'.format( time_string)) if date_time: event_data = plist_event.PlistTimeEventData() event_data.desc = 'Last login from {0:s} ({1!s})'.format( account, uid) event_data.key = 'lastLoginTimestamp' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data) time_string = policy_dict.get('failedLoginTimestamp', None) if time_string and time_string != '2001-01-01T00:00:00Z': try: date_time = dfdatetime_time_elements.TimeElements() date_time.CopyFromStringISO8601(time_string) except ValueError: date_time = None parser_mediator.ProduceExtractionWarning( 'unable to parse failed login time string: {0:s}'.format( time_string)) if date_time: event_data = plist_event.PlistTimeEventData() event_data.desc = ( 'Last failed login from {0:s} ({1!s}) ({2!s} times)').format( account, uid, policy_dict.get('failedLoginCount', 0)) event_data.key = 'failedLoginTimestamp' event_data.root = self._ROOT event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_WRITTEN) parser_mediator.ProduceEventWithEventData(event, event_data)