Devices often contain highly sensitive information which is consistently at risk to get physically lost or stolen. Setting user passwords does not guarantee data protection against unauthorized access. Attackers can simply bypass the software system of a device and access the data storage directly. Only the use of disk encryption can guarantee data confidentiality in the case that storage media are directly accessed. This document provides the steps to run a transparent root filesystem encryption using DM-Crypt.
Dm-crypt is a Linux kernel module which provides disk encryption. Taking advantage of the Linux kernel’s device-mapper infrastructure, it offers a wide range of use cases, and can run on a variety of storage block devices, even if these devices use RAID and LVM. The device-mapper is designed to provide a general and flexible way to add virtual layers over the actual block device, so that developers can implement mirroring, snapshot, cascade, and encryption. Dm-crypt is implemented as a device mapper target and resides entirely in kernel space between Linux subsystem and actual physical block device driver, which intercepts the request to read and write with the bio parameter that is sent to the actual physical device driver, encrypts the data by using the crypto API provided by the kernel, and then writes the data back to the actual block device. The following diagram represents the overall architecture of dm-crypt.
Dm-crypt has the function of dynamic encryption. Dynamic encryption, also called real-time encryption, or transparent encryption, that the data in the process of using are automatically encrypted or decrypted without users’ intervention, and legitimate users can use the encrypted files without explicitly performing decryption.
2.2. Dm-crypt accelerated by CAAM
Dm-crypt performs cryptographic operations via the interfaces provided by the Linux kernel crypto API. The kernel crypto API defines a standard, extensible interface to ciphers and other data transformations implemented in the kernel (or as loadable modules). Dm-crypt parses the cipher specification (aes-cbc-essiv:sha256) or as kernel crypto API syntax (capi:xts(aes)-plain64) passed as part of its mapping table and instantiates the corresponding transforms via the kernel crypto API.
To list the available cryptographic transformations on the target:
# cat /proc/crypto
name : cbc(aes)
driver : cbc-aes-caam
module : kernel
priority : 3000
refcnt : 1
selftest : passed
internal : no
type : givcipher
async : yes
blocksize : 16
min keysize : 16
max keysize : 32
ivsize : 16
geniv : <built-in>
If there is more than one implementation for a specific transformation, dm-crypt selects the implementation with higher priority.
i.MX SoC provides modular and scalable hardware encryption through NXP's Cryptographic Accelerator and Assurance Module (CAAM, also known as SEC4). To have dm-crypt accelerated by CAAM, the driver should register a transformation implementation with a higher priority.
In the output above, there is 2 implementations of cbc(aes) transformation.
The CAAM-backed implementation has a priority of 3000 while the software implementation has a value of 300. This means that if you instruct dm-crypt to use cbc(aes) cipher, the CAAM-backed implementation will be used.
To list all transformations registered by CAAM driver:
# grep -B1 -A2 caam /proc/crypto|grep -v kernel
name : rsa
driver : rsa-caam
priority : 3000
name : xcbc(aes)
driver : xcbc-aes-caam
priority : 3000
When using DM-Crypt with CAAM, a deadlock is observed. CAAM queueing mechanism is based on circular buffers called job rings. They allow applications to schedule jobs for execution and retrieve the results. When the job ring is full, the CAAM driver returns -EBUSY without backlogging the request while DM-Crypt waits for a competition with -EINPROGRESS. This makes the request will never get completed and a deadlock situation occurs.
A work which aims to provide correct backlog handling in CAAM driver is ongoing.
The following hands-on describes steps to encrypt root filesystem using AES in XTS mode. This mode of operation is not provided by CAAM.
3. Root filesystem encryption
This scenario makes use of DM-Crypt to create a transparently encrypted Root Filesystem. dm-crypt LUKS mode is applied in this case.
Linux IMX disk is generally divided into the following sections: boot partition and system partition. The boot partition contains the kernel and device tree blob. The system partition contains root file system. The root file system includes the following subdirectories: /bin, /sbin, /etc, /dev, /usr et. /bin includes frequently-used commands of Linux; /sbin stores administrative programs; /etc contains various configuration files such as device configuration information; /dev contains all external devices used in the Linux system; /usr contains user binaries, their documentation, libraries and header files.
3.1. Backup the Rootfs
All data on the root partition of your target will be irrevocably overwritten during container creation,
thus, backing data is a mandatory step.
The backup will be restored later to populate the new file system which will be encrypted.
Several technics and tools can be used to backup data. This section exclusively focus on Tape Archiver (tar) which is the most popular Linux backup tool.
On the target run:
# cd /run/media/sda1/
# tar -cvpzf rootfs.tar.gz --exclude=/boot --one-file-system /
The c option creates the backup file.
The v option gives a more verbose output while the command is running. This option can also be safely eliminated.
The p option preserves the file and directory permissions.
The z option compresses the backup file with 'gzip' to make it smaller.
The f option needs to go last because it allows you to specify the name and location of the backup file which follows next in the command (in our case this is the /rootfs.tar.gz file).
--one-file-system - Do not include files on a different filesystem. In case of /home is in another partition, it will not be backed up. In our case, /home is mount in the same partition as root.
--exclude can be used to avoid filesystems you do not want to back up.
/proc, /sys, /mnt, /media, /run and /dev are not required to be backup.
Verify that the data was correctly backed up before stepping into the process.
The first step is to install a toolchain for ARM64.
- Install cross compilers on host machine:
$ sudo apt-get install binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- Lib curses to run menuconfig
$ sudo apt-get install libncurses-dev libncursesw5-dev ncurses-dev make
3.3. Creating a working space
The working space holds all the components which will be used later.
$ cd ~
$ mkdir workspace && cd workspace && export WORKSAPCE=$PWD
3.4. Kernel configuration
Get Kernel sources
$ cd $WORKSAPCE
$ git clone git://source.codeaurora.org/external/imx/linux-imx
$ cd linux-imx/
$ git checkout -b imx_4.14.78_1.0.0_ga origin/imx_4.14.78_1.0.0_ga
Clean kernel sources
$ make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- mrproper
Configure the kernel for the default i.MX installation
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
Enable required options
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
Enable CAAM cryptographic implementations driver
-*- Cryptographic API --->
[*] Hardware crypto devices --->
<*> Freescale CAAM-Multicore driver backend
<*> Freescale CAAM Job Ring driver backend
<*> CAAM Secure Memory / Keystore API (EXPERIMENTAL)
<M> CAAM Secure Memory - Keystore Test/Example (EXPERIMENTAL)
<*> CAAM/SNVS Security Violation Handler (EXPERIMENTAL)
To use Cryptsetup with kernel backend you need to enable User-space interface for hash/cipher algorithms and random number generators. To have LUKS support enabling SHA1 is mandatory for key derivation. This provides us with all the hardware support and acceleration provided by CAAM.
CAAM crypto hardware acceleration implementations can be accessed by Crypsetup using the AF_ALG interface.
--- Cryptographic API
<*> Userspace cryptographic algorithm configuration
<*> User-space interface for hash algorithms
<*> User-space interface for symmetric key cipher algorithms
<*> User-space interface for random number generator algorithms
Enable the Kernel to support initrd and RAM block devices.
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
() Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Device Drivers --->
[*] Block devices --->
<*> RAM block device support
(16) Default number of RAM disks
(16384) Default RAM disk size (kbytes)
Enable the Kernel to support Device Mapper and Crypt DM target.
Device Drivers --> RAID and LVM Support -->
[*] Multiple devices driver support (RAID and LVM)
<*> Device mapper support
<*> Crypt target support
Exit and save your config.
Build the Kernel
$ make ARCH=arm64 CROSS_COMPILE=”aarch64-linux-gnu-“ -j$(nproc)
The patch https://patchwork.kernel.org/patch/10167845/ breaks Device Mapper. You may consider revert the modifications made by the patch if the build fails.
- Copy arch/arm64/boot/Image to FAT32 boot partition of Sdcard.
- Copy appropriate DTB from arch/arm64/boot/dts/freescale/ to FAT32 partition of sdcard.
$ sudo mount /dev/sdb1 /mnt/sdcard
$ sudo cp arch/arm64/boot/Image /mnt/sdcard
$ sudo cp arch/arm64/boot/dts/freescale/<dtb> /mnt/sdcard
3.5. Build Initial RAM disk (initrd) for Encryption
initrd (initial ramdisk) is a memory-based file system (optionally compressed) designed to allow Linux startup process to occur in 2 phases.
First the kernel boots with the set of compiled-in drivers and modules than visits the initrd file system in the memory to make preparations prior to the real root file system.
The same goal can be achieved using Initramfs. Unlike initramfs, the kernel and initial file system are splitted, in addition, making changes to kernel or filesystem doesn't affect the other one and this is the main motivation for using initrd.
With respect to an encrypted roof file system using initrd, the system typically boots as follows:
(1) U-boot loads the kernel and initial ram disk image into memory.
(2) The kernel image is booted passing in the memory address of the initrd image.
(3) The kernel converts initrd into a "normal" RAM disk available in a special block device (/dev/ram) and frees the memory used by initrd.
(4) The kernel executes /init as its first process
(5) The script invokes Cryptsetup to open the encrypted root partition
(6) Device mapper decrypts the encrypted root partition using CAAM.
(7) The kernel assumes that the real root file system has been mounted and executes /sbin/init to perform the normal user-space boot sequence.
(8) The initrd file system is removed.
The document "Boot i.MX to Initial RAM disk (initrd)" aims to give a summary on how to set up an Initial RAM disk and boot an i.MX to it.
Do the steps described in the document until Making init file executable step.
At this level we need to add Cryptsetup binary to our RAM disk image.
3.5.1. Download and compile Device-mapper library
Please install any required third-party library which is not present in your system to /usr/aarch64-linux-gnu (libblkid, json-c etc).
Move to workspace
$ cd $WORKSPACE
Download device-mapper sources
$ git clone https://github.com/lvmteam/lvm2.git -b "2018-06-01-stable"
Compile lib device-mapper
$ cd lvm2
$ ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes ./configure --host=aarch64-linux-gnu --enable-static_link --prefix=/usr/aarch64-linux-gnu
$ make libdm -j$(nproc)
Install lib device-mapper
$ sudo make install_device-mapper
3.5.2. Download and compile Cryptsetup
To encrypt volumes dm-crypt relies on user space applications such as cryptsetup.
We compile cryptsetup with Kernel crypto-backend so all cryptographic primitives are provided by the kernel cryptographic API.
Get Cryptsetup sources
$ cd $WORKSAPCE
$ git clone https://gitlab.com/cryptsetup/cryptsetup.git
$ cd cryptsetup
$ git checkout -b v2.0.6 tags/v2.0.6
$ ./configure --host=aarch64-linux-gnu --target=aarch64-linux-gnu --enable-static-cryptsetup --enable-static --disable-shared --with-crypto_backend=kernel LIBS="-lpthread"
$ make -j$(nproc)
If you get error compiling luks2_json_metadata.c than edit it and add missing definition and make again.
$ nano +30 lib/luks2/luks2_json_metadata.c
#define FALSE 0
#define TRUE 1
The result is cryptsetup.static present in folder
$ file cryptsetup.static
cryptsetup.static: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, BuildID[sha1]=9dc1f31218bd71c9f535c184fb06ff26f692290e, not stripped
Copy Cryptsetup to RAM disk image
$ sudo cp "$WORKSPACE/cryptsetup/cryptsetup.static" /mnt/initrd/bin/cryptsetup
3.5.3. Create a key file
Luks supports multiple key slots. A key can be a passphrase entered interactively or a key file passed as an argument while opening the encrypted partition.
Both options guarantee the same level of security this means that both allows to decrypt the partition offline once having the keyfile or passphrase.
If you plan to automatically decrypt the RootFS on system boot then you may consider creating a key file and add it to the Initrd image.
In your build machine create a keyfile
# cd $WORKSPACE
# dd if=/dev/urandom of=key bs=1024 count=4
Copy it to initrd image.
cp "$WORKSPACE/key" /mnt/initrd/
Now proceed with remaining steps of creating the Initial RAM disk image.
3.6. Encrypt Root filesystem
Cryptsetup supports different encryption operating modes including plain mode and luks mode which are the most common. In luks mode the master-key is encrypted while in plain mode there is no key encryption but we still have passphrase hashing.
Create the encrypted container which will hold the rootfs
/ # cryptsetup luksFormat --cipher "capi:xts(aes)-plain64" --hash sha256 --key-size 256 /dev/mmcblk1p2
Enter uppercase YES then enter and confirm the passphrase.
You can add the created key file to the parition for an automatic unlock during boot later.
# / cryptsetup luksAddKey /dev/mmcblk1p2 /key
Map the block device to the /dev/mapper/rootfs file node, and so we can open the map block device by the file node.
Type the passphrase when prompts
/ # cryptsetup luksOpen /dev/mmcblk1p2 rootfs
Format the container. The initial device formatting may take some time, but fortunately, it must be done only once during the disk initialization phase.
/ # mke2fs -T ext4 /dev/mapper/rootfs
The newly created file system can be mounted now.
/ # mkdir -p /mnt/rootfs
/ # mount /dev/mapper/rootfs /mnt/rootfs
Mount the external media storage which contains the original Rootfs.
/ # mkdir /mnt/external
/ # mount /dev/sda1 /mnt/external
Now every file copied to /mnt/rootfs will be encrypted on the fly and written to disk.
/ # tar -xvpzf /mnt/external/rootfs.tar.gz -C /mnt/rootfs --numeric-owner
x - Tells tar to extract the file designated by the f option immediately after. In this case, the archive is /home/test/backup.tar.gz
-C <directory> - This option tells tar to change to a specific directory before extracting. In this example, we are restoring to the root directory.
--numeric-owner - This option tells tar to restore the numeric owners of the files in the archive, rather than matching to any user names in the environment you are restoring from.
Unmount an encrypted partition
/ # umount /mnt/rootfs
/ # cryptsetup luksClose rootfs
3.7. Build Initial RAM disk for Decryption
Re-do steps involved for creating the Initrd image for encryption replacing the content of the init file with the following.
/bin/busybox --install /bin
mount -t proc /proc /proc
mount -t sysfs none /sys
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /bin/mdev > /proc/sys/kernel/hotplug
cryptsetup luksOpen /dev/mmcblk1p2 rootfs
mount /dev/mapper/rootfs /mnt/rootfs
mkdir -p dev
mkdir -p proc
mkdir -p sys
mkdir -p initrd
mount --move /sys sys
mount --move /proc proc
mount --move /dev dev
pivot_root . initrd
exec chroot . sh -c 'umount /initrd; blockdev --flushbufs /dev/ram0; exec sbin/init' <dev/console >dev/console 2>&1
The init script above involves the following main tasks:
- Open/Decrypt the original root file system
- Turn into the root file system. The root change is done using pivot_root system call through pivot_root utility provided by Busybox. pivot_root moves the root file system of the current process (initrd) to the directory /mnt/rootfs/initrd and makes /mnt/rootfs the new root file system.
- Remove all accesses to the old (initrd) root file system. “exec chroot” is necessary to unmount the initrd root file system.
- Unmount the initrd file system and de-allocating the RAM disk
- Start /sbin/init in the new file system. To provide /dev/console, udev must be initialized.
To automatically decrypt the RootFS on system boot, edit line 10 on init file and modify
cryptsetup luksOpen /dev/mmcblk1p2 rootfs
cryptsetup luksOpen --key-file /key /dev/mmcblk1p2 rootfs
Copy the new uInitrd to boot partition and reboot the board.
Following is the console output of booting into an Encrypted Root filesystem.