diff options
-rw-r--r-- | backend.py | 3 | ||||
-rw-r--r-- | instdata.py | 1 | ||||
-rw-r--r-- | kickstart.py | 61 | ||||
-rw-r--r-- | livecd.py | 2 | ||||
-rw-r--r-- | storage/__init__.py | 38 | ||||
-rw-r--r-- | storage/devicelibs/crypto.py | 20 | ||||
-rw-r--r-- | storage/formats/luks.py | 64 | ||||
-rw-r--r-- | storage/partitioning.py | 4 |
8 files changed, 185 insertions, 8 deletions
diff --git a/backend.py b/backend.py index 04235a82c..5501ac801 100644 --- a/backend.py +++ b/backend.py @@ -31,6 +31,7 @@ from constants import * import isys import kickstart import packages +import storage from flags import flags log = logging.getLogger("anaconda") @@ -103,6 +104,8 @@ class AnacondaBackend: for d in glob.glob("/tmp/DD-*"): shutil.copytree(d, "/root/" + os.path.basename(d)) + storage.writeEscrowPackets(anaconda) + sys.stdout.flush() if flags.setupFilesystems: syslog.stop() diff --git a/instdata.py b/instdata.py index fdb1a32e4..220b074ab 100644 --- a/instdata.py +++ b/instdata.py @@ -78,6 +78,7 @@ class InstallData: self.upgradeRoot = None self.rootParts = None self.upgradeSwapInfo = None + self.escrowCertificates = {} if iutil.isS390() or self.anaconda.isKickstart: self.firstboot = FIRSTBOOT_SKIP diff --git a/kickstart.py b/kickstart.py index 0104d4a18..308866697 100644 --- a/kickstart.py +++ b/kickstart.py @@ -33,8 +33,9 @@ from flags import flags from constants import * import sys import string -import urlgrabber.grabber as grabber +import urlgrabber import warnings +import network import upgrade import pykickstart.commands as commands from storage.devices import * @@ -131,6 +132,33 @@ class AnacondaKSPackages(Packages): self.seen = False +def getEscrowCertificate(anaconda, url): + if not url: + return None + + if url in anaconda.id.escrowCertificates: + return anaconda.id.escrowCertificates[url] + + needs_net = not url.startswith("/") and not url.startswith("file:") + if needs_net and not network.hasActiveNetDev(): + if not anaconda.intf.enableNetwork(anaconda): + anaconda.intf.messageWindow(_("No Network Available"), + _("Encryption key escrow requires " + "networking, but there was an error " + "enabling the network on your " + "system."), type="custom", + custom_icon="error", + custom_buttons=[_("_Exit installer")]) + sys.exit(1) + + log.info("escrow: downloading %s" % (url,)) + f = urlgrabber.urlopen(url) + try: + anaconda.id.escrowCertificates[url] = f.read() + finally: + f.close() + return anaconda.id.escrowCertificates[url] + ### ### SUBCLASSES OF PYKICKSTART COMMAND HANDLERS ### @@ -153,6 +181,10 @@ class AutoPart(commands.autopart.F12_AutoPart): if self.encrypted: self.handler.id.storage.encryptedAutoPart = True self.handler.id.storage.encryptionPassphrase = self.passphrase + self.handler.id.storage.autoPartEscrowCert = \ + getEscrowCertificate(self.handler.anaconda, self.escrowcert) + self.handler.id.storage.autoPartAddBackupPassphrase = \ + self.backuppassphrase self.handler.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return retval @@ -459,15 +491,20 @@ class LogVol(commands.logvol.F12_LogVol): if lvd.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = lvd.passphrase + cert = getEscrowCertificate(self.handler.anaconda, lvd.escrowcert) if lvd.preexist: luksformat = format - device.format = getFormat("luks", passphrase=lvd.passphrase, device=device.path) + device.format = getFormat("luks", passphrase=lvd.passphrase, device=device.path, + escrow_cert=cert, + add_backup_passphrase=lvd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format - request.format = getFormat("luks", passphrase=lvd.passphrase) + request.format = getFormat("luks", passphrase=lvd.passphrase, + escrow_cert=cert, + add_backup_passphrase=lvd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) @@ -701,15 +738,20 @@ class Partition(commands.partition.F12_Partition): if pd.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = pd.passphrase + cert = getEscrowCertificate(self.handler.anaconda, pd.escrowcert) if pd.preexist: luksformat = format - device.format = getFormat("luks", passphrase=pd.passphrase, device=device.path) + device.format = getFormat("luks", passphrase=pd.passphrase, device=device.path, + escrow_cert=cert, + add_backup_passphrase=pd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format - request.format = getFormat("luks", passphrase=pd.passphrase) + request.format = getFormat("luks", passphrase=pd.passphrase, + escrow_cert=cert, + add_backup_passphrase=pd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) @@ -837,15 +879,20 @@ class Raid(commands.raid.F12_Raid): if rd.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = rd.passphrase + cert = getEscrowCertificate(self.handler.anaconda, rd.escrowcert) if rd.preexist: luksformat = format - device.format = getFormat("luks", passphrase=rd.passphrase, device=device.path) + device.format = getFormat("luks", passphrase=rd.passphrase, device=device.path, + escrow_cert=cert, + add_backup_passphrase=rd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format - request.format = getFormat("luks", passphrase=rd.passphrase) + request.format = getFormat("luks", passphrase=rd.passphrase, + escrow_cert=cert, + add_backup_passphrase=rd.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) @@ -353,6 +353,8 @@ class LiveCDCopyBackend(backend.AnacondaBackend): # setup /etc/rpm/ for the post-install environment iutil.writeRpmPlatform(anaconda.rootPath) + storage.writeEscrowPackets(anaconda) + # maybe heavy handed, but it'll do if os.path.exists(anaconda.rootPath + "/usr/bin/rhgb") or os.path.exists(anaconda.rootPath + "/usr/bin/plymouth"): anaconda.id.bootloader.args.append("rhgb quiet") diff --git a/storage/__init__.py b/storage/__init__.py index 200fd6642..57901c792 100644 --- a/storage/__init__.py +++ b/storage/__init__.py @@ -26,6 +26,7 @@ import stat import errno import sys +import nss.nss import parted import isys @@ -44,6 +45,7 @@ from formats import get_device_format_class from formats import get_default_filesystem_type from devicelibs.lvm import safeLvmName from devicelibs.dm import name_from_dm_node +from devicelibs.crypto import generateBackupPassphrase from udev import * import iscsi import fcoe @@ -164,6 +166,37 @@ def storageComplete(anaconda): if rc == 0: return DISPATCH_BACK +def writeEscrowPackets(anaconda): + log.debug("escrow: writeEscrowPackets start") + + wait_win = anaconda.intf.waitWindow(_("Running..."), + _("Storing encryption keys")) + + nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized + + backupPassphrase = generateBackupPassphrase() + try: + for device in anaconda.id.storage.devices: + log.debug("escrow: device %s: %s" % + (repr(device.path), repr(device.format.type))) + if (device.format.type == "luks" and + device.format.escrow_cert is not None): + device.format.escrow(anaconda.rootPath + "/root", + backupPassphrase) + + wait_win.pop() + except (IOError, RuntimeError) as e: + wait_win.pop() + anaconda.intf.messageWindow(_("Error"), + _("Error storing an encryption key: " + "%s\n") % str(e), type="custom", + custom_icon="error", + custom_buttons=[_("_Exit installer")]) + sys.exit(1) + + log.debug("escrow: writeEscrowPackets done") + + def undoEncryption(storage): for device in storage.devicetree.getDevicesByType("luks/dm-crypt"): if device.exists: @@ -196,6 +229,8 @@ class Storage(object): self.clearPartDisks = [] self.encryptedAutoPart = False self.encryptionPassphrase = None + self.autoPartEscrowCert = None + self.autoPartAddBackupPassphrase = False self.encryptionRetrofit = False self.reinitializeDisks = False self.zeroMbr = None @@ -567,7 +602,8 @@ class Storage(object): if kwargs.has_key("fmt_type"): kwargs["format"] = getFormat(kwargs.pop("fmt_type"), mountpoint=kwargs.pop("mountpoint", - None)) + None), + **kwargs.pop("fmt_args", {})) if kwargs.has_key("disks"): parents = kwargs.pop("disks") diff --git a/storage/devicelibs/crypto.py b/storage/devicelibs/crypto.py index 94c208143..5e7a82440 100644 --- a/storage/devicelibs/crypto.py +++ b/storage/devicelibs/crypto.py @@ -29,6 +29,26 @@ from ..errors import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) +# Keep the character set size a power of two to make sure all characters are +# equally likely +GENERATED_PASSPHRASE_CHARSET = ("0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "./") +# 20 chars * 6 bits per char = 120 "bits of security" +GENERATED_PASSPHRASE_LENGTH = 20 + +def generateBackupPassphrase(): + rnd = os.urandom(GENERATED_PASSPHRASE_LENGTH) + cs = GENERATED_PASSPHRASE_CHARSET + raw = "".join([cs[ord(c) % len(cs)] for c in rnd]) + + # Make the result easier to read + parts = [] + for i in xrange(0, len(raw), 5): + parts.append(raw[i : i + 5]) + return "-".join(parts) + def askyes(question): return True diff --git a/storage/formats/luks.py b/storage/formats/luks.py index c34868849..15b92b6ba 100644 --- a/storage/formats/luks.py +++ b/storage/formats/luks.py @@ -24,6 +24,8 @@ import os +import volume_key + from iutil import log_method_call from ..errors import * from ..devicelibs import crypto @@ -60,6 +62,8 @@ class LUKS(DeviceFormat): cipher -- cipher mode string key_size -- key size in bits exists -- indicates whether this is an existing format + escrow_cert -- certificate to use for key escrow + add_backup_passphrase -- generate a backup passphrase? """ log_method_call(self, *args, **kwargs) DeviceFormat.__init__(self, *args, **kwargs) @@ -76,6 +80,8 @@ class LUKS(DeviceFormat): # FIXME: these should both be lists, but managing them will be a pain self.__passphrase = kwargs.get("passphrase") self._key_file = kwargs.get("key_file") + self.escrow_cert = kwargs.get("escrow_cert") + self.add_backup_passphrase = kwargs.get("add_backup_passphrase", False) if not self.mapName and self.exists and self.uuid: self.mapName = "luks-%s" % self.uuid @@ -239,6 +245,64 @@ class LUKS(DeviceFormat): key_file=self._key_file, del_passphrase=passphrase) + def _escrowVolumeIdent(self, vol): + """ Return an escrow packet filename prefix for a volume_key.Volume. """ + label = vol.label + if label is not None: + label = label.replace("/", "_") + uuid = vol.uuid + if uuid is not None: + uuid = uuid.replace("/", "_") + # uuid is never None on LUKS volumes + if label is not None and uuid is not None: + volume_ident = "%s-%s" % (label, uuid) + elif uuid is not None: + volume_ident = uuid + elif label is not None: + volume_ident = label + else: + volume_ident = "_unknown" + return volume_ident + + def escrow(self, directory, backupPassphrase): + log.debug("escrow: escrowVolume start for %s" % self.device) + vol = volume_key.Volume.open(self.device) + volume_ident = self._escrowVolumeIdent(vol) + + ui = volume_key.UI() + # This callback is not expected to be used, let it always fail + ui.generic_cb = lambda unused_prompt, unused_echo: None + def known_passphrase_cb(unused_prompt, failed_attempts): + if failed_attempts == 0: + return self.__passphrase + return None + ui.passphrase_cb = known_passphrase_cb + + log.debug("escrow: getting secret") + vol.get_secret(volume_key.SECRET_DEFAULT, ui) + log.debug("escrow: creating packet") + default_packet = vol.create_packet_assymetric_from_cert_data \ + (volume_key.SECRET_DEFAULT, self.escrow_cert, ui) + log.debug("escrow: packet created") + with open("%s/%s-escrow" % (directory, volume_ident), "wb") as f: + f.write(default_packet) + log.debug("escrow: packet written") + + if self.add_backup_passphrase: + log.debug("escrow: adding backup passphrase") + vol.add_secret(volume_key.SECRET_PASSPHRASE, backupPassphrase) + log.debug("escrow: creating backup packet") + backup_passphrase_packet = \ + vol.create_packet_assymetric_from_cert_data \ + (volume_key.SECRET_PASSPHRASE, self.escrow_cert, ui) + log.debug("escrow: backup packet created") + with open("%s/%s-escrow-backup-passphrase" % + (directory, volume_ident), "wb") as f: + f.write(backup_passphrase_packet) + log.debug("escrow: backup packet written") + + log.debug("escrow: escrowVolume done for %s" % repr(self.device)) + register_device_format(LUKS) diff --git a/storage/partitioning.py b/storage/partitioning.py index 80724d67d..36cb861b4 100644 --- a/storage/partitioning.py +++ b/storage/partitioning.py @@ -65,9 +65,13 @@ def _createFreeSpacePartitions(anaconda): for disk in disks: if anaconda.id.storage.encryptedAutoPart: fmt_type = "luks" + fmt_args = {"escrow_cert": anaconda.id.storage.autoPartEscrowCert, + "add_backup_passphrase": anaconda.id.storage.autoPartAddBackupPassphrase} else: fmt_type = "lvmpv" + fmt_args = {} part = anaconda.id.storage.newPartition(fmt_type=fmt_type, + fmt_args=fmt_args, size=1, grow=True, disks=[disk]) |