I2L 2025 Groupe10

De wiki-se.plil.fr
Révision datée du 11 janvier 2026 à 16:11 par Aameloot (discussion | contributions) (→‎Rendu final)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

Proposition de système (étudiants)

Logo de l'entreprise locale BiniouKey.
BiniouKey, bien plus qu'une clé de sécurité.

Notre système est une clé de sécurité, capable de sauvegarder et de générer des mots de passe TRNG (True RNG), c.-à.-d. des mots de passe réellement aléatoire, et non pseudo-aléatoires.

Plusieurs méthodes différentes sont identifiées pour générer des nombres vraiment aléatoires :

  • La méthode "simple", qui va consister à mesurer la tension d'une broche analogique flottante, correspondante au bruit électrique présent aux alentours de la clé.
  • La méthode "complexe", qui va consister à utiliser un circuit spécialisé de génération de bruit, composé d'un thermistor, d'un amplificateur et de condensateurs, afin de capter le bruit thermique du thermistor.

La clé de sécurité va proposer plusieurs fonctionnalités, accessibles via un petit écran type SSD1306 et une croix directionnelle.

La première fonctionnalité va être de simplement pouvoir générer des mots de passe aléatoires d'une longueur voulue.

Le seconde fonctionnalité va être de pouvoir protéger un ordinateur grâce à la clé et à la communication USB entre la clé et l'ordinateur. Lors de la configuration de l'ordinateur, ce dernier va demander ses identifiants à la clé, qui va générer une paire identifiant/mot de passe qu'elle va stocker en ROM, et les transmettre à l'ordinateur. Ensuite, lorsque l'on voudra déverrouiller l'ordinateur, il faudra, sur la clé, s'authentifier en sélectionnant l'identifiant de l'ordinateur. La clé enverra alors le mot de passe à l'ordinateur, qui pourra se déverrouiller.

Les différentes fonctionnalités seront accessibles via un menu affiché sur l'écran, et navigable grâce à la croix directionnelle.

SarrasinDefender, sécuriser vos fichiers comme jamais !

Ci-dessous deux schémas montrant la configuration du mot de passe et l'authentification :

Les différentes étapes lors de la configuration du mot de passe entre l'ordinateur et la clé
Schéma montrant la procédure d'authentification entre l'utilisateur, son ordinateur et la clé de sécurité.

Contre-proposition (intervenant)

OK pour la proposition.

Une bibliothèque pour gérer le contrôleur graphique SDD1306 est disponible [1].

Proposition définitive (étudiants)

Répartition du travail (étudiants)

Carte

Schéma initial (étudiants)

Schéma de la carte

Carte routée (intervenant)

Vous utiliserez la carte avec l'écran OLED.

Composants (intervenant)

Carte réalisée (intervenant)

Photo de la carte

La carte est entiérement soudée à l'exception de la thermistance.

Travaux (étudiants)

Journal de bord

Fonctionnement du programme avec l'antenne comme source d'entropie.
La BinouKey affiche des chaînes de 16 caractères à partir du bruit électronique capté.
  • 13/10/2025. A ce jour, nous avons pu tester l'allumage et l'extinction d'une DEL à intervalles réguliers. Egalement, nous avons conditionné l'allumage et l'extinction des DELs selon le bruit électrique capté par le thermistor et l'antenne.
  • 16/10/2025. Pendant ces deux séances, nous avons réussi à afficher sur l'écran une chaîne de 16 caractères générés suite à la captation du bruit électronique. Cependant, le programme plante aléatoirement (freeze de l'écran et de la DEL clignotante).. Nous avons également réussi à enregistrer la BiniouKey en tant que périphérique USB appartenant à la S.A.R.L. BigoudenCrypt. Il ne reste qu'à inclure cet enregistrement au reste de notre programme pour que tout fonctionne ensemble.
  • 25/10/2025.
    • BiniouKey (microcontrôleur) : intégration de la bibliothèque LUFA avec du code qui permet de récupérer des données envoyées depuis l'ordinateur. On a donc un périphérique USB enregistré en tant que tel, avec du code qui fonctionne 🎉
    • Ordinateur : programme C avec libusb qui permet de rechercher la BiniouKey (routine de recherche, puis pause du programme tant qu'elle n'a pas été trouvée), puis qui envoie une chaîne de caractère sur l'endpoint OUT (type BULK) au périphérique pour afficher ce message sur l'écran. Malheureusement, pas encore possible de tester le bon fonctionnement de la fonctionnalité de bout en bout car le noyau tient toujours l'interface de la BiniouKey et je n'arrive pas à le détacher (macOS qui j'avoue sur ce coup là est pas pratique).
  • Quelque part entre le 25/10 et le 13/11
    • Ordinateur : tout compte fait on a réussi à faire fonctionner libusb avec la Binioukey, l'interface est claim par notre programme.
  • 13/11/2025
    • BiniouKey (microcontrôleur) :
      • Modification de la configuration pour faire en sorte que les endpoints fonctionnent (on pensait qu'on pouvait mettre les valeurs qu'on veut entre 1 et 16 pour les endpoints mais en réalité ça doit être entre 1 et 4...). Les endpoints sont passés en INTERRUPT tout compte fait.
      • 1er test pour s'assurer que tout va bien : on écrit une chaîne de caractère dans l'endpoint OUT (cf. travaux ordinateur du 25/10) et on arriver à le récupérer avec succès avec la BiniouKey. On peut donc envoyer du texte depuis l'ordinateur vers le périphérique pour l'afficher sur l'écran.
      • Nous avons également réussi à faire fonctionner le minuteur de la carte, pour avoir (pour l'instant) une DEL clignotante en plus d'écouter les messages de l'endpoint OUT pour les afficher.
      • 2ème test et premier vrai prototype de BiniouKey qui fonctionne : en reprenant le code de génération aléatoire avec le thermistor ou le pin, on peut envoyer du texte avec l'endpoint IN vers l'ordinateur.
    • Ordinateur :
      • Le code était principalement bon lors des fois précédentes, il suffisait de changer l'ID de l'interface. On peut donc envoyer une chaîne de caractère au périphérique.
      • Gestion de l'endpoint IN pour un premier prototype de BiniouKey : on peut récupérer les données envoyés depuis le périphérique, et on les copie dans le presse-papier (Linux: xclip, macOS: pbpaste, Windows: clip)
Occurrence de chaques caractères obtenables lors de la génération d'un mot de passe, pour des amplifications différentielles allant de x1 à x200. (n=64000 caractères soit 4000 mots de passe de 16 caractères)
  • 15/11/2025
    • Au fur et à mesure des mesures sur l'ADC, nous nous sommes rendu compte que certains caractères reviennent très souvent. Nous avons donc faitdes statistiques sur les mots de passes générés avec différentes configurations de l'amplificateur différentiel qui nous permet de d'obtenir plusieurs niveaux d'amplification (0 dB (1x), 10 dB (10x), 16dB (40x), ou 23dB (200x)).
  • 25/11/2025
    • Nous avons réalisé un POC pour le logiciel SarrasinDefender. Le programme principal de ce logiciel, sans interaction avec la BiniouKey pour l'instant, est une CLI dotée des commandes suivantes :
      • encrypt <filepath> <key_id> : le programme créer un fichier .gpg chiffré de manière symétrique à partir du chemin absolu d'un fichier. La passphrase utilisée pour chiffrer ce fichier est <key_id>. A terme, <key_id> devra être un indice permettant à l'ordinateur de demander à la BiniouKey la clé sauvée à l'indice <key_id>.
      • decrypt <filepath> <key_id> : le programme réaliser la mécanique inverse en prenant en entrée un chemin absolu vers un fichier .gpg.
  • 11/12/2025
    • Nous avons implémenté les fonctions permettant de stocker des chaînes de 16 caractères (i.e. une chaîne générée par la BiniouKey) dans sa mémoire persistante. En l'occurrence, on enregistre l'indice de la prochaine clé à écrire (entre 0 et 10 exclus, 255 signifiant que tous les emplacements destinés à contenir des clés sont remplis). L'indice est récupéré une fois lors de l'allumage de la clé afin de minimiser les accès lecture/écriture sur EEPROM. Lors d'une sauvegarde d'une nouvelle clé, l'indice est incrémenté et sauvegardé sur l'EEPROM.
    • Egalement, quelques amorces avec de la communication bi-directionnelle entre SarrasinDefender et la BiniouKey mais le fonctionnement n'est pas constant.
    • En cours : changement de la librairie SSD1306, celle actuellement utilisée étant défaillante.
  • Entre le 12/12/2025 et le 08/01/2026
    • Après de trop nombreuses galères, la librairie SSD1306 a été changée pour utiliser celle proposée par l'enseignant. Il y a encore le souci de crash aléatoire et des petits détails à fixer (clear d'une ligne, affichage d'une chaîne de 16 caractères qui plante parfois etc.). Grâce à cela, l'écran est à peu près fonctionnel et l'on a pu tester le stockage et la lecture sur l'EEPROM, qui fonctionne... ouf!
  • 8/01/2026
    • Apport de corrections rudimentaires pour nettoyer une ou plusieurs lignes à l'écran.
    • Création d'un fichier permettant de vérifier si un des boutons est pressé. Le fichier principal exploite dans sa boucle principale ce fichier pour lancer des actions simples (par exemple, "réinitialiser la mémoire EEPROM" à l'appui du bouton gauche, "générer une nouvelle clé et la sauvegarder au premier emplacement disponible dans l'EEPROM" à l'appui sur le bouton du haut...).
    • Petite refonte du logiciel SarasinDefender pour qu'il soit en écoute USB uniquement pour le chiffrement et le déchiffrement de fichiers. Lorsque l'utilisateur tape encrypt <file> ou decrypt <file>, le logiciel attend jusqu'à 30 secondes une réponse de la Binioukey. L'appui sur le bouton central permet d'envoyer la clé à SarasinDefender.
  • 09/01/2026
    • Création de nouvelles fonctionnalités sur la BiniouKey : envoyer à SarrasinDefender la clé sélectionnée (bouton central) et supprimer de la mémoire la clé sélectionnée (bouton bas).
  • 10/01/2026
  • 11/01/2026
    • Rajout de la fonctionnalité copier vers le presse papier
    • Clean code + quelques correctifs en vitesse et joli README

Rendu final

Le projet

  • Le système embarqué BiniouKey permet de se constituer un "portefeuille" de mots de passe de 16 caractères générés aléatoirement à partir du bruit électronique environnant. La gestion des clés se fait à-même le système grâce à un écran et des boutons, la clé est donc totalement autonome.
    • Le bouton du haut génère et sauvegarde une clé. Si la mémoire est pleine (i.e. les 8 emplacements dédiés au stockage des clés sont occupés), une erreur s'affiche.
    • Le bouton de gauche supprime toutes les clés sauvées en mémoire.
    • Le bouton de droite parcourt les clés stockées en affichant son identifiant et la valeur de celle-ci. Le système reboucle lorsque la fin de la liste est atteinte.
    • Le bouton du bas supprime la clé courante.
    • Le bouton central envoie la clé courante au logiciel SarrasinDefender.
  • Le logiciel SarrasinDefender est une solution en ligne de commande qui chiffre et déchiffre des fichiers via GPG à l'aide des clés générées et stockées sur la BiniouKey. La récupération d'une clé se fait grâce à une communication USB établie entre SarasinDefender et BiniouKey.
    • La commande debug [on|off] active ou désactive le mode debug. Son activation permet de voir les logs de libusb et les clés en clair reçues de la BiniouKey.
    • La commande encrypt <file> chiffre un fichier. Elle attend jusqu'à 30 secondes une clé envoyée par la BiniouKey (via la pression sur le bouton central). En cas de succès, un fichier .gpg est créé dans le même répertoire que le fichier passé en paramètre.
    • La commande decrypt <file> déchiffre un fichier comportant l'extension .gpg. Comme pour la commande précédente, elle attend une clé émise par le contrôleur.
    • La commande generate demande à la BiniouKey de générer et de sauvegarder une clé. Le retour de cette commande est un accusé réception par la BiniouKey (i.e. pas de retour de succès ou d'échec de la génération).
    • La commande copy [--sanitize] permet de copier un mot de passe récupérer depuis la BiniouKey dans le presse-papier, pour utiliser ses mots de passe dans n'importe quel contexte. La récupération des mots de passe s'effectue de la même manière que pour encrypt ou decrypt.
    • Démonstration du fonctionnement entre la BiniouKey et SarrasinDefender
      La commande exit quitte le programme.

Spécifications techniques

  • La BiniouKey utilise un écran SSD1306 pour faire apparaître les menus et l'interface utilisateur.
  • La génération des mots de passe est réalisée grâce à une lecture du bruit électronique d'un thermistor ou d'un floating pin (dans la version finale, le floating pin est utilisé)
    • Pour une génération plus diversifiée, nous avons utilisé un gain x200 de l'ADC (voir statistiques réalisées dans le journal de bord, cf. entrée du 15/11/2025)
  • Les clés de la BiniouKey sont stockées dans l'EEPROM de l'AVR pour qu'elles soient persistées, même si la carte est débranchée.
  • La BiniouKey est enregistrée comme périphérique USB grâce à la librairie LUFA.
    • Deux endpoints de transfert IN et OUT sont implémentés (type INTERRUPT)
    • L'endpoint de contrôle est également implémenté pour une action spécifique.
  • Nous avons fait le choix de faire notre programme côté host (Sarrasin Defender) en C avec la librairie libsusb de base.


PS : compte tenu de la complexité de la proposition initiale et des nombreuses difficultés rencontrées sur l'intégration et l'exploitation de la librairie de gestion de l'écran SSD1306, nous avons légèrement adapté notre version "ordinateur" du projet. Cette nouvelle proposition plus réaliste offre des fonctionnalités qui nous semblent plus utiles (chiffrement/déchiffrement de fichiers plutôt que bloquage de l'ordinateur).

Extraits significatifs de code (étudiants)

Lien vers le dépôt GitLab de BiniouKey : https://gitlab.dpt-info.univ-littoral.fr/binioukey/binioukey

Lien vers le dépôt GitLab de SarrasinDefender : https://gitlab.dpt-info.univ-littoral.fr/binioukey/sarrasin-defender

Sauvegarde des clés

Découpage de la mémoire EEPROM

Les clés sont des données qui doivent persister dans la mémoire EEPROM de la BiniouKey. Nous avons opté pour une capacité de, tenez-vous bien... 8 clés ! La mémoire EEPROM est découpée selon le schéma ci-contre. Le premier octet représente le référentiel de sauvegarde où chaque bit représente la disponibilité de l'emplacement pour stocker une clé (0 = l'emplacement est libre, 1 = l'emplacement est occupé). A sa suite sont stockés 8 blocs de 16 octets où chaque bloc est une clé.


Le fichier projectfiles/StorageHandler.c contient toutes les fonctions permettant de gérer les opérations liées au stockage. L'extrait de code ci-dessous montre comment lire et modifier la valeur du référentiel de sauvegarde à l'aide des fonctions proposées par avr.
#include <avr/io.h>
#include <avr/eeprom.h>

#define EEPROM_TABLE_BASE_ADDR ((uint8_t*) 0)

uint8_t get_table_base_addr(void) {
    return eeprom_read_byte(EEPROM_TABLE_BASE_ADDR);
}

void reset_storage() {
    // Remise du référentiel de sauvegarde à 0 
    // Les anciennes données des clés resteront en mémoire mais seront 
    // considérées comme "libres" et écrasables.
    eeprom_update_byte(EEPROM_TABLE_BASE_ADDR, 0x0);
}

Exploitation des boutons

Le fichier projectfiles/Button.h contient le code métier permettant d'initialiser les boutons et de déterminer si l'on appuie dessus.
#include <avr/io.h>

#define UP_BUTTON PB4

void setup_buttons() {
    DDRB &= ~(1 << UP_BUTTON); // Configure en entrée
    PORTB |= (1 << UP_BUTTON); // Active la résistance de pull-up
    ...
}

bool is_up_button_pressed(void) {
    return !(PINB & (1 << UP_BUTTON));
}

// Exemple d'utilisation
int main() {
    for (;;) {
        if (is_up_button_pressed()) {
			// Code métier
			_delay_ms(100);  // Délai pour l'anti-rebond
        }
    }
}

Exploitation de l'écran

Non sans mal, nous avons utilisé la librairie ss1306[1] et écrit le fichier projectfiles/Display.h qui contient quelques fonctions utilitaires l'exploitant afin de faciliter l'intégration et la maintenabilité dans le code.


Pour rappel, l'écran comporte 8 lignes. La police choisie (font6x8) permet d'afficher 21 caractères par ligne. La fonction clear_line affiche 21 espaces, ce qui n'est pas très propre d'un point de vue qualité de code mais celle fournie par la bibliothèque ne fonctionne pas donc bon... On fait comme on peut hein...
#include "ssd1306.h"

void setup_screen() {
    ssd1306_init();
    ssd1306_setFixedFont(ssd1306xled_font6x8);
    clear_screen();
}


void display_string(const char* str, const int line) {
    if (0 <= line && line <= 8) {
        clear_line(line);
        ssd1306_printFixed(0, line * 8, str, STYLE_NORMAL);
    }
}

void clear_line(const int line) {
    ssd1306_printFixed(0, line * 8, "                     ", STYLE_NORMAL);
}

Enregistrement comme périphérique USB avec LUFA

La BiniouKey est enregistrée comme périphérique USB grâce à la bibliothèque LUFA. Nous avons donc pu enregistrer 2 endpoints IN et OUT pour communiquer directement avec l'host (Sarrasin Defender).

Au final, nous n'avons pas utilisé l'endpoint OUT (celui qui envoie des données du host vers le device). Nous utilisons par contre l'endpoint IN (envoie des données du device vers le host) pour transférer les clés. Ci-dessous, le code contenu dans le fichier projectfiles/Usb.c des 2 endpoints qui gèrent respectivement 16 et 8 octets de données.
char *handle_out(void) {
    // On prend l'endpoint out, et on configure la réception des données
    Endpoint_SelectEndpoint(VENDOR_OUT_EPADDR);
    static char buffer[VENDOR_OUT_EPSIZE + 1];

    // On check si on a des données qui arrivent
    if (Endpoint_IsOUTReceived()) {

        // Si on a le droit de lire
        if (Endpoint_IsReadWriteAllowed()) {
            uint8_t bytesRead = 0;
            // Temps qu'on a des données ou qu'on a pas rempli le tableau de données
            while (Endpoint_BytesInEndpoint() && bytesRead < sizeof(buffer) - 1) {
                buffer[bytesRead++] = Endpoint_Read_8();
            }
            buffer[bytesRead] = '\0';

            // Clear et retourne les données
            Endpoint_ClearOUT();
            return buffer;
        }
        // On a reçu mais sans données : on ack, on retourne NULL
        Endpoint_ClearOUT();
    }
    return NULL;
}

void handle_in(char* data) {

    // Sélection endpoint
    Endpoint_SelectEndpoint(VENDOR_IN_EPADDR);

    // Est-ce qu'on peut bien envoyer des trucs
    if (Endpoint_IsINReady()) {
        // On écrit 8B à la fois
        for (uint8_t i = 0; i < VENDOR_IN_EPSIZE; i++) {
            Endpoint_Write_8(data[i]);
        }

        // Fin de l'envoi (ack)
        Endpoint_ClearIN();
    }
}
Nous avions besoin d'un moyen pour l'host de demander à la BiniouKey de générer une clé. Au début, nous avons utilisé l'endpoint OUT mais sans données. L'endpoint de contrôle semblait être plus approprié pour ce genre de fonctionnalité, nous avons donc implémenté celui-ci dans le fichier principal Binioukey.c (dans la méthode qui était déjà déclarée par LUFA mais laissée vide).
void EVENT_USB_Device_ControlRequest(void)
{
	// 1. Request type est de notre vendor, et le requête vient de l'host vers nous
	if (((USB_ControlRequest.bmRequestType & (CONTROL_REQTYPE_TYPE | CONTROL_REQTYPE_DIRECTION)) ==
		(REQTYPE_VENDOR | REQDIR_HOSTTODEVICE)))
	{
		// 2. Vérification du type de commande (définie par la même constante)
		if (USB_ControlRequest.bRequest == CONTROL_ENDPOINT_CMD_GENERATE)
		{
			// 3. Endpoint 0
			Endpoint_SelectEndpoint(ENDPOINT_CONTROLEP);

			// Si on avait des données à lire on le ferait ici
			// là on en a pas donc pas besoin de lire quoi que ce soit

			// 4. Acknowledge du message
			Endpoint_ClearSETUP();
			Endpoint_ClearStatusStage();

			// On met le flag à true (la génération est faite dans la boucle principale)
			generate_key_requested = true;
		}
	}
}
Pour éviter de faire trop de traitement dans les méthodes de la LUFA, nous avons une variable globale et le traitement est géré dans la boucle principale, plus tard.
// Request de génération de clé depuis l'endpoint de contrôle
volatile bool generate_key_requested = false;

int main(void)
{
    // setups...
    
    // Boucle principale
    for (;;)
    {
        // c'est ici que les méthodes de gestion in/ou et control seront lancées
        USB_USBTask(); 
        
        // On a reçu une demande de génération de clé : on le fait
    	if (generate_key_requested) {

    		const int status = generate_and_save(key_buffer);

    		generate_key_requested = false;
    		continue;
    	}
    	
    	// autres tâches...
    }
}

Communication avec le périphérique USB depuis l'hôte

Depuis Sarrasin Defender, nous communiquons avec le périphérique pour :

  1. Demander à la BiniouKey de générer une clé
  2. Attendre que la BiniouKey nous envoie un mot de passe
Pour nous faciliter la tâche, nous avons une fonction utilisée dans le main qui nous permet de récupérer toutes les informations sur le périphérique USB et les stocker dans une struct pour les réutiliser plus tard.
/**
 * Représente une BiniouKey sous forme de périphérique USB
 */
typedef struct {
    libusb_device* device;
    struct libusb_device_descriptor *descriptor;
    struct libusb_config_descriptor *configuration;
    libusb_device_handle* handle;
} BiniouKey;
Cette struct est initialisée une fois et utilisée à plusieurs endroit dans le programme. Le code ci-dessous montre les deux fonctions utilisées pour récupérer une clé et demander une génération de mot de passe.
char* get_key(const BiniouKey* binioukey, const int debug_mode, const int sanitise) {

    const int timeout_in = 30000;
    int transfered;

    unsigned char data[BINIOUKEY_MAX_KEY_SIZE + 1]; // size passphrase + \0

    printf("Select a key from your BiniouKey and press CENTRE BUTTON\n");

    const int status = libusb_interrupt_transfer(
        binioukey->handle, VENDOR_IN_EPADDR,
        data, BINIOUKEY_MAX_KEY_SIZE, &transfered, timeout_in
    );

    if (status != 0) {
        perror("[KO] Couldn't read data from the Binioukey\n");
        return NULL;
    }

    if (transfered != BINIOUKEY_MAX_KEY_SIZE) {
        perror("[KO] Not enough bytes were received");
        return NULL;
    }

    data[transfered] = '\0';

    if (debug_mode == 0) {
        printf("[OK] Passphrase received from the Binioukey: %s\n", data);
    }


     // Allocation dynamique de la chaîne échappée
    if (sanitise == 0) {
        const size_t max = 2 * strlen((char*)data) + 1;
        char *escaped = malloc(max);

        sanitize_passphrase(escaped, (char*)data);
        return escaped; // L'appelant devra free()
    }
    // Cas où on ne sanitise pas : on renvoie quand même une chaîne allouée avec malloc
    // Comme ça on peut free() dans tous les cas et c'est safe!
    else {
        char* no_escape = malloc(sizeof(char) * (transfered + 1));
        memcpy(no_escape, data, transfered + 1);
        return no_escape;
    }
}
Pour cette fonction, on retourne une chaîne de caractère correspondant au mot de passe, récupérée grâce à libusb_interrupt_transfer et transférée à travers l'endpoint IN.
int create_key(const BiniouKey* binioukey) {
    // setup des variables
    const uint8_t request_type = LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT;
    const uint8_t request = CONTROL_ENDPOINT_CMD_GENERATE;
    const uint16_t value = 0;
    const uint16_t index = 0;
    unsigned char *data = NULL;
    const uint16_t length = 0;
    const unsigned int timeout = 5000;

    // envoie du truc de transfert
    const int status = libusb_control_transfer(
        binioukey->handle,
        request_type,
        request,
        value,
        index,
        data,
        length,
        timeout
    );

    if (status < 0) {
        fprintf(stderr, "[KO] Control transfer failed: %s\n", libusb_error_name(status));
    }

    return status;
}
L'endpoint de contrôle n'envoyant pas de donnée et n'en attendant pas en retour (en tout cas pour notre cas d'utilisation), on peut simplement attendre que le device envoie son acknowledge avant de considérer que la clé a été générée.

Rendus (étudiants)

Projet KiCAD : Fichier:I2L-2025-Carte-G10-final.zip

Programmes :