[blivet] Allow specifying minimum entropy when creating LUKS

Vratislav Podzimek vpodzime at redhat.com
Fri Aug 29 12:21:12 UTC 2014


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:
+                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:
+        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



More information about the anaconda-patches mailing list