blog

RSS

Blog

Berkeley is famous for LSD and BSD UNIX. I don't think that it is a coincidence.

Après Adieu GnuPG ! chiffrer des secrets sans GnuPG, suite de cette série de posts sur l'authentification forte : il s'agit maintenant de protéger mes clefs SSH pour m'authentifier et ce, avec des YubiKeys ne supportant pas GnuPG (tout jeton FIDO devrait convenir).

Depuis OpenSSH 8.2 (améliorations dans 8.3), les clefs SSH peuvent être protégées par FIDO U2F (et stockées sur le jeton). La spécification est dans le dépôt.

Avant de commencer, installer YubiKey manager pour changer le PIN de l'applet FIDO (différent de PIV, pas de PUK pour FIDO).

Générons une telle clef :

$ ssh-keygen -t ecdsa-sk -O resident -O verify-required -O application=ssh:perso
Generating public/private ecdsa-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter PIN for authenticator:
You may need to touch your authenticator (again) to authorize key generation.
Enter file in which to save the key (/home/nomp/.ssh/id_ecdsa_sk):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/nomp/.ssh/id_ecdsa_sk
Your public key has been saved in /home/nomp/.ssh/id_ecdsa_sk.pub
The key fingerprint is:
SHA256:0uHAzPX9VEMlWe+fXVtwt/RCwGJTyO9+A2TcS43tO8g pc@plop
The key's randomart image is:
+-[ECDSA-SK 256]--+
|        .. +o o==|
|     + . .*....oo|
|      = ...=..+=+|
|       + .  =+=+*|
|      . S  + .o+=|
|       .    o ..O|
|           ....o+|
|            .Eoo |
|             . ..|
+----[SHA256]-----+

Pour les jetons FIDO (et pas FIDO2), seules les clefs P-256 sont possibles.

Ici, on stocke la clef SSH sur le jeton FIDO (-O resident) avec l'« application » ssh:perso (pour différencier les clefs si on doit en avoir plusieurs) ; par défaut, la chaîne ssh: est utilisée. Enfin, on impose la vérification du PIN de l'usager à chaque signature avec -O verify-required.

Déplaçons les fichiers /home/nomp/.ssh/id_ecdsa_sk et /home/nomp/.ssh/id_ecdsa_sk.pub. Avec :

$ ssh-keygen -K

on vérifie que la clef est extractible et donc correctement stockée sur le jeton (cette commande extrait la clef (privée) pour l'enregistrer sur le disque) ; évidemment, le PIN FIDO est demandé. On peut alors supprimer les fichiers /home/nomp/.ssh/id_ecdsa_sk*. On charge maintenant la clef avec la commande :

$ ssh-add -K

Le PIN FIDO est demandé. La commande :

$ ssh-add -L
sk-ecdsa-sha2-nistp256@openssh.com AAA...NoOg==

affiche la clef publique ; on note que son type est préfixé par sk- (pour security key).

NB : comme le jeton Yubico se bloque après trois tentatives erronées, un PIN de six caractères protège correctement la clef SSH.

Lors de chaque accès SSH, la YubiKey clignote et il faut appuyer sur le jeton pour que l'authentification se fasse si on n'a pas ajouté -O verify-required lors de l'étape de création. Sinon, le PIN est demandé.

Du point de vue de l'administrateur, il est possible d'appliquer des politiques d'authentification différentes selon qu'on s'authentifie avec une clef protégée par FIDO ou non. Le serveur peut requérir une preuve de présence (toucher la clef) lors de chaque authentification. Enfin, ces clefs peuvent être intégrées à une autorité de certification, comme les clefs SSH traditionnelles.

Imaginons deux groupes d'utilisateurs Unix. Un groupe, sksshusers dispose de clefs SSH protégées par FIDO, stockées dans des jetons comme les YubiKeys. Un autre groupe, ici sshusers, n'en dispose pas. Dans la configuration du daemon sshd(8) l'administrateur peut imposer des contraintes d'authentification différentes :

Match Group sksshusers,!sshusers Address *
    AuthenticationMethods publickey
    # generated by:
    # $ ssh -Q PubkeyAcceptedKeyTypes | grep sk- | tr '\n' ',' | sed -e 's/,$/\n/'
    PubkeyAcceptedKeyTypes sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,webauthn-sk-ecdsa-sha2-nistp256@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
    Match all

Match Group sshusers,!sshskusers Address *
    AuthenticationMethods publickey,password
    Match all

On peut ajouter une politique PubkeyAuthOptions, par exemple pour imposer la saisie du PIN (verify-required), que la clef ait été créée avec cette option ou pas.

Cette configuration offre une authentification renforcée : le groupe sshusers s'authentifie par clef SSH et mot de passe, le groupe sshusers s'authentifie par clef SSH et jeton FIDO. On peut ajouter une politique PubkeyAuthOptions, par exemple pour imposer la saisie du PIN (verify-required) du jeton FIDO, que la clef SSH ait été créée avec cette option ou pas.

NB : couplé à authpf(8) sous OpenBSD ou à un filtrage du propriétaire des sockets avec skuid dans nftables(8) sous Linux, on peut, sur une passerelle SSH, avoir des ACL par utilisateur… mais c'est une autre histoire.

2023/04/02 23:06 · pc

Depuis… 2014 déjà, j'utilisais des clefs YubiKey, exclusivement pour OpenPGP. Avec GnuPG, j'avais donc de quoi signer et chiffrer des fichiers (j'utilisais le merveilleux https://www.passwordstore.org/ aka pass) et de quoi m'authentifier par SSH. Et tout cela, de manière transparente, que ce soit sur macOS, Debian, NetBSD, OpenBSD ou RHEL. Qui plus est, dans les trois cas (authentification, (dé)chiffrement, signature), j'avais deux facteurs avec chiffrement fort.

Mon employeur a acquis des clefs certifiées ANSSI (Yubico lance la série YubiKey 5 CSPN, la première gamme d’authentificateurs multi-protocoles FIDO2/WebAuthn/PIV/OTP certifiés CSPN (certification ANSSI)). Ces clefs n'offrent pas OpenPGP, uniquement PKCS#11 (PIV) et FIDO ; les YubiKey NEO, 4 et 5 fournissent aussi PKCS#11 et FIDO.

Avec les trois logiciels suivants, je retrouve mon cher pass : passage utilise rage, ce dernier sait utiliser une clef sur une YubiKey avec age-plugin-yubikey. Rage est une implémentation en Rust d'age, outil particulièrement simple de chiffrement de fichiers dont la spécification se lit ici : https://age-encryption.org/v1.

Les clefs utilisées nativement par age sont des EC (Ed25519), le chiffrement est assuré par Chacha20-Poly1305 ; chaque fichier est chiffré par sa propre clef symétrique, aléatoire, chiffrée par la clef EC. Comme PKCS#11 n'offre pas Ed25519, le plugin age-plugin-yubikey crée des clefs P-256.

Avant de changer de gestionnaire de mot de passe, il faut accéder à la clef et la configurer. Pour cela, installer OpenSC et son provider PKCS#11 que l'on retrouve dans les gestionnaires de paquets sous différents noms :

  1. pcscd sous Debian/Ubuntu
  2. pcsc-lite sous NetBSD, OpenBSD et RHEL

NB : sur macOS, CryptoTokenKit utilise OpenSC dans une version modifiée qui ne supporte pas les courbes elliptiques, installer la version sur Github.

age supporte de chiffrer un même secret avec plusieurs clefs. Ainsi, on dispose d'une solution de redondance avec plusieurs jetons ou (pas exclusif) de partage de secrets à plusieurs, chacun sa (ou ses…) clefs de chiffrement, indispensable pour travailler en équipe !

Avant de générer une clef, installer YubiKey manager pour changer PIN, PUK et clef de gestion de l'applet PIV. age-plugin-yubikey ne supporte encore que TDES comme algorithme de clef de gestion, ce qui n'est pas dérangeant étant donné le tout petit volume de données au regard de ce dont sweet32 a besoin.

2023/04/02 23:05 · pc

De nos jours, tout le monde vante l'authentification multi-facteurs comme la solution à tous les maux du phishing. À y regarder de plus près, chaque constructeur, chaque éditeur de logiciel, etc. fournit sa solution d'authentification forte. Par exemple, sur les équipements réseau, chaque éditeur impose son autorité de certification si on veut authentifier les accès à privilèges par certificat X.509. Ça n'est pas viable si on veut s'épargner la gestion d'une autorité de certification.

Il existe des solutions d'authentification forte normalisées et sûres comme FIDO ou OATH. Heureux propriétaire de YubiKey depuis presque dix ans, je n'utilisais jusqu'à il y a peu que HMAC-SHA1 et OpenPGP mais j'utilise de plus en plus FIDO (clefs SSH) et surtout OATH TOTP.

Commençons par TOTP : il s'agit de Time-based One Time Password c'est à dire mot de passe à usage unique basé sur le temps : l'horloge fait varier le mot de passe. L'algorithme est spécifié dans la RFC 6238 ; il est décrit dans Mot de passe à usage unique basé sur le temps et utilise un secret partagé (20 octets aléatoires), stocké sur un serveur et dans la YubiKey. L'échange de secrets se fait par une URI OATH détaillée sur cette page, URI souvent échangée par QR code.

NB : évidemment, il faut synchroniser les horloges, NTP sert à ça.

Depuis la version 2.5 d'OpenLDAP, est intégré (cf. bug #9437) slapo-otp(5). Ce module intègre un schéma ad hoc pour stocker des clefs TOTP en plus du mot de passe userPassword. Il suffit de charger le module binaire.

Soit une politique (l'OID 1.2.840.113549.2.7 définit HMAC-SHA1) :

dn: cn=oathtokens,ou=policies,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: oathTOTPParams
cn: oathtokens
sn: oathtokens
oathOTPLength: 6
oathHMACAlgorithm: 1.2.840.113549.2.7
oathTOTPTimeStepPeriod: 30
oathTOTPTimeStepWindow: 3
structuralObjectClass: person

Ajoutons maintenant à un posixAccount les attributs suivants :

dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: posixAccount
...
oathSecret:: A3ag...d17I=
oathTOTPParams: cn=oathtokens,ou=policies,dc=example,dc=com
oathTokenSerialNumber: 19201802
oathTOTPToken: uid=jdoe,ou=people,dc=example,dc=com

oathSecret contient le secret partagé encodé en base64 (ici oathSerialNumber contient le numéro de série du jeton YubiKey, c'est purement optionnel).

Lorsque jdoe s'authentifie, il saisit son mot de passe suivi des six chiffres donnés par sa « calculatrice OATH » (un script, une application comme Yubico Authenticator, …). Par exemple monpasswordamoiquejai123456.

Ainsi, toutes les applications dont l'authentification est adossée à cet annuaire offrent maintenant une authentification à deux facteurs pour ce DN : accès RADIUS aux équipements réseau, applications web diverses et variées, pam_ldap, bref tout ce qui repose sur LDAP.

NB : si les entrées oath* sont supprimées, seul son mot de passe stocké dans userPassword est requis.

MinTOTP propose en quelques lignes de Python une « calculatrice TOTP ». Il ne manque plus qu'un peu de glue code pour :

  • générer le secret partagé
  • l'enregistrer au bon format dans l'annuaire
  • le présenter à l'utilisateur sous forme de QR code (qrcode)

NB : sur OpenBSD, le module login_totp-and-pwd(8) permet lui aussi une authentification à deux facteurs avec OATH TOTP. Par contre, il faut saisir 123456/password et non pas password123456 ie. dans l'ordre inverse et avec un / entre les deux.

Pour finir, rappelons que le secret TOTP peut être stocké dans un jeton USB comme une YubiKey mais aussi dans un gestionnaire de mots de passe sérieux comme Keepass (ou une variante compatible) ou encore FreeOTP.

2023/04/02 19:26 · pc

J'ai deux Yubikey pour mes clefs OpenPGP, deux clefs matérielles avec les mêmes clefs et sous-clefs OpenPGP. Pour SSH, j'utilise la clef OpenPGP ad hoc.

L'agent GnuPGP identifie les clefs par leur numéro de série et lorsque j'échange, il se plaint avec une erreur sign_and_send_pubkey me demandant l'autre clef.

Pour changer sans supprimer ~/.gnupg/private-keys-v1.d il suffit de lancer :

gpg-connect-agent "scd serialno" "learn --force" /bye
2022/05/20 17:19 · pc

Pour charger mes podcasts, j'utilisais https://castget.johndal.com/. Depuis quelques années, les outils de « balado-diffusion » semblent s'éloigner des standards du Web. En particulier, certains spécifient le même fichier avec des paramètres /file?param=value, ce que castget ne sait pas gérer. Comme il avait d'autres limitations (renommage des épisodes téléchargés), je teste https://github.com/brokkr/poca.

Et pour réécrire mes souscriptions, voici un script trivial :

#!/usr/bin/env python3
#
 
import configparser
 
 
config = configparser.ConfigParser()
config.read('castgetrc')
 
for entry in config.sections():
        output, url, title, album, artist = '', '', '', '', ''
        for key in config[entry]:
                if 'url' == key:
                        url = config[entry][key]
                elif 'spool' == key:
                        title = config[entry][key].split('/')[-1]
                elif 'id3album' == key:
                        album = config[entry][key].strip('"')
                elif 'id3leadartist' == key:
                        artist = config[entry][key]
 
        if 0 == len(album):
                album = entry
 
        output += '    <!-- =========================== -->\n'
        output += '    <subscription category="Podcasts">\n'
        output += '      <title>' + title + '</title>\n'
        output += '      <url>' + url + '</url>\n'
        output += '      <from_the_top>yes</from_the_top>\n'
        output += '      <rename divider="_" space="_">\n'
        output += '        <episode_title/>\n'
        output += '        <date/>\n'
        output += '      </rename>\n'
        output += '      <metadata>\n'
        output += '        <album>' + album + '</album>\n'
        if 0 < len(artist):
                output += '        <artist>' + artist + '</artist>\n'
        output += '      </metadata>\n'
        output += '    </subscription>\n'
        print(output)
2022/05/05 20:53 · pc
  • blog.txt
  • Dernière modification : 2011/07/16 19:03
  • de pc