Vi finns på Product Hunt i dag! Skriv en kommentar

Authenticator 1.16.0 är här. Se vad som är nytt

Så fungerar det

En teknisk översikt för säkerhetsgranskare. Allt här går att stämma av mot källkoden — även de delar som klär oss sämre, och som vi skriver ut i stället för att utelämna.

Arkitektur

Tillägget är ett Chrome-tillägg med Manifest V3 som genererar TOTP-koder lokalt med OTPAuth. Att generera koder, lagra konton och kryptera dem sker helt utan backend, och det finns inga användarkonton. Vi driver en enda server, api.authenticator.sh, för gratisplanen och Pro — den beskrivs nedan — och den tar aldrig emot en hemlighet, ett kontonamn, en utfärdare eller en kod.

Den servern har inget nyckelmaterial för någons konton: det finns ingen nyckeldeponering och ingen återställningsväg som vi kontrollerar. Om en användare glömmer lösenordet och tappar bort återställningskoden kan vi inte hjälpa till — så är det tänkt.

Kryptografi

Lösenordsskydd är valfritt och av som standard. När det är på:

Kryptering
AES-256-GCM, slumpmässig 96-bitars IV per post och per omskrivning
Nyckelhärledning
PBKDF2-HMAC-SHA256, 600 000 iterationer, slumpmässigt 128-bitars salt (enligt OWASP)
Huvudnyckel
256 bitar, från crypto.getRandomValues, genereras en gång; lösenordet omsluter den bara
Fingeravtryck
HMAC-SHA256 under en HKDF-härledd undernyckel, används för att slå ihop poster mellan enheter
Lagring av upplåst nyckel
chrome.storage.session — endast i minnet, skrivs aldrig till disk, rensas när webbläsaren stängs och är oåtkomlig för content scripts
Automatiskt lås
Vid varje öppning / efter 5, 15 eller 60 minuters inaktivitet / tills webbläsaren stängs
Implementation
Enbart WebCrypto. Ingen primitiv är egenskriven och inget kryptobibliotek paketeras.

Varför två nyckelnivåer

Data krypteras aldrig direkt med en nyckel härledd ur lösenordet. En slumpmässig huvudnyckel krypterar posterna; PBKDF2-utdata omsluter bara den huvudnyckeln. Två följder av det, och båda spelar roll:

  • Att byta lösenord skriver om 32 byte i stället för att kryptera om varje post, varje säkerhetskopia och varje export. Det är just vid sådana massomskrivningar som lagringssystem tappar data.
  • En 160-bitars återställningskod kan omsluta samma huvudnyckel oberoende, så ett glömt lösenord går att ta sig förbi. Koden visas en gång, måste skrivas in igen för att bekräftas och byts ut efter varje användning. Ingenting skrivs till disk förrän användaren har bekräftat den.

Vad som krypteras och vad som inte gör det

Med lösenordsskydd på krypteras hela kontoposten utom dess identifierare och fingeravtryck — inklusive tjänstens namn, så en stulen profil avslöjar inte vilka tjänster användaren har konton hos. När skyddet slås på krypteras posterna, verifieras genom att dekrypteras och jämföras, och först därefter tas varje kopia i klartext bort: webbläsarens lokala lagring, synk och alla sju roterande säkerhetskopior.

Rakt på sak, eftersom det påverkar er bedömning:

  • Med lösenordsskyddet avstängt lagras konton okrypterade och replikeras, om Chrome Sync är på, via användarens eget Google-konto. De når aldrig oss, men Google har dem. Synk går att stänga av, vilket också tar bort det som redan ligger där.
  • Användningshistoriken per webbplats — vilket konto som används på vilken domän — lagras lokalt och omfattas inte av valvet medan den samlas in. Att slå på lösenordsskydd raderar den; den går också att stänga av separat.

Hotmodell

Skyddar mot

  • En stulen eller kopierad profilkatalog i webbläsaren
  • Infostealer-skadeprogram som tömmer webbläsardata i klump
  • Någon med fysisk åtkomst till en obevakad dator
  • Kontoposter som når Google via Chrome Sync
  • En förlorad eller stulen säkerhetskopia (lösenordsskyddad export)

Skyddar inte mot

  • Skadlig kod som kör som användaren medan valvet är upplåst
  • En keylogger som fångar lösenordet när det skrivs
  • En komprometterad webbläsare eller ett komprometterat operativsystem
  • Intrång hos de tredjepartstjänster som koderna gäller
  • En okrypterad export du själv valt att skriva — vanlig JSON, otpauth://-länkar eller CXF kan läsas av vem som helst som öppnar filen

Kort sagt: det här skyddar data i vila. Det försvarar inte, och kan inte försvara, en enhet som redan står under en angripares kontroll medan den används.

Behörigheter

Manifestet deklarerar fem behörigheter och inga host-behörigheter:

storage
Konton och inställningar i lokal lagring, den upplåsta nyckeln i sessionslagring och den valfria synkkopian
activeTab
Läser den aktiva flikens värdnamn för att lyfta fram rätt konto, och fångar fliken när användaren skannar en QR-kod från skärmen
contextMenus
Lägger till posten ”Infoga 2FA-kod” i högerklicksmenyn, och bara på redigerbara fält på http(s)-sidor
scripting
Injicerar den enda funktion som skriver koden i fältet — för en enda flik och en enda anrop, först efter att den menyposten valts
sidePanel
Öppnar appen i Chromes sidopanel när den ytan är vald; det ger ingen åtkomst till någon sida

Det finns Det finns inga content scripts, inga tabs-, cookies- eller webRequest-behörigheter och inga web-accessible resources. Ingenting av vårt finns i en sida förrän du ber om en kod där; det som körs då injiceras för just den fliken och just det anropet, under det medgivande ditt eget klick ger, och lämnar inget kvar.

Kamera

QR-skanning med kameran körs på en tilläggssida i en flik, inte i popupen — en popup förstörs när den tappar fokus, vilket är precis vad en behörighetsfråga gör. Det här kräver ingen behörighet i manifestet: det använder webbläsarens vanliga kamerafråga, som ges per tilläggs-origin och kan återkallas i webbplatsinställningarna, och den kommer aldrig förrän användaren öppnar skannern. Video avkodas lokalt och skickas eller sparas aldrig.

Gratisplanen, Pro och det dagliga anropet

Sedan 1.14.0 kontaktar tillägget api.authenticator.sh en gång om dagen — när webbläsaren startar eller efter att popupen har ritats upp, aldrig på vägen till att visa en kod. Förfrågan består av en sluten lista med fält och byggs i en enda funktion i src/billing/sync.ts; ett test ser till att den håller sig till den listan:

iid
Ett slumpmässigt id som den här installationen har skapat. Inte kopplat till något Google-konto, någon e-postadress eller webbläsaren — om inte användaren köper Pro: då kopplar köpet installationen det gjordes från, och varje installation där licensnyckeln är aktiv, till e-postadressen som betalningen gjordes med
ver, ui_lang, browser_lang
Tilläggets version, gränssnittets språk, webbläsarens språk
installed_at, legacy, legacy_src
När installationen kom till, och om den fanns redan innan betalplanen infördes
accounts, groups
Hur många konton den har och i hur många grupper — antal, aldrig vilka konton eller vad grupperna heter
vault, sync, open_mode, view_mode, style, dark, os
Om lösenordsskydd och synk är på, och hur tillägget öppnas, hur kontolistan visas (normal, kompakt eller med dolda koder), och vilken stil som används, ljus eller mörk, och operativsystemet (Windows, macOS, Linux, ChromeOS)
pid, lid, has_key, key_hint
Vilken signerad policy och licens installationen har just nu, om den redan har licensnyckeln, och de fyra sista tecknen i den
opens
Hur många gånger tillägget har öppnats varje dag sedan det förra anropet (högst de senaste 14 dagarna) — ett antal per dag, inget om vad som tittades på
events
Köade händelser kring betalning, från en sluten lista: gränsen nåddes, betalväggen visades eller stängdes, en plan valdes, ett köp eller en nyckel prövades

Skickas aldrig: hemligheter, kontonamn, utfärdare, koder eller webbplatserna där de används. Servern tar landet ur förfrågan redan vid nätverkskanten (edge) och sparar inte IP-adressen. Avinstallationssidan får samma installations-id som ?i=: den meddelar servern att installationen har tagits bort och kopplar ett frivilligt enkätsvar till den.

Det som sparas i chrome.storage.local för detta: installations-id:t, den senaste signerade policyn och licensen, licensnyckeln som den här installationen har (inskriven, eller mottagen efter ett köp på den), antalet öppningar per dag tills servern har fått dem, samt de köade händelserna — inga kontodata. Medan synk är på sparas dessutom två saker i Chrome Sync — om installationen är äldre än betalplanen, och den licensnyckeln — så att användarens andra Chrome-profiler får med sig dem och slår på Pro av sig själva. Stängs synk av tas båda bort ur Chrome Sync, och slås den på igen läggs de tillbaka.

Vad som kommer tillbaka, och hur tillägget hanterar det:

Token
Policyn (gratisplanens kontogräns) och licensen (Pro) är JWS signerade med ES256 (ECDSA P-256)
Verifiering
WebCrypto, mot de publika nycklar som är inkompilerade i bygget (src/billing/keys.ts). Ett bygge för Chrome Web Store litar bara på produktionsnyckeln, och byggkontrollen stoppar ett bygge som bär på någon annan
Offline
Gränsen och licensen kontrolleras lokalt, utifrån de senaste signerade token. Tills en signerad policy har tagits emot begränsas ingenting — en server som inte går att nå kan aldrig låsa ute någon
Vad gränsen kan göra
Hindra att ett konto läggs till, och ingenting annat. Den kan inte dölja, låsa, ta bort eller ändra ett konto, och inte heller blockera koder, export, säkerhetskopior, återställning eller synk
Köp
Stripe Checkout i en ny flik, med Link som säljare (merchant of record). Köparens e-postadress når oss från Stripe, för att utfärda licensnyckeln och kunna återställa den; nycklar lagras som en hash, plus en krypterad kopia så att en borttappad nyckel kan skickas igen
Licensnyckel
Svaret på det dagliga anropet innehåller själva nyckeln bara för installationen där Pro köptes, och bara tills de fyra tecken den rapporterar i key_hint stämmer med den nyckeln — så ersätter en nyckel som köps senare en äldre; en installation där nyckeln skrevs in får aldrig någon skickad

Gränsen är inte DRM: koden är öppen källkod, och en fork kan ta bort den. Det som aldrig får vara möjligt är att någon skaffar eller använder en annan persons licens, eller gör någon annans installation begränsad — det är en sårbarhet och omfattas av vår säkerhetspolicy.

Utgående nätverkstrafik

Två slags förfrågningar gör tillägget på eget initiativ: klockkontrollen och det dagliga anropet för gratisplanen och Pro. Allt annat nedan sker för att användaren klickat på något. Manifest V3 förbjuder fjärrkod, och inget av det här bär med sig konton, hemliga nycklar eller genererade koder. Hela listan:

VärdNärInnehåller användardata?
time.akamai.com
timeapi.io
cloudflare.com
Kontroll av klockavvikelse, cachad; går ingen källa att nå säger Inställningar det. TOTP slutar fungera vid felgående klocka.Nej
authenticator.shVälkomstsida vid installation; feedbacksida vid avinstallation, med installations-id:t i adressen; hjälp- och supportsidor när de öppnas från popup-fönstret.Installations-id:t, bara på avinstallationssidan; annars nej (vanliga webbserverloggar)
api.authenticator.shDet dagliga anropet för gratisplanen och Pro; anrop för kassa, licens och kundportal när användaren köper Pro, anger en nyckel, återställer ett köp eller hanterar en prenumeration.Fälten som listas ovan — aldrig kontodata. En licensnyckel eller en e-postadress bara när användaren själv skriver in en
checkout.stripe.com
billing.stripe.com
Stripe Checkout och kundportalen, öppnas i en flik när användaren köper Pro eller hanterar en prenumeration.Det köparen fyller i där. Stripes och Links egna villkor gäller
chromewebstore.google.comButikssidan, öppnas i en flik först vid klick: recensionsformuläret från betygsfrågan.Nej (för Google är det ett besök på en butikssida som vilket annat som helst)

Inga typsnitt, skript, stilmallar eller bilder laddas från tredjepartsvärdar. Allt som behövs för att rita upp gränssnittet ligger i paketet.

Verifiera det vi publicerar

Ingenting av ovanstående behöver tas på förtroende. Tillägget är öppen källkod och varje release går att återskapa:

  1. Ladda ner den publicerade .crx från Chrome Web Store och packa upp den
  2. Checka ut motsvarande git-tagg
  3. Kör npm ci && npm run build på Node 20 LTS
  4. Jämför den resulterande dist/ med det uppackade paketet

Med varje GitHub-release publiceras en SHA-256 för varje fil i den byggda dist/, som SHA256SUMS-v<version>.txt och i sha256sum-format, så steg 4 blir en enda körning av sha256sum -c. Skillnader bör begränsas till filordning inuti arkiv och blanktecken i minifierad utdata mellan Node-patchversioner.

Körtidsberoendena är medvetet få — OTPAuth, React, jsQR, Zustand, Framer Motion och Lucide — låsta av en incheckad lockfil. En CycloneDX-SBOM finns på begäran.

Läs källkoden

Det vi inte har

Vi är en liten oberoende leverantör. Om något av följande är ett absolut krav i er process är det bättre att ni vet det nu än tre veckor in i en utvärdering:

  • Ingen SOC 2-, ISO 27001- eller motsvarande certifiering
  • Inget externt penetrationstest beställt hittills
  • Inget betalt bug bounty-program

Det vi erbjuder i stället är full insyn i källkoden, ett verifierbart bygge, en minimal behörighetsyta och en server som era hemligheter, kontonamn och koder aldrig ens skickas till.

Kontakta oss

Sårbarhetsrapporter och säkerhetsfrågor: security@authenticator.sh. Vår policy för ansvarsfullt röjande, omfattning, svarstider och safe harbour-villkor finns på sidan säkerhetspolicy; vad som lagras och vad som lämnar enheten står i integritetspolicyn.

Vi svarar gärna på ett skriftligt leverantörsformulär eller går igenom koden tillsammans med ett säkerhetsteam.

För allt som inte rör säkerhet — installation, kontoåterställning, faktureringsfrågor vid en utrullning — se support. Skicka inte sårbarhetsrapporter dit; den brevlådan hanteras inte som konfidentiell.