Aug 26 - Enhance security
CI / Python lint (flake8) (push) Has been cancelled
CI / Python syntax check (push) Has been cancelled
CI / Alembic migration chain (push) Has been cancelled
CI / JavaScript syntax check (push) Has been cancelled
CI / Build extension zip (push) Has been cancelled
CI / Python lint (flake8) (push) Has been cancelled
CI / Python syntax check (push) Has been cancelled
CI / Alembic migration chain (push) Has been cancelled
CI / JavaScript syntax check (push) Has been cancelled
CI / Build extension zip (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
"""add recovery_verifier to users
|
||||
|
||||
Revision ID: j0k1l2m3n4o5
|
||||
Revises: i9j0k1l2m3n4
|
||||
Create Date: 2026-08-26 00:00:00.000000
|
||||
|
||||
Decouples the account-recovery challenge-response proof from enc_key_salt.
|
||||
|
||||
Before this change the recovery proof was HMAC-SHA256(key=enc_key_salt, msg=nonce).
|
||||
Because enc_key_salt is also the PBKDF2 salt for the vault key and is handed to
|
||||
the client at login, anyone who learned enc_key_salt could forge a recovery proof
|
||||
and pull the entire encrypted vault from the unauthenticated /recovery/items
|
||||
endpoint — bypassing MFA entirely.
|
||||
|
||||
recovery_verifier is an independent 256-bit value derived client-side from the
|
||||
recovery code alone:
|
||||
|
||||
verifier = PBKDF2(recovery_code, "passkeeper-recovery-verifier:" + email,
|
||||
200_000 iter, SHA-256) → 64 hex chars
|
||||
|
||||
It is stored server-side purely as the HMAC key for the recovery challenge, and
|
||||
is never used for any encryption. Knowing enc_key_salt no longer grants the
|
||||
ability to forge a proof.
|
||||
|
||||
NULL = legacy recovery code (created before this migration). Those accounts fall
|
||||
back to the old enc_key_salt-keyed proof so existing recovery codes keep working;
|
||||
the settings UI prompts the user to regenerate.
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = 'j0k1l2m3n4o5'
|
||||
down_revision = 'i9j0k1l2m3n4'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'users',
|
||||
sa.Column('recovery_verifier', sa.String(64), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('users', 'recovery_verifier')
|
||||
Reference in New Issue
Block a user