"""A parser for the Chrome Preferences file."""
import codecs
import json
import os
from dfdatetime import posix_time as dfdatetime_posix_time
from dfdatetime import webkit_time as dfdatetime_webkit_time
from plaso.containers import events
from plaso.lib import errors
from plaso.parsers import interface
from plaso.parsers import manager
[docs]
class ChromeContentSettingsExceptionsEventData(events.EventData):
"""Chrome content settings exceptions event data.
Attributes:
last_visited_time (dfdatetime.DateTimeValues): date and time the URL was
last visited.
permission (str): permission.
primary_url (str): primary URL.
secondary_url (str): secondary URL.
"""
DATA_TYPE = "chrome:preferences:content_settings:exceptions"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.last_visited_time = None
self.permission = None
self.primary_url = None
self.secondary_url = None
[docs]
class ChromeExtensionsAutoupdaterEventData(events.EventData):
"""Chrome Extension Autoupdater event data.
Attributes:
message (str): message.
recorded_time (dfdatetime.DateTimeValues): date and time the entry
was recorded.
"""
DATA_TYPE = "chrome:preferences:extensions_autoupdater"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
# TODO: refactor this in something more descriptive.
self.message = None
self.recorded_time = None
[docs]
class ChromeExtensionInstallationEventData(events.EventData):
"""Chrome extension event data.
Attributes:
extension_identifier (str): extension identifier.
extension_name (str): extension name.
installation_time (dfdatetime.DateTimeValues): date and time the Chrome
extension was installed.
path (str): path.
"""
DATA_TYPE = "chrome:preferences:extension_installation"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.extension_identifier = None
self.extension_name = None
self.installation_time = None
self.path = None
[docs]
class ChromePreferencesParser(interface.FileObjectParser):
"""Parses Chrome Preferences files."""
NAME = "chrome_preferences"
DATA_FORMAT = "Google Chrome Preferences file"
REQUIRED_KEYS = frozenset(["browser", "extensions"])
_ENCODING = "utf-8"
# TODO: add site_engagement & ssl_cert_decisions.
_EXCEPTIONS_KEYS = frozenset(
[
"geolocation",
"media_stream_camera",
"media_stream_mic",
"midi_sysex",
"notifications",
"push_messaging",
]
)
_MAXIMUM_FILE_SIZE = 16 * 1024 * 1024
def _ExtractExtensionInstallEvents(self, settings_dict, parser_mediator):
"""Extract extension installation events.
Args:
settings_dict (dict[str: object]): settings data from a Chrome Preferences
file.
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
"""
for extension_id, extension in sorted(settings_dict.items()):
install_time = extension.get("install_time")
if not install_time:
parser_mediator.ProduceExtractionWarning(
f"installation time missing for extension ID {extension_id!s}"
)
continue
try:
install_time = int(install_time, 10)
except ValueError:
parser_mediator.ProduceExtractionWarning(
(
f"unable to convert installation time for extension ID "
f"{extension_id!s}"
)
)
continue
manifest = extension.get("manifest")
if not manifest:
parser_mediator.ProduceExtractionWarning(
f"manifest missing for extension ID {extension_id!s}"
)
continue
event_data = ChromeExtensionInstallationEventData()
event_data.extension_identifier = extension_id
event_data.extension_name = manifest.get("name")
event_data.installation_time = dfdatetime_webkit_time.WebKitTime(
timestamp=install_time
)
event_data.path = extension.get("path")
parser_mediator.ProduceEventData(event_data)
def _ExtractContentSettingsExceptions(self, exceptions_dict, parser_mediator):
"""Extracts site specific events.
Args:
exceptions_dict (dict): Permission exceptions data from Chrome Preferences
file.
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
"""
for permission in exceptions_dict:
if permission not in self._EXCEPTIONS_KEYS:
continue
exception_dict = exceptions_dict.get(permission, {})
for urls, url_dict in exception_dict.items():
last_used = url_dict.get("last_used")
if not last_used:
continue
timestamp = int(last_used * 1000000)
# If secondary_url is '*', the permission applies to primary_url.
# If secondary_url is a valid URL, the permission applies to
# elements loaded from secondary_url being embedded in primary_url.
primary_url, secondary_url = urls.split(",")
event_data = ChromeContentSettingsExceptionsEventData()
event_data.last_visited_time = (
dfdatetime_posix_time.PosixTimeInMicroseconds(timestamp=timestamp)
)
event_data.permission = permission
event_data.primary_url = primary_url or None
event_data.secondary_url = secondary_url or None
parser_mediator.ProduceEventData(event_data)
[docs]
def ParseFileObject(self, parser_mediator, file_object):
"""Parses a Chrome preferences file-like object.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
file_object (dfvfs.FileIO): file-like object.
Raises:
WrongParser: when the file cannot be parsed.
"""
# First check for initial character being open brace.
if file_object.read(1) != b"{":
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] {display_name!s} is not a valid Chrome Preferences "
f"file, missing opening brace"
)
file_object.seek(0, os.SEEK_SET)
# Note that _MAXIMUM_FILE_SIZE prevents this read to become too large.
file_content = file_object.read()
try:
file_content = codecs.decode(file_content, self._ENCODING)
except UnicodeDecodeError:
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] {display_name!s} is not a valid Chrome Preferences "
f"file, unable to decode as UTF-8"
)
# Second verify it is valid JSON.
try:
json_dict = json.loads(file_content)
except OSError as exception:
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] Unable to open file {display_name!s} with error: "
f"{exception!s}"
)
except ValueError as exception:
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] {display_name!s} is not a valid Chrome Preferences "
f"file, unable to decode as JSON with error: {exception!s}"
)
# Third verify the file has the correct keys for a Chrome Preferences file.
if not set(self.REQUIRED_KEYS).issubset(set(json_dict.keys())):
raise errors.WrongParser("File does not contain Chrome Preferences data")
extensions_setting_dict = json_dict.get("extensions")
if not extensions_setting_dict:
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] {display_name!s} is not a valid Chrome Preferences "
f"file, does not contain extensions value"
)
extensions_dict = extensions_setting_dict.get("settings")
if not extensions_dict:
display_name = parser_mediator.GetDisplayName()
raise errors.WrongParser(
f"[{self.NAME!s}] {display_name!s} is not a valid Chrome Preferences "
f"file, does not contain extensions settings value"
)
extensions_autoupdate_dict = extensions_setting_dict.get("autoupdate")
if extensions_autoupdate_dict:
autoupdate_lastcheck_timestamp = extensions_autoupdate_dict.get(
"last_check"
)
if autoupdate_lastcheck_timestamp:
autoupdate_lastcheck = int(autoupdate_lastcheck_timestamp, 10)
event_data = ChromeExtensionsAutoupdaterEventData()
event_data.message = "Chrome extensions autoupdater last run"
event_data.recorded_time = dfdatetime_webkit_time.WebKitTime(
timestamp=autoupdate_lastcheck
)
parser_mediator.ProduceEventData(event_data)
autoupdate_nextcheck_timestamp = extensions_autoupdate_dict.get(
"next_check"
)
if autoupdate_nextcheck_timestamp:
autoupdate_nextcheck = int(autoupdate_nextcheck_timestamp, 10)
event_data = ChromeExtensionsAutoupdaterEventData()
event_data.message = "Chrome extensions autoupdater next run"
event_data.recorded_time = dfdatetime_webkit_time.WebKitTime(
timestamp=autoupdate_nextcheck
)
parser_mediator.ProduceEventData(event_data)
browser_dict = json_dict.get("browser")
if browser_dict and "last_clear_browsing_data_time" in browser_dict:
last_clear_history_timestamp = browser_dict.get(
"last_clear_browsing_data_time"
)
if last_clear_history_timestamp:
last_clear_history = int(last_clear_history_timestamp, 10)
event_data = ChromeExtensionsAutoupdaterEventData()
event_data.message = "Chrome history was cleared by user"
event_data.recorded_time = dfdatetime_webkit_time.WebKitTime(
timestamp=last_clear_history
)
parser_mediator.ProduceEventData(event_data)
self._ExtractExtensionInstallEvents(extensions_dict, parser_mediator)
profile_dict = json_dict.get("profile")
if profile_dict:
content_settings_dict = profile_dict.get("content_settings")
if content_settings_dict:
exceptions_dict = content_settings_dict.get("exceptions")
if exceptions_dict:
self._ExtractContentSettingsExceptions(
exceptions_dict, parser_mediator
)
manager.ParsersManager.RegisterParser(ChromePreferencesParser)