Как говорила моя бабушка - причудливой инфре причудливые решения. Так до недавнего времени у меня жил VPN-gateway на RPi3. Жил, потому что нежданно-негаданно малинка померла, и коль заменить RPi3 на что-то другое я хотел больше, чем заниматься его ремонтом - пришлось собрать новый :)

А раз уж подвернулся такой повод, будем ставить Armbian с Full Disk Encryption (FDE), опциональным Network-Bound Disk Encryption (NBDE) и поддержкой Trusted Platform Module (TPM). Но обо всем по порядку, не переключайтесь.

О железках

В качестве SBC мой выбор пал на Orange Pi Zero 3, причин тому несколько:

  1. достаточно производительный для моих текущих запросов
  2. с гигабитной сетевухой на борту
  3. не малинка
  4. лежал без дела в столе, грешновато как ни крути ;)
  5. просто красавчик размером 50x55mm:

И OPTIGA™ SLB 9670 от Infineon в роли TPM2.0 чипа, конкретно у меня в исполнении для материнок MSI:

Вместо покупки готового модуля можно было бы собрать и самому (на JLCPCB даже SLB9670VQ12FW643XUMA2 + монтаж можно заказать, а не только печатную плату), но мне было лениво, а выгоды по деньгам (при штучном производстве офк) никакой. Да и мелкий колхоз как-то ламповее что-ли, чего скрывать :)

Если говорить о том, почему я вообще взял SLB9670, то тут тоже без открытий:

  • дешевый и популярный
  • без проблем отработал у меня пару лет от звонка до звонка в исполнении GeeekPi TPM2.0 Module for Raspberry Pi
  • есть in-kernel драйвер

Готовим Armbian

Я знаю два способа получить FDE для Armbian:

  1. сделать все руками из готового образа
  2. собрать образ самостоятельно с включенным FDE

Сборка Armbian процесс не хитрый, грех не воспользоваться. Для всяких подобных штук у меня есть выделенная машинка, потому топаю на неё и собираю Armbian для Orange Pi Zero 3:

  • клонирую репо и переключаюсь на стабильную версию (люблю иметь полную историю):
% git clone https://github.com/armbian/build armbian-build
% cd armbian-build
% git checkout v24.11.1                                
Previous HEAD position was 270da9d96 sunxi-6.6: Fix incomplete: add community support for LonganPi 3H (#7547)
HEAD is now at dd379da88 Release: set version and froze sources
  • запускаю сборку:
% ./compile.sh \
  CRYPTROOT_ENABLE=yes \
  CRYPTROOT_PASSPHRASE=changeme \
  CRYPTROOT_SSH_UNLOCK=yes \
  CRYPTROOT_SSH_UNLOCK_PORT=2222
  • значения флажков можно посмотреть в Armbian: Build Switches, но они выглядят очевидными:
    • CRYPTROOT_ENABLE=yes - включает шифрование rootfs
    • CRYPTROOT_PASSPHRASE=changeme - задает passphrase для шифрования, но я на 100% уверен, что он останется в каких-нить логах, потому проще сменить после
    • CRYPTROOT_SSH_UNLOCK - включает dropbear-initramfs для анлока диска по SSH
    • CRYPTROOT_SSH_UNLOCK_PORT=2222 - порт, на котором будет слушать dropbear
  • по окончании стягиваю к себе:
    • получившийся образ: Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_6.6.60-crypt.img
    • SSH ключ для похода в dropbear (изменим его позже): Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_.key
  • монтирую образ:
# kpartx -v -a Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_6.6.60-crypt.img
add map loop0p1 (254:1): 0 524288 linear 7:0 8192
add map loop0p2 (254:2): 0 4194304 linear 7:0 532480
  • добавляю новый passphrase:
# cryptsetup luksAddKey /dev/mapper/loop0p2
Enter any existing passphrase: 
Enter new passphrase for key slot: 
Verify passphrase: 
  • удаляю старый:
# cryptsetup luksRemoveKey /dev/mapper/loop0p2
Enter passphrase to be deleted: 
  • на всякий случай меняю master ключ (не уверен в необходимости, но хуже не будет):
# cryptsetup reencrypt /dev/mapper/loop0p2
Enter passphrase for key slot 1: 
Finished, time 00m04s, 2032 MiB written, speed 431.1 MiB/s
  • размонтирую образ:
# kpartx -d Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_6.6.60-crypt.img
  • и заливаю его на флешку:
# dd if=Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_6.6.60-crypt.img of=/dev/sdb bs=1M status=progress
2413821952 bytes (2.4 GB, 2.2 GiB) copied, 75 s, 32.2 MB/s
2308+0 records in
2308+0 records out
2420113408 bytes (2.4 GB, 2.3 GiB) copied, 111.504 s, 21.7 MB/s
# sync

На этом подготовка окончена – можно вставлять флешку, подключать ethernet, включать питание и идти настраивать систему.

Первый запуск

И первое, что нужно сделать - разлочить наш rootfs, не зря шифровали же. Сделать это можно как из терминала (например, по UART), так и походом по SSH (спасибо CRYPTROOT_SSH_UNLOCK):

# ssh -l root -i Armbian-unofficial_24.11.1_Orangepizero3_bookworm_current_.key -p 2222 10.13.0.197
Warning: Permanently added '[10.13.0.197]:2222' (ED25519) to the list of known hosts.
Please unlock disk armbian-root: 
Error: Timeout reached while waiting for PID 184.
Connection to 10.13.0.197 closed.

По SSH же можно и продолжить настройку, потому жду какое-то время, пока Armbian запустится (первый запуск всегда долгий - файлуху нужно поресайзить и все такое). Снова иду по SSH (на этот раз настоящий) для последующей настройки:

  • ssh -l root -p 22 10.13.0.197, дефолтный пароль: 1234
  • прохожу мастер, делаю первичную настройку
  • заменяю authorized_keys для dropbear-initramfs на собственный:
piwg:~:# wget -q https://pub.s3.buglloc.cc/authorized_keys -O /etc/dropbear/initramfs/authorized_keys
piwg:~:# update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.6.60-current-sunxi64
update-initramfs: Armbian: Converting to u-boot format: /boot/uInitrd-6.6.60-current-sunxi64
Image Name:   uInitrd
Created:      Sun Feb 23 12:07:00 2025
Image Type:   AArch64 Linux RAMDisk Image (gzip compressed)
Data Size:    20579752 Bytes = 20097.41 KiB = 19.63 MiB
Load Address: 00000000
Entry Point:  00000000
update-initramfs: Armbian: Symlinking /boot/uInitrd-6.6.60-current-sunxi64 to /boot/uInitrd
'/boot/uInitrd' -> 'uInitrd-6.6.60-current-sunxi64'
update-initramfs: Armbian: done.
  • перезагружаюсь, проверяю, что все правки успешно подхватились

Вуаля, имеем минимально настроенную систему с FDE и возможность удаленного анлока.

Network-Bound Disk Encryption

Мне очень нравится NBDE в моей домашней инфре, тем более, что для этого у меня есть целая отдельная железка (см. Homemade FDE: BoundBoxESP). Самое время сходить по SSH и настроить boundbox-initramfs:

  • клонирую репо и ставлю нужные скрипты:
piwg:~$ git clone --depth=1 https://github.com/buglloc/boundbox-initramfs.git
piwg:~$ cd boundbox-initramfs/
piwg:~/boundbox-initramfs$ sudo make install
install -Dm755 "etc/initramfs-tools/hooks/boundbox" "/etc/initramfs-tools/hooks/boundbox"
install -Dm755 "etc/initramfs-tools/scripts/local-top/boundbox" "/etc/initramfs-tools/scripts/local-top/boundbox"
install -Dm755 "etc/initramfs-tools/scripts/local-bottom/boundbox"	"/etc/initramfs-tools/scripts/local-bottom/boundbox"
install -Dm755 "sbin/boundbox-initramfs"	"/usr/local/sbin/boundbox-initramfs"
install -Dm755 "etc/boundbox/initramfs/boundbox.conf"	"/etc/boundbox/initramfs/boundbox.conf"
  • генерирую новый приватный ключ для SSH и known_hosts для моего инстанса BoundBox:
piwg:~/boundbox-initramfs$ sudo boundbox-initramfs gen
Generate /etc/boundbox/initramfs/key
Generating public/private ed25519 key pair.
Your identification has been saved in /etc/boundbox/initramfs/key
Your public key has been saved in /etc/boundbox/initramfs/key.pub
The key fingerprint is: SHA256:CRl8q2MC11llOyZfM9DaiQYZOUYLKOxLgHixl32kQ6w [email protected]
Check connection to bb.buglloc.cc and generate /etc/boundbox/initramfs/known_hosts
Warning: Permanently added 'bb.buglloc.cc' (ED25519) to the list of known hosts.
Done
  • связываю волюм с BoundBox (в конце видно, что добавился новый keyslot и токен boundbox):
piwg:~/boundbox-initramfs$ cat /etc/crypttab 
# <target name>	<source device>		<key file>	<options>
armbian-root UUID=58d41e13-18ce-4d44-8625-b4bf23ae2fc0 none luks

piwg:~/boundbox-initramfs$ sudo boundbox-initramfs bind --dev UUID=58d41e13-18ce-4d44-8625-b4bf23ae2fc0
Generate salt
Calling boundbox: bb.buglloc.cc
Trying to find free luks slot
Using slot: 1
Check existing token id for slot: 1
Adding luks key to slot '1' from 'UUID=58d41e13-18ce-4d44-8625-b4bf23ae2fc0' with token id ''
Enter any existing passphrase: 
Adding luks metadata token id '' for slot '1' from 'UUID=58d41e13-18ce-4d44-8625-b4bf23ae2fc0'
Done
LUKS header information
Version:       	2
Epoch:         	29
Metadata area: 	16384 [bytes]
Keyslots area: 	16744448 [bytes]
UUID:          	58d41e13-18ce-4d44-8625-b4bf23ae2fc0
Label:         	(no label)
Subsystem:     	(no subsystem)
Flags:       	(no flags)

Data segments:
  0: crypt
	offset: 16777216 [bytes]
	length: (whole device)
	cipher: aes-xts-plain64
	sector: 512 [bytes]

Keyslots:
  0: luks2
	Key:        512 bits
	Priority:   normal
	Cipher:     aes-xts-plain64
	Cipher key: 512 bits
	PBKDF:      argon2id
	Time cost:  6
	Memory:     1048576
	Threads:    4
	Salt:       7b a7 68 23 64 38 c9 60 3c 4b 90 a4 9d cf a2 df 
	           24 93 c2 85 3e 25 e4 48 38 f8 07 39 a6 8e 97 7e 
	AF stripes: 4000
	AF hash:    sha256
	Area offset:32768 [bytes]
	Area length:258048 [bytes]
	Digest ID:  1
  1: luks2
	Key:        512 bits
	Priority:   normal
	Cipher:     aes-xts-plain64
	Cipher key: 512 bits
	PBKDF:      pbkdf2
	Hash:       sha256
	Iterations: 1000
	Salt:       ad 7c 09 55 44 6b db ce 6b 5e c8 24 4b 20 cf 8c 
	           1f 59 62 1e c7 ff 79 32 81 1d 12 ea d4 6b c3 3e 
	AF stripes: 4000
	AF hash:    sha256
	Area offset:290816 [bytes]
	Area length:258048 [bytes]
	Digest ID:  1
Tokens:
  0: boundbox
	Keyslot:    1
Digests:
  1: pbkdf2
	Hash:       sha256
	Iterations: 1000
	Salt:       d1 d2 c7 89 05 64 f9 30 43 65 11 a6 2f 02 6d a2 
	           2c 49 10 8d 61 e5 04 05 75 1b 73 91 89 05 42 78 
	Digest:     62 71 f0 27 db 95 9c a6 19 53 a7 e9 70 e8 64 b5 
	           c7 f8 e7 88 f7 22 ed 3a 29 dc df aa a9 f7 e8 6c
  • обновляю initramfs (подрос на мегабайт с прошлого раза):
piwg:~/boundbox-initramfs$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.6.60-current-sunxi64
update-initramfs: Armbian: Converting to u-boot format: /boot/uInitrd-6.6.60-current-sunxi64
Image Name:   uInitrd
Created:      Tue Feb 25 20:59:30 2025
Image Type:   AArch64 Linux RAMDisk Image (gzip compressed)
Data Size:    21612568 Bytes = 21106.02 KiB = 20.61 MiB
Load Address: 00000000
Entry Point:  00000000
update-initramfs: Armbian: Symlinking /boot/uInitrd-6.6.60-current-sunxi64 to /boot/uInitrd
'/boot/uInitrd' -> 'uInitrd-6.6.60-current-sunxi64'
update-initramfs: Armbian: done.
  • перезагружаюсь и проверяю, что теперь моего участия для загрузки не нужно:
[...]
Please unlock disk armbian-root:
IP-Config: no response after 2 secs - giving up
IP-Config: end0 hardware address 02:00:b0:1a:3f:10 mtu 1500 DHCP RARP
IP-Config: end0 complete (dhcp from 10.13.0.1):
 address: 10.13.0.197      broadcast: 10.13.255.255    netmask: 255.255.0.0     
 gateway: 10.13.0.1        dns0     : 10.13.0.1        dns1   : 0.0.0.0         
 domain : lan                                                             
 rootserver: 10.13.0.1 rootpath: 
 filename  : 
Begin: Starting dropbear ... 
try to decrypt /dev/mmcblk0p2
decrypt /dev/mmcblk0p2 with token 0
calling boundbox: SHA256:[email protected]
pass key to passfido
Success: done
Warning: keyslot operation could fail as it requires more than available memory.
done.
done.
Begin: Running /scripts/local-bottom ... Begin: Stopping BoundBox ... done.
done.
Begin: Running /scripts/init-bottom ... Begin: Stopping dropbear ... done.
Begin: Bringing down end0 ... done.
Begin: Bringing down lo ... done.
done.

Welcome to Armbian-unofficial 24.11.1 bookworm!
[...]

Красота, ей богу. Теперь существует целых три способа расшифровать rootfs:

  • ручной с консоли
  • ручной (но удаленный) по SSH
  • автоматический при нахождении BoundBox в сети

Именно так, как я того и хотел ;)

TPM2.0

В целом TPM можно использовать для разных приколюх, например:

Мне же он нужен для VPN, но это скорее тема для отдельного поста (не обессудьте, текущий и так вышел большим). В этом же хотелось бы его просто завести, для чего:

  • прикидываю схему подключения, поглядывая в Orange Pi Zero 3: 26 Pin Interface:
    MSI 12Pin SPI TPMOrange Pi Zero3
    #1: 3v3#17: 3v3
    #2: SPI CS#24: SPI1_CS
    #3: MISO#21: SPI1_MISO
    #4: MOSI#19: SPI1_MOSI
    #6: SPI CLK#23: SPI1_CLK
    #7: GND#25: GND
    #8: SPI RST#26: PC10
  • перепаиваю разъем у TPM модуля, т.к. он не подходит по пинам, да и не 2.54mm
  • собираю все воедино, поглядывая в распиновку TPM модуля:
  • подаю питание, иду по SSH и добавляю нужный device tree overlay:
piwg:~:# mkdir -p /boot/overlay-user
piwg:~:# cat << '_EOF_' > /boot/overlay-user/sun50i-h616-slb9670-spi1_1.dts
/dts-v1/;
/plugin/;

/ {
    compatible = "allwinner,sun50i-h616";

    fragment@0 {
        target-path = "/aliases";
        __overlay__ {
            spi1 = "/soc/spi@5011000";
        };
    };

    fragment@1 {
        target = <&pio>;
        __overlay__ {
            spi1_cs1_reset: spi1_cs1_reset {
                pins = "PC10";
                function = "gpio_out";
            };
        };
    };

    fragment@2 {
        target = <&spi1>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";
            pinctrl-names = "default";
            pinctrl-0 = <&spi1_pins &spi1_cs1_pin>;

            slb9670: slb9670@1 {
                compatible = "infineon,slb9670", "tis,tpm2-spi", "tcg,tpm_tis-spi";
                reg = <1>;
                gpio-reset = <&spi1_cs1_reset>;
                spi-max-frequency = <32000000>;
                status = "okay";
            };
        };
    };
};
_EOF_
piwg:~:# armbian-add-overlay /boot/overlay-user/sun50i-h616-slb9670-spi1_1.dts
Compiling the overlay
Copying the compiled overlay file to /boot/overlay-user/
Reboot is required to apply the changes
  • перезагружаюсь и о чудо:
piwg:~:# dmesg | grep tpm
[   26.988879] tpm_tis_spi spi1.1: 2.0 TPM (device-id 0x1B, rev-id 16)
piwg:~:# ls -la /dev/tpm0
crw------- 1 root root 10, 224 Feb 26 22:38 /dev/tpm0
  • можно попробовать проинициализировать модуль и сгенерить какой-нить ключ:
piwg:~:# tpm2_ptool init
action: Created
id: 1
piwg:~:# tpm2_ptool addtoken --pid 1 --label sshtok --sopin my-sopin --userpin  my-userpin
piwg:~:# tpm2_ptool addkey --algorithm ecc256 --label sshtok  --key-label tst --userpin my-userpin
action: add
private:
  CKA_ID: '39323561393664343764326364353765'
public:
  CKA_ID: '39323561393664343764326364353765'
piwg:~:# pkcs11-tool --module /usr/lib/aarch64-linux-gnu/pkcs11/libtpm2_pkcs11.so --list-token-slots
Available slots:
Slot 0 (0x1): sshtok
  token label        : sshtok
  token manufacturer : Infineon
  token model        : SLB9670
  token flags        : login required, rng, token initialized, PIN initialized
  hardware version   : XXXX
  firmware version   : XXXX
  serial num         : 0000000000000000
  pin min/max        : 0/128
Slot 1 (0x2):
  token state:   uninitialized
piwg:~:# pkcs11-tool --module /usr/lib/aarch64-linux-gnu/pkcs11/libtpm2_pkcs11.so --slot-index=0 --list-objects
Using slot with index 0 (0x1)
Public Key Object; EC  EC_POINT 256 bits
  EC_POINT:   044104a4b1dc7f6cb36f95fecb15cba22d359e217e52f6c07274a791bbe1fe69b6192c1cd981f74e6fb05d330e6dfb59c2f84e30942e7415b25425c15758a74035d8da
  EC_PARAMS:  06082a8648ce3d030107
  label:      tst
  ID:         39323561393664343764326364353765
  Usage:      encrypt, verify
  Access:     local

Пам-пам, можно идти настраивать VPN и все, что нужно.

Conclusion

Надеюсь, мне удалось показать, что не стоит бояться ни FDE, ни TPM даже на SBC. В общем случае все настраивается без особых сюрпризов, разве что с Device Tree overlay придется чуть повозиться (но это не точно). В остальном же, не забывайте, что необходимость любых фичей безопаности (к теме поста: FDE, Secure Boot, TPM, датчики вскрытия, etc) всегда стоит на трех китах:

  • модель угроз
  • чувство прекрасного
  • законы физики

Потому принимайте решение исходя из совокупности этих факторов, а не потому что кто-то в посте так написал. Ну и не забывайте, что серебряной пули (e.g. нельзя воткнуть TPM и больше ни о чем не думать) не существует. Я предупредил, безопасность всегда про комплекс мер ;)

А пока у меня все, не болейте (づ˶•༝•˶)づ♡