Skip to content

feat(vault): live TOTP codes from login ciphers#22

Merged
UnbreakableMJ merged 1 commit into
mainfrom
totp-codes
Jun 15, 2026
Merged

feat(vault): live TOTP codes from login ciphers#22
UnbreakableMJ merged 1 commit into
mainfrom
totp-codes

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

What

A Bitwarden login can store a TOTP secret (the totp field — an otpauth://
URI or a bare base32 secret). Vault already stored/edited it, but reading it
returned the raw secret. Now it returns the live RFC 6238 code:

vault get github --field totp   # -> 123456 (rolls over every 30 s)

In the TUI, t copies the current code for the selected item.

How

  • vault-core::totp (new module): HOTP + RFC 4226 dynamic truncation,
    hand-rolled RFC 4648 base32 decode, otpauth://totp/… parsing (honoring
    algorithm/digits/period), SHA1 default + SHA256/SHA512. One new dep:
    sha1 (RustCrypto, MIT/Apache — same family as the existing sha2/hmac).
    New Error::Totp variant.
  • vault-agent: get_item(Field::Totp) generates the code from the
    decrypted secret — the secret stays in the agent; only the short code
    crosses the socket
    . None still yields NoSuchField. The Copy path
    inherits it (so TUI t works).
  • vault-tui: tcopy_field(Field::Totp, "TOTP code") beside c/u/o.
  • vault-cli: FieldArg::Totp already mapped — no code change, the value is
    now the live code.

Scope

Standard authenticator secrets only (SHA1/256/512). otpauth://hotp, Steam, and
other non-standard types are rejected; a live ticking readout in the TUI detail
pane and exposing the raw secret are tracked follow-ups.

Tests

  • vault-core: the RFC 6238 SHA1 vector (T=5994287082), base32 decode of
    the seed (+ case/space tolerance, error cases), and otpauth-URI vs bare-secret
    parsing + digit clamping — all pure/fast.
  • vault-agent: get_item(Field::Totp) over a known base32 secret returns a
    6-digit numeric string (and not the raw secret); a missing field →
    NoSuchField.

Local CI-exact gates all green: fmt, fresh isolated clippy -D warnings,
cargo test --workspace --all-targets under RUSTFLAGS=-D warnings,
cargo deny check (new sha1 dep — licenses ok), both headless builds.

🤖 Generated with Claude Code

A login's stored totp field (otpauth:// URI or bare base32 secret) now yields
the current RFC 6238 code instead of the raw secret.

- vault-core: new `totp` module — HOTP + RFC 4226 dynamic truncation,
  hand-rolled base32 decode, otpauth:// parsing; SHA1 default + SHA256/SHA512.
  One new dep: sha1 (RustCrypto). Error::Totp variant.
- vault-agent: get_item(Field::Totp) generates the code from the decrypted
  secret (the secret stays in the agent; only the code crosses the socket);
  None still yields NoSuchField. The Copy path inherits it.
- vault-tui: `t` copies the current code (beside c/u/o).
- vault-cli: FieldArg::Totp already maps; `vault get … --field totp` now prints
  the live code.

Tests: RFC 6238 SHA1 vector + base32 + otpauth parse (vault-core); an agent
test that Field::Totp returns a 6-digit code (not the secret).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@UnbreakableMJ
UnbreakableMJ merged commit 72cee8f into main Jun 15, 2026
8 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the totp-codes branch June 15, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant