An open-source PKCS#11 v3.2 cryptographic module under Simorgh Labs
Non certifié. FreeHSM est construit selon les exigences FIPS 140-3 niveau 1 et Critères Communs EAL4+, et documenté selon leurs méthodologies. Un certificat coûte plus que ce projet n’aura jamais, et ce coût est précisément la barrière qui écarte les organismes publics, les universités et les pays en développement d’une cryptographie auditable. Ce qu’un certificat atteste, la discipline peut le rendre vérifiable — par quiconque, gratuitement.
Ce document est un livrable d’évaluation, rédigé comme la méthodologie l’exige, comme si un certificat existait. Lire « certifié » partout comme « la configuration selon laquelle ce module est construit ». Il est publié comme exemple travaillé, non dans le cadre d’une soumission.
TOE : FreeHSM Cryptographic Module (la version que vous avez construite ; v1.0.0-FIPS, dans les brouillons antérieurs, était un espace réservé et n’a jamais existé comme release)
Audience : opérateurs Security Officer (SO) et User d’une TOE installée
Pré-requis : la TOE a été installée et amenée à l’état opérationnel sécurisé per AGD_PRE.fr.md
| Rôle | Identifiant | Services autorisés |
|---|---|---|
| Security Officer (CO) | CKU_SO |
C_InitToken, C_InitPIN, C_SetPIN (propre), revue d’audit |
| User | CKU_USER |
C_GenerateKey, C_GenerateKeyPair, C_Encrypt/Decrypt, C_Sign/Verify, C_Digest, C_DeriveKey, C_Wrap/Unwrap, C_SetPIN (propre) |
Services anonymes (pré-login) :
| Service | But |
|---|---|
C_Initialize |
Bring-up du module (1×/processus) |
C_GetInfo |
Identité du module |
C_GetSlotList |
Énumérer les slots |
C_GetSlotInfo |
État du slot |
C_GetTokenInfo |
État du token (compteurs PIN inclus) |
C_OpenSession |
Ouvrir session pour login |
C_CloseSession |
Fermer une session |
C_GetMechanismList |
Lister les CKM_* dispatchables |
C_GetMechanismInfo |
Flags et plage de clés par mécanisme |
#include <pkcs11.h>
CK_FUNCTION_LIST_PTR p11;
C_GetFunctionList(&p11); /* chargé depuis libfreehsm.so */
p11->C_Initialize(NULL); /* déclenche integrity check + KAT */
CK_SESSION_HANDLE s;
p11->C_OpenSession(slot, CKF_RW_SESSION|CKF_SERIAL_SESSION,
NULL, NULL, &s);
p11->C_Login(s, CKU_USER, (CK_UTF8CHAR_PTR)pin, strlen(pin));
/* ... appeler C_GenerateKey, C_Encrypt, etc. ... */
p11->C_Logout(s); /* zéroïse la DEK dans cette session */
p11->C_CloseSession(s);
p11->C_Finalize(NULL);
Après C_Finalize, le processus ne doit appeler aucun autre C_* avant un nouveau C_Initialize. Le faire retourne CKR_CRYPTOKI_NOT_INITIALIZED (0x190).
| Cas d’usage | Mécanisme recommandé |
|---|---|
| Chiffrement symétrique au repos | CKM_AES_GCM (IV 96 bits, tag 128 bits) — pas CKM_AES_CBC_PAD, cf. §3.1 |
| Chiffrement symétrique en transit | CKM_AES_GCM ou CKM_AES_CCM |
| Authentification symétrique | CKM_SHA256_HMAC ou CKM_AES_CMAC |
| MAC streaming (gros messages) | CKM_KMAC128 / CKM_KMAC256 |
| Signature asymétrique (classique) | CKM_SHA384_RSA_PKCS_PSS ou CKM_ECDSA_SHA384 |
| Signature asymétrique (PQ) | CKM_ML_DSA (jeu paramètre ML-DSA-65) |
| Signature asymétrique (PQ, pré-hachage) | CKM_HASH_ML_DSA_<hash>, ou CKM_HASH_ML_DSA quand l’appelant hache |
| Signature asymétrique (hybride) | Retiré. CKM_HYBRID_ED25519_ML_DSA_65 a été désannoncé le 2026-09-24 (#17) : les deux composants signaient le message nu et les résultats étaient concaténés, de sorte que la moitié Ed25519 s’extrait comme signature autonome valide — le défaut de non-séparabilité que draft-ietf-lamps-pq-composite-sigs §2.2 existe pour empêcher. CKM_COMPOSITE_MLDSA65_ED25519 le remplace, dans le profil all-mechanisms uniquement. |
| Encapsulation de clé (classique) | CKM_ECDH1_DERIVE sur P-256/384 ; X25519/X448 par le même mécanisme avec une clé CKK_EC_MONTGOMERY, dans le profil all-mechanisms avec FHSM_INTEGRITY_ALLOW_UNSIGNED=1 uniquement (le fournisseur FIPS d’OpenSSL que charge un module signé n’a aucune des deux courbes). PKCS#11 ne définit pas de CKM_X25519_DERIVE. |
| Encapsulation de clé (PQ) | CKM_ML_KEM (jeu paramètre ML-KEM-768) |
| Encapsulation de clé (hybride) | Retiré. CKM_HYBRID_X25519_ML_KEM_768 a été désannoncé le 2026-09-24 (#17) ; aucun organisme de normalisation ne spécifie cette construction. Utilisez CKM_ML_KEM et un échange classique séparément si vous avez besoin des deux. |
| Dérivation KEK par mot de passe | CKM_PKCS5_PBKD2 avec ≥ 200 000 itér. |
| Dérivation de clé protocole | CKM_HKDF_DERIVE (SHA-256+) |
Tout mécanisme hors liste approuvée (CKM_MD5, CKM_DES3_CBC, CKM_RSA_PKCS…) est rejeté au dispatch et retourne FHSM_RV_FIPS_NOT_APPROVED (0x80000003).
Deux mécanismes de la liste approuvée portent une réserve que l’approbation FIPS n’exprime pas, parce que l’approbation porte sur l’algorithme et la réserve porte sur le protocole que vous construisez avec. Les deux sont utilisables et aucun n’est un défaut du module ; le choix vous revient.
CKM_AES_CBC_PAD est un oracle de padding. Le déchiffrement indique à
l’appelant si le padding PKCS#7 était bien formé — CKR_OK quand oui,
CKR_ENCRYPTED_DATA_INVALID quand non. Ce seul bit, répété sur des chiffrés
choisis, permet de reconstituer le clair octet par octet (Vaudenay 2002 ;
POODLE, CVE-2014-3566). C’est inhérent : CBC-PAD ne porte aucun tag
d’authentification, donc C_Decrypt ne peut pas distinguer « corrompu » de
« pas pour vous » sans dire lequel, et renvoyer du clair aléatoire à la place
serait pire pour tout appelant honnête.
Mesuré sur ce module : 99,6 % des chiffrés corrompus sont refusés, et les 0,4 % qui déchiffrent sont ceux dont le dernier bloc aléatoire porte par hasard un padding valide — c’est le taux théorique de toute implémentation correcte. Le module se comporte comme spécifié. L’exposition est dans le protocole.
Utilisez
CKM_AES_GCMouCKM_AES_KEY_WRAPpartout où un attaquant peut soumettre des chiffrés de son choix et observer si le déchiffrement a réussi.CKM_AES_CBC_PADconvient aux données que vous déchiffrez pour vous-même — un fichier au repos, une sauvegarde — sans adversaire aux commandes du déchiffrement.
CKM_AES_CBC et CKM_AES_CTR n’offrent aucune intégrité. Pas d’oracle,
puisqu’il n’y a pas de padding à vérifier, mais aucune détection d’altération
non plus : sous CTR, un bit retourné dans le chiffré retourne le bit
correspondant du clair, silencieusement. Associez-les à un CKM_SHA256_HMAC sur
le chiffré (encrypt-then-MAC, RFC 7366), ou prenez CKM_AES_GCM qui fait les
deux en une passe.
Voir R2 dans docs/PKCS11_CHECK_FINDINGS.md pour la mesure, et
tests/test_cbc_pad_oracle.c pour le garde-fou de non-régression.
p11-kit server, moins de mécanismes atteignent le moduleUne application qui atteint le token par p11-kit server et
p11-kit-client.so ne voit que les mécanismes que la couche RPC de p11-kit
sait transporter. Le module n’y est pour rien : un appel que p11-kit ne
transporte pas est refusé avec CKR_MECHANISM_INVALID avant de quitter le
client, et le log d’audit n’enregistre rien.
Ce qui passe dépend de la version de p11-kit (vérifié le 2026-09-30) :
| p11-kit | mécanismes post-quantiques transportés |
|---|---|
| 0.26.5 et antérieures (toutes les versions publiées à ce jour) | aucun |
master depuis le 2026-09-08 (PR #745, commit 63c7bc4f) |
CKM_ML_KEM, CKM_ML_DSA, CKM_SLH_DSA et leurs générateurs de paires de clés, HashML-DSA et HashSLH-DSA |
| toute version | pas CKM_COMPOSITE_MLDSA65_ED25519 |
Jusqu’à une version de p11-kit postérieure à 0.26.5, l’accès distant est donc réservé à RSA, ECDSA, AES et HMAC. Le composite reste exclu même ensuite : son code point est défini par le fournisseur, et p11-kit transporte les mécanismes qu’il nomme. Une modification qui transporte tout mécanisme pris sans paramètre, composite compris, est proposée en amont comme PR #779 de p11-kit et n’est pas fusionnée. L’issue #778, qui signalait la limite, a été close comme résolue le 2026-09-18 par #745.
Vérifiez ce que transporte le p11-kit installé plutôt que de vous fier à ce
tableau ; docs/P11_KIT_REMOTING.md contient la mesure et la mise en place.
C_InitPIN. Le PIN SO n’est déverrouillé que par C_InitToken, ce qui détruit tous les objets du slot.Entre tentatives, C_Login peut retourner FHSM_RV_PIN_THROTTLED (0x80000004). Le client DEVRAIT :
Marteler à travers le throttle ne change pas le résultat ; le cooldown survit au redémarrage du processus.
Ne placez pas le log d’audit sur tmpfs, et vérifiez au lieu de supposer.
La garantie du log est qu’une ligne est durable avant que l’opération qu’elle
enregistre ne retourne — voir docs/AUDIT_DURABILITY.md. Sur un système de
fichiers volatile, fdatasync retourne immédiatement sans barrière, donc la
garantie est silencieusement absente : rien n’échoue, rien n’est journalisé à
ce sujet, et le log paraît parfaitement normal jusqu’à ce qu’une coupure de
courant en emporte la fin.
C’est facile à faire par accident. /tmp est en tmpfs par défaut sur Debian
actuel, et FHSM_AUDIT_LOG peut pointer le log ailleurs que dans le répertoire
des tokens. Le symptôme est un log d’une rapidité invraisemblable :
make tests/bench_fsync_floor
./tests/bench_fsync_floor /var/lib/freehsm # là où le log vivra
C’est le contrôle le plus court et il n’implique pas le module : un append, un
fdatasync, répétés. Il nomme le système de fichiers mesuré, imprime la
distribution, et sort non nul si la barrière a retourné trop vite pour avoir
atteint un stockage stable. Utilisez son code de sortie dans un script de
provisionnement ; un message qu’un opérateur doit lire est un message qu’un
opérateur peut manquer.
filesystem ext4 on /dev/sdc (/sessions)
min 2.058 ms
p50 2.553 ms
...
contre, sur tmpfs :
NOT A DURABILITY MEASUREMENT.
A barrier that returns in 0.000 ms did not reach stable storage -- this is
tmpfs, where fdatasync is a no-op.
Une barrière durable coûte des millisecondes sur le stockage mesuré jusqu’ici.
Des microsecondes signifient qu’aucune barrière n’a eu lieu. tests/bench_audit_rate
mesure la même chose à travers le module si vous voulez aussi le coût propre du
log, mais l’outil de plancher est celui à lancer en premier : il n’échoue que
pour une seule raison.
La même prudence vaut pour un hyperviseur qui acquitte les barrières sans les honorer ; le cache d’E/S hôte de VirtualBox fait exactement cela, et aucun outil ici ne peut voir au travers. Un hyperviseur qui ment produit un chiffre en millisecondes plausible et aucun avertissement d’aucune sorte. Si la garantie compte, le stockage doit être digne de confiance à un niveau inférieur à ce module.
Chaque ouverture du module crée son propre log, audit.log.NNNNNN, parce qu’une
chaîne de hachage a exactement un auteur : deux processus partageant un fichier
reprenaient chacun la chaîne depuis sa queue et se croyaient chacun le successeur
de la même ligne, ce qui la détruisait. Deux outils ordinaires lancés en même
temps y suffisaient.
Pour vous, cela signifie :
freehsm-audit verify /var/lib/freehsm/audit <audit_key_hex>
Un répertoire vérifie chaque log numéroté qu’il contient, et signale les trous dans la numérotation. Un trou signifie qu’un fichier a été retiré : chaque chaîne restante se vérifie encore isolément, donc la numérotation est la seule chose qui montre la suppression. Des numéros manquants à la fin ne laissent pas de trou — c’est la même troncature de fin de log décrite en §4.3, pas une faiblesse nouvelle.
Deux conséquences à anticiper :
seq fait foi ; entre fichiers vous fusionnez sur
ts, qui est une horloge murale et peut être déplacée par quiconque peut
régler l’horloge. Tenez-en compte en reconstruisant un incident à travers un
redémarrage.Le journal d’audit est produit, et la chaîne est vérifiable. L’avertissement qui figurait ici — aucun journal écrit, contrôle sur lequel il ne fallait pas compter — ne s’applique plus.
C_Initializeouvre le journal, la chaîne survit aux redémarrages, une écriture ratée verrouille le module enERREUR, et les deux vérificateurs détectent une entrée modifiée, supprimée, insérée ou réordonnée.Trois limites subsistent, à connaître avant de s’appuyer sur ce contrôle :
- Un journal tronqué à la fin n’est pas détecté, et ne peut pas l’être depuis le seul fichier : ce qui reste est une chaîne plus courte qui se vérifie parfaitement, indistinguable d’un journal qui se serait arrêté là. Atténuation : l’archivage (étape 3) et la comparaison du nombre d’entrées entre deux archives — un
seqqui recule est le signal.- Un échec d’intégrité ou de KAT au démarrage n’est pas journalisé. Le journal s’ouvre après la couche cryptographique, parce que chaîner une entrée exige HMAC. Si l’auto-test échoue, les primitives qui authentifieraient l’entrée sont celles qui viennent d’échouer. Le module verrouille en
ERREURet refuse tout, donc la condition reste observable — mais la raison ne sera pas dans le journal. Lire la sortie d’erreur du processus et les drapeaux deC_GetTokenInfodans ce cas.- La clé de chaînage ne protège pas d’un root actif. Scellée dans le TPM, elle résiste à qui emporte le disque ou modifie la chaîne de démarrage ; elle est descellée en mémoire du processus pour servir. Voir §4.4.
Le journal vit dans {tokens_dir}/audit.log, ou là où pointe
FHSM_AUDIT_LOG. Le SO DOIT :
verify est une sous-commande et la clé d’audit de 32 octets
est un argument obligatoire — le binaire s’appelle freehsm-audit, et
freehsm-audit-verify n’existe pas.
freehsm-audit verify /var/lib/freehsm/audit/slot0.audit.log <audit_key_hex_64>
login_fail, login_locked, login_throttled, integrity_fail.Une chaîne brisée est un événement de sécurité critique : le journal sur
disque a été altéré par quelqu’un ayant accès en écriture au répertoire
d’audit. Le vérificateur nomme la première ligne fautive et ce qu’il y a
trouvé — entrée modifiée, prev_hmac qui ne suit pas, ou seq décalé.
Prendre le système hors ligne et suivre la procédure d’incident (SECURITY.md).
La chaîne est authentifiée par une clé de 32 octets, provisionnée au premier
démarrage puis récupérée. Elle n’est pas dérivée de la DEK du token : le
journal ne serait alors écrivable qu’une fois connecté, et login_fail,
login_locked et integrity_fail — les trois événements que l’étape 2
demande d’investiguer — ne seraient jamais enregistrés.
| Emplacement | Quand |
|---|---|
{tokens_dir}/audit.key.tpm, scellée |
FHSM_TPM_SEALING=1 et TPM présent |
{tokens_dir}/audit.key, mode 0600 |
sinon |
Le module refuse de démarrer plutôt que de se dégrader en silence :
Vérifier un journal exige cette clé. La lire avec xxd -p -c 32
{tokens_dir}/audit.key sur l’hôte, ou l’y desceller. Un journal archivé sans
sa clé ne pourra pas être vérifié plus tard — archiver les deux séparément,
et jamais sur le même support.
Les fichiers token sont chiffrés au repos sous PIN(s). Backups sûres seulement si :
cp --preserve=mode,ownership,timestamps (ou rsync -a).p11->C_Logout(s);
p11->C_CloseSession(s);
C_Logout zéroïse la DEK en mémoire. C_CloseSession zéroïse les buffers de clés locaux à la session.
Depuis la v2.3.0, le module applique les règles de login de PKCS#11 §5.6. Le SO et le USER ne peuvent pas tenir le token en même temps : provisionner un token – C_Login en SO, C_InitPIN, puis travailler en USER – exige un C_Logout entre les deux logins, sinon C_Login répond CKR_USER_ANOTHER_ALREADY_LOGGED_IN. Avant la v2.3.0, le second login prenait le token sans rien dire. Un login SO est refusé tant qu’une session en lecture seule est ouverte (CKR_SESSION_READ_ONLY_EXISTS), et une session en lecture seule ne peut pas s’ouvrir pendant que le SO est connecté (CKR_SESSION_READ_WRITE_SO_EXISTS). Fermer la dernière session déconnecte le token, et le log d’audit l’enregistre comme un logout.
C_GenerateKey (AES-256)CK_MECHANISM mech = { CKM_AES_KEY_GEN, NULL, 0 };
CK_OBJECT_HANDLE key = 0;
CK_ULONG keylen = 32;
CK_BBOOL true_ = CK_TRUE;
CK_ATTRIBUTE templ[] = {
{ CKA_VALUE_LEN, &keylen, sizeof(keylen) },
{ CKA_TOKEN, &true_, sizeof(true_) },
{ CKA_ENCRYPT, &true_, sizeof(true_) },
{ CKA_DECRYPT, &true_, sizeof(true_) },
};
p11->C_GenerateKey(s, &mech, templ, 4, &key);
C_Encrypt (AES-GCM)CK_GCM_PARAMS gcm = {
.pIv = iv, .ulIvLen = 12,
.pAAD = aad, .ulAADLen = sizeof(aad),
.ulTagBits = 128,
};
CK_MECHANISM mech = { CKM_AES_GCM, &gcm, sizeof(gcm) };
p11->C_EncryptInit(s, &mech, key);
p11->C_Encrypt(s, plaintext, plen, ciphertext, &clen);
clen en entrée est la capacité du buffer de sortie ; en sortie, c’est la longueur réelle du chiffré (longueur du clair + 16 pour le tag). Pour un traitement multipartes, utilisez C_EncryptUpdate/C_EncryptFinal.
C_Sign (ECDSA-P-384 + SHA-384)CK_MECHANISM mech = { CKM_ECDSA_SHA384, NULL, 0 };
p11->C_SignInit(s, &mech, priv_key);
p11->C_Sign(s, msg, msg_len, sig, &sig_len);
CK_RV (hex) |
Symbole | Action opérateur |
|---|---|---|
0x00 |
CKR_OK |
continuer |
0x05 |
CKR_GENERAL_ERROR |
vérifier syslog ; si récurrent, contacter vendor |
0x06 |
CKR_FUNCTION_FAILED |
réessayer une fois |
0x60 |
CKR_KEY_HANDLE_INVALID |
handle expiré ; relancer la séquence |
0x70 |
CKR_MECHANISM_INVALID |
pas dans C_GetMechanismList ; choisir mécanisme approuvé |
0xA0 |
CKR_PIN_INCORRECT |
retry avec bon PIN ; attention au throttle |
0xA4 |
CKR_PIN_LOCKED |
appeler le SO pour déverrouiller via C_InitPIN |
0xB3 |
CKR_SESSION_HANDLE_INVALID |
ouvrir une nouvelle session |
0x101 |
CKR_USER_NOT_LOGGED_IN |
C_Login d’abord |
0x190 |
CKR_CRYPTOKI_NOT_INITIALIZED |
C_Initialize d’abord ; ne PAS appeler après C_Finalize |
0x80000001 |
FHSM_RV_KAT_FAILED |
Critique : module halted ; ne pas réutiliser ; réinstaller |
0x80000002 |
FHSM_RV_INTEGRITY_FAILED |
Critique : binaire altéré ; réinstaller depuis bundle vérifié |
0x80000003 |
FHSM_RV_FIPS_NOT_APPROVED |
bascule sur mécanisme approuvé |
0x80000004 |
FHSM_RV_PIN_THROTTLED |
attendre les ms indiquées, retry une fois |
0x80000007 |
FHSM_RV_SECURE_HEAP_EXHAUSTED |
réduire l’inventaire de clés ou augmenter secure_heap_kb |
0x80000008 |
FHSM_RV_RNG_FAILURE |
Critique : DRBG échoue auto-test ; redémarrer le processus |
0x80000009 |
FHSM_RV_PROVIDER_UNAVAILABLE |
depuis C_Initialize seulement : un fournisseur OpenSSL requis n’a pas pu être chargé. Le module écrit la raison et la pile d’erreurs OpenSSL sur stderr. Après une mise à jour d’OpenSSL, voir AGD_PRE 3.3.1 |
Les erreurs critiques verrouillent l’état module ERROR. La seule récupération est de redémarrer le processus (per FIPS 140-3 §7.10.5).
| Fréquence | Tâche |
|---|---|
| Quotidien | Confirmer santé freehsm-bound-service ; vérifier syslog |
| Hebdo | Vérifier la chaîne d’audit de chaque slot ; archiver si rotation |
| Mensuel | Rotation des PIN USER ; revue d’agrégats d’audit |
| Trimestriel | Tester make repro contre un checkout neuf |
| Annuel | Re-lancer pen-testing ; mettre à jour pins OE.OS / OE.OPENSSL |
Le module choisit entre legacy (défaut) et FIPS strict au runtime via FHSM_MODE ou la directive mode = dans /etc/freehsm/freehsm.conf.
| Mode | Activation | Comportement |
|---|---|---|
legacy (défaut) |
rien, ou FHSM_MODE=legacy |
Tous les mécanismes appelables. MD5 et SHA-1 (digest) routés vers fhsm_legacy_dispatch. DES, 3DES, RC4 retournent CKR_MECHANISM_INVALID. |
fips (strict) |
FHSM_MODE=fips ou mode = fips dans la conf |
Tout mécanisme non-FIPS-approuvé retourne CKR_MECHANISM_INVALID. Conforme SP 800-131A Rev. 3. |
Pour une éval FIPS 140-3, l’opérateur DOIT poser FHSM_MODE=fips AVANT tout C_Initialize. Le mode est mis en cache au premier lookup.
| Variable | Effet |
|---|---|
FHSM_TPM_SEALING=1 |
À l’init du token, DEK scellée au TPM 2.0 (PCR 0-7). Fichier compagnon {slot}.tok.tpm. Au login : PBKDF2-unwrap + TPM-unseal doivent matcher. Un écart, ou un unseal en échec, refuse le login avec CKR_DEVICE_ERROR — lire la note ci-dessous avant d’activer. |
FHSM_INTEGRITY_ALLOW_UNSIGNED=1 |
DEV-ONLY : bypass de la vérif d’intégrité — INTERDIT en production. |
FHSM_TPM_SEALING=1 fait appeler tpm2 par le module, qui ouvre
/dev/tpmrm0. Sur Debian et Ubuntu ce nœud est crw-rw---- root tss : le
compte qui exécute l’application PKCS#11 doit donc appartenir au groupe tss :
id -Gn | tr ' ' '\n' | grep -qx tss || sudo usermod -aG tss "$(id -un)"
L’appartenance aux groupes est fixée à l’ouverture de session : une session déjà
ouverte ne la verra pas. newgrp tss donne le groupe au shell courant sans
déconnexion.
Ne pas contourner en lançant l’application en root. Les fichiers de token seraient créés au nom de root et le service aurait ensuite besoin de root pour chaque opération — une concession plus grande que celle qu’on évite.
Sans le groupe, tout appel TPM échoue sur une erreur de permission TCTI et le
module renvoie CKR_DEVICE_ERROR avec tpm-unseal-failed dans le journal
d’audit — le même signal que des PCR déplacés. Vérifier le groupe avant de
soupçonner les PCR.
Le scellement s’appuie sur une cle primaire persistante au handle 0x81010001.
Le module ne la cree pas, et si elle manque, tout scellement echoue. Rien ne
le disait jusqu’a ce que le chemin de scellement soit exerce pour la premiere
fois sur du vrai materiel ; c’est cet oubli qu’on corrige ici.
tpm2 readpublic -c 0x81010001 # presente ? sinon :
tpm2 createprimary -C o -g sha256 -G ecc -c /tmp/primary.ctx
tpm2 evictcontrol -C o -c /tmp/primary.ctx 0x81010001
Le handle est fixe a la compilation (TPM_PARENT_HANDLE dans src/fhsm_tpm.c).
Un operateur dont le TPM utilise deja 0x81010001 pour autre chose n’a aucun
moyen de le signaler — cela a sa place dans freehsm.conf, ou ca n’est pas
encore.
scripts/validate_tpm_sealing.sh verifie la presence du handle, propose de le
provisionner, et exerce tout le cycle de scellement sur un TPM reel, y compris
le scenario de changement de PCR decrit ci-dessous. A lancer avant de confier
quoi que ce soit a FHSM_TPM_SEALING.
Le scellement lie la DEK aux PCR 0 à 7, qui mesurent le firmware et le début du boot. Une mise à jour BIOS/UEFI, une rotation de clés Secure Boot, un changement de bootloader ou de noyau en déplace au moins un. Le TPM refuse alors de rendre la DEK scellée, et tous les logins sur ce token échouent tant que vous n’agissez pas, même avec le bon PIN.
C’est le comportement attendu du boot mesuré, pas une panne. Le code retourné est
CKR_DEVICE_ERROR, volontairement distinct de CKR_PIN_INCORRECT :
| Retour | Signification | Que faire |
|---|---|---|
CKR_PIN_INCORRECT / CKR_PIN_LOCKED |
Le PIN était faux. | Gestion normale du PIN. |
CKR_DEVICE_ERROR |
Le PIN était bon ; c’est le côté TPM qui a échoué. | Voir ci-dessous. |
Trois causes produisent CKR_DEVICE_ERROR, que le journal d’audit distingue :
tpm-unseal-failed — le TPM n’a pas pu libérer la clé. Presque toujours des PCR
déplacés après une mise à jour de firmware ou de noyau ; aussi observé quand le
TPM est absent ou que le resource manager ne tourne pas.tpm-dek-mismatch — le TPM a libéré une clé, mais ce n’est pas celle que
contient le fichier token. Les deux devraient être identiques. Traiter comme
une substitution possible du fichier token ou du blob scellé, et enquêter
avant d’aller plus loin.tpm-required-but-missing — FHSM_TPM_SEALING=1 mais ce token n’a pas de
compagnon .tpm. Il a été créé sans scellement.Récupération après une mise à jour de firmware. La DEK reste récupérable
depuis le fichier token avec le PIN : le scellement est un second facteur, pas
l’unique copie. Sauvegardez {slot}.tok et {slot}.tok.tpm, puis soit vous
revenez en arrière sur le firmware pour retrouver les valeurs de PCR scellées,
soit vous re-scellez le token sous les nouvelles mesures. Perdre le seul fichier
.tpm ne fait pas perdre le token ; perdre le fichier token, si.
Un unseal en échec ne consomme plus de tentative de PIN. Avant la v1.7.0 il en consommait une, et le throttle s’aggravait avec, si bien qu’une simple mise à jour de firmware verrouillait définitivement le token d’un opérateur qui n’avait rien fait de mal. C’était un déni de service, il est corrigé. Un PIN réellement faux compte, throttle et verrouille toujours comme documenté au §5.
Faites une sauvegarde avant d’activer FHSM_TPM_SEALING sur un token qui
compte.
fhsm_rng_bytes route via fhsm_drbg_bytes : seed multi-source (getrandom + RDRAND + /dev/urandom + jitter TSC), conditionneur SHA-384, health tests SP 800-90B (RCT + APT + CRNGT), reseed auto tous les 1 MiB ou 1 h. Toute alarme de health test latche le module en FHSM_STATE_ERROR et impose un redémarrage du service. Voir RNG.md.
Chaque C_GenerateKeyPair (RSA, EC, ML-KEM, ML-DSA, SLH-DSA) est suivi d’un sign-verify (ou encap-decap) automatique avec la clé fraîchement générée. Échec → ERROR latché. ~5 ms RSA-2048, sub-ms EC, ~50 ms SLH-DSA-128s.
Les actions suivantes invalident la configuration certifiée et font perdre le certificat FIPS / CC :
FHSM_MODE non défini (= mode legacy) pour un déploiement réclamant la conformité FIPS 140-3.FHSM_INTEGRITY_ALLOW_UNSIGNED=1 en production.fips_strict=false dans freehsm.conf.libfreehsm.so par tout autre binaire, même patché./opt/freehsm/etc/.mlock sur l’hôte.audit_mandatory=false).