diff --git a/cryptodev.py b/cryptodev.py new file mode 100644 index 0000000..6b4800c --- /dev/null +++ b/cryptodev.py @@ -0,0 +1,300 @@ +import os +import iutil + +import logging +log = logging.getLogger("anaconda") + +def isLuks(device): + if not device.startswith("/"): + device = "/dev/" + device + rc = iutil.execWithRedirect("cryptsetup", + ["isLuks", device], + stdout = "/dev/null", + stderr = "/dev/null", + searchPath = 1) + if rc: + return False + else: + return True + + +"""It seems like at some point it will be useful to maintain a catalog of + the encrypted block devices in the system. Here's a start toward that end.""" + +# reference dict with underlying device basename as key, instance as value +encryptedDevs = {} + +def register_encrypted_device(dev, clobber=False): + if not isinstance(dev, EncryptedDevice): + raise ValueError, "Can only register EncryptedDevice instances." + + if dev.getDevice(encrypted=1) in encryptedDevs and not clobber: + raise RuntimeError, "Cannot register already-registered device." + elif : + + if dev.getScheme() is None: + # no need to catalog the passthrough devices + return + + encryptedDevs[dev.getDevice(encrypted=1)] = dev + +def remove_encrypted_device(devname): + try: + dev = encryptedDevs.pop(devname, None) + except KeyError: + pass + else: + del dev + +def lookup_encrypted_device(devname): + dev = encryptedDevs.get(devname) + if not dev: + for cdev in encryptedDevs.values(): + if cdev.getDevice() == devname: + dev = cdev + + return dev + + +class EncryptedBlockDevice: + """EncryptedBlockDevice doubles as a base class for encrypted block devices + and a sort of passthrough (NOP) class for devices that are not + encrypted, to minimize the effect on surrounding code.""" + def __init__(self, device=None): + self.packages = [] + self.scheme = None + self._device = None + self.setDevice(device) + + def setDevice(self, device): + """Set the underlying block device, eg: 'sdb2'.""" + if device == "none": + device = None + self._device = device + + def getNeededPackages(self): + """Returns the set of packages this encryption scheme requires.""" + return self.packages + + def getScheme(self): + """Returns the name of the encryption scheme used by the device.""" + return self.scheme + + def getDevice(self, encrypted=0): + """Returns the mapped/decrypted device minus the '/dev' prefix + (eg: 'mapper/luks-sdb2') or, if requested, the underlying + block device (eg: 'sdb2')""" + return self._device + + def crypttab(self): + """Returns a line suitable for /etc/crypttab describing the mapping.""" + return "" + + def formatDevice(self): + """Format the underlying block device so that it can be encrypted.""" + pass + + def openDevice(self): + """Open the device so it can be accessed for writing filesystems, &c.""" + pass + + def closeDevice(self): + """Close the device.""" + pass + +PassthroughCrypto = EncryptedBlockDevice + +class DMCryptDevice(EncryptedBlockDevice): + def __init__(self, device=None, name=None, passphrase=None): + #log.debug("DMCryptDevice at %x" % (id(self),)) + #log.debug(" device=%s name=%s\n passphrase='%s'" % (device, + # name, + # passphrase)) + EncryptedDevice.__init__(self, device) + self.passphrase = passphrase + self.name = name + self.nameLocked = False + self.scheme = "DMCrypt" + self.packages = ["cryptsetup-luks"] + + def setDevice(self, device): + #log.debug("setDevice: device=%s _device=%s <%x>" % (device, + # self._device, + # id(self))) + self._device = device + if device is not None: + name = "%s-%s" % (self.getScheme().lower(), + os.path.basename(device)) + self.setName(name) + + def getDevice(self, encrypted=0): + if encrypted: + dev = self._device + else: + dev = "mapper/%s" % (self.name,) + + return dev + + def setName(self, name, lock=False): + """Set the name of the mapped device, eg: 'dmcrypt-sda3'""" + if self.name and not self.getStatus(): + raise RuntimeError, "Cannot rename an active mapping." + + if self.nameLocked: + log.debug("Failed to change locked mapping name: %s" % + (self.name,)) + return + + self.name = name + if lock and name: + # don't allow anyone to lock the name as "" or None + self.nameLocked = True + + def setPassphrase(self, passphrase): + """Set the (plaintext) passphrase used to access the device.""" + self.passphrase = passphrase + + def crypttab(self): + """Return a crypttab formatted line describing this mapping.""" + format = "%-23s %-15s %s\n" + line = format % (self.name, + "/dev/%s" % (self.getDevice(encrypted=1),), + "none") + return line + + def getStatus(self): + """0 means active, 1 means inactive (or non-existent)""" + if not self.name: + return 1 + + rc = iutil.execWithRedirect("cryptsetup", + ["status", self.name], + stdout = "/dev/null", + stderr = "/dev/null", + searchPath = 1) + return rc + + def openDevice(self): + """Establish a decrypted mapping against the encrypted device.""" + if not self.getStatus(): + return + + if not self.passphrase: + raise RuntimeError, "Cannot create mapping without a passphrase." + + device = self.getDevice(encrypted=1) + if not device: + raise ValueError, "Cannot open mapping without a device." + + log.info("mapping device %s to %s" % (device, self.name)) + + p = os.pipe() + os.write(p[1], "%s\n" % (self.passphrase,)) + os.close(p[1]) + + rc = iutil.execWithRedirect("cryptsetup", + ["create", + self.name, + "/dev/%s" % (device,)], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + + def closeDevice(self): + """Remove the decrypted mapping against the encrypted device.""" + if self.getStatus(): + return + + log.info("unmapping device %s" % (self.name,)) + rc = iutil.execWithRedirect("cryptsetup", + ["remove", self.name], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + +class LUKSDevice(DMCryptDevice): + """LUKSDevice represents an encrypted block device using LUKS/dm-crypt. + It requires an underlying block device and a passphrase to become + functional.""" + def __init__(self, device=None, name=None, passphrase=None, format=0): + DMCryptDevice.__init__(self, device=device, name=name, + passphrase=passphrase) + self.format = format + self.preexist = not format + self.packages = ["cryptsetup-luks"] + self.scheme = "LUKS" + + def formatDevice(self): + """Write a LUKS header onto the device.""" + if not self.format: + return + + if not self.getStatus(): + log.debug("refusing to format active mapping %s" % (self.name,)) + return 1 + + if not self.passphrase: + raise RuntimeError, "Cannot create mapping without a passphrase." + + device = self.getDevice(encrypted=1) + if not device: + raise ValueError, "Cannot open mapping without a device." + + log.info("formatting %s as %s" % (device, self.getScheme())) + p = os.pipe() + os.write(p[1], "%s\n" % (self.passphrase,)) + os.close(p[1]) + + rc = iutil.execWithRedirect("cryptsetup", + ["-q", "luksFormat", + "/dev/%s" % (device,)], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + self.format = 0 + return rc + + def openDevice(self): + if not self.getStatus(): + # already mapped + return + + if not self.passphrase: + raise RuntimeError, "Cannot create mapping without a passphrase." + + device = self.getDevice(encrypted=1) + if not device: + raise ValueError, "Cannot open mapping without a device." + + log.info("mapping %s device %s to %s" % (self.getScheme(), + device, + self.name)) + + p = os.pipe() + os.write(p[1], "%s\n" % (self.passphrase,)) + os.close(p[1]) + + rc = iutil.execWithRedirect("cryptsetup", + ["luksOpen", + "/dev/%s" % (device,), + self.name], + stdin = p[0], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + + def closeDevice(self): + log.info("unmapping %s device %s" % (self.getScheme(), self.name)) + rc = iutil.execWithRedirect("cryptsetup", + ["luksClose", self.name], + stdout = "/dev/null", + stderr = "/dev/tty5", + searchPath = 1) + return rc + + diff --git a/fsset.py b/fsset.py index 61ba6a0..0a61ddb 100644 --- a/fsset.py +++ b/fsset.py @@ -30,6 +30,7 @@ import raid import lvm import types from flags import flags +from cryptodev import * import rhpl from rhpl.translate import _, N_ @@ -180,6 +181,7 @@ class FileSystemType: self.supportsFsProfiles = False self.fsProfileSpecifier = None self.fsprofile = None + self.supportsEncryption = False def isKernelFS(self): """Returns True if this is an in-kernel pseudo-filesystem.""" @@ -343,6 +345,7 @@ class reiserfsFileSystem(FileSystemType): self.packages = [ "reiserfs-utils" ] self.maxSizeMB = 8 * 1024 * 1024 + self.supportsEncryption = True def formatDevice(self, entry, progress, chroot='/'): devicePath = entry.device.setupDevice(chroot) @@ -389,6 +392,7 @@ class xfsFileSystem(FileSystemType): self.supported = 0 self.packages = [ "xfsprogs" ] + self.supportsEncryption = True def formatDevice(self, entry, progress, chroot='/'): devicePath = entry.device.setupDevice(chroot) @@ -438,6 +442,7 @@ class jfsFileSystem(FileSystemType): self.packages = [ "jfsutils" ] self.maxSizeMB = 8 * 1024 * 1024 + self.supportsEncryption = True def labelDevice(self, entry, chroot): devicePath = entry.device.setupDevice(chroot) @@ -505,6 +510,7 @@ class extFileSystem(FileSystemType): self.packages = [ "e2fsprogs" ] self.supportsFsProfiles = True self.fsProfileSpecifier = "-T" + self.supportsEncryption = True def labelDevice(self, entry, chroot): devicePath = entry.device.setupDevice(chroot) @@ -657,6 +663,7 @@ class lvmPhysicalVolumeDummyFileSystem(FileSystemType): self.maxSizeMB = 8 * 1024 * 1024 self.supported = 1 self.packages = [ "lvm2" ] + self.supportsEncryption = True def isMountable(self): return 0 @@ -700,6 +707,7 @@ class swapFileSystem(FileSystemType): self.linuxnativefs = 1 self.supported = 1 self.maxLabelChars = 15 + self.supportsEncryption = True def mount(self, device, mountpoint, readOnly=0, bindMount=0, instroot = None): @@ -790,6 +798,7 @@ class FATFileSystem(FileSystemType): self.maxSizeMB = 1024 * 1024 self.name = "vfat" self.packages = [ "dosfstools" ] + self.supportsEncryption = True def formatDevice(self, entry, progress, chroot='/'): devicePath = entry.device.setupDevice(chroot) @@ -1149,6 +1158,11 @@ class FileSystemSet: for entry in self.entries: if entry.device.getDevice() == dev: return entry + + # getDevice() will return the mapped device if using LUKS + if entry.device.device == dev: + return entry + return None def copy (self): @@ -1222,6 +1236,14 @@ MAILADDR root return cf return + def crypttab(self): + """set up /etc/crypttab""" + crypttab = "" + for entry in self.entries: + crypttab += entry.device.crypto.crypttab() + + return crypttab + def write (self, prefix): f = open (prefix + "/etc/fstab", "w") f.write (self.fstab()) @@ -1234,6 +1256,12 @@ MAILADDR root f.write (cf) f.close () + crypttab = self.crypttab() + if crypttab: + f = open(prefix + "/etc/crypttab", "w") + f.write(crypttab) + f.close() + # touch mtab open (prefix + "/etc/mtab", "w+") f.close () @@ -1812,6 +1840,7 @@ MAILADDR root if entry.mountpoint == "swap" and not swapoff: continue entry.umount(instPath) + entry.device.cleanupDevice() class FileSystemSetEntry: def __init__ (self, device, mountpoint, @@ -1934,24 +1963,30 @@ class FileSystemSetEntry: class Device: - def __init__(self, device = "none"): + def __init__(self, device = "none", encryption=None): self.device = device self.label = None self.isSetup = 0 self.doLabel = 1 self.deviceOptions = "" + if encryption is None: + self.crypto = PasshthroughCrypto(device=device) + else: + self.crypto = encryption + if device not in ("none", None): + self.crypto.setDevice(device) def getComment (self): return "" def getDevice (self, asBoot = 0): - return self.device + return self.crypto.getDevice() def setupDevice (self, chroot='/', devPrefix='/dev/'): - return self.device + return self.crypto.getDevice() def cleanupDevice (self, chroot, devPrefix='/dev/'): - pass + self.crypto.closeDevice() def solidify (self): pass @@ -1983,17 +2018,17 @@ class DevDevice(Device): """Device with a device node rooted in /dev that we just always use the pre-created device node for.""" def __init__(self, dev): - Device.__init__(self) - self.device = dev + Device.__init__(self, device=dev) def getDevice(self, asBoot = 0): - return self.device + return self.crypto.getDevice() def setupDevice(self, chroot='/', devPrefix='/dev'): #We use precreated device but we have to make sure that the device exists - path = '/dev/%s' % (self.getDevice(),) - isys.makeDevInode(self.getDevice(), path) - return path + path = '/dev/%s' % (self.crypto.getDevice(encrypted=1),) + isys.makeDevInode(self.crypto.getDevice(encrypted=1), path) + self.crypto.openDevice() + return self.crypto.getDevice() class RAIDDevice(Device): # XXX usedMajors does not take in account any EXISTING md device @@ -2004,8 +2039,8 @@ class RAIDDevice(Device): # members is a list of Device based instances that will be # a part of this raid device def __init__(self, level, members, minor=-1, spares=0, existing=0, - chunksize = 64): - Device.__init__(self) + chunksize = 64, encryption=None): + Device.__init__(self, encryption=encryption) self.level = level self.members = members self.spares = spares @@ -2039,6 +2074,8 @@ class RAIDDevice(Device): RAIDDevice.usedMajors[minor] = None self.device = "md" + str(minor) self.minor = minor + + self.crypto.setDevice(self.device) # make sure the list of raid members is sorted self.members.sort() @@ -2137,14 +2174,20 @@ class RAIDDevice(Device): self.isSetup = 1 else: isys.raidstart(self.device, self.members[0]) - return node + + self.crypto.formatDevice() + self.crypto.openDevice() + return "%s/%s" % (devPrefix, self.crypto.getDevice()) def getDevice (self, asBoot = 0): if not asBoot: - return self.device + return self.crypto.getDevice() else: return self.members[0] + def cleanupDevice (self, chroot, devPrefix='/dev'): + self.crypto.closeDevice() + def solidify(self): return @@ -2232,20 +2275,27 @@ class LogicalVolumeDevice(Device): class PartitionDevice(Device): - def __init__(self, partition): - Device.__init__(self) + def __init__(self, partition, encryption=None): if type(partition) != types.StringType: raise ValueError, "partition must be a string" - self.device = partition + Device.__init__(self, device=partition, encryption=encryption) (disk, pnum) = getDiskPart(partition) if isys.driveIsIscsi(disk): self.setAsNetdev() + def getDevice(self, asBoot = 0): + return self.crypto.getDevice() + def setupDevice(self, chroot="/", devPrefix='/dev'): - path = '%s/%s' % (devPrefix, self.getDevice(),) - isys.makeDevInode(self.getDevice(), path) - return path + path = '%s/%s' % (devPrefix, self.crypto.getDevice(encrypted=1),) + isys.makeDevInode(self.crypto.getDevice(encrypted=1), path) + self.crypto.formatDevice() + self.crypto.openDevice() + return "%s/%s" % (devPrefix, self.crypto.getDevice()) + + def cleanupDevice (self, chroot, devPrefix='/dev'): + self.crypto.closeDevice() class PartedPartitionDevice(PartitionDevice): def __init__(self, partition): diff --git a/gui.py b/gui.py index 5a65a9a..72ab20b 100755 --- a/gui.py +++ b/gui.py @@ -617,6 +617,58 @@ class InstallKeyWindow: def destroy(self): self.win.destroy() +class luksPassphraseWindow: + def __init__(self, passphrase=None): + luksxml = gtk.glade.XML(findGladeFile("lukspassphrase.glade"), domain="anaconda") + self.passphraseEntry = luksxml.get_widget("passphraseEntry") + self.passphraseEntry.set_visibility(False) + self.confirmEntry = luksxml.get_widget("confirmEntry") + self.confirmEntry.set_visibility(False) + self.win = luksxml.get_widget("luksPassphraseDialog") + self.okButton = luksxml.get_widget("okbutton1") + self.minimumLength = 8 # arbitrary; should probably be much larger + if passphrase: + self.initialPassphrase = passphrase + self.passphraseEntry.set_text(passphrase) + self.confirmEntry.set_text(passphrase) + else: + self.initialPassphrase = "" + + addFrame(self.win) + + def run(self): + self.win.show() + while True: + self.passphraseEntry.grab_focus() + self.rc = self.win.run() + if self.rc == gtk.RESPONSE_OK: + passphrase = self.passphraseEntry.get_text() + confirm = self.confirmEntry.get_text() + if passphrase != confirm: + # FIXME: a messageWindow would help here + self.confirmEntry.set_text("") + continue + + if len(passphrase) < self.minimumLength: + # FIXME: a messageWindow would help here + self.passphraseEntry.set_text("") + self.confirmEntry.set_text("") + continue + else: + self.passphraseEntry.set_text(self.initialPassphrase) + self.confirmEntry.set_text(self.initialPassphrase) + + return self.rc + + def getPassphrase(self): + return self.passphraseEntry.get_text() + + def getrc(self): + return self.rc + + def destroy(self): + self.win.destroy() + class SaveExceptionWindow: def __init__(self, anaconda, longTracebackFile=None, screen=None): exnxml = gtk.glade.XML(findGladeFile("exnSave.glade"), domain="anaconda") @@ -1044,6 +1096,13 @@ class InstallInterface: d.destroy() return ret + def getLuksPassphrase(self, passphrase = ""): + d = luksPassphraseWindow(passphrase) + rc = d.run() + passphrase = d.getPassphrase().strip() + d.destroy() + return passphrase + def beep(self): gtk.gdk.beep() diff --git a/iw/partition_dialog_gui.py b/iw/partition_dialog_gui.py index 042be17..2f7b02c 100644 --- a/iw/partition_dialog_gui.py +++ b/iw/partition_dialog_gui.py @@ -22,6 +22,7 @@ from rhpl.translate import _, N_ import gui from fsset import * +from cryptodev import LUKSDevice from partRequests import * from partition_ui_helpers_gui import * from constants import * @@ -128,6 +129,23 @@ class PartitionEditor: else: primonly = None + if self.lukscb and self.lukscb.get_active(): + if request.encryption and \ + request.encryption.getScheme() == "LUKS": + passphrase = request.encryption.passphrase + else: + passphrase = "" + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and ((not request.encryption) or \ + request.encryption.getScheme() != "LUKS"): + request.encryption = LUKSDevice(passphrase=passphrase, + format=1) + elif passphrase: + request.encryption.setPassphrase(passphrase) + request.encryption.format = 1 + else: + request.encryption = None + if not self.newbycyl: if self.fixedrb.get_active(): grow = None @@ -224,6 +242,28 @@ class PartitionEditor: else: request.mountpoint = None + if self.fsoptionsDict.has_key("lukscb"): + lukscb = self.fsoptionsDict["lukscb"] + else: + lukscb = None + + if request.format and lukscb and lukscb.get_active(): + if request.encryption and \ + request.encryption.getScheme() == "LUKS": + passphrase = request.encryption.passphrase + else: + passphrase = "" + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and ((not request.encryption) or \ + request.encryption.getScheme() != "LUKS"): + request.encryption = LUKSDevice(passphrase=passphrase, + format=1) + elif passphrase: + request.encryption.setPassphrase(passphrase) + request.encryption.format = 1 + else: + request.encryption = None + err = request.sanityCheckRequest(self.partitions) if err: self.intf.messageWindow(_("Error With Request"), @@ -292,9 +332,12 @@ class PartitionEditor: lbl = createAlignedLabel(_("File System _Type:")) maintable.attach(lbl, 0, 1, row, row + 1) + self.lukscb = gtk.CheckButton(_("Encrypt partition")) + self.lukscb.set_data("formatstate", 1) self.newfstypeCombo = createFSTypeMenu(self.origrequest.fstype, fstypechangeCB, self.mountCombo, + self.lukscb, availablefstypes = restrictfs) lbl.set_mnemonic_widget(self.newfstypeCombo) maintable.attach(self.newfstypeCombo, 1, 2, row, row + 1) @@ -461,6 +504,18 @@ class PartitionEditor: maintable.attach(self.primonlycheckbutton, 0, 2, row, row+1) row = row + 1 + # checkbutton for encryption using dm-crypt/LUKS + if self.origrequest.type == REQUEST_NEW: + if self.origrequest.encryption and \ + self.origrequest.encryption.getScheme() == "LUKS": + self.lukscb.set_active(1) + else: + self.lukscb.set_active(0) + maintable.attach(self.lukscb, 0, 2, row, row + 1) + row = row + 1 + else: + self.lukscb = None + # put main table into dialog self.dialog.vbox.pack_start(maintable) self.dialog.show_all() diff --git a/iw/partition_ui_helpers_gui.py b/iw/partition_ui_helpers_gui.py index be66780..35e4cba 100644 --- a/iw/partition_ui_helpers_gui.py +++ b/iw/partition_ui_helpers_gui.py @@ -115,9 +115,26 @@ def setMntPtComboStateFromType(fstype, mountCombo): mountCombo.set_data("prevmountable", fstype.isMountable()) -def fstypechangeCB(widget, mountCombo): +def setLuksState(fstype, mountCombo, lukscb): + if lukscb is None: + return + + mntpoint = mountCombo.get_children()[0].get_text() + formatstate = lukscb.get_data("formatstate") + if fstype.supportsEncryption and mntpoint not in ("/", "/boot"): + lukscb.set_data("luks_ok", 1) + if formatstate: + lukscb.set_sensitive(1) + else: + lukscb.set_active(0) + lukscb.set_sensitive(0) + lukscb.set_data("luks_ok", 0) + +def fstypechangeCB(widget, data): + (mountCombo, lukscb) = data fstype = widget.get_active_value() setMntPtComboStateFromType(fstype, mountCombo) + setLuksState(fstype, mountCombo, lukscb) def createAllowedDrivesStore(disks, reqdrives, drivelist, selectDrives=True, disallowDrives=[]): @@ -158,7 +175,7 @@ def createAllowedDrivesList(disks, reqdrives, selectDrives=True, disallowDrives= # pass in callback for when fs changes because of python scope issues -def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, +def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, lukscb = None, availablefstypes = None, ignorefs = None): store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) fstypecombo = datacombo.DataComboBox(store) @@ -191,24 +208,36 @@ def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, i = i + 1 fstypecombo.set_active(defindex) + setLuksState(fstypecombo.get_active_value(), mountCombo, lukscb) if fstypechangeCB and mountCombo: - fstypecombo.connect("changed", fstypechangeCB, mountCombo) + fstypecombo.connect("changed", fstypechangeCB, (mountCombo, lukscb)) if mountCombo: mountCombo.set_data("prevmountable", fstypecombo.get_active_value().isMountable()) - mountCombo.connect("changed", mountptchangeCB, fstypecombo) + mountCombo.connect("changed", mountptchangeCB, (fstypecombo, lukscb)) return fstypecombo -def mountptchangeCB(widget, fstypecombo): +def mountptchangeCB(widget, data): + (fstypecombo, lukscb) = data if rhpl.getArch() == "ia64" and widget.get_children()[0].get_text() == "/boot/efi": fstypecombo.set_active_text("vfat") + setLuksState(fstypecombo.get_active_value(), widget, lukscb) + def formatOptionCB(widget, data): - (combowidget, mntptcombo, ofstype) = data + (combowidget, mntptcombo, ofstype, lukscb) = data combowidget.set_sensitive(widget.get_active()) + if lukscb is not None: + lukscb.set_data("formatstate", widget.get_active()) + luksok = lukscb.get_data("luks_ok") + if not widget.get_active(): + lukscb.set_active(0) + lukscb.set_sensitive(0) + elif luksok: + lukscb.set_sensitive(1) # inject event for fstype menu if widget.get_active(): @@ -239,6 +268,7 @@ def noformatCB(widget, data): migraterb - radiobutton for migrate fs migfstype - menu for migrate fs types migfstypeMenu - menu for migrate fs types + lukscb - checkbutton for 'encrypt using LUKS/dm-crypt' """ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, ignorefs=[]): @@ -267,9 +297,13 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, if origrequest.format: formatrb.set_active(1) + # this gets added to the table a bit later on + lukscb = gtk.CheckButton(_("Encrypt Partition")) + maintable.attach(formatrb, 0, 1, row, row + 1) fstypeCombo = createFSTypeMenu(ofstype, fstypechangeCB, - mountCombo, ignorefs=ignorefs) + mountCombo, ignorefs=ignorefs, + lukscb=lukscb) fstypeCombo.set_sensitive(formatrb.get_active()) maintable.attach(fstypeCombo, 1, 2, row, row + 1) row = row + 1 @@ -278,7 +312,7 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, mountCombo.set_data("prevmountable", ofstype.isMountable()) formatrb.connect("toggled", formatOptionCB, - (fstypeCombo, mountCombo, ofstype)) + (fstypeCombo, mountCombo, ofstype, lukscb)) noformatrb.connect("toggled", noformatCB, (fstypeCombo, mountCombo, origrequest.origfstype)) @@ -300,16 +334,24 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo, row = row + 1 migraterb.connect("toggled", formatOptionCB, - (migfstypeCombo, mountCombo, ofstype)) + (migfstypeCombo, mountCombo, ofstype, None)) else: migraterb = None migfstypeCombo = None row = row + 1 + if origrequest.encryption and origrequest.encryption.getScheme() == "LUKS": + lukscb.set_active(1) + + lukscb.set_sensitive(formatrb.get_active()) + lukscb.set_data("formatstate", formatrb.get_active()) + maintable.attach(lukscb, 0, 2, row, row + 1) + row = row + 1 + rc = {} for var in ['noformatrb', 'formatrb', 'fstypeCombo', - 'migraterb', 'migfstypeCombo']: + 'migraterb', 'migfstypeCombo', 'lukscb']: if eval("%s" % (var,)) is not None: rc[var] = eval("%s" % (var,)) diff --git a/iw/raid_dialog_gui.py b/iw/raid_dialog_gui.py index 69e1cf6..a723f4a 100644 --- a/iw/raid_dialog_gui.py +++ b/iw/raid_dialog_gui.py @@ -25,6 +25,7 @@ from rhpl.translate import _, N_ import gui from fsset import * from raid import availRaidLevels +from cryptodev import LUKSDevice from partRequests import * from partition_ui_helpers_gui import * from constants import * @@ -166,6 +167,23 @@ class RaidEditor: request.format = self.formatButton.get_active() else: request.format = 0 + + if self.lukscb and self.lukscb.get_active(): + if request.encryption and \ + request.encryption.getScheme() == "LUKS": + passphrase = request.encryption.passphrase + else: + passphrase = "" + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and ((not request.encryption) or \ + request.encryption.getScheme() != "LUKS"): + request.encryption = LUKSDevice(passphrase=passphrase, + format=1) + elif passphrase: + request.encryption.setPassphrase(passphrase) + request.encryption.format = 1 + else: + request.encryption = None else: if self.fsoptionsDict.has_key("formatrb"): formatrb = self.fsoptionsDict["formatrb"] @@ -201,6 +219,24 @@ class RaidEditor: else: request.mountpoint = None + lukscb = self.fsoptionsDict.get("lukscb") + if request.format and lukscb and lukscb.get_active(): + if request.encryption and \ + request.encryption.getScheme() == "LUKS": + passphrase = request.encryption.passphrase + else: + passphrase = "" + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and ((not request.encryption) or \ + request.encryption.getScheme() != "LUKS"): + request.encryption = LUKSDevice(passphrase=passphrase, + format=1) + elif passphrase: + request.encryption.setPassphrase(passphrase) + request.encryption.format = 1 + else: + request.encryption = None + err = request.sanityCheckRequest(self.partitions) if err: self.intf.messageWindow(_("Error With Request"), @@ -283,6 +319,10 @@ class RaidEditor: maintable.attach(self.mountCombo, 1, 2, row, row + 1) row = row + 1 + # we'll maybe add this further down + self.lukscb = gtk.CheckButton(_("Encrypt partition")) + self.lukscb.set_data("formatstate", 1) + # Filesystem Type if not origrequest.getPreExisting(): lbl = createAlignedLabel(_("_File System Type:")) @@ -290,6 +330,7 @@ class RaidEditor: self.fstypeCombo = createFSTypeMenu(origrequest.fstype, fstypechangeCB, self.mountCombo, + self.lukscb, ignorefs = ["software RAID", "PPC PReP Boot", "Apple Bootstrap"]) lbl.set_mnemonic_widget(self.fstypeCombo) maintable.attach(self.fstypeCombo, 1, 2, row, row + 1) @@ -409,6 +450,15 @@ class RaidEditor: if origrequest.getPreExisting(): maintable.attach(self.formatButton, 0, 2, row, row + 1) row = row + 1 + + # checkbutton for encryption using dm-crypt/LUKS + if self.origrequest.encryption and \ + self.origrequest.encryption.getScheme() == "LUKS": + self.lukscb.set_active(1) + else: + self.lukscb.set_active(0) + maintable.attach(self.lukscb, 0, 2, row, row + 1) + row = row + 1 else: (row, self.fsoptionsDict) = createPreExistFSOptionSection(self.origrequest, maintable, row, self.mountCombo) diff --git a/packages.py b/packages.py index aeefde2..bbf2b8c 100644 --- a/packages.py +++ b/packages.py @@ -206,7 +206,7 @@ def setFileCons(anaconda): "/etc/blkid.tab", "/etc/blkid.tab.old", "/etc/mtab", "/etc/fstab", "/etc/resolv.conf", "/etc/modprobe.conf", "/etc/modprobe.conf~", - "/var/log/wtmp", "/var/run/utmp", + "/var/log/wtmp", "/var/run/utmp", "/etc/crypttab", "/dev/log", "/var/lib/rpm", "/", "/etc/raidtab", "/etc/mdadm.conf", "/etc/hosts", "/etc/sysconfig/network", "/etc/udev/rules.d/70-persistent-net.rules", diff --git a/partRequests.py b/partRequests.py index 3fbb812..92c8a4f 100644 --- a/partRequests.py +++ b/partRequests.py @@ -152,6 +152,9 @@ class RequestSpec: self.dev = None """A Device() as defined in fsset.py to correspond to this request.""" + self.encryption = None + """An optional EncryptedDevice() describing block device encryption.""" + def __str__(self): if self.fstype: fsname = self.fstype.getName() @@ -259,6 +262,10 @@ class RequestSpec: if self.mountpoint in mustbeonlinuxfs: return _("This mount point must be on a linux file system.") + # FIXME: this will need to become more sophisticated at some point + if self.encryption and self.mountpoint in ('/', '/boot'): + return _("This mount point cannot be on an encrypted partition.") + return None # requestSkipList is a list of uids for requests to ignore when @@ -445,6 +452,11 @@ class PartitionSpec(RequestSpec): else: pre = "Existing" + if self.encryption is None: + crypto = "None" + else: + crypto = self.encryption.getScheme() + str = ("%(n)s Part Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" " type: %(fstype)s format: %(format)s \n" " device: %(dev)s drive: %(drive)s primary: %(primary)s\n" @@ -452,7 +464,7 @@ class PartitionSpec(RequestSpec): " start: %(start)s end: %(end)s migrate: %(migrate)s " " fslabel: %(fslabel)s origfstype: %(origfs)s\n" " options: '%(fsopts)s'\n" - " fsprofile: %(fsprofile)s" % + " fsprofile: %(fsprofile)s encryption: %(encryption)s" % {"n": pre, "mount": self.mountpoint, "id": self.uniqueID, "fstype": fsname, "format": self.format, "dev": self.device, "drive": self.drive, "primary": self.primary, @@ -460,13 +472,19 @@ class PartitionSpec(RequestSpec): "start": self.start, "end": self.end, "migrate": self.migrate, "fslabel": self.fslabel, "origfs": oldfs, - "fsopts": self.fsopts, "fsprofile": self.fsprofile}) + "fsopts": self.fsopts, "fsprofile": self.fsprofile, + "encryption": crypto}) return str def getDevice(self, partitions): """Return a device to solidify.""" - self.dev = fsset.PartitionDevice(self.device) + if self.dev: + return self.dev + + self.dev = fsset.PartitionDevice(self.device, + encryption=self.encryption) + return self.dev def getActualSize(self, partitions, diskset): @@ -608,15 +626,22 @@ class RaidRequestSpec(RequestSpec): if self.raidmembers: for i in self.raidmembers: raidmem.append(i) + + if self.encryption is None: + crypto = "None" + else: + crypto = self.encryption.getScheme() str = ("RAID Request -- mountpoint: %(mount)s uniqueID: %(id)s\n" " type: %(fstype)s format: %(format)s\n" " raidlevel: %(level)s raidspares: %(spares)s\n" - " raidmembers: %(members)s fsprofile: %(fsprofile)s\n" % + " raidmembers: %(members)s fsprofile: %(fsprofile)s\n" + " encryption: %(encryption)s" % {"mount": self.mountpoint, "id": self.uniqueID, "fstype": fsname, "format": self.format, "level": self.raidlevel, "spares": self.raidspares, "members": self.raidmembers, "fsprofile": self.fsprofile, + "encryption": crypto }) return str @@ -630,7 +655,8 @@ class RaidRequestSpec(RequestSpec): raidmems, minor = self.raidminor, spares = self.raidspares, existing = self.preexist, - chunksize = self.chunksize) + chunksize = self.chunksize, + encryption=self.encryption) return self.dev def getActualSize(self, partitions, diskset): @@ -703,6 +729,15 @@ class RaidRequestSpec(RequestSpec): rc = self.sanityCheckRaid(partitions) if rc: return rc + + # make sure we aren't attempting encrypted /boot or / + if self.mountpoint in ('/', '/boot'): + for member in self.raidmembers: + r = partitions.getRequestByID(member) + if r.encryption and r.encryption.getScheme() is not None: + return _("This mount point cannot be on a RAID device " + "containing encrypted partitions.") + return RequestSpec.sanityCheckRequest(self, partitions) class VolumeGroupRequestSpec(RequestSpec): @@ -924,4 +959,13 @@ class LogicalVolumeRequestSpec(RequestSpec): return _("Logical volume size must be larger than the volume " "group's physical extent size.") + # make sure it's not encrypted /boot or / + if self.mountpoint in ('/boot', '/'): + vgreq = partitions.getRequestByID(self.volumeGroup) + for pv in vgreq.physicalVolumes: + r = partitions.getRequestByID(pv) + if r.encryption and r.encryption.getScheme() is not None: + return _("This mount point cannot be on a logical volume " + "containing encrypted physical volumes.") + return RequestSpec.sanityCheckRequest(self, partitions, skipMntPtExistCheck) diff --git a/scripts/mk-images b/scripts/mk-images index e762e39..9cd592b 100755 --- a/scripts/mk-images +++ b/scripts/mk-images @@ -46,7 +46,8 @@ SCSIMODS="sr_mod sg st sd_mod scsi_mod iscsi_tcp" FSMODS="fat msdos vfat ext2 ext3 reiserfs jfs xfs gfs2 lock_nolock cifs" LVMMODS="dm-mod dm-zero dm-snapshot dm-mirror dm-multipath dm-round-robin dm-emc dm-crypt" RAIDMODS="md raid0 raid1 raid5 raid6 raid456 raid10" -SECSTAGE="$RAIDMODS $LVMMODS $FSMODS $IDEMODS $SCSIMODS" +CRYPTOMODS="sha256 cbc aes blkcipher" +SECSTAGE="$RAIDMODS $LVMMODS $FSMODS $IDEMODS $SCSIMODS $CRYPTOMODS" PCMCIASOCKMODS="yenta_socket i82365 tcic pcmcia" INITRDMODS="$USBMODS $FIREWIREMODS $IDEMODS $SCSIMODS $FSMODS $LVMMODS $RAIDMODS $COMMONMODS $PCMCIASOCKMODS =scsi =net" diff --git a/scripts/upd-instroot b/scripts/upd-instroot index 8312eb9..b49252a 100755 --- a/scripts/upd-instroot +++ b/scripts/upd-instroot @@ -256,7 +256,8 @@ PACKAGES="glibc glibc-common setup openssl python newt slang libselinux yum-metadata-parser gfs2-utils libvolume_id nash yum-fedorakmod libdhcp libnl libdhcp6client libdhcp4client newt-python device-mapper device-mapper-libs dmraid keyutils-libs libsemanage-python - python-pyblock mkinitrd libbdevid libbdevid-python nss nspr pcre" + python-pyblock mkinitrd libbdevid libbdevid-python nss nspr pcre + cryptsetup-luks libgcrypt libgpg-error" if [ $ARCH = i386 -o $ARCH = x86_64 ]; then PACKAGES="$PACKAGES pcmciautils" @@ -414,6 +415,7 @@ lib/terminfo sbin/badblocks sbin/busybox.anaconda sbin/clock +sbin/cryptsetup sbin/debugfs sbin/dosfslabel sbin/e2fsck diff --git a/ui/lukspassphrase.glade b/ui/lukspassphrase.glade new file mode 100644 index 0000000..38631d4 --- /dev/null +++ b/ui/lukspassphrase.glade @@ -0,0 +1,223 @@ + + + + + + + True + Enter passphrase for encrypted partition + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + True + Choose a passphrase for this encrypted partition. You will be prompted for the passphrase during system boot. + False + False + GTK_JUSTIFY_LEFT + True + False + 0 + 0.5 + 5 + 15 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 2 + 2 + False + 10 + 5 + + + + True + Enter passphrase: + False + False + GTK_JUSTIFY_RIGHT + False + False + 0 + 0.5 + 5 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Confirm passphrase: + False + False + GTK_JUSTIFY_RIGHT + False + False + 0 + 0.5 + 5 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + True + True + False + 500 + + True + + False + + + 1 + 2 + 0 + 1 + + + + + + + True + True + True + False + 500 + + True + + False + + + 1 + 2 + 1 + 2 + + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + + diff --git a/yuminstall.py b/yuminstall.py index 5f4ba6f..d61b4be 100644 --- a/yuminstall.py +++ b/yuminstall.py @@ -873,6 +873,7 @@ class YumBackend(AnacondaBackend): def selectFSPackages(self, fsset, diskset): for entry in fsset.entries: map(self.selectPackage, entry.fsystem.getNeededPackages()) + map(self.selectPackage, entry.device.crypto.getNeededPackages()) for disk in diskset.disks.keys(): if isys.driveIsIscsi(disk):