quarantine: scrub tailnet ref, add license, ship lab/ repro kit, refresh README for cohort
ci / build-test (push) Has been cancelled
ci / build-test (push) Has been cancelled
- untrack CLAUDE.md (tailnet leak) + stale HANDOFF.md; keep local via .gitignore - add source-available LICENSE (attribution on fork, royalty on commercial use) - add lab/ reproduction kit (fake-cred LIVE-VM/mock rotation POCs) + lab/README - rewrite README to current status (22 drivers, 8 LIVE-VM/14 MOCK-ONLY/0 LIVE-REAL, 247 tests) and carry the credential-handling safety rules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
# Programmatically register a Bitwarden account on a self-hosted Vaultwarden, replicating
|
||||
# the client-side registration crypto so the `bw` CLI can later log in and DECRYPT items.
|
||||
# Run with the moto-venv python (has `cryptography`). FAKE lab account only.
|
||||
#
|
||||
# argv: email password name base_url
|
||||
#
|
||||
# Bitwarden registration (PBKDF2-SHA256 KDF):
|
||||
# masterKey = PBKDF2(password, email, iters, 32)
|
||||
# masterPasswordHash = b64(PBKDF2(masterKey, password, 1, 32))
|
||||
# stretched enc/mac = HKDF-Expand(masterKey, "enc"/"mac", 32)
|
||||
# userKey (64B random) = 32B enc || 32B mac (the account symmetric key)
|
||||
# "key" = EncString(userKey, stretched.enc, stretched.mac) (type 2)
|
||||
# RSA keypair = encryptedPrivateKey EncString(PKCS8, userKey.enc, userKey.mac)
|
||||
import base64, json, os, ssl, sys, urllib.request, urllib.error
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand
|
||||
from cryptography.hazmat.primitives import hashes, hmac, serialization
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.padding import PKCS7
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
email, password, name, base_url = sys.argv[1].lower(), sys.argv[2], sys.argv[3], sys.argv[4].rstrip("/")
|
||||
ITERS = 600000
|
||||
|
||||
def pbkdf2(pw, salt, iterations, length=32):
|
||||
return PBKDF2HMAC(algorithm=hashes.SHA256(), length=length, salt=salt, iterations=iterations).derive(pw)
|
||||
|
||||
def hkdf_expand(prk, info, length=32):
|
||||
return HKDFExpand(algorithm=hashes.SHA256(), length=length, info=info.encode()).derive(prk)
|
||||
|
||||
def enc_string(plaintext, enc_key, mac_key):
|
||||
iv = os.urandom(16)
|
||||
p = PKCS7(128).padder()
|
||||
data = p.update(plaintext) + p.finalize()
|
||||
enc = Cipher(algorithms.AES(enc_key), modes.CBC(iv)).encryptor()
|
||||
ct = enc.update(data) + enc.finalize()
|
||||
h = hmac.HMAC(mac_key, hashes.SHA256()); h.update(iv + ct); mac = h.finalize()
|
||||
return "2.%s|%s|%s" % (base64.b64encode(iv).decode(),
|
||||
base64.b64encode(ct).decode(),
|
||||
base64.b64encode(mac).decode())
|
||||
|
||||
master_key = pbkdf2(password.encode(), email.encode(), ITERS, 32)
|
||||
master_password_hash = base64.b64encode(pbkdf2(master_key, password.encode(), 1, 32)).decode()
|
||||
s_enc, s_mac = hkdf_expand(master_key, "enc"), hkdf_expand(master_key, "mac")
|
||||
|
||||
user_key = os.urandom(64)
|
||||
protected_key = enc_string(user_key, s_enc, s_mac)
|
||||
|
||||
priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
priv_der = priv.private_bytes(serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.NoEncryption())
|
||||
pub_der = priv.public_key().public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
enc_priv = enc_string(priv_der, user_key[:32], user_key[32:])
|
||||
|
||||
payload = {
|
||||
"email": email, "name": name, "masterPasswordHash": master_password_hash,
|
||||
"masterPasswordHint": "", "key": protected_key, "kdf": 0, "kdfIterations": ITERS,
|
||||
"keys": {"publicKey": base64.b64encode(pub_der).decode(), "encryptedPrivateKey": enc_priv},
|
||||
}
|
||||
body = json.dumps(payload).encode()
|
||||
# Lab Vaultwarden uses a self-signed cert — accept it (throwaway, loopback only).
|
||||
ctx = ssl._create_unverified_context() if base_url.startswith("https") else None
|
||||
|
||||
for path in ("/identity/accounts/register", "/api/accounts/register"):
|
||||
req = urllib.request.Request(base_url + path, data=body,
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15, context=ctx) as r:
|
||||
print("registered via %s (HTTP %s)" % (path, r.status)); sys.exit(0)
|
||||
except urllib.error.HTTPError as e:
|
||||
print("HTTP %s %s -> %s" % (e.code, path, e.read().decode(errors="replace")[:400]), file=sys.stderr)
|
||||
except Exception as e:
|
||||
print("ERR %s -> %r" % (path, e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user