Skip to content

Commit

Permalink
Added APFS encryption support #314 (#330)
Browse files Browse the repository at this point in the history
* Added encrypted APFS support #314
  • Loading branch information
joachimmetz authored and Onager committed Nov 23, 2018
1 parent 56c1bee commit bbbbc01
Show file tree
Hide file tree
Showing 20 changed files with 474 additions and 41 deletions.
4 changes: 2 additions & 2 deletions config/dpkg/changelog
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
dfvfs (20180902-1) unstable; urgency=low
dfvfs (20181123-1) unstable; urgency=low

* Auto-generated

-- Log2Timeline maintainers <[email protected]> Sun, 02 Sep 2018 14:54:02 +0200
-- Log2Timeline maintainers <[email protected]> Fri, 23 Nov 2018 11:16:40 +0100
2 changes: 1 addition & 1 deletion dfvfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
storage media types and file formats.
"""

__version__ = '20180902'
__version__ = '20181123'
1 change: 1 addition & 0 deletions dfvfs/credentials/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Imports for the credential manager."""

from dfvfs.credentials import apfs_credentials
from dfvfs.credentials import bde_credentials
from dfvfs.credentials import encrypted_stream_credentials
from dfvfs.credentials import fvde_credentials
20 changes: 20 additions & 0 deletions dfvfs/credentials/apfs_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""The Apple File System (APFS) credentials."""

from __future__ import unicode_literals

from dfvfs.credentials import credentials
from dfvfs.credentials import manager
from dfvfs.lib import definitions


class APFSCredentials(credentials.Credentials):
"""Apple File System (APFS) credentials."""

# TODO: add support for key_data credential, needs pyfsapfs update.
CREDENTIALS = frozenset(['password', 'recovery_password'])

TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS


manager.CredentialsManager.RegisterCredentials(APFSCredentials())
2 changes: 1 addition & 1 deletion dfvfs/credentials/bde_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


class BDECredentials(credentials.Credentials):
"""BDE credentials."""
"""BitLocker Drive Encryption (BDE) credentials."""

# TODO: add support for key_data credential, needs pybde update.
CREDENTIALS = frozenset([
Expand Down
2 changes: 1 addition & 1 deletion dfvfs/credentials/fvde_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


class FVDECredentials(credentials.Credentials):
"""FVDE credentials."""
"""FileVault Drive Encryption (FVDE) credentials."""

# TODO: add support for key_data credential, needs pyfvde update.
CREDENTIALS = frozenset([
Expand Down
2 changes: 1 addition & 1 deletion dfvfs/credentials/keychain.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def SetCredential(self, path_spec, identifier, data):
Raises:
KeyError: if the credential is not supported by the path specification
type.
type.
"""
supported_credentials = manager.CredentialsManager.GetCredentials(path_spec)

Expand Down
2 changes: 1 addition & 1 deletion dfvfs/credentials/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The credentials manager uses credential (instances of Credentials) to specify
which credentials a specific path specification type supports. E.g. in case
of BitLocker Drive Encryption (BDE):
of BitLocker Drive Encryption (BDE):
* password;
* recovery password;
* startup key;
Expand Down
20 changes: 19 additions & 1 deletion dfvfs/lib/apfs_container.py → dfvfs/lib/apfs_helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""Helper functions for APFS container support."""
"""Helper functions for Apple File System (APFS) support."""

from __future__ import unicode_literals

Expand Down Expand Up @@ -30,3 +30,21 @@ def APFSContainerPathSpecGetVolumeIndex(path_spec):
volume_index = None

return volume_index


def APFSUnlockVolume(apfs_volume, path_spec, key_chain):
"""Unlocks an APFS volume using the path specification.
Args:
apfs_volume (pyapfs.volume): APFS volume.
path_spec (PathSpec): path specification.
key_chain (KeyChain): key chain.
"""
if apfs_volume.is_locked():
password = key_chain.GetCredential(path_spec, 'password')
if password:
apfs_volume.set_password(password)

recovery_password = key_chain.GetCredential(path_spec, 'recovery_password')
if recovery_password:
apfs_volume.set_recovery_password(recovery_password)
8 changes: 4 additions & 4 deletions dfvfs/vfs/apfs_container_file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from __future__ import unicode_literals

from dfvfs.lib import apfs_container
from dfvfs.lib import apfs_helper
from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import apfs_container_path_spec
Expand All @@ -23,7 +23,7 @@ def _EntriesGenerator(self):
APFSContainerPathSpec: a path specification.
"""
# Only the virtual root file has directory entries.
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(
self.path_spec)
if volume_index is not None:
return
Expand Down Expand Up @@ -135,7 +135,7 @@ def name(self):
if location is not None:
self._name = self._file_system.BasenamePath(location)
else:
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(
self.path_spec)
if volume_index is not None:
self._name = 'apfs{0:d}'.format(volume_index + 1)
Expand All @@ -162,7 +162,7 @@ def GetParentFileEntry(self):
Returns:
APFSContainerFileEntry: parent file entry or None if not available.
"""
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(
self.path_spec)
if volume_index is None:
return None
Expand Down
11 changes: 4 additions & 7 deletions dfvfs/vfs/apfs_container_file_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pyfsapfs

from dfvfs.lib import apfs_container
from dfvfs.lib import apfs_helper
from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import apfs_container_path_spec
Expand Down Expand Up @@ -81,8 +81,7 @@ def FileEntryExistsByPathSpec(self, path_spec):
Returns:
bool: True if the file entry exists.
"""
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
path_spec)
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(path_spec)

# The virtual root file has not corresponding volume index but
# should have a location.
Expand All @@ -109,8 +108,7 @@ def GetAPFSVolumeByPathSpec(self, path_spec):
Returns:
pyfsapfs.volume: an APFS volume or None if not available.
"""
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
path_spec)
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(path_spec)
if volume_index is None:
return None

Expand All @@ -125,8 +123,7 @@ def GetFileEntryByPathSpec(self, path_spec):
Returns:
APFSContainerFileEntry: a file entry or None if not exists.
"""
volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(
path_spec)
volume_index = apfs_helper.APFSContainerPathSpecGetVolumeIndex(path_spec)

# The virtual root file has not corresponding volume index but
# should have a location.
Expand Down
6 changes: 5 additions & 1 deletion dfvfs/vfs/apfs_file_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import unicode_literals

from dfvfs.lib import apfs_helper
from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import apfs_path_spec
Expand Down Expand Up @@ -63,7 +64,10 @@ def _Open(self, path_spec, mode='rb'):

fsapfs_volume = apfs_file_system.GetAPFSVolumeByPathSpec(path_spec.parent)
if not fsapfs_volume:
raise IOError('Unable to open APFS volume')
raise IOError('Unable to retrieve APFS volume')

apfs_helper.APFSUnlockVolume(
fsapfs_volume, path_spec, resolver.Resolver.key_chain)

self._fsapfs_volume = fsapfs_volume

Expand Down
24 changes: 24 additions & 0 deletions tests/credentials/apfs_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the Apple File System (APFS) credentials."""

from __future__ import unicode_literals

import unittest

from dfvfs.credentials import apfs_credentials

from tests import test_lib as shared_test_lib


class APFSCredentials(shared_test_lib.BaseTestCase):
"""Tests the Apple File System (APFS) credentials."""

def testInitialize(self):
"""Tests the __init__ function."""
test_credentials = apfs_credentials.APFSCredentials()
self.assertIsNotNone(test_credentials)


if __name__ == '__main__':
unittest.main()
24 changes: 24 additions & 0 deletions tests/credentials/bde_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the BitLocker Drive Encryption (BDE) credentials."""

from __future__ import unicode_literals

import unittest

from dfvfs.credentials import bde_credentials

from tests import test_lib as shared_test_lib


class BDECredentials(shared_test_lib.BaseTestCase):
"""Tests the BitLocker Drive Encryption (BDE) credentials."""

def testInitialize(self):
"""Tests the __init__ function."""
test_credentials = bde_credentials.BDECredentials()
self.assertIsNotNone(test_credentials)


if __name__ == '__main__':
unittest.main()
24 changes: 24 additions & 0 deletions tests/credentials/credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the credentials interface."""

from __future__ import unicode_literals

import unittest

from dfvfs.credentials import credentials

from tests import test_lib as shared_test_lib


class Credentials(shared_test_lib.BaseTestCase):
"""Tests the credentials interface."""

def testInitialize(self):
"""Tests the __init__ function."""
with self.assertRaises(ValueError):
credentials.Credentials()


if __name__ == '__main__':
unittest.main()
24 changes: 24 additions & 0 deletions tests/credentials/encrypted_stream_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the encrypted stream credentials."""

from __future__ import unicode_literals

import unittest

from dfvfs.credentials import encrypted_stream_credentials

from tests import test_lib as shared_test_lib


class APFSCredentials(shared_test_lib.BaseTestCase):
"""Tests the encrypted stream credentials."""

def testInitialize(self):
"""Tests the __init__ function."""
test_credentials = encrypted_stream_credentials.EncryptedStreamCredentials()
self.assertIsNotNone(test_credentials)


if __name__ == '__main__':
unittest.main()
24 changes: 24 additions & 0 deletions tests/credentials/fvde_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the FileVault Drive Encryption (FVDE) credentials."""

from __future__ import unicode_literals

import unittest

from dfvfs.credentials import fvde_credentials

from tests import test_lib as shared_test_lib


class FVDECredentials(shared_test_lib.BaseTestCase):
"""Tests the FileVault Drive Encryption (FVDE) credentials."""

def testInitialize(self):
"""Tests the __init__ function."""
test_credentials = fvde_credentials.FVDECredentials()
self.assertIsNotNone(test_credentials)


if __name__ == '__main__':
unittest.main()
23 changes: 11 additions & 12 deletions tests/credentials/keychain.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the keychain object."""
"""Tests for the key chain."""

from __future__ import unicode_literals

Expand All @@ -14,33 +14,32 @@


class KeychainTest(shared_test_lib.BaseTestCase):
"""Class to test the keychain object."""
"""Tests the key chain."""

# TODO: add tests for Empty
# TODO: add tests for ExtractCredentialsFromPathSpec

def testCredentialGetSet(self):
"""Tests the GetCredential and SetCredential functions."""
keychain_object = keychain.KeyChain()
test_keychain = keychain.KeyChain()

fake_path_spec = factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_FAKE, location='/test')
bde_path_spec = factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_BDE, parent=fake_path_spec)

with self.assertRaises(AttributeError):
keychain_object.SetCredential(
fake_path_spec, 'password', 'TEST')
test_keychain.SetCredential(fake_path_spec, 'password', 'TEST')

keychain_object.SetCredential(
bde_path_spec, 'password', 'TEST')
test_keychain.SetCredential(bde_path_spec, 'password', 'TEST')

credential = keychain_object.GetCredential(
fake_path_spec, 'password')
credential = test_keychain.GetCredential(fake_path_spec, 'password')
self.assertIsNone(credential)

credential = keychain_object.GetCredential(
bde_path_spec, 'password')
credential = test_keychain.GetCredential(bde_path_spec, 'password')
self.assertEqual(credential, 'TEST')

credentials = keychain_object.GetCredentials(bde_path_spec)
credentials = test_keychain.GetCredentials(bde_path_spec)
self.assertEqual(credentials, {'password': 'TEST'})


Expand Down
Loading

0 comments on commit bbbbc01

Please sign in to comment.