Nous sommes sur Product Hunt aujourd'hui ! Laisser un commentaire

Authenticator 1.16.0 est disponible. Voir les nouveautés

Comment ça marche

Un aperçu technique destiné aux auditeurs de sécurité. Tout ici est vérifiable dans le code source — y compris ce qui nous met le moins en valeur, que nous énonçons plutôt que d'omettre.

Architecture

L'extension est une extension Chrome Manifest V3 qui génère les codes TOTP localement avec OTPAuth. La génération des codes, le stockage des comptes et leur chiffrement ne font appel à aucun backend, et il n'existe pas de comptes utilisateur. Nous exploitons un seul serveur, api.authenticator.sh, pour l'offre gratuite et Pro — décrit plus bas —, et il ne reçoit jamais ni secret, ni nom de compte, ni émetteur, ni code.

Ce serveur ne détient aucun matériel de clé pour les comptes de qui que ce soit : pas de séquestre de clé et aucun chemin de récupération sous notre contrôle. Si un utilisateur oublie son mot de passe et perd son code de récupération, nous ne pouvons rien pour lui — c'est voulu.

Cryptographie

La protection par mot de passe est facultative et désactivée par défaut. Lorsqu'elle est active :

Chiffrement
AES-256-GCM, IV aléatoire de 96 bits par enregistrement et par réécriture
Dérivation de clé
PBKDF2-HMAC-SHA256, 600 000 itérations, sel aléatoire de 128 bits (recommandation OWASP)
Clé maîtresse
256 bits, issue de crypto.getRandomValues, générée une seule fois ; le mot de passe ne fait que l'envelopper
Empreintes
HMAC-SHA256 sous une sous-clé dérivée par HKDF, utilisée pour fusionner les enregistrements entre appareils
Stockage de la clé déverrouillée
chrome.storage.session — en mémoire uniquement, jamais écrite sur disque, effacée à la fermeture du navigateur, inaccessible depuis un script de contenu
Verrouillage automatique
À chaque ouverture / après 5, 15 ou 60 minutes d'inactivité / jusqu'à la fermeture du navigateur
Implémentation
WebCrypto exclusivement. Aucune primitive n'est réécrite à la main et aucune bibliothèque de cryptographie n'est embarquée.

Pourquoi deux niveaux de clé

Les données ne sont jamais chiffrées directement avec une clé dérivée du mot de passe. Une clé maîtresse aléatoire chiffre les enregistrements ; la sortie de PBKDF2 ne fait qu'envelopper cette clé maîtresse. Deux conséquences en découlent, et toutes deux comptent :

  • Changer le mot de passe réécrit 32 octets au lieu de rechiffrer chaque enregistrement, chaque sauvegarde et chaque export. C'est précisément lors de ces réécritures massives que les systèmes de stockage perdent des données.
  • Un code de récupération de 160 bits peut envelopper la même clé maîtresse indépendamment : un mot de passe oublié reste donc récupérable. Le code est affiché une fois, doit être ressaisi pour confirmation et est renouvelé après chaque usage. Rien n'est écrit sur disque tant que l'utilisateur ne l'a pas confirmé.

Ce qui est chiffré, et ce qui ne l'est pas

Avec la protection par mot de passe activée, tout l'enregistrement du compte est chiffré, hormis son identifiant et son empreinte — y compris le nom du service, si bien qu'un profil volé ne révèle pas auprès de quels services l'utilisateur a des comptes. L'activation chiffre les enregistrements, les vérifie en les déchiffrant et en les comparant, et seulement ensuite supprime toute copie en clair : stockage local du navigateur, synchronisation et les sept instantanés de sauvegarde en rotation.

Dit clairement, parce que cela compte pour votre évaluation :

  • Sans protection par mot de passe, les comptes sont stockés non chiffrés et, si Chrome Sync est actif, répliqués via le compte Google de l'utilisateur. Nous ne les recevons jamais, mais Google les détient. La synchronisation peut être désactivée, ce qui supprime aussi ce qui s'y trouve déjà.
  • L'historique d'utilisation par site — quel compte est utilisé sur quel domaine — est stocké localement et n'est pas couvert par le coffre pendant sa collecte. Activer la protection par mot de passe le supprime ; il peut aussi être désactivé séparément.

Modèle de menace

Protège contre

  • Un répertoire de profil de navigateur volé ou copié
  • Un logiciel voleur d'informations qui exfiltre en masse les données du navigateur
  • Un accès physique à une machine laissée sans surveillance
  • Des enregistrements de comptes parvenant à Google via Chrome Sync
  • Un fichier de sauvegarde perdu ou volé (export protégé par mot de passe)

Ne protège pas contre

  • Un logiciel malveillant s'exécutant sous l'identité de l'utilisateur alors que le coffre est déverrouillé
  • Un enregistreur de frappe capturant le mot de passe à la saisie
  • Un navigateur ou un système d'exploitation compromis
  • La compromission des services tiers auxquels les codes sont destinés
  • Un export non chiffré que vous avez choisi d'écrire — JSON simple, liens otpauth:// ou CXF sont lisibles par quiconque ouvre le fichier

En résumé : cela protège les données au repos. Cela ne défend pas, et ne peut pas défendre, un appareil déjà sous le contrôle d'un attaquant pendant son utilisation.

Permissions

Le manifeste déclare cinq permissions et aucune permission d'hôte :

storage
Comptes et réglages dans le stockage local, clé déverrouillée dans le stockage de session, et la copie de synchronisation facultative
activeTab
Lit le nom d'hôte de l'onglet actif pour mettre en avant le compte correspondant, et capture l'onglet lorsque l'utilisateur scanne un QR code à l'écran
contextMenus
Ajoute l'entrée « Insérer le code 2FA » au menu contextuel, et uniquement sur les champs modifiables des pages http(s)
scripting
Injecte l'unique fonction qui place le code dans le champ — pour un seul onglet et une seule invocation, seulement après que cette entrée de menu a été choisie
sidePanel
Ouvre l'application dans le panneau latéral de Chrome lorsque c'est la surface choisie ; cela ne donne accès à aucune page

Il n'y a aucun script de contenu, aucune permission tabs, cookies ou webRequest, et aucune web-accessible resource. Rien de chez nous n'existe dans une page tant que vous n'y demandez pas un code ; ce qui s'exécute alors est injecté pour cet onglet-là et cette invocation-là, sous l'autorisation que votre propre clic accorde, et ne laisse rien derrière.

Caméra

Le scan de QR code par la caméra s'exécute sur une page d'extension ouverte dans un onglet, pas dans la popup — une popup est détruite dès qu'elle perd le focus, ce que fait précisément une demande de permission. Cela ne nécessite aucune permission dans le manifeste : c'est la demande d'accès caméra standard du navigateur, accordée par origine d'extension et révocable dans les paramètres du site, et elle n'apparaît jamais avant que l'utilisateur n'ouvre le scanner. La vidéo est décodée localement, jamais transmise ni stockée.

Offre gratuite, Pro et l'appel quotidien au serveur

Depuis la 1.14.0, l'extension contacte api.authenticator.sh une fois par jour — au démarrage du navigateur, ou une fois la popup affichée, jamais sur le chemin critique de l'affichage d'un code. La requête est une liste fermée de champs, construite par une seule fonction dans src/billing/sync.ts et tenue à cette liste par un test :

iid
Un identifiant aléatoire créé par cette installation. Sans lien avec un compte Google, une adresse e-mail ou le navigateur — sauf achat de Pro : l'enregistrement de l'achat relie alors l'installation depuis laquelle l'achat a été fait, et toute installation où la clé de licence est active, à l'adresse e-mail utilisée pour payer
ver, ui_lang, browser_lang
Version de l'extension, langue de l'interface, langue du navigateur
installed_at, legacy, legacy_src
Date d'apparition de l'installation, et si elle est antérieure à la facturation
accounts, groups
Le nombre de comptes qu'elle contient et de groupes qui les rangent — des nombres, jamais lesquels ni le nom des groupes
vault, sync, open_mode, view_mode, style, dark, os
Si la protection par mot de passe et la synchronisation sont activées, et comment l'extension s'ouvre, l'affichage de la liste des comptes (normal, compact ou codes masqués), ainsi que le style utilisé, clair ou sombre, et le système d'exploitation (Windows, macOS, Linux, ChromeOS)
pid, lid, has_key, key_hint
La politique signée et la licence que l'installation détient actuellement, si elle a déjà la clé de licence, et les quatre derniers caractères de cette clé
opens
Combien de fois l'extension a été ouverte chaque jour depuis le dernier envoi (au plus les 14 derniers jours) — un nombre par jour, rien sur ce qui a été consulté
events
Les événements de facturation en attente, issus d'une liste fermée : limite atteinte, écran Pro affiché ou fermé, offre choisie, paiement ou clé tentés

Jamais envoyés : les secrets, les noms de comptes, les émetteurs, les codes, ni les sites où ils servent. Le serveur déduit le pays de la requête, en bordure de son réseau, et ne conserve pas l'adresse IP. La page de désinstallation reçoit le même identifiant d'installation sous la forme ?i= : elle signale au serveur que l'installation a été supprimée et y rattache une réponse facultative au questionnaire.

Conservés à cette fin dans chrome.storage.local : l'identifiant d'installation, la dernière politique et la dernière licence signées, la clé de licence que détient cette installation (saisie, ou reçue après un achat fait depuis elle), le nombre d'ouvertures par jour jusqu'à ce que le serveur l'ait reçu, et les événements en attente — aucune donnée de compte. Lorsque la synchronisation est active, le fait que l'installation soit antérieure à la facturation et cette clé de licence passent aussi par Chrome Sync, pour que les autres profils Chrome de l'utilisateur les récupèrent et activent Pro d'eux-mêmes ; désactiver la synchronisation les retire tous deux de Chrome Sync, la réactiver les y remet.

Ce qui revient, et comment l'extension le traite :

Jetons
La politique (la limite de comptes de l'offre gratuite) et la licence (Pro) sont des JWS signés en ES256 (ECDSA P-256)
Vérification
WebCrypto, avec les clés publiques compilées dans le build (src/billing/keys.ts). Un build du Chrome Web Store ne fait confiance qu'à la clé de production, et la vérification du build refuse tout build qui en contient une autre
Hors ligne
La limite et la licence sont vérifiées localement, à partir des derniers jetons signés. Tant qu'aucune politique signée n'a été reçue, rien n'est limité — un serveur injoignable ne peut jamais bloquer qui que ce soit
Ce que la limite peut faire
Empêcher l'ajout d'un compte, et rien d'autre. Elle ne peut ni masquer, ni verrouiller, ni supprimer, ni modifier un compte, ni bloquer les codes, l'export, les sauvegardes, la restauration ou la synchronisation
Achats
Stripe Checkout dans un nouvel onglet, avec Link comme vendeur officiel (merchant of record). L'e-mail de l'acheteur nous parvient de Stripe, pour émettre et récupérer la clé de licence ; les clés sont stockées sous forme de hachage, avec une copie chiffrée qui permet de renvoyer une clé perdue
Clé de licence
La réponse à l'envoi quotidien ne contient la clé elle-même que pour l'installation depuis laquelle Pro a été acheté, et seulement jusqu'à ce que les quatre caractères qu'elle signale dans key_hint correspondent à cette clé — une clé achetée plus tard remplace donc une plus ancienne ; une installation où la clé a été saisie n'en reçoit jamais

La limite n'est pas un DRM : le code est open source, et un fork peut la retirer. Ce qui ne doit jamais être possible, c'est qu'une personne obtienne ou utilise la licence d'une autre, ou qu'elle rende limitée l'installation de quelqu'un d'autre — ce serait une vulnérabilité, qui entre dans le périmètre de notre politique de sécurité.

Trafic sortant

L'extension émet d'elle-même deux types de requêtes : la vérification de l'horloge et l'appel de facturation quotidien. Tout le reste ci-dessous se produit parce que l'utilisateur a cliqué. Manifest V3 interdit le code distant, et rien de tout cela ne transporte de comptes, de clés secrètes ni de codes générés. La liste complète :

HôteQuandTransporte des données utilisateur ?
time.akamai.com
timeapi.io
cloudflare.com
Vérification de dérive d'horloge, mise en cache ; si aucune source n'est joignable, les Paramètres le disent. TOTP casse si l'horloge dérive.Non
authenticator.shPage de bienvenue à l'installation ; page de retour à la désinstallation, avec l'identifiant d'installation dans son adresse ; pages d'aide et d'assistance lorsqu'elles sont ouvertes depuis la fenêtre de l'extension.L'identifiant d'installation, sur la page de désinstallation uniquement ; sinon non (journaux de serveur web ordinaires)
api.authenticator.shAppel de facturation une fois par jour ; appels de paiement, de licence et de portail lorsque l'utilisateur achète Pro, saisit une clé, restaure un achat ou gère un abonnement.Les champs listés ci-dessus — jamais de données de compte. Une clé de licence ou une adresse e-mail uniquement si l'utilisateur en saisit une
checkout.stripe.com
billing.stripe.com
Stripe Checkout et portail client, ouverts dans un onglet lorsque l'utilisateur achète Pro ou gère un abonnement.Ce que l'acheteur y saisit. Les conditions propres à Stripe et à Link s'appliquent
chromewebstore.google.comFiche du store, ouverte dans un onglet sur clic uniquement : le formulaire d'avis depuis l'invite de notation.Non (Google y voit la consultation d'une page du store comme une autre)

Aucune police, aucun script, aucun style ni aucune image n'est chargé depuis un hôte tiers. Tout ce qui est nécessaire au rendu de l'interface est livré dans le paquet.

Vérifier ce que nous publions

Vous n'avez à croire aucun des points ci-dessus sur parole. L'extension est open source et chaque version est reproductible :

  1. Téléchargez le .crx publié depuis le Chrome Web Store et décompressez-le
  2. Basculez sur le tag git correspondant
  3. Lancez npm ci && npm run build avec Node 20 LTS
  4. Comparez le dist/ obtenu avec le paquet décompressé

Un SHA-256 pour chaque fichier du dist/ produit est publié à chaque version GitHub sous la forme SHA256SUMS-v<version>.txt, au format sha256sum : l'étape 4 se réduit donc à un seul appel à sha256sum -c. Les différences doivent se limiter à l'ordre des fichiers dans les archives et aux espaces dans la sortie minifiée d'une version corrective de Node à l'autre.

Les dépendances d'exécution sont délibérément peu nombreuses — OTPAuth, React, jsQR, Zustand, Framer Motion et Lucide — figées par un fichier de verrouillage versionné. Un SBOM CycloneDX est disponible sur demande.

Consulter le code source

Ce que nous n'avons pas

Nous sommes un petit éditeur indépendant. Si l'un des points suivants est une exigence stricte de votre processus, mieux vaut le savoir maintenant qu'au bout de trois semaines d'évaluation :

  • Pas de certification SOC 2, ISO 27001 ou équivalente
  • Aucun test d'intrusion externe commandé à ce jour
  • Pas de programme de bug bounty rémunéré

Ce que nous offrons à la place : une transparence totale du code source, un build vérifiable, une surface de permissions minimale et un serveur auquel ni vos secrets, ni les noms de vos comptes, ni vos codes ne sont jamais envoyés.

Nous écrire

Rapports de vulnérabilité et questions de sécurité : security@authenticator.sh. Notre politique de divulgation, son périmètre, nos délais de réponse et les conditions de safe harbour figurent sur la page politique de sécurité ; ce qui est stocké et ce qui quitte l'appareil est exposé dans la politique de confidentialité.

Nous répondons volontiers à un questionnaire écrit d'évaluation fournisseur ou parcourons le code avec une équipe sécurité.

Pour tout ce qui ne relève pas de la sécurité — installation, récupération de compte, questions de facturation liées à un déploiement — voir le support. Merci de ne pas y envoyer de rapports de vulnérabilité : cette boîte n'est pas traitée comme confidentielle.