Hôm nay chúng tôi có mặt trên Product Hunt! Để lại bình luận

Authenticator 1.16.0 đã có mặt. Xem có gì mới

Cách hoạt động

Tổng quan kỹ thuật dành cho người rà soát bảo mật. Mọi điều ở đây đều đối chiếu được với mã nguồn — kể cả những phần không có lợi cho chúng tôi, chúng tôi nói ra thay vì bỏ qua.

Kiến trúc

Tiện ích là một tiện ích Chrome theo Manifest V3, sinh mã TOTP ngay trên máy bằng OTPAuth. Việc sinh mã, lưu tài khoản và mã hóa chúng không cần đến backend nào, và không có tài khoản người dùng. Chúng tôi vận hành một máy chủ duy nhất, api.authenticator.sh, cho gói miễn phí và Pro — mô tả bên dưới — và nó không bao giờ nhận khóa bí mật, tên tài khoản, tên dịch vụ hay mã nào.

Máy chủ đó không giữ vật liệu khóa cho tài khoản của bất kỳ ai: không ký gửi khóa và không có đường khôi phục nào do chúng tôi kiểm soát. Nếu người dùng quên mật khẩu và mất mã khôi phục, chúng tôi không giúp được — đó là chủ ý thiết kế.

Mật mã

Bảo vệ bằng mật khẩu là tùy chọn và mặc định tắt. Khi bật:

Thuật toán mã hóa
AES-256-GCM, IV ngẫu nhiên 96 bit cho mỗi bản ghi và mỗi lần ghi lại
Dẫn xuất khóa
PBKDF2-HMAC-SHA256, 600.000 vòng lặp, muối ngẫu nhiên 128 bit (theo khuyến nghị OWASP)
Khóa chính
256 bit, từ crypto.getRandomValues, sinh một lần duy nhất; mật khẩu chỉ bọc lấy nó
Dấu vân tay
HMAC-SHA256 dưới một khóa con dẫn xuất bằng HKDF, dùng để hợp nhất bản ghi giữa các thiết bị
Nơi giữ khóa đã mở
chrome.storage.session — chỉ trong bộ nhớ, không bao giờ ghi xuống đĩa, xóa khi đóng trình duyệt, content script không chạm tới được
Tự động khóa
Mỗi lần mở / sau 5, 15 hoặc 60 phút không dùng / cho đến khi đóng trình duyệt
Cách hiện thực
Chỉ dùng WebCrypto. Không tự viết bất kỳ nguyên hàm mật mã nào và không đóng gói thư viện mật mã nào.

Vì sao khóa có hai tầng

Dữ liệu không bao giờ được mã hóa trực tiếp bằng khóa dẫn xuất từ mật khẩu. Một khóa chính ngẫu nhiên mã hóa các bản ghi; đầu ra PBKDF2 chỉ bọc lấy khóa chính đó. Hai hệ quả kéo theo, và cả hai đều quan trọng:

  • Đổi mật khẩu chỉ ghi lại 32 byte thay vì mã hóa lại từng bản ghi, từng bản sao lưu và từng tệp xuất. Chính những lần ghi lại hàng loạt như vậy là lúc hệ thống lưu trữ đánh mất dữ liệu.
  • Mã khôi phục 160 bit có thể bọc cùng khóa chính một cách độc lập, nên quên mật khẩu vẫn cứu được. Mã chỉ hiện một lần, phải nhập lại để xác nhận và được đổi mới sau mỗi lần dùng. Không có gì được ghi xuống đĩa cho đến khi người dùng xác nhận.

Cái gì được mã hóa, cái gì không

Khi bật bảo vệ bằng mật khẩu, toàn bộ bản ghi tài khoản được mã hóa trừ mã định danh và dấu vân tay — kể cả tên dịch vụ, nên hồ sơ trình duyệt bị đánh cắp cũng không lộ ra người dùng có tài khoản ở những dịch vụ nào. Khi bật, tiện ích mã hóa các bản ghi, kiểm chứng bằng cách giải mã và đối chiếu, rồi mới xóa mọi bản sao dạng rõ: bộ nhớ cục bộ của trình duyệt, phần đồng bộ và cả bảy ảnh chụp sao lưu luân phiên.

Nói thẳng, vì điều này ảnh hưởng đến đánh giá của bạn:

  • Khi tắt bảo vệ bằng mật khẩu, các tài khoản được lưu không mã hóa và, nếu Chrome Sync đang bật, được nhân bản qua chính tài khoản Google của người dùng. Chúng không bao giờ đến chỗ chúng tôi, nhưng Google thì giữ. Có thể tắt đồng bộ, và thao tác đó cũng xóa những gì đã nằm sẵn ở đó.
  • Lịch sử sử dụng theo từng trang — tài khoản nào dùng trên tên miền nào — được lưu cục bộ và không nằm trong phạm vi két trong lúc đang được thu thập. Bật bảo vệ bằng mật khẩu sẽ xóa nó; cũng có thể tắt riêng phần này.

Mô hình mối đe dọa

Bảo vệ trước

  • Thư mục hồ sơ trình duyệt bị đánh cắp hoặc sao chép
  • Mã độc infostealer hút toàn bộ dữ liệu trình duyệt
  • Người có quyền tiếp cận vật lý một máy bỏ không
  • Bản ghi tài khoản đi tới Google qua Chrome Sync
  • Tệp sao lưu bị mất hoặc bị lấy cắp (bản xuất có mật khẩu)

Không bảo vệ trước

  • Mã độc chạy dưới quyền người dùng trong lúc két đang mở
  • Keylogger ghi lại mật khẩu ngay khi gõ
  • Trình duyệt hoặc hệ điều hành đã bị xâm nhập
  • Việc chính các dịch vụ bên thứ ba dùng mã đó bị xâm nhập
  • Một bản xuất không mã hóa mà bạn tự chọn ghi ra — JSON thuần, liên kết otpauth:// hay CXF đều đọc được bởi bất kỳ ai mở tệp

Ngắn gọn: nó bảo vệ dữ liệu khi lưu trữ. Nó không, và không thể, bảo vệ một thiết bị đã nằm trong tay kẻ tấn công ngay trong lúc sử dụng.

Quyền

Manifest khai báo năm quyền và không có quyền host nào:

storage
Tài khoản và thiết lập trong bộ nhớ cục bộ, khóa đã mở trong bộ nhớ phiên, cùng bản sao đồng bộ tùy chọn
activeTab
Đọc tên miền của tab đang mở để làm nổi bật tài khoản tương ứng, và chụp tab khi người dùng quét mã QR từ màn hình
contextMenus
Thêm mục “Chèn mã 2FA” vào menu chuột phải, và chỉ trên các trường có thể chỉnh sửa của trang http(s)
scripting
Chèn đúng một hàm đặt mã vào ô — cho một thẻ và một lần gọi duy nhất, chỉ sau khi mục menu đó được chọn
sidePanel
Mở ứng dụng trong bảng bên của Chrome khi đó là chế độ đã chọn; nó không cấp quyền truy cập trang nào

Không có Không có content script nào, không có quyền tabs, cookies hay webRequest, và không có web-accessible resources. Không có gì của chúng tôi tồn tại trong một trang cho đến khi bạn yêu cầu mã ở đó; thứ chạy lúc ấy được chèn cho đúng thẻ đó và đúng lần gọi đó, theo quyền mà chính cú nhấp của bạn cấp, và không để lại gì.

Máy ảnh

Quét QR bằng máy ảnh chạy trên một trang của tiện ích mở trong tab, không phải trong cửa sổ popup — popup bị hủy ngay khi mất tiêu điểm, mà hộp thoại xin quyền thì làm đúng điều đó. Việc này không cần bất kỳ quyền nào trong manifest: nó dùng hộp thoại xin quyền máy ảnh chuẩn của trình duyệt, cấp theo origin của tiện ích và thu hồi được trong cài đặt trang, và chỉ xuất hiện khi người dùng mở trình quét. Video được giải mã ngay trên máy, không bao giờ được truyền đi hay lưu lại.

Gói miễn phí, Pro và lần liên lạc hằng ngày

Từ phiên bản 1.14.0, tiện ích liên lạc với api.authenticator.sh mỗi ngày một lần — khi trình duyệt khởi động, hoặc sau khi popup đã hiển thị xong, không bao giờ chen vào quá trình hiển thị mã. Yêu cầu gồm một danh sách trường khép kín, được dựng trong đúng một hàm ở src/billing/sync.ts và được một bài kiểm thử giữ đúng danh sách đó:

iid
Mã định danh ngẫu nhiên do bản cài đặt này tạo ra. Không gắn với tài khoản Google, địa chỉ email hay trình duyệt — trừ khi người dùng mua Pro: khi đó bản ghi giao dịch gắn bản cài đặt đã dùng để mua, và mọi bản cài đặt đang kích hoạt khóa giấy phép, với địa chỉ email đã dùng để thanh toán
ver, ui_lang, browser_lang
Phiên bản tiện ích, ngôn ngữ giao diện, ngôn ngữ trình duyệt
installed_at, legacy, legacy_src
Thời điểm bản cài đặt xuất hiện, và nó có từ trước khi áp dụng thanh toán hay không
accounts, groups
Bản cài đặt đang giữ bao nhiêu tài khoản và trong bao nhiêu nhóm — chỉ là con số, không bao giờ là những tài khoản nào hay tên nhóm
vault, sync, open_mode, view_mode, style, dark, os
Bảo vệ bằng mật khẩu và đồng bộ có đang bật không, và tiện ích mở ra theo cách nào, danh sách tài khoản hiển thị ra sao (bình thường, thu gọn hay ẩn mã), và đang dùng kiểu nào, sáng hay tối, và hệ điều hành (Windows, macOS, Linux, ChromeOS)
pid, lid, has_key, key_hint
Chính sách và giấy phép đã ký mà bản cài đặt đang giữ, đã có khóa giấy phép hay chưa, và bốn ký tự cuối của khóa đó
opens
Số lần tiện ích được mở vào mỗi ngày kể từ lần liên lạc trước (tối đa 14 ngày gần nhất) — chỉ là một con số cho mỗi ngày, không có gì về những gì đã được xem
events
Các sự kiện thanh toán đang chờ gửi, từ một danh sách khép kín: đã chạm giới hạn, màn hình trả phí được hiển thị hoặc bị đóng, một gói được chọn, đã thử thanh toán hoặc thử một khóa

Không bao giờ gửi: khóa bí mật, tên tài khoản, tên dịch vụ, mã, hay các trang web nơi chúng được dùng. Máy chủ lấy quốc gia từ yêu cầu ngay tại lớp biên (edge) và không lưu địa chỉ IP. Trang gỡ cài đặt nhận cùng mã định danh cài đặt đó dưới dạng ?i=: trang báo cho máy chủ biết bản cài đặt đã bị gỡ, và gắn câu trả lời khảo sát không bắt buộc với bản cài đặt đó.

Phần được lưu trong chrome.storage.local cho việc này: mã định danh cài đặt, chính sách và giấy phép đã ký gần nhất, khóa giấy phép mà bản cài đặt này đang giữ (do người dùng nhập, hoặc nhận được sau khi mua trên chính bản cài đặt này), số lần mở theo từng ngày cho đến khi máy chủ nhận được, cùng các sự kiện đang chờ gửi — không có dữ liệu tài khoản nào. Khi đồng bộ đang bật, thông tin bản cài đặt có từ trước khi áp dụng thanh toán hay không và khóa giấy phép đó cũng được đưa vào Chrome Sync, để các hồ sơ Chrome khác của người dùng nhận được và tự bật Pro; tắt đồng bộ sẽ xóa cả hai khỏi Chrome Sync, bật lại thì chúng được đưa trở lại.

Những gì được trả về, và cách tiện ích xử lý chúng:

Token
Chính sách (giới hạn tài khoản của gói miễn phí) và giấy phép (Pro) là JWS được ký bằng ES256 (ECDSA P-256)
Xác minh
WebCrypto, đối chiếu với các khóa công khai được biên dịch sẵn trong bản dựng (src/billing/keys.ts). Bản dựng cho Chrome Web Store chỉ tin khóa production, và bước kiểm tra bản dựng từ chối mọi bản mang bất kỳ khóa nào khác
Ngoại tuyến
Giới hạn và giấy phép được kiểm tra ngay trên máy, từ các token đã ký gần nhất. Cho đến khi nhận được một chính sách đã ký, không có gì bị giới hạn — máy chủ không liên lạc được thì không bao giờ có thể khóa ai ở ngoài
Giới hạn làm được gì
Ngăn việc thêm một tài khoản, và không gì khác. Nó không thể ẩn, khóa, xóa hay thay đổi tài khoản, cũng không chặn được mã, xuất, sao lưu, khôi phục hay đồng bộ
Mua hàng
Stripe Checkout trong một tab mới, với Link là bên bán hàng chính thức (merchant of record). Email của người mua đến với chúng tôi từ Stripe, để cấp và khôi phục khóa giấy phép; khóa được lưu dưới dạng hash, kèm một bản sao đã mã hóa để có thể gửi lại khóa bị mất
Khóa giấy phép
Phản hồi của lần liên lạc hằng ngày chỉ chứa chính khóa đó cho bản cài đặt đã mua Pro, và chỉ cho đến khi bốn ký tự mà bản cài đặt ấy báo trong key_hint khớp với khóa đó — nhờ vậy, khóa mua sau sẽ thay thế khóa cũ; bản cài đặt nơi khóa được nhập tay sẽ không bao giờ được gửi khóa

Giới hạn này không phải DRM: mã nguồn là mã mở, và một bản fork có thể gỡ nó đi. Điều không bao giờ được phép xảy ra là một người lấy được hoặc dùng được giấy phép của người khác, hay khiến bản cài đặt của người khác bị giới hạn — đó là lỗ hổng, và nằm trong phạm vi chính sách bảo mật của chúng tôi.

Lưu lượng ra ngoài

Có hai loại yêu cầu tự diễn ra: kiểm tra đồng hồ và lần liên lạc thanh toán hằng ngày. Mọi thứ còn lại bên dưới chỉ xảy ra vì người dùng đã bấm vào. Manifest V3 cấm mã từ xa, và không mục nào ở đây mang theo tài khoản, khóa bí mật hay mã đã sinh ra. Danh sách đầy đủ:

Máy chủKhi nàoCó mang dữ liệu người dùng không?
time.akamai.com
timeapi.io
cloudflare.com
Kiểm tra lệch đồng hồ, có cache; nếu không nguồn nào phản hồi, phần Cài đặt sẽ cho biết. Đồng hồ lệch là TOTP hỏng.Không
authenticator.shTrang chào mừng khi cài đặt; trang góp ý khi gỡ cài đặt, với mã định danh cài đặt trong địa chỉ; trang trợ giúp và hỗ trợ khi mở từ cửa sổ tiện ích.Mã định danh cài đặt, chỉ trên trang gỡ cài đặt; ngoài ra thì không (chỉ log máy chủ web thông thường)
api.authenticator.shLần liên lạc thanh toán mỗi ngày một lần; các lệnh gọi thanh toán, giấy phép và cổng khách hàng khi người dùng mua Pro, nhập khóa, khôi phục giao dịch mua hoặc quản lý gói đăng ký.Các trường liệt kê ở trên — không bao giờ có dữ liệu tài khoản. Khóa giấy phép hay địa chỉ email chỉ khi người dùng tự nhập vào
checkout.stripe.com
billing.stripe.com
Stripe Checkout và cổng khách hàng, mở trong một tab khi người dùng mua Pro hoặc quản lý gói đăng ký.Những gì người mua nhập ở đó. Áp dụng điều khoản riêng của Stripe và Link
chromewebstore.google.comTrang trên cửa hàng, chỉ mở trong tab mới khi bấm: biểu mẫu đánh giá từ lời mời đánh giá.Không (Google thấy đây là một lượt xem trang cửa hàng như mọi lượt khác)

Không phông chữ, mã, kiểu dáng hay hình ảnh nào được tải từ máy chủ bên thứ ba. Mọi thứ cần để dựng giao diện đều nằm sẵn trong gói cài đặt.

Xác minh những gì chúng tôi phát hành

Bạn không cần tin suông bất cứ điều nào ở trên. Tiện ích là mã nguồn mở và mỗi bản phát hành đều tái lập được:

  1. Tải tệp .crx đã phát hành từ Chrome Web Store và giải nén
  2. Chuyển sang đúng thẻ git tương ứng
  3. Chạy npm ci && npm run build trên Node 20 LTS
  4. So sánh thư mục dist/ thu được với gói đã giải nén

Mỗi bản phát hành trên GitHub đều kèm SHA-256 của từng tệp trong dist/ đã dựng, dưới dạng SHA256SUMS-v<version>.txt và theo định dạng sha256sum, nên bước 4 rút lại thành một lần chạy sha256sum -c. Khác biệt chỉ nên nằm ở thứ tự tệp bên trong kho nén và khoảng trắng trong đầu ra đã rút gọn giữa các bản vá Node.

Phụ thuộc lúc chạy được giữ ít một cách có chủ đích — OTPAuth, React, jsQR, Zustand, Framer Motion và Lucide — ghim bằng lockfile đã commit. SBOM theo chuẩn CycloneDX có sẵn khi được yêu cầu.

Xem mã nguồn

Những thứ chúng tôi không có

Chúng tôi là một nhà cung cấp nhỏ và độc lập. Nếu bất kỳ điểm nào dưới đây là yêu cầu bắt buộc trong quy trình của bạn, biết ngay bây giờ vẫn hơn là biết sau ba tuần đánh giá:

  • Không có chứng nhận SOC 2, ISO 27001 hay tương đương
  • Đến nay chưa đặt hàng kiểm thử xâm nhập từ bên thứ ba
  • Không có chương trình bug bounty trả thưởng

Thứ chúng tôi đưa ra để bù lại là mã nguồn minh bạch hoàn toàn, bản dựng xác minh được, bề mặt quyền tối thiểu và một máy chủ mà ngay từ đầu không bao giờ nhận khóa bí mật, tên tài khoản hay mã của bạn.

Liên hệ với chúng tôi

Báo cáo lỗ hổng và câu hỏi bảo mật: security@authenticator.sh. Chính sách công bố, phạm vi, thời gian phản hồi và điều khoản safe harbour nằm ở trang chính sách bảo mật; những gì được lưu và những gì rời khỏi thiết bị được nêu trong chính sách quyền riêng tư.

Chúng tôi sẵn lòng trả lời bảng câu hỏi đánh giá nhà cung cấp bằng văn bản hoặc cùng đội bảo mật của bạn rà từng phần mã nguồn.

Với mọi việc không thuộc phạm vi bảo mật — cài đặt, khôi phục tài khoản, thắc mắc thanh toán khi triển khai — xem hỗ trợ. Xin đừng gửi báo cáo lỗ hổng tới đó; hộp thư ấy không được xử lý như thư mật.