Oggi siamo su Product Hunt! Lascia un commento

Authenticator 1.16.0 è disponibile. Scopri le novità

Come funziona

Una panoramica tecnica per chi valuta la sicurezza. Tutto quello che c'è qui è verificabile sul codice sorgente — comprese le parti che ci fanno meno bella figura, che dichiariamo invece di ometterle.

Architettura

L'estensione è un'estensione Chrome Manifest V3 che genera i codici TOTP localmente con OTPAuth. Generare i codici, archiviare gli account e cifrarli non richiede alcun backend, e non esistono account utente. Gestiamo un solo server, api.authenticator.sh, per il piano gratuito e Pro — descritto più avanti — e non riceve mai un segreto, il nome di un account, un emittente o un codice.

Quel server non custodisce materiale crittografico per gli account di nessuno: non c'è deposito di chiavi né alcun percorso di recupero controllato da noi. Se un utente dimentica la password e perde il codice di recupero, non possiamo aiutarlo: è una scelta progettuale.

Crittografia

La protezione con password è facoltativa e disattivata per impostazione predefinita. Quando è attiva:

Cifrario
AES-256-GCM, IV casuale da 96 bit per ogni record e per ogni riscrittura
Derivazione della chiave
PBKDF2-HMAC-SHA256, 600.000 iterazioni, sale casuale da 128 bit (indicazioni OWASP)
Chiave principale
256 bit, da crypto.getRandomValues, generata una sola volta; la password si limita a incapsularla
Impronte
HMAC-SHA256 con una sottochiave derivata via HKDF, usata per unire i record tra dispositivi
Archiviazione della chiave sbloccata
chrome.storage.session — solo in memoria, mai scritta su disco, cancellata alla chiusura del browser, irraggiungibile dai content script
Blocco automatico
A ogni apertura / dopo 5, 15 o 60 minuti di inattività / fino alla chiusura del browser
Implementazione
Solo WebCrypto. Nessuna primitiva è scritta a mano e nessuna libreria crittografica è inclusa nel pacchetto.

Perché due livelli di chiave

I dati non vengono mai cifrati direttamente con una chiave derivata dalla password. Una chiave principale casuale cifra i record; l'output di PBKDF2 si limita a incapsulare quella chiave principale. Ne discendono due conseguenze, entrambe rilevanti:

  • Cambiare la password riscrive 32 byte anziché ricifrare ogni record, ogni backup e ogni esportazione. È proprio in quelle riscritture di massa che i sistemi di archiviazione perdono dati.
  • Un codice di recupero da 160 bit può incapsulare la stessa chiave principale in modo indipendente, quindi una password dimenticata è recuperabile. Il codice viene mostrato una volta, va riscritto per conferma e viene rigenerato dopo ogni uso. Niente viene scritto su disco finché l'utente non lo ha confermato.

Cosa viene cifrato e cosa no

Con la protezione con password attiva viene cifrato l'intero record dell'account tranne identificatore e impronta — nome del servizio compreso, così un profilo rubato non rivela presso quali servizi l'utente ha un account. All'attivazione l'estensione cifra i record, li verifica decifrandoli e confrontandoli e solo allora rimuove ogni copia in chiaro: archiviazione locale del browser, sincronizzazione e tutte e sette le istantanee di backup a rotazione.

Lo diciamo chiaramente, perché conta per la vostra valutazione:

  • Con la protezione con password disattivata, gli account sono archiviati in chiaro e, se Chrome Sync è attivo, replicati attraverso l'account Google dell'utente. A noi non arrivano mai, ma Google li detiene. La sincronizzazione si può disattivare, il che rimuove anche quanto è già presente.
  • La cronologia d'uso per sito — quale account viene usato su quale dominio — è archiviata localmente e non è coperta dalla cassaforte mentre viene raccolta. Attivare la protezione con password la cancella; si può anche disattivare separatamente.

Modello di minaccia

Protegge da

  • Una cartella di profilo del browser rubata o copiata
  • Malware infostealer che esfiltra in blocco i dati del browser
  • Chi ha accesso fisico a una macchina lasciata incustodita
  • Record degli account che finiscono a Google tramite Chrome Sync
  • Un file di backup perso o rubato (esportazione protetta da password)

Non protegge da

  • Malware in esecuzione come l'utente mentre la cassaforte è sbloccata
  • Un keylogger che cattura la password mentre viene digitata
  • Un browser o un sistema operativo compromessi
  • La compromissione dei servizi di terze parti a cui i codici si riferiscono
  • Un export non cifrato che hai scelto di scrivere: JSON semplice, link otpauth:// o CXF sono leggibili da chiunque apra il file

In breve: questo protegge i dati a riposo. Non difende, e non può difendere, un dispositivo già sotto il controllo di un attaccante mentre viene usato.

Permessi

Il manifest dichiara cinque permessi e nessun permesso host:

storage
Account e impostazioni nell'archiviazione locale, la chiave sbloccata in quella di sessione e la copia opzionale di sincronizzazione
activeTab
Legge il nome host della scheda attiva per evidenziare l'account corrispondente e cattura la scheda quando l'utente scansiona un codice QR dallo schermo
contextMenus
Aggiunge la voce «Inserisci codice 2FA» al menu contestuale, e solo sui campi modificabili delle pagine http(s)
scripting
Inietta l'unica funzione che scrive il codice nel campo — per una sola scheda e una sola invocazione, solo dopo che quella voce di menu è stata scelta
sidePanel
Apre l'app nel pannello laterale di Chrome quando è la superficie scelta; non concede accesso ad alcuna pagina

Non ci sono Non ci sono content script, né permessi tabs, cookies o webRequest, né web-accessible resources. Niente di nostro esiste in una pagina finché non ci chiedi un codice; ciò che viene eseguito allora è iniettato per quella scheda e quella invocazione, sotto l'autorizzazione che concede il tuo stesso clic, e non lascia nulla dietro di sé.

Fotocamera

La scansione dei QR con la fotocamera gira su una pagina dell'estensione aperta in una scheda, non nel popup: un popup viene distrutto quando perde il focus, ed è esattamente ciò che fa una richiesta di permesso. Per questo non serve alcun permesso nel manifest: si usa la normale richiesta di accesso alla fotocamera del browser, concessa per origine dell'estensione e revocabile nelle impostazioni del sito, e non compare mai finché l'utente non apre lo scanner. Il video è decodificato localmente e non viene mai trasmesso né memorizzato.

Piano gratuito, Pro e il contatto giornaliero con il server

Dalla 1.14.0 l'estensione contatta api.authenticator.sh una volta al giorno — all'avvio del browser o dopo che il popup è stato visualizzato, mai sul percorso che porta a mostrare un codice. La richiesta è un elenco chiuso di campi, costruito in un'unica funzione in src/billing/sync.ts e vincolato a quell'elenco da un test:

iid
Un ID casuale creato da questa installazione. Non collegato a un account Google, a un indirizzo email o al browser — a meno che l'utente non acquisti Pro: in quel caso il record dell'acquisto collega l'installazione da cui ha acquistato, e ogni installazione in cui la chiave di licenza è attiva, all'indirizzo email con cui ha pagato
ver, ui_lang, browser_lang
Versione dell'estensione, lingua dell'interfaccia, lingua del browser
installed_at, legacy, legacy_src
Quando è comparsa l'installazione, e se è anteriore all'introduzione della fatturazione
accounts, groups
Quanti account contiene e in quanti gruppi: numeri, mai quali account né i nomi dei gruppi
vault, sync, open_mode, view_mode, style, dark, os
Se la protezione con password e la sincronizzazione sono attive, e come si apre l'estensione, come è mostrato l'elenco degli account (normale, compatto o con i codici nascosti), e quale stile usa, chiaro o scuro, e il sistema operativo (Windows, macOS, Linux, ChromeOS)
pid, lid, has_key, key_hint
Quale policy firmata e quale licenza ha attualmente l'installazione, se ha già la chiave di licenza e gli ultimi quattro caratteri di quella chiave
opens
Quante volte l'estensione è stata aperta ogni giorno dall'ultima richiesta (al massimo gli ultimi 14 giorni) — un conteggio per giorno, niente su cosa è stato guardato
events
Eventi di fatturazione in coda, da un elenco chiuso: è stato raggiunto il limite, il paywall è stato mostrato o chiuso, è stato scelto un piano, è stato avviato un checkout o provata una chiave

Mai inviati: segreti, nomi degli account, emittenti, codici o i siti in cui vengono usati. Il paese viene ricavato dalla richiesta già sull'edge del server, e l'indirizzo IP non viene memorizzato. La pagina di disinstallazione riceve lo stesso ID di installazione come ?i=: segnala al server che l'installazione è stata rimossa e vi collega una risposta facoltativa al sondaggio.

Conservati a questo scopo in chrome.storage.local: l'ID di installazione, l'ultima policy e l'ultima licenza firmate, la chiave di licenza che ha questa installazione (digitata, oppure ricevuta dopo un acquisto fatto da qui), i conteggi giornalieri delle aperture finché il server non li ha ricevuti e gli eventi in coda — nessun dato degli account. Con la sincronizzazione attiva finiscono in Chrome Sync anche l'informazione se l'installazione è anteriore alla fatturazione e quella chiave di licenza, così gli altri profili Chrome dell'utente li ricevono e attivano Pro da soli; disattivare la sincronizzazione li toglie entrambi da Chrome Sync, riattivarla li rimette.

Cosa torna indietro, e come lo tratta l'estensione:

Token
La policy (il limite di account del piano gratuito) e la licenza (Pro) sono JWS firmati con ES256 (ECDSA P-256)
Verifica
WebCrypto, con le chiavi pubbliche compilate nella build (src/billing/keys.ts). Una build per il Chrome Web Store si fida solo della chiave di produzione, e il controllo della build rifiuta una build che ne contenga qualsiasi altra
Offline
Il limite e la licenza sono verificati localmente, a partire dagli ultimi token firmati. Finché non è arrivata una policy firmata non c'è alcun limite: un server irraggiungibile non può mai bloccare nessuno
Cosa può fare il limite
Impedire che un account venga aggiunto, e nient'altro. Non può nascondere, bloccare, rimuovere o modificare un account, né bloccare codici, esportazione, backup, ripristino o sincronizzazione
Acquisti
Stripe Checkout in una nuova scheda, con Link come venditore ufficiale (merchant of record). L'email dell'acquirente ci arriva da Stripe, per emettere e recuperare la chiave di licenza; le chiavi sono conservate come hash, più una copia cifrata perché una chiave persa possa essere inviata di nuovo
Chiave di licenza
La risposta alla richiesta giornaliera porta la chiave vera e propria solo all'installazione da cui è stato acquistato Pro, e solo finché i quattro caratteri che questa segnala in key_hint non corrispondono a quella chiave — così una chiave acquistata dopo sostituisce una precedente; a un'installazione in cui la chiave è stata digitata non viene mai inviata

Il limite non è un DRM: il codice è open source e un fork può rimuoverlo. Ciò che non deve mai essere possibile è che una persona ottenga o usi la licenza di un'altra, o renda limitata l'installazione di qualcun altro: sarebbe una vulnerabilità, e rientra nell'ambito della nostra policy di sicurezza.

Traffico di rete in uscita

Due tipi di richiesta partono di propria iniziativa: il controllo dell'orologio e il contatto giornaliero per la fatturazione. Tutto il resto qui sotto avviene perché l'utente ha fatto clic. Manifest V3 vieta il codice remoto, e nulla di quanto segue trasporta account, chiavi segrete o codici generati. L'elenco completo:

HostQuandoTrasporta dati dell'utente?
time.akamai.com
timeapi.io
cloudflare.com
Controllo dello scarto dell'orologio, con cache; se nessuna fonte è raggiungibile, le Impostazioni lo dicono. Con l'orologio sfasato il TOTP smette di funzionare.No
authenticator.shPagina di benvenuto all'installazione; pagina di feedback alla disinstallazione, con l'ID di installazione nell'indirizzo; pagine di aiuto e assistenza quando vengono aperte dal popup.L'ID di installazione, solo sulla pagina di disinstallazione; altrimenti no (normali log del server web)
api.authenticator.shContatto giornaliero per la fatturazione; chiamate di checkout, licenza e portale quando l'utente acquista Pro, inserisce una chiave, ripristina un acquisto o gestisce un abbonamento.I campi elencati sopra, mai dati degli account. Una chiave di licenza o un indirizzo email solo quando l'utente ne digita uno
checkout.stripe.com
billing.stripe.com
Stripe Checkout e portale clienti, aperti in una scheda quando l'utente acquista Pro o gestisce un abbonamento.Ciò che l'acquirente vi inserisce. Valgono le condizioni di Stripe e di Link
chromewebstore.google.comPagina dello store, aperta in una nuova scheda solo al clic: il modulo di recensione dalla richiesta di valutazione.No (per Google è la visita a una pagina dello store come un'altra)

Nessun font, script, foglio di stile o immagine viene caricato da host di terze parti. Tutto ciò che serve a disegnare l'interfaccia è dentro il pacchetto.

Verificare ciò che pubblichiamo

Non dovete credere sulla parola a nulla di quanto sopra. L'estensione è open source e ogni rilascio è riproducibile:

  1. Scaricate il .crx pubblicato dal Chrome Web Store e decomprimetelo
  2. Passate al tag git corrispondente
  3. Eseguite npm ci && npm run build su Node 20 LTS
  4. Confrontate il dist/ ottenuto con il pacchetto decompresso

Con ogni release su GitHub pubblichiamo uno SHA-256 per ciascun file del dist/ prodotto, come SHA256SUMS-v<version>.txt e in formato sha256sum: il passo 4 si riduce quindi a una sola esecuzione di sha256sum -c. Le differenze dovrebbero limitarsi all'ordine dei file dentro gli archivi e agli spazi nell'output minificato tra versioni patch di Node.

Le dipendenze a runtime sono volutamente poche — OTPAuth, React, jsQR, Zustand, Framer Motion e Lucide — fissate da un lockfile versionato. Un SBOM CycloneDX è disponibile su richiesta.

Leggere il codice sorgente

Quello che non abbiamo

Siamo un piccolo fornitore indipendente. Se uno dei punti seguenti è un requisito vincolante per il vostro processo, è meglio saperlo ora che dopo tre settimane di valutazione:

  • Nessuna certificazione SOC 2, ISO 27001 o equivalente
  • Nessun penetration test esterno commissionato finora
  • Nessun programma di bug bounty retribuito

Quello che offriamo in cambio è piena trasparenza del codice, una build verificabile, una superficie di permessi minima e un server al quale i vostri segreti, i nomi degli account e i codici non vengono inviati affatto.

Come scriverci

Segnalazioni di vulnerabilità e domande sulla sicurezza: security@authenticator.sh. La nostra policy di divulgazione, l'ambito, i tempi di risposta e le condizioni di safe harbour sono nella pagina policy di sicurezza; cosa viene memorizzato e cosa lascia il dispositivo è descritto nell'informativa sulla privacy.

Rispondiamo volentieri a un questionario scritto di valutazione fornitori o ripercorriamo il codice insieme a un team di sicurezza.

Per tutto ciò che non riguarda la sicurezza — installazione, recupero dell'account, domande di fatturazione legate a un rollout — vedete il supporto. Vi preghiamo di non inviare lì segnalazioni di vulnerabilità: quella casella non è trattata come riservata.