TPM 2.0 et TSS en C# – maitriser votre cryptoprocesseur

Apprenez à manipuler votre cryptoprocesseur TPM 2.0 en C# à l'aide de la librairie de microsoft TSS.NET, pour générer, stocker, et lire vos clés de chiffrements.

  Publié le

Qu'est-ce qu'une puce TPM  ?

La puce TPM (pour Trusted Plateform Module ou module de plateforme sécurisée) aussi connue sous le nom de puce de Fritz est un microcontrôleur connecté via le bus SPI ou LPC à la carte mère. La puce TPM est un concept mis en place par le consortium Trusted Computing Group (TCG). La puce TPM est un cryptoprocesseur. Elle est conçue pour réaliser des opérations de chiffrement et de stockage ainsi que garantir l'intégrité du système.

La puce TPM est un équipement passif (au même titre qu'un relais), elle n'a aucune emprise sur l'ordinateur et peut uniquement recevoir des ordres de celui-ci (bien qu'elle incorpore ses propres circuits logiques et son microprogramme) et y répondre. Il existe deux versions de la puce TPM 1.2 et 2.0. Dans cette publication nous aborderons les éléments liés à la puce TPM 2.0.

Quelles sont les fonctionnalités d'une puce TPM ?

D'après les spécifications techniques établies par le Trusted Computing Group :

  • Générer des clés de chiffrement avec une entropie forte.
  • Stocker des clés de chiffrement et limiter leur utilisation dans les PCR.
  • Garantir l'intégrité du système d'exploitation en créant et en stockant des mesures d'intégrités (empreintes de contrôles/hash).
  • Permettre l'authentification physique d'un appareil grâce à la clé asymétrique RSA unique qui est gravée sur la puce TPM (EK, Endorsement Key).

Qu'est-ce que l'Endorsement Key de la puce TPM ?

Vous lirez souvent des références à EK (pour Endorsement Key) sur les sujets qui traitent de la puce TPM. L'EK c'est la clé d'approbation. Il s'agit d'une clé asymétrique contenue dans le TPM (injectée au moment de sa fabrication). L'EK est unique pour chaque TPM et permet de l'identifier. Cette clé ne peut pas être modifiée ou supprimée de la puce TPM.

Qu'est-ce que le PCR d'une puce TPM ?

Le PCR (registre de configuration de plateforme) est un emplacement mémoire dans la puce TPM qui sert au stockage des empreintes de contrôles (hash). Quand plusieurs PCR sont liées à un seul état de l'ordinateur, on parle d'une banque de registre de configuration (une liste accessible à un instant spécifique). On peut faire l'analogie suivante : PCR égal coffre-fort à empreinte spécifique à un type d'empreinte. Un PCR qui peut stocker une empreinte cryptographique SHA, ne pourra pas stocker une empreinte SHA-256 et inversement.

Comment la puce TPM sécurise-t-elle ses clés de chiffrement ?

Le cryptoprocesseur TPM incorpore une clé de chiffrement racine qui n'est jamais exposée à aucun autre composant. Cette clé sert au chiffrement et au déchiffrement des clés stockées sur la puce TPM elle-même.

Lors de la création d'une clé de chiffrement stockée sur la puce TPM, il est possible de définir si les clés publiques et privées doivent être rendues accessibles par d'autres composants (logiciel et/ou matériel). À l'inverse si la clé ne doit pas être exposée, la clé privée n'est alors pas accessible en dehors du cryptoprocesseur TPM.

Il est également possible de lier des clés de chiffrement associées à certaines mesures de contrôle de la plateforme. En d'autres termes : si lors de l'initialisation du système d'exploitation ces variables (logiciel et/ou matériel) ne correspondent pas aux valeurs attendues par le cryptoprocesseur TPM, alors ces clés sont rendues inaccessibles par la puce TPM.

Comme les clés privées stockées sur le cryptoprocesseur TPM ne le sont pas dans la mémoire contrôlée par le système d'exploitation, il est alors impossible à celui-ci d'accéder aux clés de chiffrements. La puce TPM est donc parfaitement autonome par rapport au système d'exploitation.

Comprendre le système de verrouillage de la puce TPM 2.0

La puce TPM inclut un système contre les attaques incrémentales. Ce système vise à bloquer pour une durée définie les tentatives d'accès après plusieurs échecs d'autorisations. Sous Windows 10 il est possible de réaliser 32 tentatives infructueuses avant de voir la TPM passer en état verrouillé. Quand la puce est verrouillée elle ne retourne alors que son état à savoir : verrouillée. À partir de cet état si aucune tentative infructueuse n'a lieu : la puce TPM décrémente d'une tentative, toutes les deux heures. Il faudra donc attendre 64H pour repasser à son état initial avec 32 tentatives possibles.

Quelles sont les applications pratiques des puces TPM ?

Comme exposé plus haut : la clé RSA gravée sur la puce TPM permet de garantir de l'identité d'un équipement porteur de cette puce. À partir de ce postulat il est parfaitement envisageable de pouvoir affilier des autorisations logiciels ou réseau directement à l'équipement porteur de cette clé.

Si un expert accède physiquement à la puce TPM peut-il extraire les clés de chiffrements ?

Virtuellement oui, bien que la réalisation soit d'une grande complexité sur le plan technique. Pour accéder aux clés sécurisées en interne de la puce et non accessibles de l'extérieur, le seul moyen serait d'avoir un accès physique à la puce. Pour venir se brancher directement sur les bus internes de celle-ci. Avec le risque majeur d'engendrer sa destruction. On peut donc considérer la puce TPM comme fiable sur un plan sécuritaire.

Comment initialiser ma puce TPM ?

Initialize-Tpm -AllowClear -AllowPhysicalPresence 

Comment contrôler que ma puce TPM à bien un certificat EK ?

Get-TpmEndorsementKeyInfo -Hash "Sha256"

Comment savoir si ma puce TPM est verrouillée ?

Get-Tpm

Comment créer une empreinte de passphrase propriétaire sur ma puce TPM ?

ConvertTo-TpmOwnerAuth -PassPhrase "YOURPASSPHRASE"

Comment modifier l'autorisation propriétaire de ma puce TPM ?

Set-TpmOwnerAuth -OwnerAuthorization "OLDHAHSPASSPHRASE" -NewOwnerAuthorization "NEWHAHSPASSPHRASE"

Comment déverrouiller ma puce TPM ?

Unblock-Tpm -OwnerAuthorization "YOURHASHPASSPHRASE"

Manipulation du cryptoprocesseur TPM en C#

La manipulation de la puce TPM en C# s'effectue à l'aide de la librairie TSS.NET. Cette librairie couvre l'ensemble des possibilités techniques offertes pas la puce. Elle est installable via le gestionnaire de packets NUGET. Une fois déployée sur votre projet il suffit d'appeler l'espace de nom Tpm2Lib.

using Tpm2Lib;

Comment initialiser l'accès à la puce TPM ?

L'initialisation de l'objet TPM s'effectue en trois étapes. D'abord en initialisant l'objet TbsDevice et en ouvrant la connexion. Ensuite, en initialisant l'objet Tpm2 avec pour premier paramètre l'objet TbsDevice précédemment créez.

 

TbsDevice _crypto_device = new TbsDevice();
_crypto_device.Connect();
Tpm2 _tpm = new Tpm2(_crypto_device);

Comment arrêter proprement l'utilisation de la puce TPM ?

La fermeture de la connexion à la puce TPM s'effectue, à travers la méthode dispose de l'objet Tpm2.

if (_tpm != null) _tpm.Dispose();

Comment créer un hash avec la puce TPM en C#?

Le calcul d'une empreinte cryptographique (hash) est très simple. Le premier paramètre correspond à la chaine de caractères transformer en tableau de byte. Le second au format de l'algorithme de chiffrement souhaité. Le troisième sert à spécifier le niveau de droit nécessaire pour réaliser l'opération. Enfin, le dernier paramètre est un paramètre de retour, qui permet de savoir le ticket alloué par la puce TPM pour réaliser l'opération (non utilisé dans cet exemple).

TkHashcheck validation;
byte[] hashData = _tpm.Hash(Encoding.UTF8.GetBytes(in_data),   
						   TpmAlgId.Sha256,                             
						   TpmRh.Owner,                         
						   out validation);
string str_hash = BitConverter.ToString(hashData);

Comment générer une série aléatoire à forte valeur entropique avec la puce TPM en C# ?

Pour générer une chaine aléatoire provenant du cryptoprocesseur, il suffit d'utiliser la fonction GetRandom et de lui fournir en seul paramètre une valeur comprise entre 1 et 32 pour définir le nombre de bytes que la puce va devoir nous générer.

_tpm.GetRandom(32)

Il est parfaitement possible de créer une simple fonction pour retourner des lots plus importants de bytes. Dans l'exemple suivant nous générons un tableau de 2048 bytes à forte entropie.

public byte[] generate_random_bytes(int in_size = 2048)
{
	try
	{
		int min = 32;
		if (in_size < min) in_size = min;
		int steps = in_size / min;

		List<byte> rst = new List<byte>();
		for (int i = 0; i < steps; i++)
		{
			rst.AddRange(_tpm.GetRandom(32));
		}

		return rst.ToArray();
	}
	catch (Exception)
	{

		throw;
	}
}

Comment connaître la liste des banques PCR présent sur sa puce TPM par rapport aux algorythmes ?

Exécuter dans une application console, le fragment de code suivant vous remontra la liste des index disponibles suivant les différents algorythmes supportées par la puce.

ICapabilitiesUnion caps;
_tpm.GetCapability(Cap.Pcrs, 0, 255, out caps);
PcrSelection[] pcrs = ((PcrSelectionArray)caps).pcrSelections;
foreach (PcrSelection pcrBank in pcrs)
{
	var sb = new StringBuilder();
	sb.AppendFormat("PCR bank dispo pour algo {0} index:", pcrBank.hash);
	sb.AppendLine();
	foreach (uint selectedPcr in pcrBank.GetSelectedPcrs())
	{
		sb.AppendFormat("{0},", selectedPcr);
	}
	Console.WriteLine(sb);
}

Sous la forme :

PCR bank dispo pour algo Sha index:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,
PCR bank dispo pour algo Sha256 index:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,

Comment lire le contenu d'un PCR présent sur la puce TPM ?

Pour lire le contenu contenu dans un PCR il faut initialiser un tableau d'objet PcrSelection et lui assigner au moins un objet PcrSelection avec deux paramètres, l'algorithme et le tableau uint correspondant à l'index du PCR. Puis on récupère la valeur à l'aide de la fonction PcrRead. On contrôle sa non-nullité, puis on convertit la mémoire tampon ainsi récupérée en objet TpmHash. Avant de convertir l'objet au besoin (ici en chaine de caractères).

public string get_pcr_value(uint[] in_pcr_index, TpmAlgId in_algo = TpmAlgId.Sha1)
{
	try
	{
		/**Définit la requête d'accès au contenu PCR par rapport à l'algo et aux index */
		var valuesToRead = new PcrSelection[]
		{
				new PcrSelection(in_algo,in_pcr_index)
		};

		PcrSelection[] valsRead;
		Tpm2bDigest[] values;

		/**Récupère le contenu du PCR */
		_tpm.PcrRead(valuesToRead, out valsRead, out values);

		/**Valeur introuvable à l'index spécifié pour l'algo spécifié */
		if (valsRead[0] != valuesToRead[0])
		{
			return null;
		}

		/**Récupération du HASH */
		var pcr_hash = new TpmHash(in_algo, values[0].buffer);

		/**Convertion en chaine de caractères */
		return BitConverter.ToString(pcr_hash.HashData);
	}
	catch (Exception)
	{

		throw;
	}
}