From c8ef67a79ea4f4e8df49d9de6e869c2a94de2995 Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Thu, 20 Jan 2022 13:02:36 +0100 Subject: [PATCH] Added CS type to replace FVDE #83 --- config/dpkg/control | 2 +- dependencies.ini | 2 +- dfvfs/credentials/__init__.py | 1 + dfvfs/credentials/cs_credentials.py | 19 ++ dfvfs/file_io/cs_file_io.py | 163 +++++++++ dfvfs/file_io/lvm_file_io.py | 6 +- dfvfs/lib/apfs_helper.py | 8 +- dfvfs/lib/cs_helper.py | 35 ++ dfvfs/lib/definitions.py | 7 +- dfvfs/lib/{lvm.py => lvm_helper.py} | 8 +- dfvfs/path/__init__.py | 1 + dfvfs/path/cs_path_spec.py | 73 ++++ dfvfs/path/lvm_path_spec.py | 4 +- dfvfs/resolver_helpers/__init__.py | 6 + dfvfs/resolver_helpers/cs_resolver_helper.py | 41 +++ dfvfs/vfs/cs_directory.py | 33 ++ dfvfs/vfs/cs_file_entry.py | 119 +++++++ dfvfs/vfs/cs_file_system.py | 162 +++++++++ dfvfs/vfs/lvm_file_entry.py | 5 +- dfvfs/vfs/lvm_file_system.py | 9 +- docs/sources/Path-specifications.md | 341 ++++++++++--------- docs/sources/Supported-formats.md | 2 +- requirements.txt | 2 +- setup.cfg | 2 +- tests/credentials/cs_credentials.py | 22 ++ tests/file_io/cs_file_io.py | 126 +++++++ tests/file_io/fvde_file_io.py | 3 - tests/file_io/lvm_file_io.py | 2 +- tests/lib/cs_helper.py | 61 ++++ tests/lib/lvm_helper.py | 61 ++++ tests/path/cs_path_spec.py | 94 +++++ tests/resolver_helpers/cs_resolver_helper.py | 52 +++ tests/vfs/cs_file_entry.py | 241 +++++++++++++ tests/vfs/cs_file_system.py | 178 ++++++++++ tests/vfs/fvde_file_entry.py | 1 + tests/vfs/lvm_file_entry.py | 1 + tests/vfs/lvm_file_system.py | 1 + 37 files changed, 1710 insertions(+), 184 deletions(-) create mode 100644 dfvfs/credentials/cs_credentials.py create mode 100644 dfvfs/file_io/cs_file_io.py create mode 100644 dfvfs/lib/cs_helper.py rename dfvfs/lib/{lvm.py => lvm_helper.py} (72%) create mode 100644 dfvfs/path/cs_path_spec.py create mode 100644 dfvfs/resolver_helpers/cs_resolver_helper.py create mode 100644 dfvfs/vfs/cs_directory.py create mode 100644 dfvfs/vfs/cs_file_entry.py create mode 100644 dfvfs/vfs/cs_file_system.py create mode 100644 tests/credentials/cs_credentials.py create mode 100644 tests/file_io/cs_file_io.py create mode 100644 tests/lib/cs_helper.py create mode 100644 tests/lib/lvm_helper.py create mode 100644 tests/path/cs_path_spec.py create mode 100644 tests/resolver_helpers/cs_resolver_helper.py create mode 100644 tests/vfs/cs_file_entry.py create mode 100644 tests/vfs/cs_file_system.py diff --git a/config/dpkg/control b/config/dpkg/control index 88b30baa..e1389962 100644 --- a/config/dpkg/control +++ b/config/dpkg/control @@ -9,7 +9,7 @@ Homepage: https://github.com/log2timeline/dfvfs Package: python3-dfvfs Architecture: all -Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20220112), libfshfs-python3 (>= 20220115), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220113), libfvde-python3 (>= 20160719), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20200101), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220110), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20211113), python3-dtfabric (>= 20170524), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} +Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20220112), libfshfs-python3 (>= 20220115), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220113), libfvde-python3 (>= 20220120), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20200101), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220110), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20211113), python3-dtfabric (>= 20170524), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} Description: Python 3 module of dfVFS dfVFS, or Digital Forensics Virtual File System, provides read-only access to file-system objects from various storage media types and file formats. The goal diff --git a/dependencies.ini b/dependencies.ini index 30f6e4f7..7e88040d 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -90,7 +90,7 @@ version_property: get_version() [pyfvde] dpkg_name: libfvde-python3 l2tbinaries_name: libfvde -minimum_version: 20160719 +minimum_version: 20220120 pypi_name: libfvde-python rpm_name: libfvde-python3 version_property: get_version() diff --git a/dfvfs/credentials/__init__.py b/dfvfs/credentials/__init__.py index b9ecd69c..f9b93482 100644 --- a/dfvfs/credentials/__init__.py +++ b/dfvfs/credentials/__init__.py @@ -3,6 +3,7 @@ from dfvfs.credentials import apfs_credentials from dfvfs.credentials import bde_credentials +from dfvfs.credentials import cs_credentials from dfvfs.credentials import encrypted_stream_credentials from dfvfs.credentials import fvde_credentials from dfvfs.credentials import luksde_credentials diff --git a/dfvfs/credentials/cs_credentials.py b/dfvfs/credentials/cs_credentials.py new file mode 100644 index 00000000..b329957a --- /dev/null +++ b/dfvfs/credentials/cs_credentials.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) credentials.""" + +from dfvfs.credentials import credentials +from dfvfs.credentials import manager +from dfvfs.lib import definitions + + +class CSCredentials(credentials.Credentials): + """Core Storage (CS) credentials.""" + + # TODO: add support for key_data credential, needs pyfvde update. + CREDENTIALS = frozenset([ + 'encrypted_root_plist', 'password', 'recovery_password']) + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_CS + + +manager.CredentialsManager.RegisterCredentials(CSCredentials()) diff --git a/dfvfs/file_io/cs_file_io.py b/dfvfs/file_io/cs_file_io.py new file mode 100644 index 00000000..03509f6d --- /dev/null +++ b/dfvfs/file_io/cs_file_io.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) file-like object.""" + +import os + +from dfvfs.file_io import file_io +from dfvfs.lib import cs_helper +from dfvfs.lib import errors +from dfvfs.resolver import resolver + + +class CSFile(file_io.FileIO): + """File input/output (IO) object using pyfvde.""" + + def __init__(self, resolver_context, path_spec): + """Initializes a file input/output (IO) object. + + Args: + resolver_context (Context): resolver context. + path_spec (PathSpec): a path specification. + """ + super(CSFile, self).__init__(resolver_context, path_spec) + self._file_system = None + self._fvde_logical_volume = None + + def _Close(self): + """Closes the file-like object.""" + self._fvde_logical_volume = None + + self._file_system = None + + def _Open(self, mode='rb'): + """Opens the file-like object defined by path specification. + + Args: + mode (Optional[str]): file access mode. + + Raises: + AccessError: if the access to open the file was denied. + IOError: if the file-like object could not be opened. + OSError: if the file-like object could not be opened. + PathSpecError: if the path specification is incorrect. + """ + volume_index = cs_helper.CSPathSpecGetVolumeIndex(self._path_spec) + if volume_index is None: + raise errors.PathSpecError( + 'Unable to retrieve volume index from path specification.') + + self._file_system = resolver.Resolver.OpenFileSystem( + self._path_spec, resolver_context=self._resolver_context) + fvde_volume_group = self._file_system.GetFVDEVolumeGroup() + + if (volume_index < 0 or + volume_index >= fvde_volume_group.number_of_logical_volumes): + raise errors.PathSpecError(( + 'Unable to retrieve logical volume index: {0:d} from path ' + 'specification.').format(volume_index)) + + self._fvde_logical_volume = fvde_volume_group.get_logical_volume( + volume_index) + + self._UnlockFVDELogicalVolume(self._fvde_logical_volume, self._path_spec) + + def _UnlockFVDELogicalVolume(self, fvde_logical_volume, path_spec): + """Unlocks the Core Storage (CS) logical volume if necessary. + + Args: + fvde_logical_volume (pyfvde.logical_volume): Core Storage (CS) logical + volume. + path_spec (PathSpec): path specification. + """ + if fvde_logical_volume.is_locked(): + resolver.Resolver.key_chain.ExtractCredentialsFromPathSpec(path_spec) + + password = resolver.Resolver.key_chain.GetCredential( + path_spec, 'password') + if password: + fvde_logical_volume.set_password(password) + + recovery_password = resolver.Resolver.key_chain.GetCredential( + path_spec, 'recovery_password') + if recovery_password: + fvde_logical_volume.set_recovery_password(recovery_password) + + fvde_logical_volume.unlock() + + @property + def is_locked(self): + """bool: True if the volume is locked.""" + return self._fvde_logical_volume.is_locked() + + # Note: that the following functions do not follow the style guide + # because they are part of the file-like object interface. + # pylint: disable=invalid-name + + def read(self, size=None): + """Reads a byte string from the file-like object at the current offset. + + The function will read a byte string of the specified size or + all of the remaining data if no size was specified. + + Args: + size (Optional[int]): number of bytes to read, where None is all + remaining data. + + Returns: + bytes: data read. + + Raises: + IOError: if the read failed. + OSError: if the read failed. + """ + if not self._is_open: + raise IOError('Not opened.') + + return self._fvde_logical_volume.read(size) + + def seek(self, offset, whence=os.SEEK_SET): + """Seeks to an offset within the file-like object. + + Args: + offset (int): offset to seek to. + whence (Optional(int)): value that indicates whether offset is an absolute + or relative position within the file. + + Raises: + IOError: if the seek failed. + OSError: if the seek failed. + """ + if not self._is_open: + raise IOError('Not opened.') + + self._fvde_logical_volume.seek(offset, whence) + + def get_offset(self): + """Retrieves the current offset into the file-like object. + + Returns: + int: current offset into the file-like object. + + Raises: + IOError: if the file-like object has not been opened. + OSError: if the file-like object has not been opened. + """ + if not self._is_open: + raise IOError('Not opened.') + + return self._fvde_logical_volume.get_offset() + + def get_size(self): + """Retrieves the size of the file-like object. + + Returns: + int: size of the file-like object data. + + Raises: + IOError: if the file-like object has not been opened. + OSError: if the file-like object has not been opened. + """ + if not self._is_open: + raise IOError('Not opened.') + + return self._fvde_logical_volume.size diff --git a/dfvfs/file_io/lvm_file_io.py b/dfvfs/file_io/lvm_file_io.py index 94d150cf..076f3538 100644 --- a/dfvfs/file_io/lvm_file_io.py +++ b/dfvfs/file_io/lvm_file_io.py @@ -5,7 +5,7 @@ from dfvfs.file_io import file_io from dfvfs.lib import errors -from dfvfs.lib import lvm +from dfvfs.lib import lvm_helper from dfvfs.resolver import resolver @@ -41,7 +41,7 @@ def _Open(self, mode='rb'): OSError: if the file-like object could not be opened. PathSpecError: if the path specification is incorrect. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(self._path_spec) + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(self._path_spec) if volume_index is None: raise errors.PathSpecError( 'Unable to retrieve volume index from path specification.') @@ -53,7 +53,7 @@ def _Open(self, mode='rb'): if (volume_index < 0 or volume_index >= vslvm_volume_group.number_of_logical_volumes): raise errors.PathSpecError(( - 'Unable to retrieve LVM logical volume index: {0:d} from path ' + 'Unable to retrieve logical volume index: {0:d} from path ' 'specification.').format(volume_index)) self._vslvm_logical_volume = vslvm_volume_group.get_logical_volume( diff --git a/dfvfs/lib/apfs_helper.py b/dfvfs/lib/apfs_helper.py index 6615427a..dda08419 100644 --- a/dfvfs/lib/apfs_helper.py +++ b/dfvfs/lib/apfs_helper.py @@ -2,6 +2,10 @@ """Helper functions for Apple File System (APFS) support.""" +_APFS_LOCATION_PREFIX = '/apfs' +_APFS_LOCATION_PREFIX_LENGTH = len(_APFS_LOCATION_PREFIX) + + def APFSContainerPathSpecGetVolumeIndex(path_spec): """Retrieves the volume index from the path specification. @@ -16,11 +20,11 @@ def APFSContainerPathSpecGetVolumeIndex(path_spec): return volume_index location = getattr(path_spec, 'location', None) - if location is None or not location.startswith('/apfs'): + if location is None or not location.startswith(_APFS_LOCATION_PREFIX): return None try: - volume_index = int(location[5:], 10) - 1 + volume_index = int(location[_APFS_LOCATION_PREFIX_LENGTH:], 10) - 1 except (TypeError, ValueError): volume_index = None diff --git a/dfvfs/lib/cs_helper.py b/dfvfs/lib/cs_helper.py new file mode 100644 index 00000000..2479aa1c --- /dev/null +++ b/dfvfs/lib/cs_helper.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""Helper function for Core Storage (CS) support.""" + + +_CS_LOCATION_PREFIX = '/cs' +_CS_LOCATION_PREFIX_LENGTH = len(_CS_LOCATION_PREFIX) + + +def CSPathSpecGetVolumeIndex(path_spec): + """Retrieves the volume index from the path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + int: volume index or None if not available. + """ + volume_index = getattr(path_spec, 'volume_index', None) + + if volume_index is None: + location = getattr(path_spec, 'location', None) + + if location is None or not location.startswith(_CS_LOCATION_PREFIX): + return None + + volume_index = None + try: + volume_index = int(location[_CS_LOCATION_PREFIX_LENGTH:], 10) - 1 + except ValueError: + pass + + if volume_index is None or volume_index < 0: + return None + + return volume_index diff --git a/dfvfs/lib/definitions.py b/dfvfs/lib/definitions.py index 5d4775cd..eb9ebb31 100644 --- a/dfvfs/lib/definitions.py +++ b/dfvfs/lib/definitions.py @@ -38,13 +38,13 @@ TYPE_INDICATOR_BZIP2 = 'BZIP2' TYPE_INDICATOR_COMPRESSED_STREAM = 'COMPRESSED_STREAM' TYPE_INDICATOR_CPIO = 'CPIO' +TYPE_INDICATOR_CS = 'CS' TYPE_INDICATOR_DATA_RANGE = 'DATA_RANGE' TYPE_INDICATOR_ENCODED_STREAM = 'ENCODED_STREAM' TYPE_INDICATOR_ENCRYPTED_STREAM = 'ENCRYPTED_STREAM' TYPE_INDICATOR_EWF = 'EWF' TYPE_INDICATOR_EXT = 'EXT' TYPE_INDICATOR_FAKE = 'FAKE' -TYPE_INDICATOR_FVDE = 'FVDE' TYPE_INDICATOR_GPT = 'GPT' TYPE_INDICATOR_GZIP = 'GZIP' TYPE_INDICATOR_HFS = 'HFS' @@ -68,9 +68,13 @@ TYPE_INDICATOR_XZ = 'XZ' TYPE_INDICATOR_ZIP = 'ZIP' +# Deprecated type indicator definitions. +TYPE_INDICATOR_FVDE = 'FVDE' + TYPE_INDICATORS_WITH_ENCRYPTION_SUPPORT = frozenset([ TYPE_INDICATOR_APFS_CONTAINER, TYPE_INDICATOR_BDE, + TYPE_INDICATOR_CS, TYPE_INDICATOR_FVDE, TYPE_INDICATOR_LUKSDE]) @@ -78,6 +82,7 @@ # Volume types that support encryption. ENCRYPTED_VOLUME_TYPE_INDICATORS = frozenset([ TYPE_INDICATOR_BDE, + TYPE_INDICATOR_CS, TYPE_INDICATOR_FVDE, TYPE_INDICATOR_LUKSDE]) diff --git a/dfvfs/lib/lvm.py b/dfvfs/lib/lvm_helper.py similarity index 72% rename from dfvfs/lib/lvm.py rename to dfvfs/lib/lvm_helper.py index 9c4f9634..409ab7d9 100644 --- a/dfvfs/lib/lvm.py +++ b/dfvfs/lib/lvm_helper.py @@ -2,6 +2,10 @@ """Helper functions for Logical Volume Manager (LVM) support.""" +_LVM_LOCATION_PREFIX = '/lvm' +_LVM_LOCATION_PREFIX_LENGTH = len(_LVM_LOCATION_PREFIX) + + def LVMPathSpecGetVolumeIndex(path_spec): """Retrieves the volume index from the path specification. @@ -16,12 +20,12 @@ def LVMPathSpecGetVolumeIndex(path_spec): if volume_index is None: location = getattr(path_spec, 'location', None) - if location is None or not location.startswith('/lvm'): + if location is None or not location.startswith(_LVM_LOCATION_PREFIX): return None volume_index = None try: - volume_index = int(location[4:], 10) - 1 + volume_index = int(location[_LVM_LOCATION_PREFIX_LENGTH:], 10) - 1 except ValueError: pass diff --git a/dfvfs/path/__init__.py b/dfvfs/path/__init__.py index 8459dea4..3c05ebe2 100644 --- a/dfvfs/path/__init__.py +++ b/dfvfs/path/__init__.py @@ -6,6 +6,7 @@ from dfvfs.path import bde_path_spec from dfvfs.path import compressed_stream_path_spec from dfvfs.path import cpio_path_spec +from dfvfs.path import cs_path_spec from dfvfs.path import data_range_path_spec from dfvfs.path import encoded_stream_path_spec from dfvfs.path import encrypted_stream_path_spec diff --git a/dfvfs/path/cs_path_spec.py b/dfvfs/path/cs_path_spec.py new file mode 100644 index 00000000..4ffc3fb6 --- /dev/null +++ b/dfvfs/path/cs_path_spec.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) path specification implementation.""" + +from dfvfs.lib import definitions +from dfvfs.path import factory +from dfvfs.path import path_spec + + +class CSPathSpec(path_spec.PathSpec): + """CS path specification. + + Attributes: + encrypted_root_plist (str): path to the EncryptedRoot.plist.wipekey file. + location (str): location. + password (str): password. + recovery_password (str): recovery password. + volume_index (int): logical volume index. + """ + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_CS + + def __init__( + self, encrypted_root_plist=None, location=None, password=None, + parent=None, recovery_password=None, volume_index=None, **kwargs): + """Initializes a path specification. + + Note that the CS path specification must have a parent. + + Args: + encrypted_root_plist (Optional[str]): path to the + EncryptedRoot.plist.wipekey file. + location (Optional[str]): location. + password (Optional[str]): password. + parent (Optional[PathSpec]): parent path specification. + recovery_password (Optional[str]): recovery password. + volume_index (Optional[int]): logical volume index. + + Raises: + ValueError: when parent is not set. + """ + if not parent: + raise ValueError('Missing parent value.') + + super(CSPathSpec, self).__init__(parent=parent, **kwargs) + self.encrypted_root_plist = encrypted_root_plist + self.location = location + self.password = password + self.recovery_password = recovery_password + self.volume_index = volume_index + + @property + def comparable(self): + """str: comparable representation of the path specification.""" + string_parts = [] + + if self.encrypted_root_plist: + string_parts.append('encrypted_root_plist: {0:s}'.format( + self.encrypted_root_plist)) + if self.location is not None: + string_parts.append('location: {0:s}'.format(self.location)) + if self.password: + string_parts.append('password: {0:s}'.format(self.password)) + if self.recovery_password: + string_parts.append('recovery_password: {0:s}'.format( + self.recovery_password)) + if self.volume_index is not None: + string_parts.append('volume index: {0:d}'.format(self.volume_index)) + + return self._GetComparable(sub_comparable_string=', '.join(string_parts)) + + +# Register the path specification with the factory. +factory.Factory.RegisterPathSpec(CSPathSpec) diff --git a/dfvfs/path/lvm_path_spec.py b/dfvfs/path/lvm_path_spec.py index 7130c0c1..a7ab39da 100644 --- a/dfvfs/path/lvm_path_spec.py +++ b/dfvfs/path/lvm_path_spec.py @@ -11,7 +11,7 @@ class LVMPathSpec(path_spec.PathSpec): Attributes: location (str): location. - volume_index (int): volume index. + volume_index (int): logical volume index. """ TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM @@ -24,7 +24,7 @@ def __init__(self, location=None, parent=None, volume_index=None, **kwargs): Args: location (Optional[str]): location. parent (Optional[PathSpec]): parent path specification. - volume_index (Optional[int]): volume index. + volume_index (Optional[int]): logical volume index. Raises: ValueError: when parent is not set. diff --git a/dfvfs/resolver_helpers/__init__.py b/dfvfs/resolver_helpers/__init__.py index 6c5876f2..ca7d9d5a 100644 --- a/dfvfs/resolver_helpers/__init__.py +++ b/dfvfs/resolver_helpers/__init__.py @@ -14,6 +14,12 @@ from dfvfs.resolver_helpers import compressed_stream_resolver_helper from dfvfs.resolver_helpers import cpio_resolver_helper + +try: + from dfvfs.resolver_helpers import cs_resolver_helper +except ImportError: + pass + from dfvfs.resolver_helpers import data_range_resolver_helper from dfvfs.resolver_helpers import encoded_stream_resolver_helper from dfvfs.resolver_helpers import encrypted_stream_resolver_helper diff --git a/dfvfs/resolver_helpers/cs_resolver_helper.py b/dfvfs/resolver_helpers/cs_resolver_helper.py new file mode 100644 index 00000000..df2cda9d --- /dev/null +++ b/dfvfs/resolver_helpers/cs_resolver_helper.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +"""The CS path specification resolver helper implementation.""" + +from dfvfs.file_io import cs_file_io +from dfvfs.lib import definitions +from dfvfs.resolver_helpers import manager +from dfvfs.resolver_helpers import resolver_helper +from dfvfs.vfs import cs_file_system + + +class CSResolverHelper(resolver_helper.ResolverHelper): + """Logical Volume Manager (CS) resolver helper.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_CS + + def NewFileObject(self, resolver_context, path_spec): + """Creates a new file input/output (IO) object. + + Args: + resolver_context (Context): resolver context. + path_spec (PathSpec): a path specification. + + Returns: + FileIO: file input/output (IO) object. + """ + return cs_file_io.CSFile(resolver_context, path_spec) + + def NewFileSystem(self, resolver_context, path_spec): + """Creates a new file system object. + + Args: + resolver_context (Context): resolver context. + path_spec (PathSpec): a path specification. + + Returns: + FileSystem: file system. + """ + return cs_file_system.CSFileSystem(resolver_context, path_spec) + + +manager.ResolverHelperManager.RegisterHelper(CSResolverHelper()) diff --git a/dfvfs/vfs/cs_directory.py b/dfvfs/vfs/cs_directory.py new file mode 100644 index 00000000..f13321bb --- /dev/null +++ b/dfvfs/vfs/cs_directory.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) directory implementation.""" + +from dfvfs.path import cs_path_spec +from dfvfs.vfs import directory + + +class CSDirectory(directory.Directory): + """File system directory that uses pyfvde.""" + + def _EntriesGenerator(self): + """Retrieves directory entries. + + Since a directory can contain a vast number of entries using + a generator is more memory efficient. + + Yields: + CSPathSpec: a path specification. + """ + volume_index = getattr(self.path_spec, 'volume_index', None) + location = getattr(self.path_spec, 'location', None) + + # Only the virtual root file has directory entries. + if (volume_index is None and location is not None and + location == self._file_system.LOCATION_ROOT): + fvde_volume_group = self._file_system.GetFVDEVolumeGroup() + + for volume_index in range( + 0, fvde_volume_group.number_of_logical_volumes): + location = '/cs{0:d}'.format(volume_index + 1) + yield cs_path_spec.CSPathSpec( + location=location, parent=self.path_spec.parent, + volume_index=volume_index) diff --git a/dfvfs/vfs/cs_file_entry.py b/dfvfs/vfs/cs_file_entry.py new file mode 100644 index 00000000..1e7748a1 --- /dev/null +++ b/dfvfs/vfs/cs_file_entry.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) file entry implementation.""" + +from dfvfs.lib import cs_helper +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.vfs import cs_directory +from dfvfs.vfs import file_entry + + +class CSFileEntry(file_entry.FileEntry): + """File system file entry that uses pyfvde.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_CS + + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False, fvde_logical_volume=None): + """Initializes a file entry. + + Args: + resolver_context (Context): resolver context. + file_system (FileSystem): file system. + path_spec (PathSpec): path specification. + is_root (Optional[bool]): True if the file entry is the root file entry + of the corresponding file system. + is_virtual (Optional[bool]): True if the file entry is a virtual file + fvde_logical_volume (Optional[pyfvde.logical_volume]): a Core Storage + logical volume. + + Raises: + BackEndError: when Core Storage logical volume is missing for + a non-virtual file entry. + """ + if not is_virtual and fvde_logical_volume is None: + fvde_logical_volume = file_system.GetFVDELogicalVolumeByPathSpec( + path_spec) + if not is_virtual and fvde_logical_volume is None: + raise errors.BackEndError( + 'Missing fvde logical volume in non-virtual file entry.') + + super(CSFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._name = None + self._fvde_logical_volume = fvde_logical_volume + + if self._is_virtual: + self.entry_type = definitions.FILE_ENTRY_TYPE_DIRECTORY + else: + self.entry_type = definitions.FILE_ENTRY_TYPE_FILE + + def _GetDirectory(self): + """Retrieves the directory. + + Returns: + CSDirectory: a directory. + """ + if self.entry_type != definitions.FILE_ENTRY_TYPE_DIRECTORY: + return None + + return cs_directory.CSDirectory(self._file_system, self.path_spec) + + def _GetSubFileEntries(self): + """Retrieves sub file entries. + + Yields: + CSFileEntry: a sub file entry. + """ + if self._directory is None: + self._directory = self._GetDirectory() + + if self._directory: + for path_spec in self._directory.entries: + yield CSFileEntry(self._resolver_context, self._file_system, path_spec) + + @property + def name(self): + """str: name of the file entry, without the full path.""" + if self._name is None: + location = getattr(self.path_spec, 'location', None) + if location is not None: + self._name = self._file_system.BasenamePath(location) + else: + volume_index = getattr(self.path_spec, 'volume_index', None) + if volume_index is not None: + self._name = 'cs{0:d}'.format(volume_index + 1) + else: + self._name = '' + + return self._name + + @property + def size(self): + """int: size of the file entry in bytes or None if not available.""" + if self._fvde_logical_volume is None: + return None + + return self._fvde_logical_volume.size + + def GetFVDELogicalVolume(self): + """Retrieves the Core Storage logical volume. + + Returns: + pyfvde.logical_volume: a Core Storage logical volume. + """ + return self._fvde_logical_volume + + def GetParentFileEntry(self): + """Retrieves the parent file entry. + + Returns: + CSFileEntry: parent file entry or None if not available. + """ + volume_index = cs_helper.CSPathSpecGetVolumeIndex(self.path_spec) + if volume_index is None: + return None + + return self._file_system.GetRootFileEntry() diff --git a/dfvfs/vfs/cs_file_system.py b/dfvfs/vfs/cs_file_system.py new file mode 100644 index 00000000..b46d9447 --- /dev/null +++ b/dfvfs/vfs/cs_file_system.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +"""The Core Storage (CS) file system implementation.""" + +import pyfvde + +from dfvfs.lib import cs_helper +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import cs_path_spec +from dfvfs.resolver import resolver +from dfvfs.vfs import cs_file_entry +from dfvfs.vfs import file_system + + +class CSFileSystem(file_system.FileSystem): + """File system that uses pyfvde.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_CS + + def __init__(self, resolver_context, path_spec): + """Initializes a file system. + + Args: + resolver_context (Context): resolver context. + path_spec (PathSpec): a path specification. + """ + super(CSFileSystem, self).__init__(resolver_context, path_spec) + self._fvde_volume = None + self._fvde_volume_group = None + self._file_object = None + + def _Close(self): + """Closes the file system. + + Raises: + IOError: if the close failed. + """ + self._fvde_volume_group = None + self._fvde_volume.close() + self._fvde_volume = None + self._file_object = None + + def _Open(self, mode='rb'): + """Opens the file system defined by path specification. + + Args: + mode (Optional[str]): file access mode. The default is 'rb' + read-only binary. + + Raises: + AccessError: if the access to open the file was denied. + IOError: if the file system could not be opened. + PathSpecError: if the path specification is incorrect. + ValueError: if the path specification is invalid. + """ + if not self._path_spec.HasParent(): + raise errors.PathSpecError( + 'Unsupported path specification without parent.') + + resolver.Resolver.key_chain.ExtractCredentialsFromPathSpec(self._path_spec) + + file_object = resolver.Resolver.OpenFileObject( + self._path_spec.parent, resolver_context=self._resolver_context) + + fvde_volume = pyfvde.volume() + + encrypted_root_plist = resolver.Resolver.key_chain.GetCredential( + self._path_spec, 'encrypted_root_plist') + if encrypted_root_plist: + fvde_volume.read_encrypted_root_plist(encrypted_root_plist) + + fvde_volume.open_file_object(file_object) + # TODO: implement multi physical volume support. + fvde_volume.open_physical_volume_files_as_file_objects([file_object]) + fvde_volume_group = fvde_volume.get_volume_group() + + self._file_object = file_object + self._fvde_volume = fvde_volume + self._fvde_volume_group = fvde_volume_group + + def FileEntryExistsByPathSpec(self, path_spec): + """Determines if a file entry for a path specification exists. + + Args: + path_spec (PathSpec): path specification. + + Returns: + bool: True if the file entry exists. + """ + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + + # The virtual root file has no corresponding volume index but + # should have a location. + if volume_index is None: + location = getattr(path_spec, 'location', None) + return location is not None and location == self.LOCATION_ROOT + + return ( + 0 <= volume_index < self._fvde_volume_group.number_of_logical_volumes) + + def GetFileEntryByPathSpec(self, path_spec): + """Retrieves a file entry for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + CSFileEntry: a file entry or None if not available. + """ + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + + # The virtual root file has no corresponding volume index but + # should have a location. + if volume_index is None: + location = getattr(path_spec, 'location', None) + if location is None or location != self.LOCATION_ROOT: + return None + + return cs_file_entry.CSFileEntry( + self._resolver_context, self, path_spec, is_root=True, + is_virtual=True) + + if (volume_index < 0 or + volume_index >= self._fvde_volume_group.number_of_logical_volumes): + return None + + return cs_file_entry.CSFileEntry( + self._resolver_context, self, path_spec) + + def GetFVDELogicalVolumeByPathSpec(self, path_spec): + """Retrieves a Core Storage logical volume for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + pyfvde.logical_volume: a Core Storage logical volume or None if not + available. + """ + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + if volume_index is None: + return None + + return self._fvde_volume_group.get_logical_volume(volume_index) + + def GetFVDEVolumeGroup(self): + """Retrieves the Core Storage volume group. + + Returns: + pyfvde.volume_group: a Core Storage volume group. + """ + return self._fvde_volume_group + + def GetRootFileEntry(self): + """Retrieves the root file entry. + + Returns: + CSFileEntry: root file entry or None if not available. + """ + path_spec = cs_path_spec.CSPathSpec( + location=self.LOCATION_ROOT, parent=self._path_spec.parent) + return self.GetFileEntryByPathSpec(path_spec) diff --git a/dfvfs/vfs/lvm_file_entry.py b/dfvfs/vfs/lvm_file_entry.py index 0a433d54..0d46cb74 100644 --- a/dfvfs/vfs/lvm_file_entry.py +++ b/dfvfs/vfs/lvm_file_entry.py @@ -3,7 +3,7 @@ from dfvfs.lib import definitions from dfvfs.lib import errors -from dfvfs.lib import lvm +from dfvfs.lib import lvm_helper from dfvfs.vfs import file_entry from dfvfs.vfs import lvm_directory @@ -90,6 +90,7 @@ def name(self): self._name = 'lvm{0:d}'.format(volume_index + 1) else: self._name = '' + return self._name @property @@ -114,7 +115,7 @@ def GetParentFileEntry(self): Returns: LVMFileEntry: parent file entry or None if not available. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(self.path_spec) + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(self.path_spec) if volume_index is None: return None diff --git a/dfvfs/vfs/lvm_file_system.py b/dfvfs/vfs/lvm_file_system.py index ca82172e..863e2067 100644 --- a/dfvfs/vfs/lvm_file_system.py +++ b/dfvfs/vfs/lvm_file_system.py @@ -5,7 +5,7 @@ from dfvfs.lib import definitions from dfvfs.lib import errors -from dfvfs.lib import lvm +from dfvfs.lib import lvm_helper from dfvfs.path import lvm_path_spec from dfvfs.resolver import resolver from dfvfs.vfs import file_system @@ -79,7 +79,7 @@ def FileEntryExistsByPathSpec(self, path_spec): Returns: bool: True if the file entry exists. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) # The virtual root file has no corresponding volume index but # should have a location. @@ -99,7 +99,7 @@ def GetFileEntryByPathSpec(self, path_spec): Returns: LVMFileEntry: a file entry or None if not available. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) # The virtual root file has no corresponding volume index but # should have a location. @@ -127,9 +127,10 @@ def GetLVMLogicalVolumeByPathSpec(self, path_spec): Returns: pyvslvm.logical_volume: a LVM logical volume or None if not available. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) if volume_index is None: return None + return self._vslvm_volume_group.get_logical_volume(volume_index) def GetLVMVolumeGroup(self): diff --git a/docs/sources/Path-specifications.md b/docs/sources/Path-specifications.md index 62eb9df5..436531c9 100644 --- a/docs/sources/Path-specifications.md +++ b/docs/sources/Path-specifications.md @@ -22,38 +22,44 @@ dfvfs/lib/definitions.py In your code use the type indicator as defined by dfVFS and not its value, in case it changes. The following is a list of type indicators as available in -version 20200625. - -| **Type indicator** | **Description** | -| --- | --- | -| TYPE_INDICATOR_APFS | The Apple File System (APFS) type | -| TYPE_INDICATOR_APFS_CONTAINER | The Apple File System (APFS) container volume system type | -| TYPE_INDICATOR_BDE | The BitLocker Drive Entryption (BDE) volume system type | -| TYPE_INDICATOR_COMPRESSED_STREAM | The compressed stream type | -| TYPE_INDICATOR_CPIO | The cpio archive file type | -| TYPE_INDICATOR_DATA_RANGE | The data range type | -| TYPE_INDICATOR_ENCODED_STREAM | The encoded stream type | -| TYPE_INDICATOR_ENCRYPTED_STREAM | The encrypted stream type | -| TYPE_INDICATOR_EWF | The EWF storage media image type | -| TYPE_INDICATOR_EXT | The Extended file system (ext) type | -| TYPE_INDICATOR_FAKE | The fake file system type | -| TYPE_INDICATOR_FVDE | The FileVault Drive Enryption (FVDE) volume system type | -| TYPE_INDICATOR_GZIP | The gzip compressed file type | -| TYPE_INDICATOR_LUKSDE | The LUKS drive encryption volume system type | -| TYPE_INDICATOR_LVM | The Logical Volume Manager (LVM) volume system type | -| TYPE_INDICATOR_MOUNT | Type to represent a mount point | -| TYPE_INDICATOR_NTFS | The Windows NT file system (NTFS) type | -| TYPE_INDICATOR_OS | The operating system type | -| TYPE_INDICATOR_QCOW | The QCOW storage media image type | -| TYPE_INDICATOR_RAW | The RAW storage media image type | -| TYPE_INDICATOR_SQLITE_BLOB | The SQLite binary large objects (BLOB) type | -| TYPE_INDICATOR_TAR | The tar archive file type | -| TYPE_INDICATOR_TSK | The SleuthKit file system type | -| TYPE_INDICATOR_TSK_PARTITION | The SleuthKit partition volume system type | -| TYPE_INDICATOR_VHDI | The VHD storage media image type | -| TYPE_INDICATOR_VMDK | The VMDK storage media image type | -| TYPE_INDICATOR_VSHADOW | The VSS volume system type | -| TYPE_INDICATOR_ZIP | The zip archive file type | +version 20220120. + +**Type indicator** | **Description** +--- | --- +TYPE_INDICATOR_APFS | The Apple File System (APFS) type +TYPE_INDICATOR_APFS_CONTAINER | The Apple File System (APFS) container volume system type +TYPE_INDICATOR_BDE | The BitLocker Drive Entryption (BDE) volume system type +TYPE_INDICATOR_COMPRESSED_STREAM | The compressed stream type +TYPE_INDICATOR_CPIO | The cpio archive file type +TYPE_INDICATOR_CS | The Core Storage (CS) volume system type, includes FileVault Disk Encryption (FVDE) +TYPE_INDICATOR_DATA_RANGE | The data range type +TYPE_INDICATOR_ENCODED_STREAM | The encoded stream type +TYPE_INDICATOR_ENCRYPTED_STREAM | The encrypted stream type +TYPE_INDICATOR_EWF | The EWF storage media image type +TYPE_INDICATOR_EXT | The Extended file system (ext) type +TYPE_INDICATOR_FAKE | The fake file system type +TYPE_INDICATOR_GZIP | The gzip compressed file type +TYPE_INDICATOR_LUKSDE | The LUKS drive encryption volume system type +TYPE_INDICATOR_LVM | The Logical Volume Manager (LVM) volume system type +TYPE_INDICATOR_MOUNT | Type to represent a mount point +TYPE_INDICATOR_NTFS | The Windows NT file system (NTFS) type +TYPE_INDICATOR_OS | The operating system type +TYPE_INDICATOR_QCOW | The QCOW storage media image type +TYPE_INDICATOR_RAW | The RAW storage media image type +TYPE_INDICATOR_SQLITE_BLOB | The SQLite binary large objects (BLOB) type +TYPE_INDICATOR_TAR | The tar archive file type +TYPE_INDICATOR_TSK | The SleuthKit file system type +TYPE_INDICATOR_TSK_PARTITION | The SleuthKit partition volume system type +TYPE_INDICATOR_VHDI | The VHD storage media image type +TYPE_INDICATOR_VMDK | The VMDK storage media image type +TYPE_INDICATOR_VSHADOW | The VSS volume system type +TYPE_INDICATOR_ZIP | The zip archive file type + +As of version 20220120 the following type indicators have been depracted. + +**Type indicator** | **Description** +--- | --- +TYPE_INDICATOR_FVDE | The FileVault Drive Encryption (FVDE) volume system type ## Addressing attributes @@ -65,34 +71,34 @@ path specification addressing attribute. The APFS type (TYPE_INDICATOR_APFS) is a type that addresses files stored within an Apple file system (APFS). -| **Attribute name** | **Description** | -| --- | --- | -| identifier | The identifier of the file entry within the file system. Comparable to the catalog node identifier (CNID) on HFS. | -| location | The location of the file entry | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +identifier | The identifier of the file entry within the file system. Comparable to the catalog node identifier (CNID) on HFS. +location | The location of the file entry +parent | The parent path specification ### The APFS container volume system type The APFS container type (TYPE_INDICATOR_APFS_CONTAINER) is a type that addresses volumes stored within a Apple file system (APFS) container. -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the volume within the container | -| parent | The parent path specification | -| volume_index | The index of the volume within the container | +**Attribute name** | **Description** +--- | --- +location | The location of the volume within the container +parent | The parent path specification +volume_index | The index of the volume within the container ### The BDE volume system type The BDE type (TYPE_INDICATOR_BDE) is a type that addresses volumes stored within a BitLocker encrypted volume. -| **Attribute name** | **Description** | -| --- | --- | -| password | The password to unlock the BitLocker volume | -| parent | The parent path specification | -| recovery_password | The recovery password to unlock the BitLocker volume | -| startup_key | The name of the startup key file to unlock the BitLocker volume | +**Attribute name** | **Description** +--- | --- +password | The password to unlock the BitLocker volume +parent | The parent path specification +recovery_password | The recovery password to unlock the BitLocker volume +startup_key | The name of the startup key file to unlock the BitLocker volume **Note that it is recommended to use the credential manager instead of providing decryption keys (credentials) in a path specification.** @@ -102,54 +108,71 @@ decryption keys (credentials) in a path specification.** The compressed stream type (TYPE_INDICATOR_COMPRESSED_STREAM) is an internal type that defines the following addressing attributes: -| **Attribute name** | **Description** | -| --- | --- | -| compression_method | The method used to compress the stream | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +compression_method | The method used to compress the stream +parent | The parent path specification ### The cpio archive file type The cpio type (TYPE_INDICATOR_CPIO) is a type that addresses files stored within the cpio archive file format. -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the file entry within the cpio archive | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +location | The location of the file entry within the cpio archive +parent | The parent path specification + +### The CS volume system type + +The CS type (TYPE_INDICATOR_CS) is a type that addresses volumes stored +within a Core Storage (CS) volume system. + +**Attribute name** | **Description** +--- | --- +encrypted_root_plist | The path of the EncryptedRoot.plist.wipekey file to unlock a FileVault volume +location | The location of the volume within the CS volume system +password | The password to unlock an encrypted logical volume +parent | The parent path specification +recovery_password | The recovery password to unlock an encrypted logical volume +volume_index | The index of the logical volume within the CS volume system + +**Note that it is recommended to use the credential manager instead of providing +decryption keys (credentials) in a path specification.** ### The data range type The data range type (TYPE_INDICATOR_DATA_RANGE) is an internal type that defines the following addressing attributes: -| **Attribute name** | **Description** | -| --- | --- | -| range_offset | The offset, in bytes, relative to the start of the parent file entry, where the data range starts | -| range_size | The size, in bytes, of the data range | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +range_offset | The offset, in bytes, relative to the start of the parent file entry, where the data range starts +range_size | The size, in bytes, of the data range +parent | The parent path specification ### The encoded stream type The encoded stream type (TYPE_INDICATOR_ENCODED_STREAM) is an internal type that defines the following addressing attributes: -| **Attribute name** | **Description** | -| --- | --- | -| encoding_method | The method used to encode the stream | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +encoding_method | The method used to encode the stream +parent | The parent path specification ### The encrypted stream type The encrypted stream type (TYPE_INDICATOR_ENCRYPTED_STREAM) is an internal type that defines the following addressing attributes: -| **Attribute name** | **Description** | -| --- | --- | -| cipher_mode | The cipher mode used by the encryption method, for example XTS | -| encryption_method | The method used to encrypt the stream, for example AES | -| initialization_vector | The initialization vector used to encrypt the stream | -| key | The key used to encrypt the stream | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +cipher_mode | The cipher mode used by the encryption method, for example XTS +encryption_method | The method used to encrypt the stream, for example AES +initialization_vector | The initialization vector used to encrypt the stream +key | The key used to encrypt the stream +parent | The parent path specification **Note that it is recommended to use the credential manager instead of providing decryption keys (credentials) in a path specification.** @@ -159,9 +182,9 @@ decryption keys (credentials) in a path specification.** The EWF type (TYPE_INDICATOR_EWF) is a type that addresses storage media images stored within the [Expert Witness (Compression) Format](https://forensicswiki.xyz/wiki/index.php?title=Encase_image_file_format). -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification **Note that at the moment this type is not addressable as a file system.** @@ -172,32 +195,32 @@ stored within the [Expert Witness (Compression) Format](https://forensicswiki.xy The EXT type (TYPE_INDICATOR_EXT) is a type that addresses files stored within a Extended file system (ext). -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the file entry | -| inode | The inode number of the file entry | +**Attribute name** | **Description** +--- | --- +location | The location of the file entry +inode | The inode number of the file entry ### The fake file system type The FAKE type (TYPE_INDICATOR_FAKE) is a virtual file system intended for testing purposes. -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the file entry | -| parent | The parent path specification, must be None | +**Attribute name** | **Description** +--- | --- +location | The location of the file entry +parent | The parent path specification, must be None ### The FVDE volume system type The FVDE type (TYPE_INDICATOR_FVDE) is a type that addresses volumes stored within a FileVault encrypted CoreStorage volume. -| **Attribute name** | **Description** | -| --- | --- | -| encrypted_root_plist | The path of the EncryptedRoot.plist.wipekey file to unlock the FileVault volume | -| password | The password to unlock the FileVault volume | -| parent | The parent path specification | -| recovery_password | The recovery password to unlock the FileVault volume | +**Attribute name** | **Description** +--- | --- +encrypted_root_plist | The path of the EncryptedRoot.plist.wipekey file to unlock the FileVault volume +password | The password to unlock the FileVault volume +parent | The parent path specification +recovery_password | The recovery password to unlock the FileVault volume **Note that it is recommended to use the credential manager instead of providing decryption keys (credentials) in a path specification.** @@ -207,63 +230,63 @@ decryption keys (credentials) in a path specification.** The GZIP type (TYPE_INDICATOR_GZIP) is a type that addresses data stored within the [gzip compressed stream file format](https://forensicswiki.xyz/wiki/index.php?title=Gzip). -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification ### The LUKSDE volume system type The LUKSDE type (TYPE_INDICATOR_LUKSDE) is a type that addresses volumes stored within a LUKS encrypted volume. -| **Attribute name** | **Description** | -| --- | --- | -| password | The password to unlock the FileVault volume | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +password | The password to unlock the FileVault volume +parent | The parent path specification ### The LVM volume system type The LVM type (TYPE_INDICATOR_LVM) is a type that addresses volumes stored within a Logical Volume Manager (LVM) volume system. -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the volume within the LVM volume system | -| parent | The parent path specification | -| volume_index | The index of the volume within the LVM volume system | +**Attribute name** | **Description** +--- | --- +location | The location of the volume within the LVM volume system +parent | The parent path specification +volume_index | The index of the logical volume within the LVM volume system ### The mount type The MOUNT type (TYPE_INDICATOR_MOUNT) is a type that defines a mount point within dfVFS. Also see [the mount point manager](developer/Internals.md). -| **Attribute name** | **Description** | -| --- | --- | -| identifier | The identifier of the mount point | -| parent | The parent path specification, must be None | +**Attribute name** | **Description** +--- | --- +identifier | The identifier of the mount point +parent | The parent path specification, must be None ### The NTFS file system type The NTFS type (TYPE_INDICATOR_NTFS) is a type that addresses files stored within a Windows NT file system (NTFS). -| **Attribute name** | **Description** | -| --- | --- | -| data_stream | The name of the data stream in the file entry | -| location | The location of the file entry | -| mft_attribute | The index of the $FILE_NAME of the MFT attribute within the MFT entry that contains the name of the file entry | -| mft_entry | The identifier of the MFT entry within the file system | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +data_stream | The name of the data stream in the file entry +location | The location of the file entry +mft_attribute | The index of the $FILE_NAME of the MFT attribute within the MFT entry that contains the name of the file entry +mft_entry | The identifier of the MFT entry within the file system +parent | The parent path specification ### The operating system type The OS type (TYPE_INDICATOR_OS) is a type that addresses files stored within an operating system. -| **Attribute name** | **Description** | -| --- | --- | -| location | The operating system specific location of the file entry which corresponds to the path.
E.g. C:\Windows\System32\config\SAM or /etc/passwd | -| parent | The parent path specification, must be None | +**Attribute name** | **Description** +--- | --- +location | The operating system specific location of the file entry which corresponds to the path.
E.g. C:\Windows\System32\config\SAM or /etc/passwd +parent | The parent path specification, must be None ### The QCOW storage media image type @@ -271,9 +294,9 @@ The QCOW type (TYPE_INDICATOR_QCOW) is a type that addresses storage media images stored within the [QCOW image format](https://forensicswiki.xyz/wiki/index.php?title=QCOW_Image_Format), version 1, 2 and 3. -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification **Note that at the moment this type is not addressable as a file system.** @@ -282,9 +305,9 @@ version 1, 2 and 3. The RAW storage media image type (TYPE_INDICATOR_RAW) is a type that addresses storage media images stored within the [RAW image format](https://forensicswiki.xyz/wiki/index.php?title=Raw_Image_Format). -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification **Note that at the moment this type is not addressable as a file system.** @@ -293,23 +316,23 @@ storage media images stored within the [RAW image format](https://forensicswiki. The SQlite blob type (TYPE_INDICATOR_SQLITE_BLOB) is a type that addresses files stored within a blob within a SQLite file. -| **Attribute name** | **Description** | -| --- | --- | -| column_name | The name of the column in which the blob is stored | -| parent | The parent path specification | -| row_condition | A condition that matches the row in which the blob is stored | -| row_index | The index of the row in which the blob is stored | -| table_name | The name of the table in which the blob is stored | +**Attribute name** | **Description** +--- | --- +column_name | The name of the column in which the blob is stored +parent | The parent path specification +row_condition | A condition that matches the row in which the blob is stored +row_index | The index of the row in which the blob is stored +table_name | The name of the table in which the blob is stored ### The tar archive file type The TAR type (TYPE_INDICATOR_TAR) is a type that addresses files stored within the [tar archive file format](https://forensicswiki.xyz/wiki/index.php?title=Tar). -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the file entry within the tar archive | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +location | The location of the file entry within the tar archive +parent | The parent path specification **Note that to access e.g. a .tar.gz the a path specification of type TAR should be stacked on top of one of type GZIP.** @@ -318,11 +341,11 @@ the [tar archive file format](https://forensicswiki.xyz/wiki/index.php?title=Tar The TSK type (TYPE_INDICATOR_TSK) is a type that addresses files stored within a SleuthKit supported file system. -| **Attribute name** | **Description** | -| --- | --- | -| inode | The inode number of the file entry | -| location | The location of the file entry | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +inode | The inode number of the file entry +location | The location of the file entry +parent | The parent path specification ### The SleuthKit volume system type @@ -333,21 +356,21 @@ consists of support for the [APM](https://forensicswiki.xyz/wiki/index.php?title [MBR](https://forensicswiki.xyz/wiki/index.php?title=Master_boot_record) partitioning systems. -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the volume within the volume system | -| parent | The parent path specification | -| part_index | The SleuthKit part index that indicates the volume within the volume system | -| start_offset | The start offset, in bytes, of the volume within the volume system | +**Attribute name** | **Description** +--- | --- +location | The location of the volume within the volume system +parent | The parent path specification +part_index | The SleuthKit part index that indicates the volume within the volume system +start_offset | The start offset, in bytes, of the volume within the volume system ### The VHD storage media image type The VHDI type (TYPE_INDICATOR_VHDI) is a type that addresses storage media images stored within the [Virtual Hard Disk Image format](https://forensicswiki.xyz/wiki/index.php?title=Virtual_Hard_Disk_(VHD)). -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification **Note that at the moment this type is not addressable as a file system.** @@ -356,9 +379,9 @@ images stored within the [Virtual Hard Disk Image format](https://forensicswiki. The VMDK type (TYPE_INDICATOR_VMDK) is a type that addresses storage media images stored within the [VMWare Virtual Disk Format](https://forensicswiki.xyz/wiki/index.php?title=VMWare_Virtual_Disk_Format_(VMDK)). -| **Attribute name** | **Description** | -| --- | --- | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +parent | The parent path specification **Note that at the moment this type is not addressable as a file system.** @@ -367,18 +390,18 @@ images stored within the [VMWare Virtual Disk Format](https://forensicswiki.xyz/ The VSHADOW type (TYPE_INDICATOR_VSHADOW) is a type that addresses volumes stored within the [Volume Shadow Snapshots (VSS)](https://forensicswiki.xyz/wiki/index.php?title=Windows_Shadow_Volumes). -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the volume within the volume system | -| parent | The parent path specification | -| store_index | The store index of the volume within the volume system | +**Attribute name** | **Description** +--- | --- +location | The location of the volume within the volume system +parent | The parent path specification +store_index | The store index of the volume within the volume system ### The zip archive file type The ZIP type (TYPE_INDICATOR_ZIP) is a type that addresses files stored within the [zip archive file format](https://forensicswiki.xyz/wiki/index.php?title=Zip). -| **Attribute name** | **Description** | -| --- | --- | -| location | The location of the file entry within the zip archive | -| parent | The parent path specification | +**Attribute name** | **Description** +--- | --- +location | The location of the file entry within the zip archive +parent | The parent path specification diff --git a/docs/sources/Supported-formats.md b/docs/sources/Supported-formats.md index 62210d7e..b5e1f7d4 100644 --- a/docs/sources/Supported-formats.md +++ b/docs/sources/Supported-formats.md @@ -23,7 +23,7 @@ The information below is based of version 20220108 * Apple Partition Map (APM) (Requires: [libtsk](https://github.com/sleuthkit/sleuthkit/)/[pytsk](https://github.com/py4n6/pytsk)) * Apple File System (APFS) container version 2 (Requires: [libfsapfs/pyfsapfs](https://github.com/libyal/libfsapfs)) * BitLocker Disk Encryption (BDE) (Requires: [libbde/pybde](https://github.com/libyal/libbde)) -* FileVault Disk Encryption (FVDE) (or FileVault 2) (Requires: [libfvde/pyfvde](https://github.com/libyal/libfvde)) +* Core Storage (CS) including FileVault Disk Encryption (FVDE) (or FileVault 2) (Requires: [libfvde/pyfvde](https://github.com/libyal/libfvde)) * GPT (Requires: [libvsgpt/pyvsgpt](https://github.com/libyal/libvsgpt) or [libtsk](https://github.com/sleuthkit/sleuthkit/)/[pytsk](https://github.com/py4n6/pytsk)) * LVM (Requires: [libvslvm/pyvslvm](https://github.com/libyal/libvslvm)) * At the moment only single physical volume LVM support diff --git a/requirements.txt b/requirements.txt index 3b4e2cb0..3a2a3a09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ libfsext-python >= 20220112 libfshfs-python >= 20220115 libfsntfs-python >= 20211229 libfsxfs-python >= 20220113 -libfvde-python >= 20160719 +libfvde-python >= 20220120 libfwnt-python >= 20210717 libluksde-python >= 20200101 libmodi-python >= 20210405 diff --git a/setup.cfg b/setup.cfg index 224b4322..1a2bb61c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ requires = libbde-python3 >= 20140531 libfshfs-python3 >= 20220115 libfsntfs-python3 >= 20211229 libfsxfs-python3 >= 20220113 - libfvde-python3 >= 20160719 + libfvde-python3 >= 20220120 libfwnt-python3 >= 20210717 libluksde-python3 >= 20200101 libmodi-python3 >= 20210405 diff --git a/tests/credentials/cs_credentials.py b/tests/credentials/cs_credentials.py new file mode 100644 index 00000000..1a623db4 --- /dev/null +++ b/tests/credentials/cs_credentials.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the Core Storage (CS) credentials.""" + +import unittest + +from dfvfs.credentials import cs_credentials + +from tests import test_lib as shared_test_lib + + +class CSCredentials(shared_test_lib.BaseTestCase): + """Tests the Core Storage (CS) credentials.""" + + def testInitialize(self): + """Tests the __init__ function.""" + test_credentials = cs_credentials.CSCredentials() + self.assertIsNotNone(test_credentials) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/file_io/cs_file_io.py b/tests/file_io/cs_file_io.py new file mode 100644 index 00000000..dfb12e9e --- /dev/null +++ b/tests/file_io/cs_file_io.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the file-like object implementation using pyfvde.""" + +import unittest + +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import factory as path_spec_factory +from dfvfs.resolver import resolver + +from tests.file_io import test_lib + + +class CSFileTestWithKeyChainTest(test_lib.ImageFileTestCase): + """Tests the Core Storage (CS) file-like object. + + The credentials are passed via the key chain. + """ + + _FVDE_PASSWORD = 'fvde-TEST' + + _INODE_PASSWORDS_TXT = 26 + _INODE_ANOTHER_FILE = 24 + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + super(CSFileTestWithKeyChainTest, self).setUp() + test_path = self._GetTestFilePath(['fvdetest.qcow2']) + self._SkipIfPathNotExists(test_path) + + test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + test_qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_QCOW, parent=test_os_path_spec) + self._tsk_partition_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK_PARTITION, location='/p1', + parent=test_qcow_path_spec) + self._cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._tsk_partition_path_spec, + volume_index=0) + + resolver.Resolver.key_chain.SetCredential( + self._cs_path_spec, 'password', self._FVDE_PASSWORD) + + def testOpenCloseInode(self): + """Test the open and close functionality using an inode.""" + self._TestOpenCloseInode(self._cs_path_spec) + + def testOpenCloseLocation(self): + """Test the open and close functionality using a location.""" + self._TestOpenCloseLocation(self._cs_path_spec) + + # Try open with a path specification that has no parent. + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs1', + parent=self._tsk_partition_path_spec) + path_spec.parent = None + + with self.assertRaises(errors.PathSpecError): + self._TestOpenCloseLocation(path_spec) + + def testSeek(self): + """Test the seek functionality.""" + self._TestSeek(self._cs_path_spec) + + def testRead(self): + """Test the read functionality.""" + self._TestRead(self._cs_path_spec) + + +class CSFileWithPathSpecCredentialsTest(test_lib.ImageFileTestCase): + """Tests the Core Storage (CS) file-like object. + + The credentials are passed via the path specification. + """ + _FVDE_PASSWORD = 'fvde-TEST' + + _INODE_PASSWORDS_TXT = 26 + _INODE_ANOTHER_FILE = 24 + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + super(CSFileWithPathSpecCredentialsTest, self).setUp() + test_path = self._GetTestFilePath(['fvdetest.qcow2']) + self._SkipIfPathNotExists(test_path) + + test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + test_qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_QCOW, parent=test_os_path_spec) + self._tsk_partition_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_TSK_PARTITION, location='/p1', + parent=test_qcow_path_spec) + self._cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._tsk_partition_path_spec, + password=self._FVDE_PASSWORD, volume_index=0) + + def testOpenCloseInode(self): + """Test the open and close functionality using an inode.""" + self._TestOpenCloseInode(self._cs_path_spec) + + def testOpenCloseLocation(self): + """Test the open and close functionality using a location.""" + self._TestOpenCloseLocation(self._cs_path_spec) + + # Try open with a path specification that has no parent. + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs1', + parent=self._tsk_partition_path_spec) + path_spec.parent = None + + with self.assertRaises(errors.PathSpecError): + self._TestOpenCloseLocation(path_spec) + + def testSeek(self): + """Test the seek functionality.""" + self._TestSeek(self._cs_path_spec) + + def testRead(self): + """Test the read functionality.""" + self._TestRead(self._cs_path_spec) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/file_io/fvde_file_io.py b/tests/file_io/fvde_file_io.py index eb429696..d97160a3 100644 --- a/tests/file_io/fvde_file_io.py +++ b/tests/file_io/fvde_file_io.py @@ -94,9 +94,6 @@ def setUp(self): definitions.TYPE_INDICATOR_FVDE, parent=self._tsk_partition_path_spec, password=self._FVDE_PASSWORD) - resolver.Resolver.key_chain.SetCredential( - self._fvde_path_spec, 'password', self._FVDE_PASSWORD) - def testOpenCloseInode(self): """Test the open and close functionality using an inode.""" self._TestOpenCloseInode(self._fvde_path_spec) diff --git a/tests/file_io/lvm_file_io.py b/tests/file_io/lvm_file_io.py index 5643b50d..4fa3e17b 100644 --- a/tests/file_io/lvm_file_io.py +++ b/tests/file_io/lvm_file_io.py @@ -16,7 +16,7 @@ class LVMFileTest(shared_test_lib.BaseTestCase): - """The unit test for the Logical Volume Manager (LVM) file-like object.""" + """Tests the Logical Volume Manager (LVM) file-like object.""" def setUp(self): """Sets up the needed objects used throughout the test.""" diff --git a/tests/lib/cs_helper.py b/tests/lib/cs_helper.py new file mode 100644 index 00000000..5d6b58f6 --- /dev/null +++ b/tests/lib/cs_helper.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the helper functions for Core Storage (CS) support.""" + +import unittest + +from dfvfs.lib import cs_helper +from dfvfs.path import cs_path_spec +from dfvfs.path import fake_path_spec + +from tests import test_lib as shared_test_lib + + +class CSHelperTest(shared_test_lib.BaseTestCase): + """Tests for the helper functions for Core Storage (CS) support.""" + + def testCSPathSpecGetVolumeIndex(self): + """Tests the CSPathSpecGetVolumeIndex function.""" + test_fake_path_spec = fake_path_spec.FakePathSpec(location='/') + + path_spec = cs_path_spec.CSPathSpec( + parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = cs_path_spec.CSPathSpec( + volume_index=1, parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', volume_index=1, parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs', parent=test_fake_path_spec) + + volume_index = cs_helper.CSPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lib/lvm_helper.py b/tests/lib/lvm_helper.py new file mode 100644 index 00000000..d1c1ce1f --- /dev/null +++ b/tests/lib/lvm_helper.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the helper functions for Logical Volume Manager (LVM) support.""" + +import unittest + +from dfvfs.lib import lvm_helper +from dfvfs.path import lvm_path_spec +from dfvfs.path import fake_path_spec + +from tests import test_lib as shared_test_lib + + +class LVMHelperTest(shared_test_lib.BaseTestCase): + """Tests for the helper functions for Logical Volume Manager (LVM) support.""" + + def testLVMPathSpecGetVolumeIndex(self): + """Tests the LVMPathSpecGetVolumeIndex function.""" + test_fake_path_spec = fake_path_spec.FakePathSpec(location='/') + + path_spec = lvm_path_spec.LVMPathSpec( + parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + + path_spec = lvm_path_spec.LVMPathSpec( + location='/lvm2', parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = lvm_path_spec.LVMPathSpec( + volume_index=1, parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = lvm_path_spec.LVMPathSpec( + location='/lvm2', volume_index=1, parent=test_fake_path_spec) + + self.assertIsNotNone(path_spec) + + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) + self.assertEqual(volume_index, 1) + + path_spec = lvm_path_spec.LVMPathSpec( + location='/lvm', parent=test_fake_path_spec) + + volume_index = lvm_helper.LVMPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/path/cs_path_spec.py b/tests/path/cs_path_spec.py new file mode 100644 index 00000000..2310f851 --- /dev/null +++ b/tests/path/cs_path_spec.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the CS path specification implementation.""" + +import unittest + +from dfvfs.path import cs_path_spec + +from tests.path import test_lib + + +class CSPathSpecTest(test_lib.PathSpecTestCase): + """Tests for the CS path specification implementation.""" + + def testInitialize(self): + """Tests the path specification initialization.""" + path_spec = cs_path_spec.CSPathSpec(parent=self._path_spec) + + self.assertIsNotNone(path_spec) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', parent=self._path_spec) + + self.assertIsNotNone(path_spec) + + path_spec = cs_path_spec.CSPathSpec( + parent=self._path_spec, volume_index=1) + + self.assertIsNotNone(path_spec) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', parent=self._path_spec, volume_index=1) + + self.assertIsNotNone(path_spec) + + with self.assertRaises(ValueError): + cs_path_spec.CSPathSpec(parent=None) + + with self.assertRaises(ValueError): + cs_path_spec.CSPathSpec( + parent=self._path_spec, bogus='BOGUS') + + def testComparable(self): + """Tests the path specification comparable property.""" + path_spec = cs_path_spec.CSPathSpec(parent=self._path_spec) + + self.assertIsNotNone(path_spec) + + expected_comparable = '\n'.join([ + 'type: TEST', + 'type: CS', + '']) + + self.assertEqual(path_spec.comparable, expected_comparable) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', parent=self._path_spec) + + self.assertIsNotNone(path_spec) + + expected_comparable = '\n'.join([ + 'type: TEST', + 'type: CS, location: /cs2', + '']) + + self.assertEqual(path_spec.comparable, expected_comparable) + + path_spec = cs_path_spec.CSPathSpec( + parent=self._path_spec, volume_index=1) + + self.assertIsNotNone(path_spec) + + expected_comparable = '\n'.join([ + 'type: TEST', + 'type: CS, volume index: 1', + '']) + + self.assertEqual(path_spec.comparable, expected_comparable) + + path_spec = cs_path_spec.CSPathSpec( + location='/cs2', parent=self._path_spec, volume_index=1) + + self.assertIsNotNone(path_spec) + + expected_comparable = '\n'.join([ + 'type: TEST', + 'type: CS, location: /cs2, volume index: 1', + '']) + + self.assertEqual(path_spec.comparable, expected_comparable) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/resolver_helpers/cs_resolver_helper.py b/tests/resolver_helpers/cs_resolver_helper.py new file mode 100644 index 00000000..be302bc3 --- /dev/null +++ b/tests/resolver_helpers/cs_resolver_helper.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the CS resolver helper implementation.""" + +import unittest + +from dfvfs.lib import definitions +from dfvfs.path import factory as path_spec_factory +from dfvfs.resolver_helpers import cs_resolver_helper + +from tests.resolver_helpers import test_lib + + +class CSResolverHelperTest(test_lib.ResolverHelperTestCase): + """Tests for the CS resolver helper implementation.""" + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + super(CSResolverHelperTest, self).setUp() + + test_path = self._GetTestFilePath(['fvdetest.qcow2']) + self._SkipIfPathNotExists(test_path) + + test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + test_qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_QCOW, parent=test_os_path_spec) + self._gpt_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_GPT, location='/p1', + parent=test_qcow_path_spec) + + def testNewFileObject(self): + """Tests the NewFileObject function.""" + test_cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + + resolver_helper_object = cs_resolver_helper.CSResolverHelper() + self._TestNewFileObject(resolver_helper_object, test_cs_path_spec) + + def testNewFileSystem(self): + """Tests the NewFileSystem function.""" + test_cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + + resolver_helper_object = cs_resolver_helper.CSResolverHelper() + self._TestNewFileSystem(resolver_helper_object, test_cs_path_spec) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/vfs/cs_file_entry.py b/tests/vfs/cs_file_entry.py new file mode 100644 index 00000000..2b5c7908 --- /dev/null +++ b/tests/vfs/cs_file_entry.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for the file entry implementation using pyfvde.""" + +import unittest + +from dfvfs.lib import definitions +from dfvfs.path import factory as path_spec_factory +from dfvfs.resolver import context +from dfvfs.vfs import cs_file_entry +from dfvfs.vfs import cs_file_system + +from tests import test_lib as shared_test_lib + + +class CSFileEntryTest(shared_test_lib.BaseTestCase): + """Tests the CS file entry.""" + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + self._resolver_context = context.Context() + + test_path = self._GetTestFilePath(['fvdetest.qcow2']) + self._SkipIfPathNotExists(test_path) + + test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + test_qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_QCOW, parent=test_os_path_spec) + self._gpt_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_GPT, location='/p1', + parent=test_qcow_path_spec) + self._cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec) + + self._file_system = cs_file_system.CSFileSystem( + self._resolver_context, self._cs_path_spec) + self._file_system.Open() + + def tearDown(self): + """Cleans up the needed objects used throughout the test.""" + self._resolver_context.Empty() + + # fvdeinfo -o $(( 40 * 512 )) fuse/qcow1 + # fvdeinfo 20220120 + # + # Logical volume: 1 is locked and a password is needed to unlock it. + # + # Password: + # + # Core Storage information: + # + # Logical volume group: + # Identifier : 94923b58-9f31-4988-8707-cb90c8e45a46 + # Name : TESTLVG + # Number of physical volumes : 1 + # Number of logical volumes : 1 + # + # Physical volume: 1 + # Identifier : 3273a055-3b8b-47e8-b970-df35eecda81b + # Size : 511 MiB (536829952 bytes) + # Encryption method : AES-XTS + # + # Logical volume: 1 + # Identifier : 420af122-cf73-4a30-8b0a-a593a65fbef5 + # Name : TestLV + # Size : 160 MiB (167772160 bytes) + + def testIntialize(self): + """Test the __init__ function.""" + file_entry = cs_file_entry.CSFileEntry( + self._resolver_context, self._file_system, self._cs_path_spec, + is_virtual=True) + + self.assertIsNotNone(file_entry) + + # TODO: test _GetDirectory + # TODO: test _GetSubFileEntries + + def testName(self): + """Test the name property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, 'cs1') + + def testSize(self): + """Test the size property.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.size, 167772160) + + # TODO: test GetFVDELogicalVolume + + def testGetParentFileEntry(self): + """Tests the GetParentFileEntry function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + parent_file_entry = file_entry.GetParentFileEntry() + self.assertIsNotNone(parent_file_entry) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + parent_file_entry = file_entry.GetParentFileEntry() + self.assertIsNone(parent_file_entry) + + def testGetStat(self): + """Tests the GetStat function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + stat_object = file_entry.GetStat() + + self.assertIsNotNone(stat_object) + self.assertEqual(stat_object.type, stat_object.TYPE_FILE) + self.assertEqual(stat_object.size, 167772160) + + def testIsFunctions(self): + """Test the Is? functions.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertFalse(file_entry.IsRoot()) + self.assertFalse(file_entry.IsVirtual()) + self.assertTrue(file_entry.IsAllocated()) + + self.assertFalse(file_entry.IsDevice()) + self.assertFalse(file_entry.IsDirectory()) + self.assertTrue(file_entry.IsFile()) + self.assertFalse(file_entry.IsLink()) + self.assertFalse(file_entry.IsPipe()) + self.assertFalse(file_entry.IsSocket()) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertTrue(file_entry.IsRoot()) + self.assertTrue(file_entry.IsVirtual()) + self.assertTrue(file_entry.IsAllocated()) + + self.assertFalse(file_entry.IsDevice()) + self.assertTrue(file_entry.IsDirectory()) + self.assertFalse(file_entry.IsFile()) + self.assertFalse(file_entry.IsLink()) + self.assertFalse(file_entry.IsPipe()) + self.assertFalse(file_entry.IsSocket()) + + def testSubFileEntries(self): + """Test the sub file entries iteration functionality.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_sub_file_entries, 1) + + expected_sub_file_entry_names = ['cs1'] + + sub_file_entry_names = [] + for sub_file_entry in file_entry.sub_file_entries: + sub_file_entry_names.append(sub_file_entry.name) + + self.assertEqual( + len(sub_file_entry_names), len(expected_sub_file_entry_names)) + self.assertEqual( + sorted(sub_file_entry_names), sorted(expected_sub_file_entry_names)) + + def testDataStreams(self): + """Test the data streams functionality.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_data_streams, 1) + + data_stream_names = [] + for data_stream in file_entry.data_streams: + data_stream_names.append(data_stream.name) + + self.assertEqual(data_stream_names, ['']) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + self.assertEqual(file_entry.number_of_data_streams, 0) + + data_stream_names = [] + for data_stream in file_entry.data_streams: + data_stream_names.append(data_stream.name) + + self.assertEqual(data_stream_names, []) + + def testGetDataStream(self): + """Tests the GetDataStream function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + data_stream_name = '' + data_stream = file_entry.GetDataStream(data_stream_name) + self.assertIsNotNone(data_stream) + self.assertEqual(data_stream.name, data_stream_name) + + data_stream = file_entry.GetDataStream('bogus') + self.assertIsNone(data_stream) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/vfs/cs_file_system.py b/tests/vfs/cs_file_system.py new file mode 100644 index 00000000..98cd339c --- /dev/null +++ b/tests/vfs/cs_file_system.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for a file system implementation using pyfvde.""" + +import unittest + +from dfvfs.lib import definitions +from dfvfs.path import factory as path_spec_factory +from dfvfs.resolver import context +from dfvfs.vfs import cs_file_system + +from tests import test_lib as shared_test_lib + + +class CSFileSystemTest(shared_test_lib.BaseTestCase): + """Tests the CS file system.""" + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + self._resolver_context = context.Context() + + test_path = self._GetTestFilePath(['fvdetest.qcow2']) + self._SkipIfPathNotExists(test_path) + + test_os_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_OS, location=test_path) + test_qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_QCOW, parent=test_os_path_spec) + self._gpt_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_GPT, location='/p1', + parent=test_qcow_path_spec) + self._cs_path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec) + + def tearDown(self): + """Cleans up the needed objects used throughout the test.""" + self._resolver_context.Empty() + + # fvdeinfo -o $(( 40 * 512 )) fuse/qcow1 + # fvdeinfo 20220120 + # + # Logical volume: 1 is locked and a password is needed to unlock it. + # + # Password: + # + # Core Storage information: + # + # Logical volume group: + # Identifier : 94923b58-9f31-4988-8707-cb90c8e45a46 + # Name : TESTLVG + # Number of physical volumes : 1 + # Number of logical volumes : 1 + # + # Physical volume: 1 + # Identifier : 3273a055-3b8b-47e8-b970-df35eecda81b + # Size : 511 MiB (536829952 bytes) + # Encryption method : AES-XTS + # + # Logical volume: 1 + # Identifier : 420af122-cf73-4a30-8b0a-a593a65fbef5 + # Name : TestLV + # Size : 160 MiB (167772160 bytes) + + def testOpenAndClose(self): + """Test the open and close functionality.""" + file_system = cs_file_system.CSFileSystem( + self._resolver_context, self._cs_path_spec) + self.assertIsNotNone(file_system) + + file_system.Open() + + def testFileEntryExistsByPathSpec(self): + """Test the file entry exists by path specification functionality.""" + file_system = cs_file_system.CSFileSystem( + self._resolver_context, self._cs_path_spec) + self.assertIsNotNone(file_system) + + file_system.Open() + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + self.assertTrue(file_system.FileEntryExistsByPathSpec(path_spec)) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + self.assertTrue(file_system.FileEntryExistsByPathSpec(path_spec)) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs1', + parent=self._gpt_path_spec) + self.assertTrue(file_system.FileEntryExistsByPathSpec(path_spec)) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=9) + self.assertFalse(file_system.FileEntryExistsByPathSpec(path_spec)) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs0', + parent=self._gpt_path_spec) + self.assertFalse(file_system.FileEntryExistsByPathSpec(path_spec)) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs9', + parent=self._gpt_path_spec) + self.assertFalse(file_system.FileEntryExistsByPathSpec(path_spec)) + + def testGetFileEntryByPathSpec(self): + """Tests the GetFileEntryByPathSpec function.""" + file_system = cs_file_system.CSFileSystem( + self._resolver_context, self._cs_path_spec) + self.assertIsNotNone(file_system) + + file_system.Open() + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/', + parent=self._gpt_path_spec) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, '') + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=0) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, 'cs1') + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs1', + parent=self._gpt_path_spec) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, 'cs1') + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, parent=self._gpt_path_spec, + volume_index=9) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNone(file_entry) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs0', + parent=self._gpt_path_spec) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNone(file_entry) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_CS, location='/cs9', + parent=self._gpt_path_spec) + file_entry = file_system.GetFileEntryByPathSpec(path_spec) + + self.assertIsNone(file_entry) + + def testGetRootFileEntry(self): + """Test the get root file entry functionality.""" + file_system = cs_file_system.CSFileSystem( + self._resolver_context, self._cs_path_spec) + self.assertIsNotNone(file_system) + + file_system.Open() + + file_entry = file_system.GetRootFileEntry() + + self.assertIsNotNone(file_entry) + self.assertEqual(file_entry.name, '') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/vfs/fvde_file_entry.py b/tests/vfs/fvde_file_entry.py index ff2479dc..292ed67f 100644 --- a/tests/vfs/fvde_file_entry.py +++ b/tests/vfs/fvde_file_entry.py @@ -22,6 +22,7 @@ class FVDEFileEntryTest(shared_test_lib.BaseTestCase): def setUp(self): """Sets up the needed objects used throughout the test.""" self._resolver_context = context.Context() + test_path = self._GetTestFilePath(['fvdetest.qcow2']) self._SkipIfPathNotExists(test_path) diff --git a/tests/vfs/lvm_file_entry.py b/tests/vfs/lvm_file_entry.py index b4545b1a..b24eef56 100644 --- a/tests/vfs/lvm_file_entry.py +++ b/tests/vfs/lvm_file_entry.py @@ -19,6 +19,7 @@ class LVMFileEntryTest(shared_test_lib.BaseTestCase): def setUp(self): """Sets up the needed objects used throughout the test.""" self._resolver_context = context.Context() + test_path = self._GetTestFilePath(['lvm.raw']) self._SkipIfPathNotExists(test_path) diff --git a/tests/vfs/lvm_file_system.py b/tests/vfs/lvm_file_system.py index 70e8f3de..73d0a7c7 100644 --- a/tests/vfs/lvm_file_system.py +++ b/tests/vfs/lvm_file_system.py @@ -18,6 +18,7 @@ class LVMFileSystemTest(shared_test_lib.BaseTestCase): def setUp(self): """Sets up the needed objects used throughout the test.""" self._resolver_context = context.Context() + test_path = self._GetTestFilePath(['lvm.raw']) self._SkipIfPathNotExists(test_path)