High assurance device security with CAAM encryption keys
The number of embedded systems is growing fast. One of the industries making use of them is the car industry. And Self-driving vehicles are pushing systems to the limits as they are equipped with a lot of sensors, network connectivity and processors based on many heterogeneous specialized SoCs. The number of security vulnerabilities and thus, the attack surface of an autonomous vehicle is undoubtedly increasing because of that.
Nowadays, hackers are becoming increasingly skillful. These skills coupled with low-cost offensive devices can enable them to break into car security systems.
In most cases, the security architecture of a self-driving car contains cryptographic modules that allow confidentiality, authenticity and data integrity by way of secrets or keys.
But If a malicious entity breaks the car’s security and finds the encryption keys, the whole system can collapse, and one can only imagine the damage this could create.
When data passed to other modules is encrypted, the corresponding cryptographic keys are often stored in RAM and can be stolen by malware using cold boot attack techniques. A cold boot attack consists of extracting keys in volatile memory after resetting the machine.
In this article we decided to investigate a method to protect the encryption keys of an embedded system running Linux on an i.MX SoC equipped with the CAAM cryptographic module. This has been tested on a Boundary i.MX6 Sabre Lite and the NXP i.MX8M evaluation board.
Some definitions around CAAM and encryption keys
What is the Cryptographic Acceleration and Assurance Module (CAAM)?
CAAM is the cryptographic acceleration and assurance module included in many i.MX SoC designs and serves as NXP’s cryptographic acceleration hardware. It implements block encryption, hashing and authentication algorithms, a secure memory controller and a hardware random number generator, among other related functionalities. Notably, the module also implements specific hardware-backed cryptographic keys which can be called CAAM keys.
CAAM encryption keys, why red and black?
These encryption keys are implemented in, and backed by, secure memory residing in the module. This allows the system to protect user keys against attacks such as bus snooping. Likewise, any operations making use of these keys are done inside CAAM. Two key types are defined in CAAM: red and black. The user is able to store secrets as red keys in CAAM, and read them back from secure memory as is. Black keys, however, can only be read back in an encrypted form readable only by the same CAAM instance which encrypted it beforehand. When CAAM is instructed to use a black key as an encryption key, CAAM automatically decrypts the black key and places it directly into a key register before using the decrypted value in the user-specified cryptographic operation. From the kernel’s point of view, these intermediate operations are opaque. Black keys are going to be the focus of this article going forward.
What is a Cryptographic blob?
Data stored in non-volatile memory is vulnerable to attacks when the SoC’s software and hardware security mechanisms are not working. CAAM can protect data for long term storage by encrypting it with a secure non-volatile encryption key unique for each device, preventing the data encrypted on one device from being copied and decrypted on a different one, thereby protecting the data from hackers. The result of this mechanism is called a cryptographic blob. CAAM red and black keys can then be stored in a secure way on non-volatile memory.
Proposal of a CAAM-backed block encryption solution to prevent the exposure of encryption keys
Knowing all this, we thought it is possible to use this hardware to encrypt our non-volatile storage volumes. This gives the assurance that if the attacker gets hold of the storage device, it cannot be read back except with the help of the same CAAM instance which was initially used to encrypt the volume and by extension, the same i.MX SoC.
The principle: prevent attacks on encryption keys with a mix of hardware and software mechanisms
Our proposed solution is a mix of hardware and software mechanisms intended to prevent attacks on encryption keys when a device block is encrypted using cryptsetup with dm-crypt in a Linux system. Encryption keys are stored in the Linux key retention service in their encrypted form (as black keys).
The Linux key retention service introduced with the Linux 2.6 kernel comprises the keyctl utility and its corresponding keyctl() system call. This system is intended to store and reference keys of varying sensitivity in the kernel. The key retention service is generic to make it relatively easy to add a new specific key type.
This is what we have done: we added a new key type called caam_key in the kernel. It supports the use of CAAM red and black keys.
Cryptsetup is a tool used to set up encryption parameters afterwards used by dm-crypt which does the kernel-side device block encryption. By default, cryptsetup has two execution modes: the plain mode uses a plain text key derived from a passphrase; and the LUKS mode uses the LUKS (Linux Unified Key Setup) standard, allowing the use of up to 8 passphrases.
We have added a new mode in cryptsetup: the KRING mode which makes use of the Linux key retention service. The CAAM black key is directly pulled from the kernel keyring with keyctl() system call. This key is unencrypted on-the-fly in CAAM when the AES-CBC cipher is called from the kernel crypto API by dm-crypt. The crypto API is a generic kernel API which either implements cryptographic operations in software or uses hardware cryptographic devices like CAAM.
Simplified step by step scheme of our CAAM-backed block encryption
- Generate a black encryption key with the CAAM
- Put it in a cryptographic blob
- Use the Linux key retention service to store it
- Use cryptsetup with KRING mode to create your device block
- That is all!
All these previous steps can be automated in a file script.
Finally, after launching the file script, you can see your device protected by a black encryption key in /dev/mapper/<name>. The plain text key version will never be either in RAM or on the disk, as it is only stored in this form in CAAM’s registers. If an unauthorized software tries to read these registers, CAAM would freeze and all volatile data would be deleted. The volume is accessible only on the i.MX device which initially created the key.
Two kernel modules have been created to accomplish what has been described before:
caam_key and caam_blob. This enables the user to create, load and store red or black CAAM encryption keys, as well as export them as cryptographic blobs for long-term storage.
An initial version of this was made for the i.MX6 Sabre Lite on a 4.4 kernel. After a round of improvements and refactoring, this also worked on the i.MX8M evaluation board with a 4.14 kernel. Making it work on the latter was not the most trivial task, as the boot flow of the board had been significantly changed compared to what existed on the i.MX6 before. Since ATF (ARM Trusted Firmware) was now part of the board’s early boot process, secure memory pages had to be allocated from there as privileges related to this would be dropped before handing over execution to U-Boot. By default, zero pages were allocated for Linux by ATF on the i.MX8M whereas U-Boot would allocate two of them on the i.MX6. Once we got secure memory pages allocated on the i.MX8M, it was easier to get the rest up and running.
We have made the test on a i.MX 6 Sabre Lite and on the i.MX 8M evaluation board but I have to say that it was difficult to set up in a real environment.
What’s new in the pipe to make this even better?
Recently things have gone a bit further.
Cryptsetup 2 has been released with new improvements:
- LUKS2 on-disk header format, allowing the storage of unbounded keys to support hardware-backed keys in a generic way
- The code has been made more modular and plugin-friendly
These two improvements make it feasible to add CAAM encryption key support to Cryptsetup version 2 without being invasive in its codebase. These improvements would use the kernel infrastructure created beforehand. One of the goals would be to port over the existing user-space bits from Cryptsetup 1 over to the second version, but this requires refactoring beforehand to integrate and take advantage of the features of the newer codebase.
We have showed that full-disk encryption using hardware-backed secure encryption keys is feasible on the i.MX 6 and the i.MX 8M. The use-case for this kind of setup exists, and a lot of time had already been put beforehand by others to enable this kind of work by us. Even though the policies regarding the allocation of secure memory pages are more complicated on the i.MX 8M, they also are more logical, as there is a better distinction in which parts of the software stack would be supposed to fill a given role to enable a globally more secure system.
It seems to make life really easier. If you have tested it I would be happy to have your feedback!
Special Thanks to Marius Bagassien who started this article some time ago and left it to me to finish the work :)