Automatic LUKS volumes unlocking using a TPM2 chip

I joined Red Hat a few months ago, and have been working on improving the Trusted Platform Module 2.0 (TPM2) tooling, towards having a better TPM2 support for Fedora on UEFI systems.

For brevity I won’t explain in this post what TPMs are and their features, but assume that readers are already familiar with trusted computing in general. Instead, I’ll explain what we have been working on, the approach used and what you might expect on Fedora soon.

For an introduction to TPM, I recommend Matthew Garret’s excellent posts about the topic, Philip Tricca’s presentation about TPM2 and the official Trusted Computing Group (TCG) specifications. I also found “A Practical Guide to TPM 2.0” book to be much easier to digest than the official TCG documentation. The book is an open access one, which means that’s freely available.

LUKS volumes unlocking using a TPM2 device

Encryption of data at rest is a key component of security.  LUKS provides the ability to encrypt Linux volumes, including both data volumes and the root volume containing the OS. The OS can provide the crypto keys for data volumes, but something has to provide the key for the root volume to allow the system to boot.

The most common way to provide the crypto key to unlock a LUKS volume,  is to have a user type in a LUKS pass-phase during boot. This works well for laptop and desktop systems, but is not well suited for servers or virtual machines since is an obstacle for automation.

So the first TPM feature we want to add to Fedora (and likely one of the most common use cases for a TPM) is the ability to bind a LUKS volume master key to a TPM2. That way the volume can be automatically unlocked (without typing a pass-phrase) by using the TPM2 to obtain the master key.

A key point here is that the actual LUKS master key is not present in plain text form on the system, it is protected by TPM encryption.

Also, by sealing the LUKS master key with a specific set of Platform Configuration Registers (PCR), one can make sure that the volume will only be unlocked if the system has not been tampered with. For example (as explained in this post), PCR7 is used to measure the UEFI Secure Boot policy and keys. So the LUKS master key can be sealed against this PCR, to avoid unsealing it if Secure Boot was disabled or the used keys were replaced.

Implementation details: Clevis

Clevis is a plugable framework for automated decryption that has a number of “pins”, where each pin implements an {en,de}cryption support using a different backend. It also has a command line interface to {en,de}crypt data using these pins, create complex security policies and bind a pin to a LUKS volume to later unlock it.

Clevis relies on the José project, which is an C implementation of the Javascript Object Signing and Encryption (JOSE) standard. It also uses the LUKSMeta project to store a Clevis pin metadata in a LUKS volume header.

On encryption, a Clevis pin takes some data to encrypt and a JSON configuration to produce a JSON Web Encryption (JWE) content. This JWE has the data encrypted using a JSON Web KEY (JWK) and information on how to obtain the JWK for decryption.

On decryption, the Clevis pin obtains a JWK using the information provided by a JWE and decrypts the ciphertext also stored in the JWE using that key.

Each Clevis pin defines their own JSON configuration format, how the JWK is created, where is stored and how to retrieve it.

As mentioned, Clevis has support to bind a pin with a LUKS volume. This means that a LUKS master key is encrypted using a pin and the resulting JWE is stored in a LUKS volume meta header. That way Clevis is able to later decrypt the master key and unlock the LUKS volume. Clevis has dracut and udisks2 support to do this automatically and the next version of Clevis will also include a command line tool to unlock non-root (data) volumes.

Clevis TPM2 pin

Clevis provides a mechanism to automatically supply the LUKS master key for the root volume. The initial implementation of Clevis has support to obtain the LUKS master key from a network service, but we have extended Clevis to take advantage of a TPM2 chip, which is available on most servers, desktops and laptops.

By using a TPM, the disk can only be unlocked on a specific system – the disk will neither boot nor be accessed on another machine.

This implementation also works with UEFI Secure Boot, which will prevent the system from being booted if the firmware or system configuration has been modified or tampered with.

To make use of all the Clevis infrastructure and also be able to use the TPM2 as a part of more complex security policies, the TPM2 support was implemented as a clevis tpm2 pin.

On encryption the tpm2 pin generates a JWK, creates an object in the TPM2 with the JWK as sensitive data and binds the object (or seals if a PCR set is defined in the JSON configuration) to the TPM2.

The generated JWE contains both the public and wrapped sensitive portions of the created object, as well as information on how to unseal it from the TPM2 (hashing and key encryption algorithms used to recalculate the primary key, PCR policy for authentication, etc).

On decryption the tpm2 pin takes the JWE that contains both the sealed object and information on how to unseal it,  loads the object into the TPM2 by using the public and wrapped sensitive portions and unseals the JWK to decrypt the ciphertext stored in the JWE.

The changes haven’t been merged yet, since the pin is using features from tpm2-tools master so we have to wait for the next release of the tools. And also there are still discussions on the pull request about some details, but it should be ready to land soon.

Usage

The Clevis command line tools can be used to encrypt and decrypt data using a TPM2 chip. The tpm2 pin has reasonable defaults but one can configure most of its parameters using the pin JSON configuration (refer to the Clevis tpm2 pin documentation for these), e.g:

$ echo foo | clevis encrypt tpm2 '{}' > secret.jwe

And then the data can later be decrypted with:

$ clevis decrypt < secret.jwe
foo

To seal data against a set of PCRs:

$ echo foo | clevis encrypt tpm2 '{"pcr_ids":"8,9"}' > secret.jwe

And to bind a tpm2 pin to a LUKS volume:

$ clevis luks bind -d /dev/sda3 tpm2 '{"pcr_ids":"7"}'

The LUKS master key is not stored in raw format, but instead is wrapped with a JWK that has the same entropy than the LUKS master key. It’s this JWK that is sealed with the TPM2.

Since Clevis has both dracut and udisks2 hooks, the command above is enough to have the LUKS volume be automatically unlocked using the TPM2.

The next version of Clevis also has a clevis-luks-unlock command line tool, so a LUKS volume could be manually unlocked with:

$ clevis luks unlock -d /dev/sda3

Using the TPM2 as a part of more complex security policies

One of Clevis supported pins is the Shamir Shared Secret (SSS) pin, that allows to encrypt a secret using a JWK that is then split into different parts. Each part is then encrypted using another pin and a threshold is chose to decide how many parts are needed to reconstruct the encryption key, so the secret can be decrypted.

This allows for example to split the JWK used to wrap the LUKS mater key in two parts. One part of the JWK could be sealed with the TPM2 and another part be stored in a remote server. By sealing a JWK that’s only one part of the needed key to decrypt the LUKS master key, an attacker obtaining the data sealed in the TPM won’t be able to unlock the LUKS volume.

The Clevis encrypt command for this particular example would be:

$ clevis luks bind -d /dev/sda3 sss '{"t": 2, "pins": \
  {"http":{"url":"http://server.local/key"}, "tpm2": \
  {"pcr_ids":"7"}}}'

Limitations of this approach

One problem with the current implementation is that Clevis is a user-space tool and so it can’t be used to unlock a LUKS volume that has an encrypted /boot directory. The boot partition still needs to remain unencrypted so the bootloader is able to load a Linux kernel and an initramfs that contains Clevis, to unlock the encrypted LUKS volume for the root partition.

Since the initramfs is not signed on a Secure Boot setup, an attacker could replace the initramfs and unlock the LUKS volume. So the threat model meant to protect is for an attacker that can get access to the encrypted volume but not to the trusted machine.

There are different approaches to solve this limitation. The previously mentioned post from Matthew Garret suggests to have a small initramfs that’s built into the signed Linux kernel. The only task for this built-in initramfs would be to unseal the LUKS master key, store it into the kernel keyring and extend PCR7 so the key can’t be unsealed again. Later the usual initramfs can unlock the LUKS volume by using the key already stored in the Linux kernel.

Another approach is to also have the /boot directory in an encrypted LUKS volume and provide support for the bootloader to unseal the master key with the TPM2, for example by supporting the same JWE format in the LUKS meta header used by Clevis. That way only a signed bootloader would be able to unlock the LUKS volume that contains /boot, so an attacker won’t be able to tamper the system by replacing the initramfs since it will be in an encrypted partition.

But there is work to be done for both approaches, so it will take some time until we have protection for this threat model.

Still, having an encrypted root partition that is only automatically unlocked on a trusted machine has many use cases. To list a few examples:

  • Stolen physical disks or virtual machines images can’t be mounted on a different machine.
  • An external storage media can be bind to a set of machines, so it can be automatically unlocked only on trusted machines.
  • A TPM2 chip can be reset before sending a laptop to repair, that way the LUKS volume can’t be automatically unlocked anymore.
  • An encrypted volume can be bound to a TPM2 if there is no risk of someone having physical access to the machine but unbound again when there is risk. So the machine can be automatically unlocked on safe places but allow to require a pass-phrase on unsafe places.

Acknowledgements

I would like to thanks Nathaniel McCallum and Russell Doty for their feedback and suggestions for this article.

RoDI support for the Gazebo simulator

In a previous post I talked about the RoDI educational robot and the rodi_robot package I wrote to control it within the Robot Operating System (ROS).

The problem was that having access to a RoDI was the only way to thinker with it. And although it has a very low cost (and is one of the cheapest with ROS support), it would be nice to learn how to control RoDI even without having access to one.

So since I’m using the Gazebo simulator while learning about ROS, I thought that adding support for RoDI in Gazebo would be an excellent opportunity to learn more about its internals.

The rodi_gazebo project implements both a Gazebo model for RoDI and a plugin that mimics the robot’s default firmware rodi-web HTTP API. This means that there’s no difference from a client point of view to communicate with either a real RoDI or a simulated one in Gazebo.

I want to thank Gary who helped me integrating the RoDI STL into the model and answered tons of questions about Gazebo  🙂

The README file explains how to build the plugin and use the RoDI model, I hope this could be useful for the RoDI community.

Happy hacking!

default_gzclient_camera1-2017-01-07t01_15_04-501691

ROS support for the RoDI educational robot

RoDI is an open hardware and software, wireless, low cost and easy to use educational robot designed and built by Gary Servin. The name RoDI is an acronym of “Robot Didáctico Inalámbrico”, which in Spanish means wireless didactic robot.

The robot has a custom PCB based on the ATMEGA328p microcontroller that has a bunch of sensors (ultrasonic, infrared, light), actuators (motors, led, RGB led), a rechargeable battery over micro USB and a ESP8266 WiFi module to interact with the ATMEGA over a wireless connection.

rodi.jpeg

The Arduino UNO board has the same ATMEGA328p microcontroller so the Arduino IDE and libraries can be used to program the robot’s firmware.

But since the goal is to be use RoDI as an educational tool for kids, its default’s firmware (rodi-web developed by Martin Abente) is a small daemon that accepts HTTP requests, to control the robot using a very simple API.

Because the robot can be controlled over HTTP, it’s very easy to develop clients for RoDI. For example there is a Python client library (rodi-py),  Android (rodroid) and IOS (RoDios) client apps and even a plugin for the Turtleblocks visual programming tool (rodi-plugin-turtlebots) so the robot can be controlled using visual building blocks. The robot can even be controlled using commands like curl and wget so the entry level is really low!

I’m learning about the Robot Operating System (ROS) in my free time, so I thought that an easy first project would be to add ROS support for RoDI. One way to do this is to use the rosserial_arduino package, and in fact Gary already has a custom RoDI firmware that does exactly this. The firmware subscribes to a cmd_vel topic and receives Twist messages to move the robot, and publish the sonar data as Range messages into a ultrasound topic.

But a disadvantage of this approach is that the default rodi-web firmware has to be replaced in order to control the robot from ROS. So I wrote the rodi_robot package that also subscribes to the cmd_vel topic and publishes the sonar data to a ultrasound topic, but does a translation between ROS messages and HTTP requests to use the rodi-web API directly. That way, RoDI is supported by ROS without the need to replace its default firmware.

I hope this package can be useful for people wanting to learn ROS using a (not simulated) robot, since its low cost makes RoDI very affordable.

Happy hacking!

Collabora contributions to Linux kernel 4.1

Linux 4.1 was released last week and like previous kernel releases, this version again contains contributions made by Collabora engineers as a part of our current projects.

On this release, not only Collabora contributed several patches for different subsystems but also for the first time made it to LWN list of most active employers for a Linux kernel release and Tomeu Vizoso was listed as one of the most active developers by changed lines.

In total 68 patches were contributed to the 4.1 release. These were for:

  • Fixes and improvements to the DRM core atomic support.
  • Fix for Exynos DRM FIMD buffer size calculation.
  • More cleanup and fixes for Exynos DRM in preparation to finish porting the driver to support Atomic Mode Settings for v4.2
  • Add MMC/SDIO power sequencing to make the WiFi chip work on Snow and Peach Pit/Pi Chromebooks.
  • Various fixes and improvement for the ChromeOS Embeded Controller drivers.
  • Add default console serial port configuration for Exynos Chromebooks to avoid having to define a tty in the kernel command line.
  • Enable needed drivers in the Exynos default configuration file.
  • Fix error code propagation in the MMC power sequencing core.
  • Restore clocks needed during suspend for Exynos5 machines to prevent failing to resume.
  • Make the Mwifiex chip on Exynos Chromebooks to keep power during suspend to prevent the driver not allow the system to enter into a suspend state.
  • Fix output disable race on the Samsung PWM driver.
  • Split out touchpad initialisation logic on the Atmel MaxTouch driver.
  • Various enhancements for the Tegra ASoC driver.
  • Add a Device Tree to support the Nyan Blaze Chromebook and factor out common snippets with the Nyan Big Chromebook Device Tree.
  • Add trackpad, WiFi and GPIO restart support for the Nyan Chromebooks.
  • Add support for the Tegra124 Activity Monitor (ACTMON).
  • Fill EMC timings for Tegra Nyan Chromebooks.
  • Many fixes and improvements for the Tegra device frequency scaling driver.
  • Fix the Tegra DRM driver by resetting the SOR to a known state.
  • Enable many drivers in multi_v7 default configuration, that are needed by Tegra Chromebooks.
  • Fix the cros_ec keyboard driver to avoid loosing the key pressed during suspend to resume the system.
  • Fix USB not working on Tegra124 based boards.

Following is the complete list of patches merged in this kernel release:

Collabora contributions to Linux kernel 4.0

Linux 4.0 was released a couple of weeks ago and like previous kernel releases, this version again contains contributions made by Collabora engineers as a part of our current projects.

In total 59 patches were contributed to the 4.0 release. These were for:

  • Fix graphics DP and HDMI display for Exynos DRM.
  • More preparation work to add Atomic Mode Settings support to the Exynos DRM driver.
  • Add support for Power, Lid keys and built-in USB camera to Peach Pi/Pit and Snow Chromebooks.
  • Configure regulators operating modes on suspend for Peach Pi/Pit Chromebooks to reduce power consumption.
  • Add DISP1 power domain and related clocks to have proper display support on Exynos5420 machines.
  • Extend the MMC simple power sequencing provider to support a reference clock and more than one reset GPIO.
  • Fix various regressions in the common clock framework exposed by the per-user clock changes.
  • Fix S3C Real-Time-Clock that was not working on many Exynos SoCs.
  • Fix a bug in the regulator framework that tried to enable regulators that were already enabled.
  • Fix a reboot and poweroff hang on Exynos machines cause by a hang in the samsung serial driver.
  • Various fixes to the Samsung MFC driver.
  • Add support for the Exynos5422 Odroid XU3 board.
  • Enable needed Kconfig symbols on the exynos, omap2plus and multi_v7 defconfigs.
  • Add a devfreq driver for the Tegra Activity Monitor

Following is the complete list of patches merged in this kernel release:

Collabora contributions to Linux kernel 3.19

Linux 3.19 was released last week and this version again contains contributions made by Collabora engineers as a part of our current projects.

In total 60 patches were contributed to the 3.19 release. These were for:

  • Work in the Intel i915 DRM driver to support atomic plane updates.
  • Fixes and removal of unnecessary layers in the Exynos DRM driver as a preparation to add atomic mode settings support.
  • Add support to the regulator framework to define regulators initial and suspend operating modes.
  • Changes to the max77802 regulator driver to support regulator operating modes changes.
  • Fix the Real-Time-Clock in the Snow, Peach Pit and Pi Chromebooks.
  • Enable kernel config options to have display panel working on Exynos boards.
  • Allow the regulator drivers to be enabled and disabled when Exynos system enters and leave suspend states.
  • Many cleanup and fixes to the common clock framework and clock drivers as a preparation for the per-user clock API change.

Following is the complete list of patches merged in this kernel release:

Collabora contributions to the Linux kernel 3.18

Linux 3.18 was released this week and like in previous kernel releases, it contains contributions made by Collabora engineers as a part of the projects involving the Linux kernel.

In total 45 patches were contributed to the 3.18 release. These were for:

  • Cleanups for the Intel i915 DRM driver
  • Various cleanups for the max77686 clock, rtc and regulator drivers.
  • Adding max77802 clock, rtc and regulator drivers.
  • Fixing regmap DT endianess parsing logic.
  • Improving the power model in the Exynos5 Peach Pit and Pi Chromebooks DT.
  • Using the regulator_get_voltage() function to get the mmc OCR mask.
  • Adding max77802 PMIC, ISL29018 sensor and atmel touchpad for Peach boards DT.
  • Setting the correct clock rate for i2c7 in Exynos5 Peach Pit and Pi DT.
  • Enable atmel touchpad, cgroups, sbs battery and atmel touchpad in exynos defconfig.
  • Fixing variable initialization for different regulator drivers.
  • Fixing MFC v5 support in the s5p-mfc driver.
  • Making module autoloading to work for i2c cros-ec-tunnel and cros_ec_keyb drivers.
  • Explicitly configure USB dual role mode as host for Exynos boards.
  • Adding a PM_QOS_MEMORY_BANDWIDTH pm_qos class.
  • Enabling gcov-based kernel profiling for ARM

Following is the complete list of patches merged in this kernel release:

An update on Device Tree support for IGEP boards

Linux v3.13 has been released a couple of days ago and this is the first kernel version that has complete Device Tree support for IGEP boards.

Since the initial DT support that I talked before, the following peripherals were added:

Ethernet
NAND flash
– USB HOST and OTG
Wifi/BT combo (support added by Enric Balletbo)

The only remaining bit in DT is video since the DT bindings for the OMAP Display Sub-System (DSS) have not landed in mainline yet so display support is still provided using the OMAP platform data quirk infrastructure.

So, this make the IGEP the first OMAP3 device to complete the transition to Device Tree based booting and the board file could finally be removed.

The migration from board files to Device Tree booting was not trivial as I initially thought since OMAP GPMC and GPIO DT support was not mature enough so I had to add support for GPMC DT ethernet and fix some annoying bugs.

U-Boot backend support for OSTree

Recently I had to work with OSTree which I think is a very promising and interesting project.

The OSTree site advertises it as

a tool for managing bootable, immutable, versioned filesystem trees

and it uses a combination of chroot and hard links to provide atomic upgrades and rollback of filesystem trees. You can refer to the project’s online manual and README for information about the motivation behind OSTree and its implementation.

When updating the kernel and initial RAM disk images OSTree creates Boot Loader Specification entries. These are drop-in snippets that define a kernel and init ramdisk that have to be used and boot arguments that the bootloader has to pass to the kernel command line on boot.

Currently only gummiboot and GRUB have support for these boot loader snippets.

We wanted to use OSTree on embedded devices and since most boards use U-Boot as their boot loader I needed to figure out what was the best approach to add a U-Boot support for OSTree.

Obviously this would have been to add boot loader spec entries support to U-Boot but there are two issues with this approach:

a) U-Boot does not currently support multi-boot

Since U-Boot is designed for embedded it just boots a single default operating system while the boot loader specification was designed for multi-boot. You can drop any number of snippets under /boot/loader/entries and the boot loader should populate its boot menu.

b) Not every vendor allows you to replace the boot loader

Some vendors do not allow to replace the boot loader binaries (i.e: store it on a read-only memory), implements DRM that requires binaries to be signed or are using a very old U-Boot version which would require to backport this support.

So, the solution was to make OSTree to generate boot information instead that could be used by any already deployed U-Boot. The same approach is used on OSTree to support syslinux.

U-Boot allows to modify boards default boot commands by reading and executing a bootscript file (boot.scr) or importing a plain text file (uEnv.txt) that contains environment variables that could parameterize its default boot command or a bootscript.

So, on deploy or upgrade OSTree reads the Boot Loader Specification snippets files and generates a uEnv.txt file that contains the booting information in a format that could be understood by U-Boot.

The uEnv.txt env var file is created in the path /boot/loader.${bootversion}/uEnv.txt. Also, a /boot/uEnv.txt symbolic link to loader/uEnv.txt is created so U-Boot can always import the file from a fixed path.

Since U-Boot does not support a menu to list a set of Operative Systems, the most recent boot loader entry from the list is used.

To boot an OSTree using the generated uEnv.txt file, a board has to parameterize its default boot command using the following variables defined by OSTree:

    ${kernel_image}:  path to the Linux kernel image
    ${ramdisk_image}: path to the initial ramdisk image
    ${bootargs}:      parameters passed to the kernel command line

Alternatively, for boards that don’t support this scheme, a bootscript that overrides the default boot command can be used.

An example of such a bootscript could be:

    setenv scriptaddr 0x40008000
    setenv kernel_addr 0x40007000
    setenv ramdisk_addr 0x42000000
    load mmc 0:1 ${scriptaddr} uEnv.txt
    env import -t ${scriptaddr} ${filesize}
    load mmc 0:1 ${kernel_addr} ${kernel_image}
    load mmc 0:1 ${ramdisk_addr} ${ramdisk_image}
    bootz ${kernel_addr} ${ramdisk_addr}

For more information you can refer to the commit that added U-Boot as backend and OStree test for U-Boot.

I would like to thank my employer Collabora for sponsoring this work.

mmap support for Raspberry Pi bcm2835 ALSA driver

Because I work on an awesome company I spent last week improving the Raspberry Pi ALSA driver.

A long standing issue was that the driver did not support memory-mapped I/O mode for audio stream transfers.

ALSA supports two transfers methods for PCM playback: Read/Write transfer where samples are written to the device using standard read and write functions and Direct Read/Write transfers where samples can be written directly to a mapped memory area and the driver is signaled once this has been done.

ALSA provides an API for both cases and each application using ALSA to access the audio device can choose which API to use. But since mmap was not supported by the bcm2835 driver, applications using the mmap API did not work on the Raspberry Pi.

To bypass hardware limitation such as the lack of mmap support, ALSA provides a set of PCM plug-ins that can be used to extend functionality and features of PCM devices.

These plug-ins are used by defining a custom /etc/asound.conf or .asoundrc and one of them is the mmap emulation plug-in (mmap_emul) which emulates mmap access using a set of read/write transfers. This plug-in was needed on the Raspberry Pi to allow applications using ALSA mmap API to work. But of course it introduces some latency and requires a custom configuration to enable it.

So, the best approach was to just add mmap support to the ALSA driver and get rid of the mmap emulation plug-in. Normally this is straightforward since the kernel allows memory areas to be mapped to user-space address space so applications can have direct access to device memory.

This was not the case on the Raspberry Pi since the PCM samples are processed by its VideoCore IVI GPU. The ARM11 CPU communicates with the VideoCore co-processor using a message passing interface (vchiq) which means that the CPU is not able to directly address the audio device hardware buffers and audio samples have to be sent to the device using vchiq messages.

Since hardware buffers can’t be directly mapped to user-space memory, an intermediate buffer is needed. This intermediate buffer is mapped to user-space so applications can store the audio samples there. Once user-space has finished writing the PCM samples they are pushed to VideoCore as vchiq messages.

Fortunately, the kernel ALSA subsystem provides a PCM indirect API which are a set of helper functions and a data structure to manage the intermediate and hardware buffers. It is really helpful so you don’t have to write all the buffer management.

So, after figuring out all of this I wrote a patch to add mmap support to the Raspberry Pi ALSA driver and send a pull request that also contained other improvements to the driver.

Since it has been merged on the Raspberry Pi kernel, this feature will be available on the next raspbian release but if you want to use it now you can do the following:

$ git clone git://github.com/raspberrypi/tools.git
$ git clone git://github.com/raspberrypi/linux.git
$ cd linux
$ git checkoub -b rpi-3.6.y origin/rpi-3.6.y
$ export ARCH=arm
$ export CROSS_COMPILE=../tools/arm-bcm2708/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
$ make bcmrpi_defconfig
$ make prepare modules_prepare
$ make M=sound/arm
$ sudo cp sound/arm/snd-bcm2835.ko /media/rootfs/lib/modules/3.6.11+/kernel/sound/arm/
$ sudo rm /media/rootfs/etc/asound.conf

The last step is very important since using both a mmap capable ALSA driver and the PCM mmap emulation plug-in does not work.

I want to thank my employer Collabora for allowing me to work on this: