[blivet] Allow specifying minimum entropy when creating LUKS
Anne Mulhern
amulhern at redhat.com
Fri Aug 29 15:02:07 UTC 2014
----- Original Message -----
> From: "Vratislav Podzimek" <vpodzime at redhat.com>
> To: anaconda-patches at lists.fedorahosted.org
> Sent: Friday, August 29, 2014 8:21:12 AM
> Subject: [blivet] Allow specifying minimum entropy when creating LUKS
>
> Creating new LUKS format requires high-quality random data and such quality
> may
> be specified by minimum entropy of kernel's random data pool required. Blivet
> thus need to check such entropy and wait for it to become high enough if
> requested by the user code (Anaconda, OpenLMI, blivet-gui, etc.).
>
> In order to allow the user code to give a hint to the users about what's
> happening and how they can help with entropy generation, blivet also needs a
> callback that is called in case of LUKS format being created with not enough
> entropy available.
>
> Related: rhbz#1073679
> Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
> ---
> blivet/callbacks.py | 9 ++++++---
> blivet/deviceaction.py | 13 ++++++++++++-
> blivet/devicefactory.py | 13 ++++++++++---
> blivet/devicelibs/crypto.py | 11 ++++++++++-
> blivet/formats/luks.py | 8 +++++++-
> blivet/partitioning.py | 25 ++++++++++++++++++-------
> blivet/util.py | 4 ++++
> 7 files changed, 67 insertions(+), 16 deletions(-)
>
> diff --git a/blivet/callbacks.py b/blivet/callbacks.py
> index 2f7a9cf..b2e8c3a 100644
> --- a/blivet/callbacks.py
> +++ b/blivet/callbacks.py
> @@ -34,12 +34,14 @@ _CallbacksRegister = namedtuple("_CallbacksRegister",
> ["create_format_pre",
> "create_format_post",
> "resize_format_pre",
> - "resize_format_post"])
> + "resize_format_post",
> + "wait_for_entropy"])
>
> def create_new_callbacks_register(create_format_pre=None,
> create_format_post=None,
> resize_format_pre=None,
> - resize_format_post=None):
> + resize_format_post=None,
> + wait_for_entropy=None):
> """
> A function for creating a new opaque object holding the references to
> callbacks. The point of this function is to hide the implementation of
> such
> @@ -49,7 +51,8 @@ def create_new_callbacks_register(create_format_pre=None,
> """
>
> return _CallbacksRegister(create_format_pre, create_format_post,
> - resize_format_pre, resize_format_post)
> + resize_format_pre, resize_format_post,
> + wait_for_entropy)
>
> CreateFormatPreData = namedtuple("CreateFormatPreData",
> ["msg"])
> diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py
> index fff5f39..a17ade1 100644
> --- a/blivet/deviceaction.py
> +++ b/blivet/deviceaction.py
> @@ -24,10 +24,11 @@
> from . import util
>
> from . import udev
> +from .util import get_current_entropy
> from .devices import StorageDevice
> from .devices import PartitionDevice
> from .devices import LVMLogicalVolumeDevice
> -from .formats import getFormat
> +from .formats import getFormat, luks
> from .storage_log import log_exception_info
> from parted import partitionFlag, PARTITION_LBA
> from .i18n import _, N_
> @@ -552,6 +553,16 @@ class ActionCreateFormat(DeviceAction):
>
> self.device.disk.format.commitToDisk()
>
> + if isinstance(self.device.format, luks.LUKS):
> + # LUKS needs to wait for random data entropy if it is too low
> + required_entropy = self.device.format.min_luks_entropy
> + current_entropy = get_current_entropy()
> + if required_entropy > 0 and current_entropy < required_entropy:
minimum entropy less than 0 treated as expected. Also, might be useful to rename
required_entropy min_required_entropy here.
> + if callbacks and callbacks.wait_for_entropy:
> + msg = _("Not enough entropy to create LUKS format. "
> + "%d bits are needed.") % required_entropy
> + callbacks.wait_for_entropy(msg, required_entropy)
> +
> self.device.format.create(device=self.device.path,
> options=self.device.formatArgs)
> # Get the UUID now that the format is created
> diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
> index 7926d39..4e123b0 100644
> --- a/blivet/devicefactory.py
> +++ b/blivet/devicefactory.py
> @@ -218,7 +218,7 @@ class DeviceFactory(object):
> label=None, raid_level=None, encrypted=False,
> container_encrypted=False, container_name=None,
> container_raid_level=None, container_size=SIZE_POLICY_AUTO,
> - name=None, device=None):
> + name=None, device=None, min_luks_entropy=0):
> """
> :param storage: a Blivet instance
> :type storage: :class:`~.Blivet`
> @@ -258,6 +258,9 @@ class DeviceFactory(object):
> :type container_encrypted: bool
> :keyword container_size: requested container size
> :type container_size: :class:`~.size.Size`
> + :keyword min_luks_entropy: minimum entropy in bits required for
> + LUKS format creation
> + :type min_luks_entropy: int
>
> """
>
> @@ -297,6 +300,7 @@ class DeviceFactory(object):
>
> self.child_factory = None
> self.parent_factory = None
> + self.min_luks_entropy = min_luks_entropy
>
> # used for error recovery
> self.__devices = []
> @@ -607,6 +611,7 @@ class DeviceFactory(object):
>
> fmt = getFormat(self.fstype,
> mountpoint=self.mountpoint,
> + min_luks_entropy=self.min_luks_entropy,
> **fmt_args)
> luks_device = LUKSDevice("luks-" + device.name,
> parents=[device], fmt=fmt)
> @@ -685,7 +690,8 @@ class DeviceFactory(object):
> if parent_container:
> parent_container.parents.remove(self.device)
> leaf_format = self.device.format
> - self.storage.formatDevice(self.device, getFormat("luks"))
> + self.storage.formatDevice(self.device, getFormat("luks",
> +
> min_luks_entropy=self.min_luks_entropy))
> luks_device = LUKSDevice("luks-%s" % self.device.name,
> fmt=leaf_format,
> parents=self.device)
> @@ -1026,7 +1032,8 @@ class PartitionSetFactory(PartitionFactory):
> if container:
> container.parents.remove(member)
>
> - self.storage.formatDevice(member, getFormat("luks"))
> + self.storage.formatDevice(member, getFormat("luks",
> +
> min_luks_entropy=self.min_luks_entropy))
> luks_member = LUKSDevice("luks-%s" % member.name,
> parents=[member],
> fmt=getFormat(self.fstype))
> diff --git a/blivet/devicelibs/crypto.py b/blivet/devicelibs/crypto.py
> index b911e94..ccc8aa3 100644
> --- a/blivet/devicelibs/crypto.py
> +++ b/blivet/devicelibs/crypto.py
> @@ -21,12 +21,15 @@
> #
>
> import random
> +import time
> from pycryptsetup import CryptSetup
>
> from ..errors import CryptoError
> from ..size import Size
> +from ..util import get_current_entropy
>
> LUKS_METADATA_SIZE = Size("2 MiB")
> +MIN_CREATE_ENTROPY = 256 # bits
>
> # Keep the character set size a power of two to make sure all characters are
> # equally likely
> @@ -65,7 +68,8 @@ def luks_status(name):
>
> def luks_format(device,
> passphrase=None,
> - cipher=None, key_size=None, key_file=None):
> + cipher=None, key_size=None, key_file=None,
> + min_entropy=0):
> # pylint: disable=unused-argument
> if not passphrase:
> raise ValueError("luks_format requires passphrase")
> @@ -88,6 +92,11 @@ def luks_format(device,
> if cipherMode: kwargs["cipherMode"] = cipherMode
> if key_size: kwargs["keysize"] = key_size
>
> + if min_entropy >= 0:
minimum entropy less than zero treated as expected value.
> + while get_current_entropy() < min_entropy:
> + # wait for entropy to become high enough
> + time.sleep(1)
> +
> rc = cs.luksFormat(**kwargs)
> if rc:
> raise CryptoError("luks_format failed for '%s'" % device)
> diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py
> index bcececb..d01bc6a 100644
> --- a/blivet/formats/luks.py
> +++ b/blivet/formats/luks.py
> @@ -71,6 +71,9 @@ class LUKS(DeviceFormat):
> :type escrow_cert: str
> :keyword add_backup_passphrase: generate a backup passphrase?
> :type add_backup_passphrase: bool.
> + :keyword min_luks_entropy: minimum entropy in bits required for
> + format creation
> + :type min_luks_entropy: int
>
> .. note::
>
> @@ -97,6 +100,7 @@ class LUKS(DeviceFormat):
> self._key_file = kwargs.get("key_file")
> self.escrow_cert = kwargs.get("escrow_cert")
> self.add_backup_passphrase = kwargs.get("add_backup_passphrase",
> False)
> + self.min_luks_entropy = kwargs.get("min_luks_entropy", 0)
>
> if not self.mapName and self.exists and self.uuid:
> self.mapName = "luks-%s" % self.uuid
> @@ -220,7 +224,9 @@ class LUKS(DeviceFormat):
> passphrase=self.__passphrase,
> key_file=self._key_file,
> cipher=self.cipher,
> - key_size=self.key_size)
> + key_size=self.key_size,
> + min_entropy=self.min_luks_entropy)
> +
> except Exception:
> raise
> else:
> diff --git a/blivet/partitioning.py b/blivet/partitioning.py
> index 48d72ff..32fcf68 100644
> --- a/blivet/partitioning.py
> +++ b/blivet/partitioning.py
> @@ -69,7 +69,7 @@ def _getCandidateDisks(storage):
>
> return disks
>
> -def _scheduleImplicitPartitions(storage, disks):
> +def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> """ Schedule creation of a lvm/btrfs member partitions for autopart.
>
> We create one such partition on each disk. They are not allocated
> until
> @@ -79,6 +79,9 @@ def _scheduleImplicitPartitions(storage, disks):
> :type storage: :class:`~.Blivet`
> :param disks: list of partitioned disks with free space
> :type disks: list of :class:`~.devices.StorageDevice`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
> :returns: list of newly created (unallocated) partitions
> :rtype: list of :class:`~.devices.PartitionDevice`
> """
> @@ -95,7 +98,8 @@ def _scheduleImplicitPartitions(storage, disks):
> fmt_args = {"passphrase": storage.encryptionPassphrase,
> "cipher": storage.encryptionCipher,
> "escrow_cert": storage.autoPartEscrowCert,
> - "add_backup_passphrase":
> storage.autoPartAddBackupPassphrase}
> + "add_backup_passphrase":
> storage.autoPartAddBackupPassphrase,
> + "min_luks_entropy": min_luks_entropy}
> else:
> if storage.autoPartType in (AUTOPART_TYPE_LVM,
> AUTOPART_TYPE_LVM_THINP):
> fmt_type = "lvmpv"
> @@ -111,7 +115,7 @@ def _scheduleImplicitPartitions(storage, disks):
>
> return devs
>
> -def _schedulePartitions(storage, disks):
> +def _schedulePartitions(storage, disks, min_luks_entropy=0):
> """ Schedule creation of autopart partitions.
>
> This only schedules the requests for actual partitions.
> @@ -120,6 +124,9 @@ def _schedulePartitions(storage, disks):
> :type storage: :class:`~.Blivet`
> :param disks: list of partitioned disks with free space
> :type disks: list of :class:`~.devices.StorageDevice`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
> :returns: None
> :rtype: None
> """
> @@ -196,7 +203,8 @@ def _schedulePartitions(storage, disks):
> fmt_args = {"passphrase": storage.encryptionPassphrase,
> "cipher": storage.encryptionCipher,
> "escrow_cert": storage.autoPartEscrowCert,
> - "add_backup_passphrase":
> storage.autoPartAddBackupPassphrase}
> + "add_backup_passphrase":
> storage.autoPartAddBackupPassphrase,
> + "min_luks_entropy": min_luks_entropy}
> else:
> fmt_type = request.fstype
> fmt_args = {}
> @@ -328,13 +336,16 @@ def _scheduleVolumes(storage, devs):
> # schedule the device for creation
> storage.createDevice(dev)
>
> -def doAutoPartition(storage, data):
> +def doAutoPartition(storage, data, min_luks_entropy=0):
> """ Perform automatic partitioning.
>
> :param storage: a :class:`~.Blivet` instance
> :type storage: :class:`~.Blivet`
> :param data: kickstart data
> :type data: :class:`pykickstart.BaseHandler`
> + :param min_luks_entropy: minimum entropy in bits required for
> + luks format creation
> + :type min_luks_entropy: int
>
> :attr:`Blivet.doAutoPart` controls whether this method creates the
> automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> @@ -369,7 +380,7 @@ def doAutoPartition(storage, data):
> raise NoDisksError(_("No usable disks selected"))
>
> disks = _getCandidateDisks(storage)
> - devs = _scheduleImplicitPartitions(storage, disks)
> + devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> log.debug("candidate disks: %s", disks)
> log.debug("devs: %s", devs)
>
> @@ -377,7 +388,7 @@ def doAutoPartition(storage, data):
> raise NotEnoughFreeSpaceError(_("Not enough free space on disks for
> "
> "automatic partitioning"))
>
> - _schedulePartitions(storage, disks)
> + _schedulePartitions(storage, disks, min_luks_entropy=min_luks_entropy)
>
> # run the autopart function to allocate and grow partitions
> doPartitioning(storage)
> diff --git a/blivet/util.py b/blivet/util.py
> index a15bb4a..649b8a7 100644
> --- a/blivet/util.py
> +++ b/blivet/util.py
> @@ -454,3 +454,7 @@ def variable_copy(obj, memo, omit=None, shallow=None,
> duplicate=None):
> setattr(new, attr, copy.deepcopy(value, memo))
>
> return new
> +
> +def get_current_entropy():
> + with open("/proc/sys/kernel/random/entropy_avail", "r") as fobj:
> + return int(fobj.readline())
> --
> 1.9.3
>
> _______________________________________________
> anaconda-patches mailing list
> anaconda-patches at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
>
0 is a very good value for minimum entropy.
I think that a minimum entropy less than zero should
be treated as an exceptional condition (rather than an
expected one). I've marked the places where it seems to
be being treated as expected.
- mulhern
More information about the anaconda-patches
mailing list