URL:
https://linuxfr.org/news/utilisation-d-un-tpm-pour-l-authentification-ssh
Title: Utilisation d’un TPM pour l’authentification SSH
Authors: gouttegd
Benoît Sibaud et Ysabeau
Date: 2020-05-25T10:31:55+02:00
License: CC by-sa
Tags: tpm, ssh et sécurité
Score: 4
Après son [Bien démarrer avec GnuPG](
https://linuxfr.org/news/bien-demarrer-avec-gnupg), [gouttegd](
https://linuxfr.org/users/gouttegd) nous livre un nouveau journal sur l’utilisation du Trusted Platform Module (TPM) pour s’authentifier auprès d’un serveur SSH. Ce journal a été converti en dépêche et enrichi des premiers commentaires.
----
[Journal à l'origine de la dépêche](
https://linuxfr.org/users/gouttegd/journaux/utilisation-d-un-tpm-pour-l-authentification-ssh)
----
J’ai récemment fait l’acquisition d’un nouveau PC portable équipé entre autres choses d’un _Trusted Platform Module_ (TPM) — comme la plupart des PC portables de nos jours, puisqu’à ce qu’il me semble la présence d’un TPM est un pré-requis pour vendre un PC avec une version récente de Windows). Je n’ai pas encore étudié tout ce qu’il serait possible de faire avec ce TPM, mais dans ce journal je vais décrire comment l’utiliser pour s’authentifier auprès d’un serveur SSH.
## Support noyau
Vérifions d’abord à quel matériel j’ai affaire et s’il est bien reconnu par le noyau :
```sh
# dmesg | grep tpm
[ 16.880645] tpm_tis IFX0783:00: 2.0 TPM (device-id 0x1B, rev-id 22)
```
C’est donc une puce Infineon (`IFX`), gérée par le module `tpm_tis` (et les modules associés `tpm_tis_core`, `tpm_crb`, `tpm`).
Si vous êtes de celles et ceux qui compilent leur propre noyau, assurez-vous que toutes les options `CONFIG_TCG_*`, dans _Devices Drivers > Characters devices > TPM Hardware Support_, sont activées.
## La pile logicielle
Ensuite, il nous faut un ensemble de bibliothèques et d’outils en espace utilisateur, ce que le _Trusted Computing Group_ appelle la _pile logicielle TPM_ (_TPM Software Stack_ ou TSS). On aura pris soin de noter plus haut, dans la sortie de _dmesg_, que la puce TPM de ce portable est un TPM **2.0** ; cela signifie notamment qu’il est inutile d’essayer [TrouSerS](
http://trousers.sourceforge.net/), qui est une pile logicielle TPM pour GNU/Linux ne supportant que les TPM ≤ 1.2 — c’est dommage, car la plupart des documents disponibles sur le Net concernant l’utilisation d’un TPM sous GNU/Linux ne parlent que de TrouSerS.
À la place de TrouSerS, il y a deux piles logicielles TPM 2.0 disponibles : une provenant d’IBM ([IBM TPM 2.0 TSS](
https://sourceforge.net/projects/ibmtpm20tss)), et une provenant de la “[communauté logicielle tpm2](
https://tpm2-software.github.io/)” (principalement poussée par Intel). J’ai choisi cette dernière, qui semble fournir davantage d’outils pour l’intégration dans un système GNU/Linux — par exemple un moteur OpenSSL et surtout une interface PKCS#11, dont on reparlera plus bas.[^1]
### La pile de base
Installons donc le cœur de la pile Intel, la bibliothèque [tpm2-tss](
https://github.com/tpm2-software/tpm2-tss) et les outils associés. La bibliothèque est peut-être déjà disponible dans les dépôts de votre distribution (c’est le cas sous [Debian](
https://packages.debian.org/source/buster/tpm2-tss)), sinon, _use the source, Luke_ :
```sh
$ wget
https://github.com/tpm2-software/tpm2-tss/releases/download/2.4.1/tpm2-tss-2.4.1.tar.gz
$ tar xf tpm2-tss-2.4.1.tar.gz
$ cd tpm2-tss-2.4.1
$ ./configure --with-udevrulesdir=/lib/udev/rules.d \
--with-udevrulesprefix=50- \
--localstatedir=/var \
--sysconfdir=/etc \
--disable-weakcrypto
$ make
# make install-strip
```
> Dans le reste de ce journal, nous installerons systématiquement tout à partir des sources. Il vous appartiendra de vérifier préalablement si des paquets sont disponibles pour votre distribution de prédilection et de privilégier cette voie le cas échéant. À l’heure où ces lignes sont écrites, Debian Buster par exemple fournit _tpm2-tss_, _tpm2-tools_, et _tpm2-abrmd_, mais _tpm2-pkcs11_ n’est disponible que dans Sid et _tpm2-tss-engine_ n’est disponible dans aucune version.
>
> Pour les utilisateurs de Slackware, je fournis des SlackBuilds pour tous les projets mentionnés ici : [tpm2-tss](
https://git.incenp.org/damien/slackbuilds/src/branch/current-20191101/l/tpm2-tss), [tpm2-tools](
https://git.incenp.org/damien/slackbuilds/src/branch/current-20191101/ap/tpm2-tools), [tpm2-abrmd](
https://git.incenp.org/damien/slackbuilds/src/branch/current-20191101/l/tpm2-abrmd), [tpm2-pkcs11](
https://git.incenp.org/damien/slackbuilds/src/branch/current-20191101/l/tpm2-pkcs11), et [tpm2-tss-engine](
https://git.incenp.org/damien/slackbuilds/src/branch/current-20191101/l/tpm2-tss-engine).
Installons ensuite [les outils](
https://github.com/tpm2-software/tpm2-tools) permettant de manipuler le TPM. Pour ce qu’on veut faire, on ne les utilisera pas directement, mais l’interface PKCS#11 que nous installerons plus en aura besoin.
```sh
$ wget
https://github.com/tpm2-software/tpm2-tools/releases/download/4.2/tpm2-tools-4.2.tar.gz
$ tar xf tpm2-tools-4.2.tar.gz
$ cd tpm2-tools-4.2
$ ./configure
$ make
# make install-strip
```
Il faut ensuite décider de la méthode d’accès au TPM pour les utilisateurs de la machine, sachant qu’il y a trois options :
* Laisser les utilisateurs accéder au périphérique `/dev/tpm0` directement ; ce n’est pas recommandé par les développeurs et je confirme en effet que sur ma machine, ça ne marche pas quand on veut utiliser l’interface PKCS#11 comme on le fera plus loin.
* Laisser les utilisateurs accéder au périphérique `/dev/tpmrm0` directement ; il s’agit du _gestionnaire de ressources_ (_resources manager_) du TPM fourni par le noyau.
* Utiliser un gestionnaire de ressources en espace utilisateur, le _TPM Access Broker and Resource Manager daemon_ (TABRMD) ; seul le démon utilisera le TPM directement, les autres programmes interagiront avec le démon. C’est l’approche recommandée.
Nous utiliserons le démon, donc installons-le [projet tpm2-abrmd](
https://github.com/tpm2-software/tpm2-abrmd).
```sh
$ wget
https://github.com/tpm2-software/tpm2-abrmd/releases/download/2.3.2/tpm2-abrmd-2.3.2.tar.gz
$ tar xf tpm2-abrmd-2.3.2.tar.gz
$ cd tpm2-abrmd-2.3.2
$ ./configure --with-systemdsystemunitdir=/usr/lib/systemd/system \
--with-systemdpresetdir=/usr/lib/systemd/system-preset \
--sysconfdir=/etc \
--localstatedir=/var
$ make
# make install-strip
```
Le démon a besoin d’un compte utilisateur dédié avec un accès en lecture/écriture au périphérique `/dev/tpm0`. Si vous l’avez installé depuis les dépôts de votre distribution, l’auteur du paquet a probablement déjà fait le nécessaire et vous pouvez sauter le reste de cette section ; sinon, lisez ce qui suit.
Le projet _tpm2-tss_ est fourni avec une règle Udev donnant accès au TPM à un compte utilisateur appelé *tss* ; assurez-vous qu’un tel compte existe, donnez-lui les droits sur les dossiers dont aura besoin le démon (ces dossiers sont normalement créés lors du `make install` de _tpm2-tss_) puis rechargez et appliquez les règles Udev :
```sh
# groupadd --system tss
# useradd --system --comment "TPM2 Software Stack" --home-dir /var/lib/tpm2-tss --gid tss tss
# chown -R tss:tss /var/lib/tpm2-tss /var/run/tpm2-tss
# chmod 755 /var/lib/tpm2-tss /var/run/tpm2-tss
# udevadm control --reload-rules
# udevadm trigger
```
Le projet _tpm2-abrmd_ est fourni avec une « unité » systemd ; si votre distribution utilise systemd, vous ne devriez pas avoir besoin de faire quoi que ce soit (à part peut-être un `systemctl daemon-reload` pour être sûr que la nouvelle unité est prise en compte). Dans le cas contraire, il vous appartient d’élaborer un script de démarrage qui convienne au système d’initialisation de votre distribution, quel qu’il soit. Prenez garde que _tabrmd_ fait partie de cette nouvelle génération de démons qui ne connaissent rien d’autre que systemd : le démon considère comme acquis qu’il est géré par systemd et du coup ne prend pas la peine d’écrire son PID quelque part ou même de se détacher du terminal, votre script de démarrage devra se charger de ça.
### L’interface PKCS#11
L’interface PKCS#11, fournie par le projet [tpm2-pkcs11](
https://github.com/tpm2-software/tpm2-pkcs11), est une couche qui permet d’utiliser le TPM comme si c’était un jeton cryptographique compatible avec la spécification [PKCS#11](
http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html). Elle rend le TPM utilisable par n’importe quel programme capable d’interagir avec un tel jeton, comme OpenSSH par exemple.
```sh
$ wget
https://github.com/tpm2-software/tpm2-pkcs11/releases/download/1.0/tpm2-pkcs11-1.2.0.tar.gz
$ tar xf tpm2-pkcs11-1.2.0.tar.xf
$ cd tpm2-pkcs11-1.2.0
$ ./configure
$ make
# make install-strip
```
### L’interface OpenSSL
Dernier composant de la pile Intel que nous installerons, le projet [tpm2-tss-engine](
https://github.com/tpm2-software/tpm2-tss-engine) fournit un « moteur » OpenSSL, exposant les fonctions cryptographiques du TPM à travers OpenSSL. Elle n’est pas nécessaire pour SSH, mais je l’installe quand même puisqu’au-delà de SSH, le but de tout ceci est d’explorer ce que je peux faire de mon TPM.
```sh
$ wget
https://github.com/tpm2-software/tpm2-tss-engine/releases/download/v1.0.1/tpm2-tss-engine-1.0.1.tar.gz
$ tar xf tpm2-tss-engine-1.0.1.tar.gz
$ cd tpm2-tss-engine-1.0.1
$ ./configure --sysconfdir=/etc
$ make
# make install-strip
```
### Configurer l’accès au TPM pour les différents composants
Comme nous avons décidé plus haut d’accéder au TPM par l’intermédiaire du gestionnaire de ressources en espace utilisateur (TABRMD), nous devons définir les variables d’environnement suivantes :
```sh
export TPM2TOOLS_TCTI=tabrmd:
export TPM2_PKCS11_TCTI=tabrmd:
export TPM2TSSENGINE_TCTI=tabrmd:
```
Cela indiquera respectivement à _tpm2-tools_, _tpm2-pkcs11_, et _tpm2-tss-engine_ de ne pas chercher à accéder au TPM directement mais de passer par le gestionnaire de ressources (TCTI signifie _TPM Control Transmission Interface_) ; sans cela, les différents outils ne contacteraient le gestionnaire de ressources qu’après avoir bruyamment échoué à ouvrir le périphérique `/dev/tpm0` eux-mêmes. Ces variables ne sont pas _strictement_ nécessaires, mais les définir permet d’éviter de polluer la console avec des messages d’erreur inutiles.
> Pour information, la syntaxe de la valeur attendue par toutes ces variables est _méthode:paramètres_, où _méthode_ peut être :
>
> * `device`, pour accéder au TPM directement ; dans ce cas _paramètres_ indique le chemin d’accès au périphérique (exemple : `device:/dev/tpm0`) ;
> * `tabrmd`, pour accéder au TPM via le gestionnaire de ressources ; _paramètres_ peut alors contenir l’adresse IP et le numéro de port où ledit gestionnaire attend ses clients (exemple : `tabrmd:host=127.0.0.1,port=555`), ou bien rester vide pour utiliser les valeurs par défaut ;
> * `mssim`, pour accéder à un émulateur logiciel de TPM comme [ibmswtpm2](
http://ibmswtpm.sourceforge.net/ibmswtpm2.html) (pratique pour apprendre à utiliser un TPM ou développer et tester des logiciels faisant usage d’un TPM) ; _paramètres_ peut alors contenir l’adresse IP et le numéro de port de l’émulateur, comme pour la méthode `tabrmd`.
## Utilisation avec SSH
### Créer un jeton PKCS#11
OpenSSH est nativement capable d’utiliser un jeton PKCS#11, nous utiliserons donc l’interface _tpm2-pkcs11_ installée plus haut. Il nous faut pour ça commencer par créer le dossier où _tpm2-pkcs11_ stockera la clef et les autres données dont il a besoin :
```sh
$ mkdir ~./tpm2_pkcs11
$ tpm2_ptool init
action: Created
id: 1
```
En effet, et de manière quelque peu contre-intuitive, la clef privée que nous allons créer ne sera _pas_ stockée dans le TPM. À la place, la clef sera stockée dans un fichier SQLite dans le dossier `.tpm2_pkcs11`, chiffrée de telle sorte qu’elle ne peut être déchiffrée _que_ par le TPM. Chaque fois que la clef privée sera requise (par exemple pour s’authentifier auprès d’un serveur SSH), la bibliothèque _tpm2-pkcs11_ enverra la clef chiffrée au TPM en même temps que les données à signer ; le TPM déchiffrera la clef et l’utilisera immédiatement pour signer les données, puis renverra à _tpm2-pkcs11_ le résultat de l’opération de signature.
> Ce mode de fonctionnement a été conçu ainsi pour permettre d’utiliser un nombre illimité de clefs différentes avec un TPM dont la capacité de stockage est très réduite.
Nous pouvons maintenant créer un (pseudo) jeton PKCS#11 :
```sh
$ tpm2_ptool addtoken --pid=1 --label=mylabel --sopin=XXXX --userpin=YYYY
```
La valeur de `--pid` doit correspondre à l’identifiant de l’objet primaire renvoyé par la commande `init` un peu plus haut (`id: 1`). Le `label` peut être choisi librement, mais doit être unique parmi tous les jetons que vous créez (si jamais vous en créez plusieurs). Enfin, `sopin` et `userpin` sont respectivement le PIN administrateur du jeton (_Security Officer PIN_) et le PIN utilisateur.
Maintenant que nous avons un jeton, on peut y créer une clef, par exemple une clef ECC basée sur la courbe NIST P-256 :
```sh
$ tpm2_ptool addkey --label=mylabel --userpin=YYYY --algorithm=ecc256
```
### Utiliser le jeton avec SSH
Il nous faut obtenir la partie publique de la clef que nous venons de créer, afin d’autoriser cette clef sur le serveur SSH auquel on veut se connecter. On utilise pour ça la commande _ssh-keygen_ standard de OpenSSH, en lui indiquant d’obtenir la clef depuis notre jeton PKCS#11:
```sh
$ ssh-keygen -D /usr/local/lib/pkcs11/libtpm2_pkscs11.so > key.pub
```
> Ajustez le chemin d’accès au module `libtpm2_pkcs11.so` en fonction de votre système et de la manière dont vous avez installé le projet _tpm2-pkcs11_.
Ajoutez le contenu du fichier `key.pub` au fichier `~/.ssh/authorized_key` de votre compte utilisateur sur le serveur SSH. Vous pouvez maintenant tenter de vous connecter à ce serveur en utilisant le jeton :
```sh
$ ssh -I /usr/local/lib/pkcs11/libtpm2-pkcs11.so myserver.example.org
Enter PIN for 'myabel': YYYY
```
Pour éviter d’avoir à spécifier le chemin vers le module `libtpm2-pkcs11.so` à chaque appel, vous pouvez utiliser l’option `PKCS11Provider` dans le fichier de configuration de SSH (globalement ou pour un hôte donné). Si vous utilisez en parallèle d’autres clefs SSH que vous chargez normalement dans un agent SSH, vous devez aussi désactiver l’utilisation de l’agent pour les hôtes pour lesquels vous comptez utiliser le jeton TPM (sinon SSH contactera systématiquement l’agent sans jamais essayer le jeton):
```
Host myserver.example.org
PKCS11Provider /usr/local/lib/pkcs11/libtpm2-pkcs11.so
IdentityAgent none
```
## Utilisation avec OpenSSL
Maintenant qu’on peut utiliser le TPM pour SSH, ce qui était l’objectif premier, voyons rapidement comment on peut aussi l’utiliser avec OpenSSL.
Confirmons d’abord que l’interface OpenSSL (_tpm2-tss-engine_) a été installée correctement et profitons-en pour voir les fonctions du TPM que cette interface rend disponible, en demandant à OpenSSL les capacités du moteur `tpm2tss` :
```sh
$ openssl engine -c tpm2tss
(tpm2tss) TPM2-TSS engine for OpenSSL
[RSA, RAND]
```
Le moteur est donc effectivement bien installé, et donne accès aux fonctions relatives à RSA ainsi qu’au générateur de nombres aléatoires.
> J’ignore pourquoi les fonctions ECDSA ne sont pas disponibles, alors que mon TPM les prend en charge (et que _tpm2-pkcs11_ les utilise sans problèmes). Il s’agit sans doute d’une limitation, voire d’un bug, de _tpm2-tss-engine_.
On pourra donc obtenir du TPM, par exemple, 20 octets aléatoires (encodés en base64) via la commande suivante :
```sh
$ openssl rand -engine tpm2tss -base64 20
engine "tpm2tss" set.
cO1eoOMocHPlvUxFB9tTDzOwZyE=
```
Générons une clef RSA à présent :
```sh
$ tpm2tss-genkey -a rsa -s 2048 -p XXXXXXXX tpmkey.pem
```
La commande produit un fichier `tpmkey.pem` contenant la clef générée. Elle est sous forme chiffrée et ne peut être déchiffrée et utilisée que par le TPM ; _XXXXXXXX_ est le mot de passe à présenter au TPM pour le déchiffrement.
L’utilisation de la clef passe par les commandes OpenSSL classiques, auxquelles on précise d’utiliser le moteur `tpm2tss`. Par exemple, pour chiffrer puis déchiffrer un fichier, on commence par extraire la partie publique de la clef :
```sh
$ openssl rsa -engine tpm2tss -inform engine -in tpmkey.pem -pubout -outform pem -out publickey.pem
engine "tpm2tss" set.
Enter password for user key: XXXXXXXX
writing RSA key
```
Le moteur `tpm2tss` envoie la clef chiffrée contenue dans le fichier `tpmkey.pem` vers le TPM, celui-ci la déchiffre (si l’utilisateur fournit le bon mot de passe) et renvoie à OpenSSL la partie publique. Utilisons cette dernière pour chiffrer un fichier (le TPM n’est pas impliqué ici, le chiffrement ne faisant appel qu’à la clef publique) :
```sh
$ openssl pkeyutl -pubin -inkey publickey.pem -in cleartext.txt -encrypt -out ciphertext.dat
```
Pour déchiffrer, on utilise à nouveau le moteur `tpm2tss` :
```sh
$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey tpmkey.pem -decrypt -in ciphertext.dat -out deciphered.txt
engine "tpm2tss" set.
Enter password for user key: XXXXXXXX
```
Le moteur envoie au TPM à la fois la clef chiffrée `tpmkey.pem` et le fichier à déchiffrer `ciphertext.dat`. Le TPM commence par déchiffrer la clef elle-même (encore une fois sous réserve que l’utilisateur saississe le bon mot de passe) puis l’utilise pour déchiffrer le fichier et renvoie le texte clair correspondant à OpenSSL.
## TPM et génération pseudo-aléatoire
(ajouté tiré d’un [fil de discussion dans le journal](
https://linuxfr.org/users/gouttegd/journaux/utilisation-d-un-tpm-pour-l-authentification-ssh#comment-1812030))
Le TPM est une des sources d’entropie possibles pour alimenter le périphérique /dev/hwrng, comme on peut le voir dans /sys/class/misc/hw_random :
```
# cat /sys/class/misc/hw_random/rng_available
tpm-rng-0 via
```
Et voici une machine sur laquelle c’est la source actuellement utilisée :
```
# cat /sys/class/misc/hw_random/rng_current
tpm-rng-0
```
Donc lire depuis /dev/hwrng va directement taper dans le TPM, sans avoir besoin de passer par OpenSSL.
Si on veut utiliser cette source pour alimenter le pool d’entropie du noyau (celui qui est derrière les périphériques /dev/(u)random ou l’appel getrandom(2)), on peut utiliser des outils comme rngd(8).
[^1]: Une autre raison pour ne pas s’attarder sur le code d’IBM est que le fait que ledit code est fourni sous la forme d’une _tar bomb_… C’est con, mais je n’ai aucune envie d’utiliser un code écrit par quelqu’un qui ne sait pas utiliser _tar_ correctement.