Chiffrement sécurisé sur les SoC STM32MP15

24 février 2025 ― Pierre-Loup GOSSE

Résumé ― Le boot sécurisé et le chiffrement des données vont de pair dans la sécurisation d'un système embarqué. Le premier est supporté sur les SoC STM32MP15xC/F tandis que le second l'est moins en comparaison à la série STM32MP13 et son module SAES. Nous allons voir dans cet article la configuration nécessaire pour chiffrer le rootfs à partir de l'OP-TEE et d'une HUK sur les SoC STM32MP15. Nous présenterons en fin d'article un layer Yocto pour mettre en démonstration le chiffrement depuis un initramfs.

Introduction

Sous Linux l'outil cryptsetup permet avec LUKS de chiffrer une partition à partir d'une passphrase.

dd if=/dev/random bs=4 count=4 > passphrase
cryptsetup --type luks2 luksFormat /dev/mmcblk1p1 < passphrase

Généralement longue et complexe, cette passphrase est un secret où son accès et son provisionnement doivent être sécurisés. Plusieurs solutions existent pour gérer des secrets sur les systèmes embarqués. Dans le cadre de cet article nous allons nous concentrer sur l'architecture ARM et en particulier sur son intégration sur la série STM32MP15.

Voic deux liens utiles pour la suite d'article :

Secure world & OP-TEE

La technologie ARM TrustZone permet de dédoubler le système pour créer deux espaces d'exécution : un secure world et un non-secure world. Un Trusted Execution Environment (TEE) s'exécute sur le secure world pour réaliser des opérations sensibles sur le CPU. Un Rich Execution Environment (REE) s'exécute sur le normal world, par exemple le kernel Linux. Les deux mondes communiquent par un secure path appelé le Secure Monitor.

L'OP-TEE est une implémentation d'un TEE respectant l'API GlobalPlatform, dont notamment le Trusted Storage API permettant de stocker des données et des clés.

Utiliser l'OP-TEE + Linux nécessite de modifier la chaîne de démarrage. Cet article ne détail pas les changements nécessaire, se référer à la page Boot Chain Overview pour plus de compréhension.

Approfondir
Voici quelques références pour appronfondir le sujet :

Secure storage

L'OP-TEE implémente l'API Trusted Storage avec le secure storage décliné en deux versions :

Dans les deux cas, l'OP-TEE repose sur les drivers du REE pour effectuer les opérations (lecture / écriture). Les instructions sont envoyées depuis le secure world vers le driver OP-TEE présent dans le normal world à travers le Secure Monitor. Les opérations sont atomiques et les données sont chiffrées par un jeu de clés propre à chaque device calculé à chaque démarrage : les clés sont gardées dans une secure memory sans être écrite sur le disque.

Ainsi, utiliser le secure storage pour stocker des secrets c'est déléguer la gestion des clés et le chiffrement des données à l'OP-TEE.

HUK

Pour assurer un jeu de clés différent pour chaque device, l'OP-TEE se repose sur l'Hardware Unique Key. La HUK est une clé de dérivation de 128-bits, elle est utilisée pour créer de nouvelle clé réduisant ainsi son exposition : si une clé dérivée est compromise cela ne compromet pas les autres clés dérivées. Le secure storage se base en partie sur la HUK avec la Secure Storage Key (SSK).

L'implémentation et la méthode de récupération de la HUK est définie par le constructeur de la SoC. Sur les SoC STM32MP, la HUK est stockée dans des OTP sécurisés (upper OTP, cf. chapitre 3 du manuel RM0436). Sur les séries STM32MP13/25 la HUK est déjà provisionnée. Cependant, sa lecture n'est pas rendu possible : son stockage est directement lié au module SAES qui dérive la HUK en une Derived HUK (DHUK) utilisé pour le chiffrement. Concernant notre série STM32MP15, la HUK n'est pas provisionnée, il est donc nécessaire d'implémenter logiciellement la HUK (cf. Hardware Unique Key overview).

C'est la fonction tee_otp_get_hw_unique_key de l'OP-TEE qui définie la méthode de récupération de la HUK. Sans redéfinition du constructeur cette fonction retourne une HUK constante.

core/kernel/otp_stubs.c

/*
 * Override these in your platform code to really fetch device-unique
 * bits from e-fuses or whatever.
 *
 * The default implementation just sets it to a constant.
 */
__weak TEE_Result tee_otp_get_hw_unique_key(struct tee_hw_unique_key *hwkey)
{
    memset(&hwkey->data[0], 0, sizeof(hwkey->data));
    return TEE_SUCCESS;
}

Provisionnement de la HUK

La HUK n'étant pas provisionnée par STM, son provisionnement est donc à la charge de l'utilisateur. Pour ce faire, il faut d'abord configurer l'OP-TEE pour lui indiquer l'emplacement de la HUK dans les OTP. La HUK faisant 128-bits il faut 4 OTP contiguës ou non. Généralement les OTP 60 à 63 sont utilisées.

Il existe deux méthodes pour définir l'emplacement (cf. HUK: Software implementation) :

  • À la compilation avec CFG_STM32MP15_HUK_OTP_BASE pour une HUK contiguë ou CFG_STM32MP15_HUK_BSEC_KEY_0/1/2/3 pour une HUK non contiguë.
  • Depuis le device tree en activant l'option de compilation CFG_STM32_HUK_FROM_DT. Un nœud huk-otp dans le device tree doit définir l'emplacement du premier OTP de la HUK, celle-ci doit cependant être contiguë.

Le fichier core/drivers/stm32mp15_huk.c redéfinie la fonction tee_otp_get_hw_unique_key et récupère la HUK depuis les OTP. Voici un extrait simplifié :

core/drivers/stm32mp15_huk.c

static TEE_Result get_otp_pos(uint32_t otp_id[HUK_NB_OTP])
{
#ifdef CFG_STM32_HUK_FROM_DT
    return pos_from_dt(otp_id);
#else /* CFG_STM32_HUK_FROM_DT */
    otp_id[0] = CFG_STM32MP15_HUK_BSEC_KEY_0;
    otp_id[1] = CFG_STM32MP15_HUK_BSEC_KEY_1;
    otp_id[2] = CFG_STM32MP15_HUK_BSEC_KEY_2;
    otp_id[3] = CFG_STM32MP15_HUK_BSEC_KEY_3;

    return TEE_SUCCESS;
#endif /* CFG_STM32_HUK_FROM_DT */
}

TEE_Result tee_otp_get_hw_unique_key(struct tee_hw_unique_key *hwkey)
{
    uint32_t otp_key[HUK_NB_OTP] = { };
    uint32_t otp_id[HUK_NB_OTP] = { };
    uint32_t *key = otp_key;
    bool lock = true;

    [...]

    ret = get_otp_pos(otp_id);
    if (ret)
            return ret;

    for (i = 0; i < HUK_NB_OTP; i++) {
            ret = stm32mp15_read_otp(otp_id[i], key++, &lock);
            if (ret)
                    goto out;
    }

    memcpy(hwkey->data, otp_key, HW_UNIQUE_KEY_LENGTH);

    [...]
}

Par défaut, les upper OTP ne sont pas accessibles pas le non-secure (normal) world, c'est-à-dire U-Boot ou Linux. Ainsi, pour provisionner la HUK depuis le non-secure world il est nécessaire de configurer le périphérique BSEC dans le device tree de l'OP-TEE. Ce périphérique contrôle l'accès aux OTP, les upper OTP peuvent être rendu accessible depuis le device tree avec les propriétés suivantes :

  • st,non-secure-otp : autorise la lecture/écriture depuis le non-secure world.
  • st,non-secure-otp-provisioning : autorise l'écriture depuis le non-secure world, mais bloque la lecture.

La seconde propriété est appropriée pour provisionner des secrets depuis le non-secure world utilisé par le secure world, dans notre exemple la HUK. Ainsi, il est préferable de configurer l'emplacement de la HUK depuis le device tree en ajoutant les éléments suivants au nœud bsec :

  &bsec {
    huk_otp: huk-otp@f0 {
      reg = <0xf0 0x10>;
      st,non-secure-otp-provisioning;
    };
  };

Il est ensuite possible, à partir du framework NVMEM présent sous Linux, d'écrire dans les OTP avec le pseudo-fichier /sys/bus/nvmem/devices/stm32-romem0/nvmem :

# Génération d'une HUK de 128-bits
dd if=/dev/random bs=4 count=4 > huk
# Écriture de la HUK à l'OTP 60, 61, 62 et 63
dd if=huk of=/sys/bus/nvmem/devices/stm32-romem0/nvmem count=4 seek=60 bs=4

Par curiosité vous pouvez essayer d'écrire dans des OTP protégés, vous devriez avoir un erreur de lecture : le BSEC vous en interdit l'accès. Avec la propriété st,non-secure-otp-provisioning la lecture est permise mais retourne des zéros.

Il est aussi possible d'écire dans les OTP depuis U-Boot avec la commande fuse ou encore stm32key (plutôt utilisée pour flasher la PKH dans le cadre du boot sécurisé).

Important
Le BSEC utilise un cache, dit shadow, des OTP. Il est nécessaire de redémarrer la board pour assurer que les données écrites soient prises en compte.

Génération d'une passphrase

À partir des fonctionnalités de l'OP-TEE, deux méthodes s'offrent donc à nous pour sécuriser une passphrase :

  1. Générer une passphrase et l'écrire dans le secure storage.
  2. Dériver la passphrase à partir de la HUK.

La première méthode demande de configurer le secure storage dans un emplacement non chiffré (pour rappel, c'est l'OP-TEE qui chiffrera le contenu), soit en utilisant le RPMB FS (à condition que la board ait une eMMC), soit en utilisant le REE FS (en prenant en compte l'emplacement du secure storage).

La seconde méthode est plus simple à mettre en œuvre, à partir d'une simple chaîne de caractère - par exemple "passphrase" - l'OP-TEE peut en dériver une clé de 16 à 32 octets identique à chaque dérivation mais différente pour chaque HUK. Si cependant le besoin projet nécessite une même passphrase pour tous les devices, il est possible d'utiliser la HUK dérivée en une wrapped key pour chiffrer la passphrase commune et stocker celle-ci dans une partition non-chiffrée.

Dans le cadre de notre démonstration nous allons utiliser la seconde méthode.

Trusted Application

Dans les deux cas de figure, écrire dans le secure storage ou dériver la HUK sont des opérations initiées depuis le REE mais exécutées dans le TEE. Il est donc nécessaire d'utiliser une Trusted Application (TA) pour exécuter les opérations dans l'OP-TEE et d'utiliser une Client Application (CA) pour appeler la TA depuis Linux.

Il existe deux types de TA :

  • Pseudo Trusted Application (PTA), inclus directement dans les sources de l'OP-TEE (core/pta). Par exemple le PTA hwrng pour générer des nombres aléatoire, stats pour récupérer des statistiques de l'OP-TEE ou encore system qui permet notamment de dériver la HUK.
  • User Mode Trusted Application, ou Plain TA, présent dans le système de fichiers du REE à l'emplacement /lib/optee_armtz. Ces TA peuvent aussi appeler des PTA.

Le code source des user mode TA sont souvent accompagné de leur CA (dossier host), voici quelques exemples de TA. Il est aussi possible d'appeler un PTA depuis un CA, par exemple le PTA stats.

Les TA sont identifiées à partir de leur UUID et offre une liste de commande invocable. Pour mieux comprendre le fonctionnement d'une TA se référer à la documentation Building Trusted Application. Voici article qui détail l'écriture de sa propre TA.

Note
Pour protéger les TA de modifications malveillantes depuis le REE FS, les TA sont signées par l'OP-TEE et vérifiées lors de leur chargement. Par défaut, une paire de clés RSA de développement est utilisée. En production il est nécessaire d'ajouter sa propre paire de clés. Se référer à la section Signing TAs.

Note
L'exécution de TA sur l'OP-TEE requière la configuration du profil secure and system services, qui n'est pas le profil par défaut pour la STM32MP15.

Derived unique key TA

La PTA system propose la commande PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY pour dériver la HUK à partir d'une chaîne de caractère, dit extra string. La dérivation étant sensible, cette PTA ne peut pas être appelée depuis un CA (cf. la fonction open_session) mais uniquement depuis une TA. En effet, les TA étant signées cela permet d'assurer que l'invocation est de confiance. De ce fait, j'ai devéloppé une user mode TA en me basant sur les travaux du projet optee_test.

Le projet se nomme optee-derive-unique-key, c'est un simple wrapper pour invoquer la commande PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY. Une fois compilé et installé, il devient facile de générer une passphrase unique pour chaque device depuis l'user space Linux à partir d'une extra string :

~$ optee-derive-unique-key -d "passphrase" | hexdump 
0000000 0b0c fdac 2e4f 07b1 e54f b9c0 eb39 453d
0000010 17df f8d1 25a1 f3eb 4afe 34b8 abd4 b4fd

Cette passphrase peut ainsi être utilisée pour chiffrer le rootfs une première fois, puis le déchiffrer à chaque démarrage depuis un initramfs. C'est ce que nous allons mettre en pratique dans la suite de cet article.

Notez cependant que l'extra string n'est pas un secret. Ainsi, si un utilisateur est capable d'exécuter la TA il sera en mesure de dériver l'extra string est récupérer la passphrase. Pour palier ce problème, la TA propose une fonction close qui permet de fermer l'accès à la commande de dérivation jusqu'au prochain reboot :

~$ optee-derive-unique-key -c
Derivation from this TA is now closed until next reboot
~$ optee-derive-unique-key -d "passphrase"
Derivation denied

Cela concerne uniquement cette TA, il est donc important d'assurer que la dérivation de la HUK ne reste pas accessible aux utilisateurs via d'autres TA.

Layer meta-stm32mp15-demo-encrypt

Nous avons vu les différents éléments nécessaire à configurer pour générer une passphrase sécurisée et ainsi chiffrer des données. Afin de démontrer le fonctionnement de bout en bout, j'ai construit un layer Yocto meta-stm32mp15-demo-encrypt. Ce layer est volontairement orienté pour la STM32MP15, mais mise à part le provisionnement de la HUK le layer peut se porter pour un device qui utilise l'OP-TEE et une HUK.

Ce layer inclus :

  • Des scripts pour provisionner la HUK, chiffrer et déchiffrer une partition, présents dans la recette recipes-extended/demo-encrypt/demo-encrypt.bb.
  • La compilation et l'installation du TA optee-derive-unique-key avec la recette recipes-security/optee-derive-unique-key/optee-derive-unique-key.bb.
  • Un initramfs script pour exécuter un shell dans l'initramfs et tester les scripts, recipes-core/initrdscripts.
  • Une image ramdisk incluant ces éléments, recipes-core/images/ramdisk-demo-encrypt.bb.

Ce layer n'inclus pas l'ajout du nœud huk-otp dans le device tree de l'OP-TEE. Ce layer dépend du layer meta-st-stm32mp.

Intégration

Une fois le layer ajouté à votre projet, il suffit d'utiliser le ramdisk comme initramfs dans votre configuration, par exemple local.conf :

local.conf

INITRAMFS_IMAGE = "ramdisk-demo-encrypt"

N'oubliez pas d'ajouter le nœud huk-otp au device tree de l'OP-TEE comme expliqué dans la section précédente Provisionnement de la HUK.

Ce layer a été testé sur une board custom avec le SoM DHCOR STM32MP15. À titre d'exemple, voici la configuration machine dh-stm32mp1-dhcor-custom.conf basée sur meta-st-stm32mp/conf/machine/stm32mp1.conf et le fichier sdcard-stm32mp157c-dhcor-custom.wks.in pour générer une image wic pour carte SD, et les quelques lignes ajoutées à la configuration locale :

local.conf

MACHINE = "dh-stm32mp1-dhcor-custom"
WKS_FILE = "sdcard-stm32mp157c-dhcor-custom.wks.in"
INITRAMFS_IMAGE = "ramdisk-demo-encrypt"

Démonstration

L'objectif de la démonstration est de démarrer sur l'initramfs, exécuter manuellement les opérations de provisionning, chiffrement et déchiffrement puis la fermeture du TA.

Pour pousser la démonstration, nous allons installer un rootfs (core-image-minimal) sur la partition chiffrée et démarrer dessus. Le rootfs contient la TA et sera déjà présent au format ext4 dans la partition rootfs non chiffré. On exécute le kernel avec l'option root= pour automatiquement monter et démarrer sur le rootfs, dans le cadre de cette démonstration l'option est root=/dev/mapper/mmcblk0p5.

Au démarrage, le ramdisk exécute un shell :

Demo encrypt shell.

 - demo-huk-provisionning [OTP_base]
   Flash a ramdom 128 bits (4 OTP) HUK key in the OTP and reboot.
   Default OTP base is 60 (i.e. OTP 60, 61, 62, 63).

 - demo-encrypt <device> [extra_string]
   Encrypt the selected device.
   The passphrase is derived from the HUK with the extra string,
   default is "passphrase".

 - demo-decrypt <device> [extra_string]
   Decrypt the selected device.
   The passphrase is derived from the HUK with the extra string,
   default is "passphrase".

 - optee-derive-unique-key -c
   Close the access to HUK derivation from this TA until next reboot.

Si ce n'est pas fait, il faut provisionner la HUK dans les OTP en exécutant le script demo-huk-provisionning. Attention, l'écriture dans les OTP n'étant possible qu'une fois, vous ne pourrez pas modifier la HUK par la suite. Si vous souhaitez tester sans écrire la HUK dans les OTP vous pouvez configurer l'OP-TEE avec CFG_STM32_HUK_TESTKEY et dans ce cas ne pas exécuter ce script. Se référer à la page OP-TEE configuration switches.

Une fois la HUK provisionnée, et le boîtier redémarré, on exécute les étapes suivantes:

  • On déplace le rootfs dans /tmp :

~$ mount /dev/mmcblk0p5 /mnt 
~$ mv /mnt/rootfs.ext4 /tmp 
~$ umount /mnt

  • On chiffre / déchiffre la partition :

~$ demo-encrypt /dev/mmcblk0p5
Encrypt...
Encrypted.
~$ demo-decrypt /dev/mmcblk0p5
Decrypt...
Decrypted (/dev/mapper/mmcblk0p5).

  • On écrit le rootfs dans la partition chiffrée :

~$ dd if=/tmp/rootfs.ext4 of=/dev/mapper/mmcblk0p5

  • On ferme l'accès à la TA:

~$ optee-derive-unique-key -c
Derivation from this TA is now closed until next reboot

  • On sort du shell (exit), les initramfs scripts suivants s'exécutent et le kernel poursuit son exécution sur le rootfs. Une fois loggé, on exécute de nouveau la TA:

~$ optee-derive-unique-key -d "passphrase"
Derivation denied

Conclusion

Nous avons vu dans cet article qu'il est tout de même possible de chiffrer un rootfs de façon sécurisé sans avoir besoin de stocker la passphrase sur la SoC STM32MP15 en utilisant la HUK et en protégeant son accès.

Nous n'avons pas abordé dans cet article le sujet du boot sécurisé (secure boot) qui permet d'assurer l'authenticité et l'intégrité des binaires de la chaîne de démarrage dont l'OP-TEE fait partie. Il est essentiel de le mettre en place auquel cas sans secure boot un tiers malveillant peut tout simplement remplacer l'OP-TEE et ainsi venir lire notre HUK. Le sujet est traité dans le wiki STM How to enable secure boot on STM32 MPU.

Bibliographie

Cryptsetup, https://www.ibm.com/docs/en/order-management-sw/10.0?topic=considerations-encrypting-data-partitions-using-luks
LUKS, https://fr.wikipedia.org/wiki/LUKS
Wiki STM, https://wiki.st.com/stm32mpu/wiki/Category:Embedded_software_components
Manuel de référence STM32MP15x (RM0436), https://www.st.com/resource/en/reference_manual/rm0436-stm32mp157-advanced-armbased-32bit-mpus-stmicroelectronics.pdf
ARM TrustZone, https://www.arm.com/technologies/trustzone-for-cortex-a
Trusted Execution Environment, https://en.wikipedia.org/wiki/Trusted_execution_environment
OP-TEE, https://optee.readthedocs.io/en/latest/general/about.html
API GlobalPlatform, https://optee.readthedocs.io/en/latest/architecture/globalplatform_api.html
Boot Chain Overview, https://wiki.st.com/stm32mpu/wiki/Boot_chain_overview
What is TrustZone?, https://www.trustonic.com/technical-articles/what-is-trustzone/
An Exploration of ARM TrustZone Technology, https://genode.org/documentation/articles/trustzone
TrustZone : sécuriser un client TLS avec OP-TEE, https://linuxembedded.fr/2023/07/trustzone-securiser-un-client-tls-avec-op-tee
Secure storage, https://optee.readthedocs.io/en/latest/architecture/secure_storage.html
REE FS Secure Storage, https://optee.readthedocs.io/en/latest/architecture/secure_storage.html#ree-fs-secure-storage
RPMB Secure Storage, https://optee.readthedocs.io/en/latest/architecture/secure_storage.html#rpmb-secure-storage
RPMB, https://sergioprado.blog/rpmb-a-secret-place-inside-the-emmc/
Hardware Unique Key, https://optee.readthedocs.io/en/latest/architecture/porting_guidelines.html#hardware-unique-key
Secure Storage Key, https://optee.readthedocs.io/en/latest/architecture/secure_storage.html#secure-storage-key-ssk
OTP, https://semiengineering.com/the-benefits-of-antifuse-otp/
SAES, https://wiki.st.com/stm32mpu/wiki/SAES_internal_peripheral
Hardware Unique Key overview, https://wiki.st.com/stm32mpu/wiki/Hardware_Unique_Key_overview)
HUK: Software implementation, https://wiki.st.com/stm32mpu/wiki/Hardware_Unique_Key_overview#HUK-_Software_implementation_2)
BSEC, https://wiki.st.com/stm32mpu/wiki/BSEC_internal_peripheral
Framework NVMEM, https://wiki.st.com/stm32mpu/wiki/NVMEM_overview
Fuse, https://wiki.st.com/stm32mpu/wiki/How_to_update_OTP_with_U-Boot#STM32MP_OTP
Stm32key, https://wiki.st.com/stm32mpu/wiki/How_to_use_U-Boot_stm32key_command
L'emplacement du secure storage, https://optee.readthedocs.io/en/latest/architecture/secure_storage.html#tee-file-structure-in-linux-file-system)
Trusted Application, https://optee.readthedocs.io/en/latest/architecture/trusted_applications.html
Exemples de TA, https://optee.readthedocs.io/en/latest/building/gits/optee_examples/optee_examples.html
PTA stats, https://github.com/OP-TEE/optee_test/blob/master/host/xtest/stats.c
Building Trusted Application, https://optee.readthedocs.io/en/latest/building/trusted_applications.html
Écriture de sa propre TA, https://kickstartembedded.com/2022/11/13/op-tee-part-4-writing-your-first-trusted-application/
Signing TAs, https://optee.readthedocs.io/en/latest/building/trusted_applications.html#signing-of-tas
Secure and system services, https://wiki.st.com/stm32mpu/wiki/STM32MPU_OP-TEE_profiles#OP-TEE_secure_services_profile
Open_session, https://github.com/OP-TEE/optee_os/blob/5ca2c36555d169a2be60964e2cbe39340c5245a4/core/pta/system.c#L355)
Optee_test, https://github.com/OP-TEE/optee_test/blob/40aacb6dc33bbf6ee329f40274bfe7bb438bbf53/ta/crypt/derive_key_taf.c
Optee-derive-unique-key, https://gitlab.com/PierreLoupG/optee-derive-unique-key
Meta-stm32mp15-demo-encrypt, https://gitlab.com/PierreLoupG/meta-stm32mp15-demo-encrypt
Meta-st-stm32mp, https://github.com/STMicroelectronics/meta-st-stm32mp
Provisionnement de la HUK, #provisionnement-de-la-huk
DHCOR STM32MP15, https://www.dh-electronics.com/en/embedded-products/dhsom/detail/dhcor-stm32mp15
Dh-stm32mp1-dhcor-custom.conf, dh-stm32mp1-dhcor-custom.conf
Sdcard-stm32mp157c-dhcor-custom.wks.in, sdcard-stm32mp157c-dhcor-custom.wks.in
OP-TEE configuration switches, https://wiki.st.com/stm32mpu/wiki/OP-TEE_configuration_switches#Generic_switches_for_secure_services
How to enable secure boot on STM32 MPU, https://wiki.st.com/stm32mpu/wiki/How_to_enable_secure_boot_on_STM32_MPU