#!/usr/bin/env python3
"""Insert a saved credential for http://victim.com:8000/ into a Chrome
'Login Data' SQLite file (Linux, --password-store=basic, v10 encryption).

Usage: python3 seed_password.py <profile-dir>
The profile must already contain an empty 'Login Data' file (run Chrome once
with this profile to create it).
"""
import os
import sqlite3
import sys

# AES-128-CBC, key = PBKDF2-HMAC-SHA1("peanuts", "saltysalt", 1, 16),
# IV = 16 spaces, prefix = "v10". See components/os_crypt/.
V10_KEY = bytes.fromhex("fd621fe5a2b402539dfa147ca9272778")
V10_IV = b" " * 16


def encrypt_v10(plaintext: str) -> bytes:
    try:
        from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
        from cryptography.hazmat.primitives import padding
        padder = padding.PKCS7(128).padder()
        data = padder.update(plaintext.encode("utf-8")) + padder.finalize()
        enc = Cipher(algorithms.AES(V10_KEY), modes.CBC(V10_IV)).encryptor()
        ct = enc.update(data) + enc.finalize()
    except ImportError:
        # Fallback: pure-python single-block AES (only for <=15-byte payloads).
        ct = _aes_cbc_one_block(plaintext.encode("utf-8"))
    return b"v10" + ct


# --- Minimal pure-Python AES-128 single-block (PKCS7) fallback ---------------
_SBOX = bytes.fromhex(
    "637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0"
    "b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b275"
    "09832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cf"
    "d0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2"
    "cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdb"
    "e0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08"
    "ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9e"
    "e1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16")
_RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36]


def _xtime(a):
    return ((a << 1) ^ 0x1B) & 0xFF if a & 0x80 else a << 1


def _key_expand(key):
    rk = list(key)
    for i in range(4, 44):
        t = rk[(i-1)*4:(i)*4]
        if i % 4 == 0:
            t = [_SBOX[t[1]] ^ _RCON[i//4 - 1], _SBOX[t[2]], _SBOX[t[3]], _SBOX[t[0]]]
        rk += [rk[(i-4)*4+j] ^ t[j] for j in range(4)]
    return [bytes(rk[16*r:16*r+16]) for r in range(11)]


def _aes_enc_block(block, rks):
    s = [block[c*4+r] for r in range(4) for c in range(4)]  # column-major
    s = [s[i] ^ rks[0][((i % 4)*4)+(i//4)] for i in range(16)]
    for rnd in range(1, 11):
        s = [_SBOX[x] for x in s]
        # ShiftRows (state stored row-major: row r at s[r*4:r*4+4])
        for r in range(1, 4):
            s[r*4:r*4+4] = s[r*4+r:r*4+4] + s[r*4:r*4+r]
        if rnd != 10:
            for c in range(4):
                a = [s[r*4+c] for r in range(4)]
                s[0*4+c] = _xtime(a[0]) ^ (_xtime(a[1]) ^ a[1]) ^ a[2] ^ a[3]
                s[1*4+c] = a[0] ^ _xtime(a[1]) ^ (_xtime(a[2]) ^ a[2]) ^ a[3]
                s[2*4+c] = a[0] ^ a[1] ^ _xtime(a[2]) ^ (_xtime(a[3]) ^ a[3])
                s[3*4+c] = (_xtime(a[0]) ^ a[0]) ^ a[1] ^ a[2] ^ _xtime(a[3])
        s = [s[i] ^ rks[rnd][((i % 4)*4)+(i//4)] for i in range(16)]
    return bytes(s[(i % 4)*4 + (i//4)] for i in range(16))


def _aes_cbc_one_block(pt: bytes) -> bytes:
    assert len(pt) < 16, "fallback only handles one block"
    pad = 16 - len(pt)
    block = bytes(b ^ V10_IV[i] for i, b in enumerate(pt + bytes([pad]*pad)))
    return _aes_enc_block(block, _key_expand(V10_KEY))
# ----------------------------------------------------------------------------


def main():
    profile = sys.argv[1]
    db_path = os.path.join(profile, "Login Data")
    if not os.path.exists(db_path):
        sys.exit(f"error: {db_path} does not exist. Run Chrome once with this "
                 "profile first.")

    origin_url = "http://victim.com:8000/login.html"
    signon_realm = "http://victim.com:8000/"
    username = "alice"
    password = "S3cr3tP@ss!"
    enc_password = encrypt_v10(password)

    conn = sqlite3.connect(db_path)
    cur = conn.cursor()
    cols = [r[1] for r in cur.execute("PRAGMA table_info(logins)").fetchall()]
    if not cols:
        sys.exit("error: 'logins' table not found in Login Data")

    values = {
        "origin_url": origin_url,
        "action_url": "http://victim.com:8000/submit",
        "username_element": "username",
        "username_value": username,
        "password_element": "password",
        "password_value": enc_password,
        "submit_element": "",
        "signon_realm": signon_realm,
        "date_created": 13300000000000000,
        "blacklisted_by_user": 0,
        "scheme": 0,
        "password_type": 0,
        "times_used": 1,
        "form_data": b"",
        "date_synced": 0,
        "display_name": "",
        "icon_url": "",
        "federation_url": "",
        "skip_zero_click": 1,
        "generation_upload_status": 0,
        "possible_username_pairs": b"",
        "id": 1,
        "date_last_used": 13300000000000000,
        "moving_blocked_for": b"",
        "date_password_modified": 13300000000000000,
        "sender_email": "",
        "sender_name": "",
        "date_received": 0,
        "sharing_notification_displayed": 0,
        "keychain_identifier": b"",
        "sender_profile_image_url": "",
    }
    row = []
    for c in cols:
        if c in values:
            row.append(values[c])
        else:
            # Default any unknown column to 0/empty.
            row.append(0)
    placeholders = ",".join("?" * len(cols))
    cur.execute(f"INSERT OR REPLACE INTO logins ({','.join(cols)}) "
                f"VALUES ({placeholders})", row)
    conn.commit()
    conn.close()
    print(f"[seed] inserted credential {username!r}/{password!r} for "
          f"{signon_realm} into {db_path}")


if __name__ == "__main__":
    main()
