Il y a quelques semaines, j'ai commencé à remarquer un phénomène étrange sur ma station de travail personnelle. A chaque démarrage à froid, le BIOS interrompait la séquence de boot en m'indiquant que des paramètres internes avaient été modifiés. J'étais alors obligé d'aller dans le BIOS, de choisir manuellement le périphérique de boot et de lancer la procédure de démarrage avant de retomber sur mon écran de Grub préferré.

Au début, je n'y ai pas trop prêté attention mais, à la longue, ça commençait à devenir pénible car j'étais sollicité à chaque démarrage. Avec le temps, j'ai essayé de corriger le problème en refaisant le tour de la configuration du BIOS. Il devait démarrer par le disque SSD qui est le périphérique de démarrage attitré.

Mais à chaque fois que je débranchait le PC (chaque fois que je l'éteinds en fait), aucun de mes paramètres n'étaient conservés. J'ai commencé à me poser d'autres questions. Encore un coup de UEFI ou de SecureBoot ? Ou alors, quelqu'un a essayé de m'implanter un module pirate ? Peut-être les chinois du FBI ?

Et puis, un détail m'a frappé, comme une évidence: l'ordinateur remettait à zéro la date du jour. A chaque démarrage de la machine, la date du jour était le 01/01/2009. Au début, je ne voyais rien car, après démarrage, cette machine utilise le protocole NTP pour synchroniser son horloge. Alors, j'ai pris mon courage à deux mains pour déterrer cette tour, enlever la poussière qui s'y était accumulée depuis plusieurs années, démonter la carte graphique pour pouvoir avoir accès à ce que je pensais être le coupable idéal: la pile de l'horloge RTC.

Voici le voltage que j'ai pu mesurer à ses bornes:

Batterie RTC HS

Verdict: à 39.6 mV, la pile est déchargée. Par bonheur, j'en avais une autre qui traînait dans mon stock et qui affichait la valeur correcte d'un peu plus de 3V. J'ai donc fait l'échange et mon problème a été résolu (attention à remettre la pile dans le bon sens, sinon ça ne marche pas).

Que retirer de cette histoire ? C'est la première fois que je vois une batterie CR2032 HS depuis le temps que je possède des ordinateurs. Ça ne m'était jamais arrivé auparavant. Ça signifie que je possède cette tour depuis longtemps. Effectivement, je l'ai achetée en 2012, ce qui fait près de 7 ans au moment de la rédaction de cet article.

Pour autant, je crois que je n'ai jamais gardé une machine aussi longtemps. C'est la machine la plus puissante de mon parc et je sais que je n'ai pas encore atteint ses limites. Je m'en sert très régulièrement pour tout un tas de travaux qui vont de compilations lourdes à de l'encodage vidéo. Et je trouve qu'elle a toujours du répondant. Serait-ce le signe que l'amélioration des performances des ordinateurs commence à stagner ? Qu'on a enfin atteint les limites ? Que les logiciels sont enfin moins consommateurs de ressources ? Que mon utilisation personnelle de cette machine est en phase avec ses capacités ? Je n'en sais rien...

... mais, sans nul doute, c'est un signe !

Posted lun. 15 juil. 2019 18:39:45 Tags:

Introduction

Cela fait maintenant de nombreuses années que je gère ma propre plate-forme publique Internet et par la même occasion ma propre plate-forme de courrier électronique. D'une manière générale, je suis assez content de ce que j'ai mis en place.

C'est souvent le cas sur le courrier électronique, au moins sur la partie serveur. Les logiciels sont "vieux" matures, ils ont fait leur preuve. Les protocoles évoluent peu. Une fois que la chaîne est en place, à part suivre les correctifs de sécurité et les évolutions majeures des logiciels serveurs, il n'y a pas grand chose à faire et c'est tant mieux.

Ceci dit, le courrier électronique continue à évoluer dans le temps, notamment à l'initiative du renforcement de la sécurité et de la protection de la vie privée. C'est le cas avec la RFC8314 qui vient un peu bousculer la manière de gérer une plate-forme de courrier électronique. Cela faisait quelques temps que je l'avais vue passer et, après avoir trouvé un peu de temps et un prétexte pour revoir la configuration de mon serveur public, j'ai essayé de la mettre en oeuvre.

Cet article a pour but d'illustrer les principaux changements que j'ai intégré sur ma plate-forme de courrier électronique pour me conformer du mieux que possible à la RFC8314.

Qu'est-ce-qu'il y a dans cette RFC ?

Dans les faits, la RFC est plutôt courte en contenus spécifiques. Pour résumer sérieusement, elle indique qu'il faut faire en sorte de chiffrer au maximum tous les canaux d'accès clients à un serveur SMTP (un MTA).

Pour rappel un MTA peut faire grosso modo deux choses: - Récupérer des courriers électroniques envoyés par un autre MTA pour un des clients de notre plate-forme. - Envoyer, pour le compte des clients de la plate-forme, des courriers électroniques à d'autres MTA.

La RFC8314 s'attaque au deuxième point. Elle stipule qu'il est maintenant temps de passer au tout chiffré pour la communication entre tous les composants serveurs de la plate-forme (MTA/MDA comme Dovecot) et les clients. Pour ce faire, deux ports réseaux sont dédiés à ces communications:

  • le port 465 pour le protocole SMTPS.
  • le port 993 pour le protocole IMAPS.

Les accès non chiffrés sont à proscrire ainsi que les accès en STARTTLS. Cette dernière technique permettait d'initier une communication chiffrée à partir d'un port en clair. Le début de la conversation réseau était néamoins en clair. C'est ce que j'avais mis en place sur ma plate-forme de courrier électronique car c'était plus simple pour moi d'avoir un seul port pour chaque service (143 pour IMAP+STARTTLS et 25 pour SMTP+STARTTLS).

De plus, cette RFC lève une grosse ambiguité sur le port SMTP à utiliser pour les clients. Jusque là, on avait le choix entre le port 25 (qui sert également pour le traffic MTA à MTA), le port 587 (pour les clients en clair et en STARTTLS) et le port 465 pour l'accès complet TLS. Ce dernier n'était qu'un standard de fait, pas un truc obligatoire et, à une époque donnée, déconseillé. Le port IANA 465 a méme été affecté à un autre protocole même si de nombreux administrateurs de MTA continuaient à l'utiliser pour leurs clients.

Pour le coup, la RFC clarifie vraiment les choses. Néanmoins, elle permet de conserver certaines anciennes configurations, histoire de gagner du temps. Néanmoins, ce n'est pas l'approche que j'ai retenue. En effet, tant qu'à faire de faire une modification de configuration, autant y aller franco et se mettre dans les clous tout de suite. De plus, comme j'ai très peu de clients à gérer, la migration est assez simple.

La RFC8314 ne s'adresse qu'aux communications clients MTA et pas à celles entre MTA car, si en règle générale, un administrateur de MTA peut avoir un controle sur les clients de sa plate-forme, ce n'est pas vrai du tout pour les autres MTA. Certains peuvent très bien ne pas savoir utiliser de canal chiffré, ou ne pas vouloir (enfin en 2019, cette affirmation tient plus de la paresse que de l'écueil technique).

Le traffic de MTA continuera donc à se faire sur le port 25, que ce soit en clair ou en STARTTLS.

Résumé de ce que nous devons faire

  • Créer une conf dans Exim pour ouvrir le port 465 en TLS v1.2.
  • Interdire la soumission authentifiée des clients sur le port 25 (y compris pour STARTTLS) tout en maintenant l'accès pour les autres MTA.
  • Modifier la conf de Dovecot pour ouvrir le port imaps et fermer le port imap en clair (TLS sur demande).
  • Ouvrir le firewall sur les nouveaux ports.
  • Publier les annonces de service dans le DNS.

Ouverture des ports au niveau du pare-feu

Nous allons commencer avec le pare-feu. En effet, ça ne sert à rien de configurer un truc si on ne peut pas le tester. Pour ma part, j'ai juste ajouté les ports TCP 465 et 993 et retiré le port TCP 143 étant donné que je vais supprimer l'accès IMAP en clair.

Voici mon fichier de configuration de nftables (/etc/nftables.conf):

#!/usr/sbin/nft -f

# Configuration NFT de serveur

# On vide toutes les règles
flush ruleset

# Table pour un serveur public un peu fermé
table inet public_server {
  # Chaîne de gestion de tout ce qui rentre
  chain entree {
    # Chaîne de type filtre, en entrée
    type filter hook input priority 0;

    # Accepter tout ce qui vient de l'interface loopback
    meta iif lo accept

    # Accepte le trafic qui vient de la machine
    ct state established,related accept

    # Réponse au ping IPv4
    ip protocol icmp accept

    # OUverture de i2pd
    meta skuid i2pd accept

    # Ouverture des ports publics
    tcp dport { 25,53,80,443,465,993 } accept
    udp dport { 53 } accept

    # Compter et journaliser ce qui est rejeté
    counter log prefix "Connexion entrante rejetée: " level warn drop
  }

  # Chaîne de gestion de tout ce qui sort
  chain sortie {
    type filter hook output priority 0;

    # On accepte les sorties vers l'interface loopback
    meta oif lo accept

    # Réponse au ping IPv4
    ip protocol icmp accept

    # Ouverture pour i2pd
    meta skuid i2pd accept

    # Ouverture des ports publics
    tcp sport { 25,53,80,443,465,993  } accept
    udp sport { 53 } accept

    # Ouverture des ports externes classiques utilisés par le système
    tcp dport { 25,53,80,143,443,465,993,995 } accept
    udp dport { 53 } accept

    # count and drop any other traffic
    counter log flags skuid prefix "Connexion sortante rejetée: "
    level warn drop
    }
}

Je vous invite à le mettre en service au moment où vous terminez les configurations des différents logiciels serveurs histoire de minimiser l'arrêt de service (lancé en l'état, le port 143 peut encore être utilisé par des clients).

Configuration d'Exim4

Introduction

Pour comprendre ce qu'il faut faire, mieux vaut d'abord lire la documentation à ce sujet.

Autre point à noter au cas où: la version d'Exim4. Pour ma part, j'utilise le backport de Debian Stretch, soit la version 4.92-8 qui est la même que celle de Debian Buster (la nouvelle distribution stable de Debian au moment de la rédaction de cet article). Je peux donc utiliser la documentation du dessus.

Autre chose à savoir, Exim4 sous Debian utilise GNUTls et non openssl. Cela a une incidence sur la configuration.

Enfin, comme si cela n'était pas déjà assez compliqué, Debian a une manière particulière de configurer Exim4. Pour ma part, j'ai choisi la configuration éclatée. Ce qui suit concerne ce type de configuration. Néanmoins, vous pouvez vous en inspirer pour faire vos modifications.

Préparation d'un fichier de paramètres Diffie-Hellman digne de ce nom

Les bonnes pratiques de sécurité recommendent d'utiliser une meilleure taille de clef dans les échanges Diffie-Hellman. En général, la taille des échanges de clef est limitée à 2048. Pour ma part, je préfère la monter à 4096 octets grâce à la commande suivante:

$ openssl dhparam 4096 > dh.pem

Attention, la génération de ce fichier prend pratiquement 1h sur ma station de travail (de 2012).

Ensuite, vous pouvez utiliser ce fichier dans Exim4 (/etc/exim4/dh.pem) et également dans Dovecot (remplacer /etc/dovecot/dh.pem). Ce fichier peut être public.

Configuration TLS d'Exim4

La première des choses, c'est de modifier la configuration principale d'Exim (le main). Sous Debian, on trouve essentiellement conf.d/main/03_exim4-config_tlsoptions pour gérer ces éléments de configuration.

### main/03_exim4-config_tlsoptions
#################################

# TLS/SSL configuration for exim as an SMTP server.
# See /usr/share/doc/exim4-base/README.Debian.gz for explanations.

.ifdef MAIN_TLS_ENABLE
# On affiche qu'on souhaite avertir tout le monde qu'on fait du TLS.
.ifndef MAIN_TLS_ADVERTISE_HOSTS
MAIN_TLS_ADVERTISE_HOSTS = *
.endif
tls_advertise_hosts = MAIN_TLS_ADVERTISE_HOSTS

# Configuration des clefs/certificats TLS
.ifdef MAIN_TLS_CERTKEY
tls_certificate = MAIN_TLS_CERTKEY
.else
.ifndef MAIN_TLS_CERTIFICATE
MAIN_TLS_CERTIFICATE = CONFDIR/exim.crt
.endif
tls_certificate = MAIN_TLS_CERTIFICATE

.ifndef MAIN_TLS_PRIVATEKEY
MAIN_TLS_PRIVATEKEY = CONFDIR/exim.key
.endif
tls_privatekey = MAIN_TLS_PRIVATEKEY
.endif

# On indique le fichier de paramétrage de Diffie-Hellman
.ifdef _HAVE_GNUTLS
tls_dhparam = /etc/exim4/dh.pem
tls_dh_max_bits = 4096
.endif

# On ajoute le port submissions
tls_on_connect_ports = 465

# On active le port submissions en plus du smtp simple
daemon_smtp_ports = 25 : 465

.else
# Si l'option MAIN_TLS_ENABLE n'est pas activée, on n'affiche rien.
tls_advertise_hosts =
.endif

Une fois rédigé, il reste à renseigner les valeurs des macros sus-citées (ce qui est en majuscule). Pour ma part, je place tout dans conf.d/main/000_localmacros:

# on active TLS:
MAIN_TLS_ENABLE = true

# Emplacements des fichiers de clef pour TLS:
MAIN_TLS_CERTIFICATE = /etc/exim4/keys/medspx.fr.cert.pem
MAIN_TLS_PRIVATEKEY = /etc/exim4/keys/medspx.fr.pkey.pem

Les fichiers de clefs mentionnés ci-dessus sont des symlinks vers une configuration Let's Encrypt.

Gestion de l'authentification

Pour ma part, je n'ai pas eu grand chose à faire si ce n'est relire ma conf existante et considérer qu'elle était suffisante, à un petit détail près. En effet, pour l'authentification, je n'ai activé que le pilote d'authentification PLAIN. Depuis le début, je fais du STARTTLS et j'ai bien sûr indiqué que la commande SMTP AUTH ne serait présentée (et activée) uniquement lorsque la communication est chiffrée (par STARTTLS ou par TLS en direct).

Néanmoins, je souhaite aussi désactiver l'authentification en dehors du port dédié à ça (le port 465). Tout ce que j'ai trouvé de bon est d'utiliser la directive server_advertise_condition et de la faire tester que le port utilisé sur le serveur est le port 465. En pratique, ça donne ces quelques lignes dans le fichier conf.d/auth/20_exim4-config_virtual:

### auth/20_exim4-config_virtual
#################################

# Configuration de l'authentification avec un compte virtuel

plain_server:
  driver = plaintext
  public_name = PLAIN
  server_condition = "${if crypteq{$auth3}{\\\{md5\\\}${extract{1}{:}{${lookup{$auth2}lsearch{CONFDIR/passwd}{$value}{fail}}}}}{1}{0}}"
  server_set_id = $auth2
  server_prompts = :
  server_advertise_condition = ${if eq{$tls_in_cipher}{} {}{${if eq{$received_port}{465} {*}{}}} }

L'astuce consiste à indiquer dans la partie de vérification de la présentation de l'option AUTH une double condition: si la connexion est chiffrée (par TLS ou STARTTLS), alors on regarde si le port utilisé est 465, dans ce cas, on renvoie '*', dans les 2 autres cas, on ne renvoie rien, ce qui a pour effet de ne pas présenter l'option AUTH et donc d'invalider toute tentative d'authentification.

On aurait pu désactiver complètement STARTTLS sur le serveur mais c'est un mécanisme de chiffrement utilisé aussi par les serveurs MTA qui vous relaient un message (et sur lesquels, vous avez peu d'impact).

Si vous lisez la variable server_condition, vous vous rendrez compte que ma méthode de gestion des comptes authentifiés est pourrie (mais j'assume). En effet, le fichier d'authentification se nomme /etc/exim4/passwd et il contient un truc du genre:

nom_du_compte:hash_md5_du_mot_de_passe

Pour générer le hash MD5 du mot de passe, il suffit de faire:

$ echo -n the_harsh_password | md5sum

C'est la méthode de stockage de mots de passe que j'utilise depuis longtemps et elle a fait ses preuves, notamment parce qu'elle se passe de tout lien entre le compte utilisé dans Exim et le compte de l'utilisateur sur le système. Ok, MD5 en 2019... Oui, j'avais la flemme de changer tous les comptes !

Remise en service

Un simple systemctl restart exim4 devrait suffire. Pour vérifier que le service est fonctionnel sur le bon port, vous pouvez utiliser ss:

# ss -tlp

Configuration de Dovecot

Planification

Maintenant, passons à Dovecot. Nous voulons supprimer l'écoute sur le port 143, activer l'écoute sur le port 993 (imaps) et également indiquer d'utiliser TLSv1.2 ainsi que l'utilisation de Diffie-Hellman un peu partout.

Pour ma part, j'utilise la version 2.3 de Dovecot (il y a eu des évolutions entre la 2.2 et la 2.3 sur TLS notamment).

Gestion des ports

Pour cette partie, il suffit d'éditer le fichier /etc/dovecot/conf.d/10-master.conf:

# On désactive le protocole IMAP sur le port réservé
service imap-login {
  inet_listener imap {
    port = 0
  }

# On active le service IMAPS
  inet_listener imaps {
    port = 993
    ssl = yes
  }

  # Nombre de connexions clientes simultanées max avant kill du processus.
  service_count = 10
}

# On désactive complètement POP3
service pop3-login {
  inet_listener pop3 {
    port = 0
  }
  inet_listener pop3s {
    port = 0
    #ssl = yes
  }
}

Implémentation de TLSv1.2

Tout se passe dans le fichier /etc/dovecot/conf.d/10-ssl.conf:

##
## SSL settings
##

# On force l'utilisation de TLS par défaut
ssl = required

# Le chemin vers les clefs/certificats pour le chiffrement
ssl_cert = </etc/letsencrypt/live/medspx.fr/fullchain.pem
ssl_key = </etc/letsencrypt/live/medspx.fr/privkey.pem

# Paramètres Diffie-Hellman
ssl_dh = </etc/dovecot/dh.pem

# Version minimum de TLS: v1.2
ssl_min_protocol = TLSv1.2

# Liste des ciphers autorisés
ssl_cipher_list = ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH

# On indique qu'il faut   suivre l'ordre des ciphers du serveur
ssl_prefer_server_ciphers = yes

# On supprime la compression TLS (qui peut poser problème)
ssl_options = no_compression

Remise en service

Un simple systemctl restart dovecot devrait suffire. Pour vérifier que le service est fonctionnel sur le bon port, vous pouvez utiliser ss:

# ss -tlp

Annonces DNS

Il reste à faire les annonces de services sur le DNS. Pour ma part, je n'ai eu à ajouter que peu de choses: les deux lignes qui suivent...

_imaps._tcp 84600 IN SRV 0 5 993 medspx.fr.
_submissions._tcp 84600 IN SRV 0 5 465 medspx.fr.

La première concerne imaps sur le port 993, la seconde le service de soumission client mail sur le port 465. Pour ce dernier, le nom du service est submissions avec un s pour indiquer que ce service est en TLS direct.

Pensez également à supprimer les anciennes déclarations des ports qui ne sont maintenant plus accessibles.

Configuration des clients

msmtp

J'utilise mstmp pour faire remonter les messages de courrier électroniques des serveurs vers mon compte (humain) afin d'avoir des informations en cas de problème.

Voici un fichier de configuration global pour msmtp (/etc/msmtprc):

# Configuration par défaut
defaults
aliases /etc/aliases
auth on
tls on
tls_starttls off

# Définition du compte
account medspx.fr
domain medspx.fr
host medspx.fr
port 465
from jerk@medspx.fr
user jerk
password password

# Activation du compte par défaut
account default : medspx.fr

La phrase importante ici c'est tls_starttls. En effet, par défaut, lorsqu'on active TLS dans msmtp, celui-ci essaye de faire du STARTTLS. Or, nous avons du TLS implicite (par défaut). Il faut donc désactiver STARTTLS et c'est ce que fait la variable tls_starttls à off.

mutt

Pour mutt, il suffit de remplacer les url du type imap: par imaps: et smtp par smtps, pardi !

Voici un exemple de configuration (~/.muttrc):

set smtp_url = "smtps://mederic.ribreux@medspx.fr/"
set spoolfile = "imaps://medspx.fr/INBOX"
set folder = "imaps://medspx.fr/"

Attention, mutt utilise la libsasl2 pour gérer l'authentification SMTP (et bizarrement, pas pour IMAP). Or, par défaut cette dernière a besoin de modules pour gérer l'authentification en mode PLAIN. Vous devez donc installer le paquet libsasl2-modules.

Thunderbird

De ce côté, rien à signaler, il suffit de modifier les protocoles STARTTLS en SSL/TLS purs. Ça marche, y compris pour la version ESR de thunderbird !

Roundcube

J'utilise Roundcube et j'ai dû modifier l'URL de connexion pour la partie IMAP. Néanmoins, ce n'est pas suffisant. En effet, je souhaite que le traffic entre Roundcube et le serveur IMAP se fasse sur localhost et non en sortant sur le réseau. Le problème de passer par localhost, c'est que le certificat qui est utilisé par Dovecot prend en compte un CN qui est le FQDN du serveur et non localhost.

Pour contourner le problème, on peut utiliser la directive de configuration 'imap_conn_options':

$config['default_host'] = 'imaps://localhost:993';
$config['imap_conn_options'] = array(
  'ssl' => array(
      'peer_name'  => 'FQDN of server'
  ),
);

Pareil pour la partie SMTP:

$config['smtp_server'] = 'ssl://localhost';
$config['smtp_conn_options'] = array(
  'ssl' => array(
        'peer_name'  => 'FQDN of server'
  ),
);

$config['smtp_port'] = 465;

Conclusions

Ouf, c'est fini et ça semble fonctionner. Encore une fois, Exim4 est riche de fonctionnalités mais reste complexe à configurer. J'ai dû passer environ 2ou 3h de recherches pour vraiment comprendre et trouver comment faire. Et encore, je n'ai pas touché à la gestion des algos de chiffrement pour TLSv1.2. En même temps, je ne fais pas ça tous les jours...

Du côté de Dovecot, ça se passe plus facilement. Seule la génération de la conf TLS prend un peu de temps pour comprendre.

Du côté de la configuration des clients, j'ai eu aussi quelques suprises, notamment pour mutt (à cause du SASL) et pour Roundcube (ce qui m'a valu quelques bannissement par fail2ban).

Ce RFC fait quand même du bien au sysadmins de la chaîne de courrier électronique. En effet, il clarifie avec précision ce qu'il faut désormais faire. Auparavant, il subsistait un problème avec le standard de fait (donc pas un standard) du port 465. J'ai toujours refusé de le mettre en place sur ma plate-forme de courrier électronique à cause de ce manque de clarification.

Maintenant, tout est (à peu près) correct et je suis reparti pour 2 ou 3 ans de paix avec mon courrier électronique qui arrive à destination et mes clients qui communiquent maintenant uniquement sur canal chiffré.

Références

Posted mar. 16 juil. 2019 23:34:57 Tags:

Introduction

Debian Buster est sortie récemment et prend en charge SecureBoot. J'avais toujours refusé de m'occuper de cette manière de booter mais, à l'occasion de la sortie de la dernière Debian, je me suis dit que je devais essayer de m'intéresser au sujet.

En suivant les instructions officielles, je suis parvenu à booter avec SecureBoot en pratiquement une seule ligne de commande et une manipulation dans le BIOS (j'étais déjà en boot UEFI).

Mais après l'effet whaou, je me suis rendu compte que Wireguard ne fonctionnait plus. En effet, ce dernier n'est (pas encore) inclus dans le noyau Linux et il est fourni via DKMS sous Debian (il est compilé à l'installation du paquet). Or, quand SecureBoot est activé, les modules non signés par la clef Debian ne sont pas activé (une conf du kernel pour ça). Sur mon portable, ça va encore plus loin: le pilote Wifi est en DKMS !

Néanmoins, j'ai lu qu'il devait être possible de signer ces modules pour pouvoir les lancer. Voici ce que j'ai trouvé (qui bien sûr n'est pas une référence)...

Pré-requis

  • Avoir un BIOS qui gère UEFI et SecureBoot.
  • Avoir installé shim-signed et consorts.
  • Avoir des modules DKMS installés (sinon, vous n'avez pas de problème avec SecureBoot).
  • Installer le paquet: mokutil

Génération des clefs de signature

Ici, rien de fantoche, on va juste créér un couple clef privée/publique en RSA sur 2048bits. La clef publique sera convertie au format DER puis uploadée dans la base MOK.

Pour des questions de sécurité et aussi de facilités, nous allons stocker ces clefs dans le répertoire /etc/dkms/keys créé en mode 700.

On va donc commencer à génerer le couple clef privée/publique. Elle sera valable 10 ans et pour l'identifier, je vais mettre un CN avec le nom de la machine:

# mkdir -p /etc/dkms/keys
# chmod 700 /etc/dkms/keys
# cd /etc/dkms/keys/
# openssl req -new -x509 -newkey rsa:2048 -keyout MOK.key -out MOK.crt -nodes -days 3650 -subj "/CN=nom_de_machine/"

Ensuite, nous allons simplement la convertir au format DER:

# openssl x509 -in MOK.crt -out MOK.der -outform DER

Ok, nos clefs sont disponibles.

Chargement des clefs dans la "base" MOK

Maintenant, il reste à injecter la clef publique dans la base MOK. Pour cela, nous allons utiliser l'outil mokutil qui indique à shim de charger des clefs au prochain reboot:

# mokutil --import /etc/dkms/MOK.der

Attention, mokutil va vous demander un mot de passe pour protéger l'accès à cette clef (uniquement pour son chargement dans la base MOK via shim). Je vous conseille de mettre un mot de passe qui se tape bien en qwerty sinon, vous pourriez très bien ne pas pouvoir le taper au clavier.

Vous pouvez maintenant redémarrer la machine et vous aurez un écran spécifique de shim pour importer la clef publiquer dans la base MOK. Oui, ça fait bizarre de devoir rebooter mais c'est la méthode sécurisée pour le faire: ça évite qu'un mex qui accès à mokutil puisse balancer des clefs dans votre dos...

Procédure de signature manuelle

Voyons maintenant comment signer nos modules pour les faires prendre en compte par MOK. Il existe tout simplement un outil dédié à ça dans le paquet linux-kbuild.

# cd /usr/lib/modules/4.19.0-5-adm64/updates/dkms/
# /usr/lib/linux-kbuild-/scripts/sign-file sha256 /etc/dkms/keys/MOK.key /etc/dkms/keys/MOK.der ./monmodule.ko

Sous Debian, les modules DKMS ne sont pas compressés donc on peut les signer directement. Si vous voulez faire un test, faites un simple modprobe sur votre module et il devrait se charger tout seul.

Automatisation

Maintenant que nous avons une méthode assez basique, il reste à l'automatiser. En effet, à chaque mise à jour du noyau, il faudrait resigner les modules. Le faire à la main à chaque fois est fastidieux. Heureusement, dkms possède des éléments permettant de faire le job. Encore faut-il les connaître.

Bon, DKMS est un utilitaire codé en Bash ! En lisant le code et aussi le manuel qui semble complet, on apprend que le fichier /etc/dkms/framework.conf contient des directives de configuration qui s'appliquent à tous les scripts dkms.conf.

En poursuivant l'étude, on voit une variable nommée POST_BUILD qui contient l'emplacement d'un script à lancer après le build des modules. Parfait, ça semble être le bon endroit pour indiquer un script de signature. Néanmoins, le script est lancé sans arguments...

Mais, pas de bol, POST_BUILD, comme d'autres variables ne sont pas accessibles via framework.conf. Le seul "truc" qu'on peut faire, c'est de nommer un fichier .conf avec le même nom que le module et y ajouter notre POST_BUILD. Un peu laborieux si on a beaucoup de modules mais ça ne se fait qu'une fois !

Avant tout ça, il nous faut un script qui puisse se lancer sans arguments et qui récupère les variables définies auparavant dans la chaîne DKMS pour nous permettre de signer le bon module (avec la bonne clef).

#!/bin/bash

# Sign a DKMS module with a key

privKey="/etc/dkms/keys/MOK.key"
pubKey="/etc/dkms/keys/MOK.der"
KERNVER=$(awk -F . '{print $1"."$2}' <<< "$(uname -r)")

cd ../$kernelver/$arch/module/

for kernel_object in *ko; do
    echo "Signing kernel_object: $kernel_object"
    /usr/lib/linux-kbuild-$KERNVER/scripts/sign-file sha256 $privKey $pubKey "$kernel_object";
done

exit 0

N"oubliez pas de le rendre exécutable par root au moins.

Ensuite, il reste à modifier le fichier /etc/dkms/monmodule.conf et y ajouter la ligne:

POST_BUILD=../../../../../../etc/dkms/sign_module.sh

Au prochain build DKMS du module, votre module sera automatiquement signé (même si en passant par apt install, on ne voit pas forcément le message de notre script).

Conclusions

Ce que j'ai décris ressemble presqu'à un hack mais ça fonctionne à peu près. Quelle joie de se dire qu'on a une machine sécurisée par SecureBoot mais qui utilise aussi des modules signés avec notre propre clef.

Avec l'implémentation Debian de SecureBoot, il n'y a finalement pas tant de choses à faire que ça et c'est tant mieux. Peut-être qu'un jour il y aura une méthode officielle pour ça ?

Références

Posted sam. 20 juil. 2019 23:56:52 Tags: