Gidon Rémi
UBS - Master CSSE 2019/2020
Sécurité avancée des systèmes d'exploitation
Cas d'une console de jeux
Tutoriel mise en place du système
- Download image
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.3.0-amd64-netinst.iso
- Flash image
# Plug USB pendrive
# check it's device with dmesg
# and replace /dev/sdX by the device found
dd if=debian-10.3.0-amd64-netinst.iso of=/dev/sdX bs=1M status=progress
- Connect the Up-Squared to a screen with HDMI or your laptop using UART
- Plug USB pendrive in Up-Squared and boot it
- Start
expert graphical installation
- User:
ossec
, password:ossec
- Partitions:
- boot vfat ESP partition 500MB, boot flag
- rootfs ext4 partition 10GB, mounted on /
- swap partition 5GB
- let left space available (~15GB) for later configuration
- Install only targeted drivers & modules
We will create our own chain of trust from the .efi
loader to the kernel. In order to achieve such chain of trust we will have to sign each component (efi loader, grub, initrd and the linux kernel).
- Create a work environment, mine is in my home
mkdir /home/ossec/keys
cd ~/keys
- First thing first, we need keys to sign or encrypt anything. Replace
CN
(Name) andO
(Organization) with the ones of your choice.
CN=ossec
O=upboard
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN PK, O=$O/" -keyout PK.key -out PK.crt -days 3650 -nodes -sha256
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN KEK, O=$O/" -keyout KEK.key -out KEK.crt -days 3650 -nodes -sha256
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN db, O=$O/" -keyout db.key -out db.crt -days 3650 -nodes -sha256
- From these
.key
and.crt
files we will create the.esl
and.auth
files required by UEFI.
GUID=$(uuidgen --random)
echo $GUID > GUID.txt
cert-to-efi-sig-list -g $GUID PK.crt PK.esl
sign-efi-sig-list -g $GUID -k PK.key -c PK.crt PK PK.esl PK.auth
cert-to-efi-sig-list -g $GUID KEK.crt KEK.esl
sign-efi-sig-list -g $GUID -a -k PK.key -c PK.crt KEK KEK.esl KEK.auth
cert-to-efi-sig-list -g $GUID db.crt db.esl
sign-efi-sig-list -g $GUID -a -k KEK.key -c KEK.crt db db.esl db.auth
- Then copy those keys to the ESP, where it's reachable from the BIOS, to update the keystore.
# copy keys in ESP to be able to reach them from BIOS
# we will create our own boot entry in /ossec
mkdir -p /boot/efi/EFI/ossec/keys
cp *.auth /boot/efi/EFI/ossec/keys
- Backup actual EFI keys In order to be able to restore the system in case we brick it during developement
efi-readvar -v PK -o old_PK.esl
efi-readvar -v KEK -o old_KEK.esl
efi-readvar -v db -o old_db.esl
efi-readvar -v dbx -o old_dbx.esl
- Update keystore in the BIOS
reboot now
- During boot, press ESC to enter BIOS.
- When prompt asks for password enter
upassw0rd
- Navigate to
Boot>Secure boot>Keys
- Update
PK
,KEK
anddb
with their.auth
counterparts - Activate secure boot (
Secure boot: [Enabled]
) - Save & Exit
We have create our root of trust ! That's the first link of the chain of trust that holds the role of verifying the first component (which will be our .efi
loader).
The next link is our bootloader. For the sake of simplicity we will use Grub2, which is by default with the distribution and supports secure boot.
We won't only enable secure boot by verying initrd
and vmlinuz
with grub, we will also harden it with a password and protect our boot configuration against modifications.
Create an environment for grub configuration.
mkdir ~/build/grub
cd ~/build/grub
Grub doesn't support .esl
and .auth
files previously generated for the UEFI, only GPG keys, so let's create our own GPG key.
apt-get install gpg
# RSA key type, 4096 key bits
gpg --full-generate-key
gpg --list-secret-keys --keyid-format LONG
# should see your newly created key
GPGID=#TODO add your GPG key ID, the one marked as ultimately trusted
As previously explained we'll add a password to protect our boot entry.
GRUB_USER="ossec" #TODO change with yours
GRUB_PASSWORD="ossec" #TODO change with yours
GRUB_PASSWORD_HASH=$(echo -e "$GRUB_PASSWORD\n$GRUB_PASSWORD" | grub-mkpasswd-pbkdf2 | grep -o "grub.*")
We will build grub in standalone
mode which means that all files will reside under the ESP partition.
For grub to be able to load those files, we need a first config file (grub.init.cfg
) located into the efi loader .efi
that will discover the partition where the needed configuration is stored.
- To locate the partition, we will use it's unique id (
fsuuid
).
# list the partitions
lsblk -f
# copy the efi fsuuid
EFI_UUID=$(lsblk -f | grep -i efi | grep -E -o "[A-Z0-9]{4}-[A-Z0-9]{4}")
- Create
grub.init.cfg
as follow
# Enforce that all loaded files must have a valid signature.
set check_signatures=enforce
export check_signatures
# Password protection
set superusers="$GRUB_USER"
export superusers
password_pbkdf2 $GRUB_USER $GRUB_PASSWORD_HASH
# Locate configuration
search --no-floppy --fs-uuid --set=root $EFI_UUID
configfile /grub.cfg
# Without this we provide the attacker with a rescue shell if he just presses
# <return> twice.
echo "grub.init.cfg did not boot the system, rebooting the system in 10 seconds."
sleep 10
reboot
- Sign the first config file (
grub.init.cfg
)
gpg --yes --default-key $GPGID --detach-sign grub.init.cfg
- Build grub in standalone mode
# signature key used
gpg --export $GPGID > ../keys/gpg.key
# build grub
grub-mkstandalone \
-d /usr/lib/grub/x86_64-efi \
-O x86_64-efi \
--modules "part_gpt fat ext2 configfile verify gcry_sha512 gcry_rsa password_pbkdf2 echo normal linux linuxefi all_video search search_fs_uuid reboot sleep" \
--pubkey ../keys/gpg.key \
--output grubx64.efi \
"boot/grub/grub.cfg=grub.init.cfg" \
"boot/grub/grub.cfg.sig=grub.init.cfg.sig" \
-v
- Sign grub
.efi
loader with UEFI keys and place it under our ESP boot entry
sbsign --key ../keys/db.key --cert ../keys/db.crt grubx64.efi
# copy our efi loader into ESP
cp grubx64.efi.signed /boot/efi/EFI/ossec/grubx64.efi
Now that we have our grubx64.efi
loader ready, let's configure it to load the next component which is the kernel
and the initrd
to boot.
- As soon as it's loaded, our kernel will search a
rootfs
to mount. We will give thefsuuid
of ourrootfs
partition whereubuntu
already is installed.
# find the rootfs fsuuid, assuming it's the first ext4 partition on the system
ROOT_UUID=$(lsblk -f | grep -i ext4 | tr -s ' ' | cut -d' ' -f3)
- Create
grub.cfg
#video mode settings
set timeout_style=menu
set timeout=2
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
#--unrestricted to allow any user to boot but only root to modify
menuentry 'ossec with Linux' --unrestricted {
echo 'Loading ossec Linux ...'
linux /vmlinuz root=UUID=$ROOT_UUID ro
echo 'Loading ossec initial ramdisk ...'
initrd /initrd.img
}
cp /boot/vmlinuz-$(uname -r) /boot/efi/vmlinuz
cp /boot/initrd.img-$(uname -r) /boot/efi/initrd.img
cp grub.cfg /boot/efi
gpg --default-key $GPGID --detach-sign /boot/efi/grub.cfg
gpg --default-key $GPGID --detach-sign /boot/efi/vmlinuz
gpg --default-key $GPGID --detach-sign /boot/efi/initrd.img
- Create a boot entry (
ossec
here) in the ESP and update boot order to put it first.
efibootmgr -c -d /dev/mmcblk0 -L "ossec" -l '\EFI\ossec\grubx64.efi'
# change boot order to ossec only
efibootmgr -o 0001
- Update boot priority in BIOS
reboot now
- During boot, press ESC to enter BIOS.
- When prompt asks for password let it empty and hit
Enter
- Navigate to
Boot>UEFI Hard Disk Drive BSS Properties
- Set our
ossec
boot entry first (a.k.aBoot Option #1 [ossec]
) - Save & Exit
- You should see a grub menu with our
ossec
entry, select it. The following should appear on the screen before loading therootfs
:
Loading ossec Linux...
Loading ossec initial ramdisk ...
We already have the ESP partition, swap partition and rootFS created during installation procedure.
We only miss particular partitions for the need of our system (proprietary gaming station).
/mnt/data
will contain user and system configuration, system and applications logs but also secrets used to verify applications.
That will be the only block-level encrypted partition on the system. We will use dm-crypt
and LUKS
to enable the encryption/decryption automatically.
The dilema here is to create a root of trust to store key for this partition, as the Up-Squared supports TPM integration we could setup a TPM to retrieve key before mounting this partition (this is not done in this tutorial because out of scope).
The second additional partition that our system will need is /mnt/apps
, it will contains all the games encrypted and signed, ready to be launched.
Every game (represented as a folder) is encrypted and every component loaded into the system (executables) are signed. The partition is mounted as read-only
and no-user
to avoid runtime modifications.
Our end goal will be:
lsblk -f
NAME FSTYPE LABEL UUID AVAIL MOUNTPOINT
mmcblk0
└─mmcblk0p1 vfat ESP XXX 500M /boot/efi
└─mmcblk0p2 ext4 ROOTFS XXX 10G /
└─mmcblk0p1 swapfs SWAP XXX 5G [SWAP]
└─mmcblk0p1 luks XXX
│ └─data ext4 DATA XXX 5G /mnt/data
└─mmcblk0p1 ext4 APPS XXX 10G /mnt/apps
- Create the partition scheme needed
# data partition
parted -s /dev/mmcblk0 mkpart ext4 15GB 20GB
# apps partition
parted -s /dev/mmcblk0 mkpart ext4 20GB 30GB
- Format the partitions according to our needs
mkfs.ext4 -L APPS /dev/mmcblk0p5
mkdir /mnt/apps
mount /dev/mmcblk0p5 /mnt/apps
dm-verity ?
- Create the key for the
data
partition
mkdir /etc/ossec
dd if=/dev/urandom of=/etc/ossec/data.key bs=512 count=8
cryptsetup --verbose luksFormat /dev/mmcblk0p4 /etc/ossec/data.key
- Setup the encrypted partition with
dm-crypt
cryptsetup -v --key-file /etc/ossec/data.key luksOpen /dev/mmcblk0p4 data
ls -l /dev/mapper/data
mkfs.ext4 -L DATA /dev/mapper/data
mkdir /mnt/data
mount /dev/mapper/data /mnt/data
- Move
/var/log
to/mnt/data/log
rsync -aqxP /var/log /mnt/data
rm -rf /var/log
ln -s /var/log /mnt/data/log
- Setup DAC
# dashboard user for console user interactions
adduser --no-create-home dashboard
mkdir /mnt/data/{secret/{apps,efi},dashboard,log}
cp -r /home/ossec/keys /mnt/data/secret
chown -R ossec /mnt/data/secret
chgrp -R ossec /mnt/data/secret
chown -R dashboard /mnt/data/dashboard
chgrp -R ossec /mnt/data/dashboard
chmod 700 /mnt/data/secret
chmod -R 600 /mnt/data/secret/*.key
chmod 750 /mnt/data/dashboard
- Setup DAC
chown -R ossec /mnt/apps
chgrp -R ossec /mnt/apps
chmod -R 700 /mnt/data/secret
mkdir /mnt/apps/app1
- Applications encryption
Using
fs-crypt
we can encrypt a folder and still operate on it transparently.
We could use other manual solutions that does not operate transparently.
tune2fs -O encrypt /dev/mmcblk0p5
fscrypt setup
fscrypt setup /mnt/apps
dd if=/dev/urandom of=/mnt/data/secret/apps/app1.key bs=1 count=32
# use raw key app1.key
fscrypt encrypt app1
echo "# App1 " > app1/app1.md
- Application authentication
Using
fs-verity
we can authenticate binaries of an application transparently.
Otherwise we could usegpg
as before to sign and verify files manually.
We disabled thero
option from/etc/fstab
for/mnt/apps
becausefs-verity
does not operate on read-only filesystems.
cd ~
sudo apt-get install libssl-dev make gcc
git clone https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git
cd fsverity-utils
make
make install
We need to add key signatures to fs-verity keyring
cd /mnt/data/secret/app1
openssl req -newkey rsa:4096 -subj "/CN=$CN, O=$O/" -nodes -keyout app1.sign.key -x509 -out app1.crt
openssl x509 -in app1.crt -out app1.der -outform der
keyctl padd asymmetric '' %keyring:.fs-verity < app1.der
# verify all files
sysctl fs.verity.require_signatures=1
Then format our partition so it supports verity
(also check kernel configuration for CONFIG_FS_VERITY=m
and CONFIG_FS_VERITY_BUILTIN_SIGNATURES=1
) and finally sign our file.
tune2fs -O verity /dev/mmcblk0p5
cd /mnt/apps/app1
fsverity sign app1.md app1.md.sig --key=/mnt/data/secret/apps/app1.sign.key --cert=/mnt/data/secret/apps/app1.crt
fsverity enable app1.md --signature=app1.md.sig
To avoid opening LUKS partion and remounting data
and apps
every boot, we can rely on /etc/fstab
and /etc/crypttab
.
Modify /etc/crypttab
to add data
data /dev/mmcblk0p4 /etc/ossec/data.key luks
Modify /etc/fstab
to add apps
and data
# debian installation partitioning
/dev/mmcblk0p2 / ext4 errors=remount-ro 0 1
/dev/mmcblk0p1 /boot/efi vfat umask=0077 0 1
/dev/mmcblk0p3 none swap sw 0 0
# equivalent as above on Debian systems
# cp /usr/share/systemd/tmp.mount /etc/systemd/system/ && systemctl enable tmp.mount
tmpfs /tmp tmpfs mode=1777,nosuid,nodev 0 0
# luks partition
/dev/mapper/data /mnt/data ext4 rw,nosuid,noexec,nodev,nouser,auto 0 2
# apps partition
/dev/mmcblk0p5 /mnt/apps ext4 rw,nodev,nouser,auto 0 2
Clean our workspace
# remove keys used to configure secure boot
rm -rf /boot/efi/EFI/ossec/keys
rm -rf /home/ossec/keys
# delete debian boot entry
efibootmgr -b 3 -B
rm -rf /boot/efi/debian
rm -rf /boot/grub
/
├── boot
│ └── efi 700 root
│ ├── EFI
│ │ └── grubx64.efi signed
│ ├── grub.cfg
│ ├── grub.cfg.sig
│ ├── initrd.img
│ ├── initrd.img.sig
│ ├── vmlinuz
│ └── vmlinuz.sig
├── mnt
│ ├── data 700 ossec crypted
│ │ ├── secret
│ │ │ ├── apps
│ │ │ │ ├── app1.key
│ │ │ │ └── app1.crt
│ │ │ ├── efi
│ │ │ │ ├── PK.key
│ │ │ │ ├── KEK.key
│ │ │ │ └── db.key
│ │ │ └── vmlinuz.sig
│ │ ├── dashboard 740 dashboard
│ │ └── log 744 root
│ └── apps 700 ossec
│ └── app1 crypted
│ ├── app1.bin
│ └── app1.sig
├── var
│ └── log -> /mnt/data/log 777 root
├── dev
│ └── mmcblk0
│ ├── mmcblk0p1 vfat ESP 500M /boot/efi
│ ├── mmcblk0p2 ext4 ROOTFS 10G /
│ ├── mmcblk0p1 swapfs SWAP 5G [SWAP]
│ ├── mmcblk0p1 luks
│ │ └── data ext4 DATA 5G /mnt/data
│ └── mmcblk0p1 ext4 APPS 10G /mnt/apps
├── etc
│ └── ossec 700 ossec
│ └── data.key 400 ossec
├── tmp tmpfs
├── home
├── media
├── opt
├── proc
├── root
├── run
├── sys
├── srv
└── usr
Secure Boot with GRUB 2 and signed Linux images and initrds
Linux: Secure Boot
How to boot Linux using UEFI with Secure Boot ?
Create a Luks encrypted partition on Linux Mint
Automount a luks encrypted volume on system start
fs-verity
fs-crypt