Skip to content

feat(yandex): full password and credit card decryption#585

Merged
moonD4rk merged 4 commits intomainfrom
feat/yandex-decryption
Apr 23, 2026
Merged

feat(yandex): full password and credit card decryption#585
moonD4rk merged 4 commits intomainfrom
feat/yandex-decryption

Conversation

@moonD4rk
Copy link
Copy Markdown
Owner

Summary

  • Implement Yandex's two-level key scheme: the Chromium master key unwraps a per-DB data key in meta.local_encryptor_data (96-byte AES-GCM blob + protobuf signature strip), and the data key decrypts row-level ciphertext.
  • Password rows use AES-GCM with per-row AAD = SHA1 over 5 form fields joined by \x00. Credit cards use AAD = row guid and store data as two JSON blobs in records(guid, public_data, private_data) rather than Chromium's flat credit_cards table.
  • types.CreditCardEntry gains optional CVC / Comment fields (empty for Chromium; filled for Yandex).
  • The pre-existing skeleton queried action_url instead of origin_url, so the AAD never matched and every row decrypted to empty. That's the root cause behind yandex passwords are not output (on windows 11) #105 / Doesn't work on Yandex browser #462 / Can't dencrypt password in yandex browser #476 — they all surfaced the same gap under different browser versions.
  • New crypto primitive: crypto.AESGCMDecryptBlob(key, blob, aad) splits [12B nonce][ct+tag] and decrypts. Generic, not Yandex-specific.
  • RFC-012 documents the design.

Deferred (follow-up PRs):

  • Master-password (RSA-OAEP + PBKDF2) unseal — profiles with non-empty active_keys.sealed_key are detected and skipped with a warning in v1.
  • Linux — Yandex Browser has no official Linux release, so no browser_linux.go entry.

Package layering

  • crypto only exposes pure crypto operations: AESGCMDecryptBlob (generic primitive) and DecryptYandexIntermediateKey (Yandex-specific because of the protobuf signature strip).
  • browser/chromium owns Yandex protocol knowledge: yandexLoginAAD lives next to the password extractor; yandexCardAAD lives next to the credit-card extractor. AAD construction is protocol logic, not crypto.

Test plan

  • `go test ./... -count=1` passes on darwin/linux/windows cross-compile
  • `golangci-lint run` reports 0 issues
  • Real Yandex data validated on Windows 10 sandbox: two stored passwords decrypt to readable ASCII
  • Full-sweep regression across 13 browsers unchanged vs. baseline (703 cookies, 0 non-ASCII)
  • Real Yandex credit-card decryption end-to-end — unit tests pass but sandbox had no cards stored; any reviewer with a Yandex profile containing a card can validate

Closes #90
Closes #105
Closes #462
Closes #476

Implement Yandex's decryption protocol end-to-end. The existing skeleton queried
the wrong URL column (action_url instead of origin_url), so the per-row AAD
never matched the one Yandex seals GCM with, and every row decrypted to empty
plaintext — that's why #105, #462, #476 kept surfacing against the "supported"
browser.

Each Ya Passman Data / Ya Credit Cards DB holds a per-DB data key inside
meta.local_encryptor_data: a protobuf-framed 96-byte blob encrypted under the
Chromium master key. The row-level data (password_value, records.private_data)
is AES-GCM with per-row AAD — SHA1 over five form fields for passwords, the
row's guid for cards. Credit cards live in records(guid, public_data,
private_data) as two JSON blobs, not Chromium's credit_cards table, so
CreditCardEntry gains optional CVC and Comment fields.

Profiles guarded by a browser-level master password (non-empty
active_keys.sealed_key) are detected and skipped with a warning; RSA-OAEP
unseal is deferred. Linux is out of scope: Yandex Browser has no Linux release.

Validated on a Windows 10 sandbox with a real Yandex profile: two stored
passwords decrypt to readable ASCII (stackoverflow and douban); full-sweep
regression across 13 browsers is unchanged (703 cookies, 0 non-ASCII).

Design documented in RFC-012.

Closes #90
Closes #105
Closes #462
Closes #476
Copilot AI review requested due to automatic review settings April 23, 2026 05:41
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 66.11111% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.89%. Comparing base (7e64d50) to head (332ddb2).

Files with missing lines Patch % Lines
browser/chromium/extract_creditcard.go 65.38% 13 Missing and 5 partials ⚠️
browser/chromium/yandex_key.go 61.29% 8 Missing and 4 partials ⚠️
browser/chromium/extract_password.go 81.48% 7 Missing and 3 partials ⚠️
crypto/crypto.go 53.84% 4 Missing and 2 partials ⚠️
crypto/yandex.go 71.42% 4 Missing and 2 partials ⚠️
browser/chromium/chromium.go 0.00% 5 Missing ⚠️
browser/chromium/source.go 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #585      +/-   ##
==========================================
- Coverage   73.59%   70.89%   -2.71%     
==========================================
  Files          59       61       +2     
  Lines        2651     3274     +623     
==========================================
+ Hits         1951     2321     +370     
- Misses        524      761     +237     
- Partials      176      192      +16     
Flag Coverage Δ
unittests 70.89% <66.11%> (-2.71%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds full Yandex Browser support for decrypting saved passwords and credit cards within the existing Chromium extraction pipeline, implementing Yandex’s two-level key hierarchy (Chromium master key → per-DB data key in meta.local_encryptor_data) and row-level AES-GCM AAD rules.

Changes:

  • Implement Yandex per-DB data-key unwrapping (meta.local_encryptor_data) plus AES-GCM row decryption using per-row AAD for passwords and credit cards.
  • Add Yandex credit-card extraction from records(guid, public_data, private_data) and route counting/extraction based on ChromiumYandex.
  • Extend types.CreditCardEntry with CVC and Comment, and add unit/integration-style tests + an RFC describing the design.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
types/models.go Extends CreditCardEntry with CVC and Comment fields for Yandex output.
rfcs/012-yandex-decryption.md Adds RFC documenting Yandex decryption design and integration points.
output/reflect_test.go Updates CSV header expectations for the new credit-card fields.
crypto/crypto.go Adds AESGCMDecryptBlob(key, blob, aad) primitive for nonce-prefixed AES-GCM blobs.
crypto/yandex.go Adds Yandex intermediate-key unwrap (DecryptYandexIntermediateKey).
crypto/yandex_test.go Adds unit tests for Yandex intermediate key unwrap and AESGCMDecryptBlob.
crypto/crypto_windows.go Removes obsolete DecryptYandex Windows-only stub.
browser/chromium/yandex_key.go Implements per-DB Yandex data-key loading + master-password gate detection.
browser/chromium/extract_password.go Implements Yandex password extraction with SHA1-based AAD and row decrypt.
browser/chromium/extract_creditcard.go Implements Yandex credit-card extraction from records JSON blobs + guid AAD decrypt.
browser/chromium/source.go Registers Yandex extractors for Password + CreditCard via a new creditCardExtractor.
browser/chromium/chromium.go Routes Yandex credit-card counting to records-based counter.
browser/chromium/yandex_testutil_test.go Adds Yandex-specific SQLite fixture builders for passwords/cards.
browser/chromium/extract_password_test.go Adds end-to-end Yandex password tests incl. master-password skip and AAD unit tests.
browser/chromium/extract_creditcard_test.go Adds end-to-end Yandex credit-card tests incl. count and guid-AAD unit test.
browser/chromium/chromium_test.go Ensures Yandex extractor map includes CreditCard override.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rfcs/012-yandex-decryption.md Outdated
2. **Open DB**: `session.Acquire` has already copied the target SQLite file to a temp path; `loadYandexDataKey` opens it there.
3. **Master-password gate**: `SELECT sealed_key FROM active_keys`. Non-empty → return `errYandexMasterPasswordSet`; the caller logs a warning and skips the profile (v1 limitation). Table missing (credit-card DB) or empty value → continue.
4. **Data key**: `SELECT value FROM meta WHERE key='local_encryptor_data'`. Find the `"v10"` byte sequence, take the 96 bytes that follow, split into 12B nonce + 84B (ciphertext+tag). AES-GCM-decrypt with the master key (no AAD). Strip the 4-byte protobuf signature `08 01 12 20`. Keep the first 32 bytes.
5. **Per-row decryption**: for each row, compute AAD (see §4.4), split `[12B nonce][ct+tag]`, call `AESGCMDecryptWithAAD(dataKey, nonce, ct, aad)`.
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC refers to AESGCMDecryptWithAAD(...) for per-row decryption, but the implementation uses crypto.AESGCMDecryptBlob(key, blob, aad) and there is no AESGCMDecryptWithAAD symbol. Please update the RFC to match the actual function/API names so readers can follow the code path accurately.

Suggested change
5. **Per-row decryption**: for each row, compute AAD (see §4.4), split `[12B nonce][ct+tag]`, call `AESGCMDecryptWithAAD(dataKey, nonce, ct, aad)`.
5. **Per-row decryption**: for each row, compute AAD (see §4.4). The encrypted row value is a blob laid out as `[12B nonce][ct+tag]`; the implementation passes that blob directly to `crypto.AESGCMDecryptBlob(dataKey, blob, aad)`.

Copilot uses AI. Check for mistakes.
Comment thread rfcs/012-yandex-decryption.md Outdated
Comment on lines +125 to +129
| `crypto/yandex.go` | Pure-Go primitives: `DecryptYandexIntermediateKey`, `AESGCMDecryptWithAAD`, `YandexLoginAAD`, `YandexCardAAD`, `YandexSignature`. Cross-platform, unit-testable on any host. |
| `crypto/yandex_test.go` | Round-trip tests using synthesized blobs (no Yandex install required). |
| `browser/chromium/yandex_key.go` | `loadYandexDataKey(dbPath, masterKey)` — opens the DB, checks `active_keys`, reads `meta.local_encryptor_data`, returns dataKey or `errYandexMasterPasswordSet`. |
| `browser/chromium/extract_password.go` | `extractYandexPasswords` — queries `origin_url, username_element, username_value, password_element, password_value, signon_realm, date_created`; computes per-row AAD; decrypts. |
| `browser/chromium/extract_creditcard_yandex.go` | `extractYandexCreditCards` + `countYandexCreditCards` — queries `records`; decrypts `private_data` with guid-AAD; parses both JSON blobs. |
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the “Code layout” table, crypto/yandex.go is listed as containing AESGCMDecryptWithAAD, YandexLoginAAD, YandexCardAAD, and YandexSignature, but those functions/constants aren’t present there (AAD helpers live under browser/chromium; the generic primitive is crypto.AESGCMDecryptBlob in crypto/crypto.go; signature is unexported). Please reconcile this section (and §5.2 / test-strategy mentions) with the current code organization.

Suggested change
| `crypto/yandex.go` | Pure-Go primitives: `DecryptYandexIntermediateKey`, `AESGCMDecryptWithAAD`, `YandexLoginAAD`, `YandexCardAAD`, `YandexSignature`. Cross-platform, unit-testable on any host. |
| `crypto/yandex_test.go` | Round-trip tests using synthesized blobs (no Yandex install required). |
| `browser/chromium/yandex_key.go` | `loadYandexDataKey(dbPath, masterKey)` — opens the DB, checks `active_keys`, reads `meta.local_encryptor_data`, returns dataKey or `errYandexMasterPasswordSet`. |
| `browser/chromium/extract_password.go` | `extractYandexPasswords` — queries `origin_url, username_element, username_value, password_element, password_value, signon_realm, date_created`; computes per-row AAD; decrypts. |
| `browser/chromium/extract_creditcard_yandex.go` | `extractYandexCreditCards` + `countYandexCreditCards` — queries `records`; decrypts `private_data` with guid-AAD; parses both JSON blobs. |
| `crypto/yandex.go` | Yandex-specific key-unwrapping logic, including `DecryptYandexIntermediateKey`. Pure Go and unit-testable on any host. |
| `crypto/crypto.go` | Generic AES-GCM primitive: `AESGCMDecryptBlob`. Shared helper used by browser-specific extractors once they have derived the correct row AAD. |
| `browser/chromium/yandex_key.go` | `loadYandexDataKey(dbPath, masterKey)` — opens the DB, checks `active_keys`, reads `meta.local_encryptor_data`, returns dataKey or `errYandexMasterPasswordSet`. |
| `browser/chromium/extract_password.go` | `extractYandexPasswords` — queries `origin_url, username_element, username_value, password_element, password_value, signon_realm, date_created`; derives the password-row AAD from those fields; decrypts via the generic AES-GCM helper. |
| `browser/chromium/extract_creditcard_yandex.go` | `extractYandexCreditCards` + `countYandexCreditCards` — queries `records`; uses `guid` as the card-row AAD when decrypting `private_data`; parses both JSON blobs. |

Copilot uses AI. Check for mistakes.
Comment thread crypto/crypto.go
// AESGCMDecryptBlob decrypts a blob shaped as [12B nonce][ciphertext+16B GCM tag] with caller-supplied AAD.
// Used by protocols that wrap AES-GCM output with a fixed-length nonce prefix (Yandex passwords/cards).
func AESGCMDecryptBlob(key, blob, aad []byte) ([]byte, error) {
if len(blob) < gcmNonceSize {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AESGCMDecryptBlob documents the blob as [12B nonce][ciphertext+16B GCM tag], but it only checks len(blob) < gcmNonceSize. For blobs that include a nonce but are missing the tag/ciphertext, callers will get a lower-level GCM error instead of the package’s sentinel errShortCiphertext. Consider validating len(blob) >= gcmNonceSize + 16 (and possibly > gcmNonceSize) before calling Open so short inputs are classified consistently.

Suggested change
if len(blob) < gcmNonceSize {
if len(blob) < gcmNonceSize+16 {

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +54
// hasMasterPassword: missing table (Ya Credit Cards) or empty sealed_key both mean false.
func hasMasterPassword(db *sql.DB) bool {
var sealed sql.NullString
if err := db.QueryRow("SELECT sealed_key FROM active_keys").Scan(&sealed); err != nil {
return false
}
return sealed.Valid && strings.TrimSpace(sealed.String) != ""
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasMasterPassword currently treats any query error as “no master password”. That means real DB issues (corruption, missing column, unexpected schema changes) will be silently ignored and the extractor will proceed, likely producing confusing decrypt failures. Consider returning (bool, error) and only treating “no such table” / sql.ErrNoRows as false; propagate other errors up via loadYandexDataKey so the caller can surface them.

Copilot uses AI. Check for mistakes.
Align the RFC with the code that actually shipped:

- §5 Code layout: drop stale references to AESGCMDecryptWithAAD / YandexLoginAAD
  / YandexCardAAD / YandexSignature (unexported or moved); add the new
  crypto.AESGCMDecryptBlob generic primitive; note that extract_creditcard_yandex.go
  was merged back into extract_creditcard.go; mention the local yandexLoginAAD /
  yandexCardAAD helpers that now live next to their extractors.
- §5.2: replace the "why AESGCMDecryptWithAAD is new" rationale with the final
  split — generic AES-GCM + AAD primitive in crypto, Yandex-specific AAD
  construction in chromium.
- §7 Test strategy: update the file/test inventory, point at the merged
  extract_creditcard_test.go, and soften the regression baseline wording to
  "0 non-ASCII" instead of a stale 574-cookie number.
RFCs document the technical design; per-maintainer sandbox state (specific
browser versions last verified, private playbook references, timestamped
"tested against Chrome 147" status markers, cookie-count baselines tied to
a single contributor's host) doesn't belong in them.

- RFC-010 §1.1: replace "Tested matrix (as of …)" with a compatibility-
  contract table; drop the "Last verified" column and the reference to
  the author's private regression playbook.
- RFC-010 §10: drop "tested against Chrome 147 family" timestamp, "✅
  verified" / "not yet sandbox-tested" status markers; keep the technical
  behavior columns.
- RFC-010 §11-12: drop "private maintainer notes" pointer and the "not
  observed on Chrome 147 sandbox outputs" / "no observed conflict" lines;
  restate the unknowns as open questions.
- RFC-012 §6: drop "as of 2026-04" timestamp on the Yandex-ABE note.
- RFC-012 §7: replace the CLAUDE.local.md / 574-cookie validation recipe
  with a one-line general statement about the full-sweep regression gate.
RFCs describe the technical design — the Yandex protocol, key hierarchy, AAD
formulas, on-disk byte layout. Go-specific implementation (which function lives
in which file, which exact signature, which SQL column list) is subject to
refactor and doesn't belong here.

- §4.2: drop references to specific Go symbols (loadYandexDataKey,
  AESGCMDecryptWithAAD) from the recovery-steps list; describe each step by
  what it does, not by which function does it.
- §5: replace the "Code layout" file-path table with a layering-rationale
  section. Keep §5.1 (why Yandex derivation stays in the extract path) and
  §5.2 (why AAD construction is not in the crypto layer), both rephrased
  without Go package paths or exported-symbol names.
- §6: drop file-path pointers (crypto/windows/abe_native/com_iid.c,
  browser/browser_linux.go, ABERetriever); describe the future paths by
  their effect on the behavior table.
- §7: replace the test-file breakdown table with a coverage summary organized
  by scenario (intermediate-key unwrap, AES-GCM-with-AAD, password/card
  extraction, AAD formulas), not by file name.
@moonD4rk moonD4rk merged commit 0c6c781 into main Apr 23, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants