Jesteśmy dziś na Product Hunt! Zostaw komentarz
Authenticator 1.16.0 jest już dostępny. Zobacz nowości
Przegląd techniczny dla osób audytujących bezpieczeństwo. Wszystko tutaj da się sprawdzić w kodzie źródłowym — łącznie z tym, co stawia nas w gorszym świetle i co nazywamy wprost, zamiast pomijać.
To rozszerzenie Chrome w Manifest V3, które generuje kody TOTP lokalnie przy użyciu OTPAuth. Generowanie kodów, przechowywanie kont i ich szyfrowanie obywają się bez żadnego backendu, a kont użytkowników nie zakładamy. Prowadzimy jeden serwer, api.authenticator.sh, na potrzeby darmowego planu i Pro — opisany niżej — i nigdy nie trafia do niego żaden sekret, nazwa konta, wystawca ani kod.
Ten serwer nie przechowuje materiału klucza do niczyich kont: nie ma depozytu kluczy ani żadnej ścieżki odzyskiwania kontrolowanej przez nas. Jeśli ktoś zapomni hasła i zgubi kod odzyskiwania, nie jesteśmy w stanie pomóc — tak to zaprojektowaliśmy.
Ochrona hasłem jest opcjonalna i domyślnie wyłączona. Gdy jest włączona:
Dane nigdy nie są szyfrowane bezpośrednio kluczem wyprowadzonym z hasła. Rekordy szyfruje losowy klucz główny; wynik PBKDF2 tylko opakowuje ten klucz główny. Wynikają z tego dwie rzeczy i obie mają znaczenie:
Przy włączonej ochronie hasłem szyfrowany jest cały rekord konta poza identyfikatorem i odciskiem — łącznie z nazwą usługi, więc skradziony profil nie zdradza, w jakich serwisach użytkownik ma konta. Włączenie ochrony szyfruje rekordy, weryfikuje je przez odszyfrowanie i porównanie, i dopiero potem usuwa wszystkie kopie jawne: z lokalnego magazynu przeglądarki, z synchronizacji i ze wszystkich siedmiu rotowanych migawek kopii zapasowych.
Mówimy wprost, bo to ma znaczenie przy ocenie:
Krótko: to chroni dane w spoczynku. Nie broni — i nie może obronić — urządzenia, które w trakcie używania jest już pod kontrolą atakującego.
Manifest deklaruje pięć uprawnień i żadnych uprawnień hosta:
Nie ma Nie ma żadnych content scriptów, żadnych uprawnień tabs, cookies ani webRequest, ani web-accessible resources. Nic naszego nie istnieje na stronie, dopóki nie poprosisz tam o kod; to, co wtedy działa, jest wstrzykiwane dla tej jednej karty i tego jednego wywołania, na podstawie zgody, której udziela Twoje własne kliknięcie, i nie zostawia po sobie niczego.
Skanowanie QR kamerą działa na stronie rozszerzenia otwartej w karcie, a nie w okienku popup — popup jest niszczony, gdy traci fokus, a prośba o uprawnienie robi dokładnie to. Do tego nie jest potrzebne żadne uprawnienie w manifeście: używana jest standardowa prośba przeglądarki o dostęp do kamery, przyznawana dla origin rozszerzenia i odwoływalna w ustawieniach witryny, i nigdy nie pojawia się, zanim użytkownik nie otworzy skanera. Obraz jest dekodowany lokalnie, nigdy nie jest przesyłany ani zapisywany.
Od wersji 1.14.0 rozszerzenie raz dziennie łączy się z api.authenticator.sh — przy starcie przeglądarki albo po wyrenderowaniu okienka popup, nigdy po drodze do wyświetlenia kodu. Żądanie to zamknięta lista pól: składa je jedna funkcja w src/billing/sync.ts, a test pilnuje, żeby nie wyszło poza tę listę:
Nigdy nie są wysyłane: sekrety, nazwy kont, wystawcy, kody ani strony, na których się ich używa. Serwer ustala kraj z żądania na brzegu sieci i nie zapisuje adresu IP. Strona odinstalowania otrzymuje ten sam identyfikator instalacji jako parametr ?i=: zgłasza serwerowi, że instalacja została usunięta, i wiąże z nią opcjonalną odpowiedź z ankiety.
Na te potrzeby w chrome.storage.local przechowywane są: identyfikator instalacji, ostatnia podpisana polityka i licencja, klucz licencyjny, który ma ta instalacja (wpisany albo otrzymany po zakupie na niej), liczby otwarć z poszczególnych dni, dopóki serwer ich nie odbierze, oraz zdarzenia w kolejce — żadnych danych kont. Gdy synchronizacja jest włączona, do Chrome Sync trafia też informacja, czy instalacja powstała przed wprowadzeniem płatności, oraz ten klucz licencyjny, żeby przejęły je pozostałe profile Chrome tego użytkownika i same włączyły Pro; wyłączenie synchronizacji usuwa oba z Chrome Sync, a ponowne włączenie je przywraca.
Co wraca z serwera i jak rozszerzenie to traktuje:
Limit nie jest DRM: kod jest otwarty, a fork może go usunąć. Nigdy natomiast nie może być możliwe, żeby ktoś zdobył lub wykorzystał cudzą licencję albo sprawił, że czyjaś instalacja zostanie ograniczona — to byłaby podatność, objęta zakresem naszej polityki bezpieczeństwa.
Samoczynnie wykonywane są dwa rodzaje żądań: sprawdzenie zegara i codzienne połączenie z serwerem rozliczeń. Cała reszta poniżej dzieje się dlatego, że użytkownik w coś kliknął. Manifest V3 zakazuje zdalnego kodu, a nic z poniższych nie przenosi kont, tajnych kluczy ani wygenerowanych kodów. Pełna lista:
| Host | Kiedy | Czy przenosi dane użytkownika? |
|---|---|---|
| time.akamai.com timeapi.io cloudflare.com | Sprawdzenie rozjazdu zegara, buforowane; gdy żadne źródło nie odpowiada, Ustawienia to pokazują. Rozjechany zegar psuje TOTP. | Nie |
| authenticator.sh | Strona powitalna przy instalacji; strona opinii przy odinstalowaniu, z identyfikatorem instalacji w adresie; strony pomocy i wsparcia, gdy otwiera się je z okienka rozszerzenia. | Identyfikator instalacji, tylko na stronie odinstalowania; poza tym nie (zwykłe logi serwera WWW) |
| api.authenticator.sh | Połączenie rozliczeniowe raz dziennie; wywołania płatności, licencji i portalu, gdy użytkownik kupuje Pro, wpisuje klucz, przywraca zakup lub zarządza subskrypcją. | Pola wymienione wyżej — nigdy dane kont. Klucz licencyjny lub adres e-mail tylko wtedy, gdy użytkownik sam go wpisze |
| checkout.stripe.com billing.stripe.com | Stripe Checkout i portal klienta, otwierane w karcie, gdy użytkownik kupuje Pro lub zarządza subskrypcją. | To, co kupujący tam wpisze. Obowiązują własne warunki Stripe i Link |
| chromewebstore.google.com | Strona w sklepie, otwierana w karcie dopiero po kliknięciu: formularz recenzji z prośby o ocenę. | Nie (dla Google to wejście na stronę sklepu jak każde inne) |
Żadne fonty, skrypty, style ani obrazy nie ładują się z zewnętrznych hostów. Wszystko, co potrzebne do wyrenderowania interfejsu, jest w paczce.
Nic z powyższego nie trzeba przyjmować na wiarę. Rozszerzenie ma otwarty kod, a każde wydanie jest powtarzalne:
.crx z Chrome Web Store i rozpakuj gonpm ci && npm run build na Node 20 LTSdist/ z rozpakowaną paczkąPrzy każdym wydaniu na GitHubie publikujemy SHA-256 dla każdego pliku w zbudowanym dist/, jako SHA256SUMS-v<version>.txt i w formacie sha256sum — dzięki temu krok 4 sprowadza się do jednego uruchomienia sha256sum -c. Różnice powinny ograniczać się do kolejności plików w archiwach i białych znaków w zminifikowanym wyniku między wersjami łatek Node.
Zależności w czasie działania są celowo nieliczne — OTPAuth, React, jsQR, Zustand, Framer Motion i Lucide — przypięte zacommitowanym plikiem lock. SBOM w formacie CycloneDX udostępniamy na życzenie.
Zobacz kod źródłowyJesteśmy małym niezależnym dostawcą. Jeśli którykolwiek z poniższych punktów jest w waszym procesie wymogiem twardym, lepiej dowiedzieć się teraz niż po trzech tygodniach oceny:
W zamian oferujemy pełną jawność kodu, weryfikowalną kompilację, minimalny zestaw uprawnień i serwer, do którego w ogóle nie są wysyłane wasze sekrety, nazwy kont ani kody.
Zgłoszenia podatności i pytania o bezpieczeństwo: security@authenticator.sh. Polityka ujawniania, zakres, czasy reakcji i warunki safe harbour są na stronie polityki bezpieczeństwa; co jest przechowywane i co opuszcza urządzenie, opisuje polityka prywatności.
Chętnie wypełnimy pisemną ankietę oceny dostawcy albo przejdziemy przez kod razem z zespołem bezpieczeństwa.
We wszystkim, co nie dotyczy bezpieczeństwa — instalacja, odzyskiwanie konta, pytania o rozliczenia przy wdrożeniu — zajrzyj do pomocy. Prosimy nie wysyłać tam zgłoszeń podatności: ta skrzynka nie jest traktowana jako poufna.