3.8 KiB
Pass-OTP compatibility
crates/storage owns OTP key URI parsing, code generation, entry discovery and
mutation, HOTP counter updates, and presentation payloads. The CLI only reads
input, asks for typed confirmations, invokes the Rust service, and presents the
returned zeroizing bytes. Runtime code never launches pass, gpg,
oathtool, otptool, qrencode, or a shell.
Key URIs and entry layout
IronStorage reads and writes the standard otpauth://totp/... and
otpauth://hotp/... lines used by pass-otp. URI validation covers a Base32
secret, decoded issuer and account label, SHA-1/SHA-256/SHA-512 algorithm,
six- or eight-digit output, positive TOTP period, and required HOTP counter.
Defaults are SHA-1, six digits, and a 30-second TOTP period. Duplicate known
parameters, mismatched label/query issuers, invalid type-specific parameters,
and malformed percent or Base32 encoding are typed failures.
An explicitly supplied URI is preserved byte-for-byte. otp insert --secret
constructs the same default TOTP URI shape as upstream and percent-encodes its
issuer and account. Without an explicit entry path, the decoded issuer and
account produce issuer/account; an account without an issuer produces the
account path. The frontend must confirm that derived path before storage
changes anything.
Within a multiline password entry, the URI must begin at the start of its own
line. otp append adds a line when none exists or replaces the existing line
while preserving every other byte. Multiple URI lines are rejected as
ambiguous instead of guessing which token to use. Validation, confirmation,
recipient resolution, encryption, the atomic repository write, and the
embedded Git commit form one storage-owned operation with rollback on commit
failure.
Codes and counters
HOTP implements RFC 4226 dynamic truncation. TOTP implements RFC 6238 by using the selected Unix-time step as the HOTP counter. The HMAC digest and formatted code are zeroized after use, and tests cover the published SHA-1, SHA-256, and SHA-512 vectors, alternate periods, six/eight digits, and counters.
For pass-otp compatibility, a stored HOTP counter records the last-used
counter. Code generation checks for concurrent entry changes, increments the
counter, rewrites only its URI value, atomically encrypts the updated entry,
and commits Increment HOTP counter for <entry>. before returning the code. A
validation, encryption, concurrency, or Git failure therefore never exposes a
code whose counter update was not committed.
OTP codes support terminal or secret-safe clipboard presentation. URI output supports terminal, clipboard, and the shared storage-owned QR matrix renderer. Clipboard and QR requests never print the underlying code or URI as plaintext.
Every generated code carries an OtpCodeValidity value from crates/storage.
Timed { valid_until, period } identifies the exclusive Unix-time boundary and
complete display interval for TOTP; frontends call its remaining_at method to
present a countdown and request a replacement at zero. The CounterBased
variant identifies the HOTP counter whose increment was committed and must be
described as counter-based rather than time-limited. Frontends must not recover
periods from OTP URIs, decrement an assumed interval, or infer the kind from
formatted text. This same contract is intended for the terminal, desktop,
Apple, AutoFill, and watch interfaces.
The CLI preserves code-only standard output for pass-compatible pipelines and reports the non-secret validity description on standard error. Clipboard lifecycle feedback remains separate, and clipboard-only presentation does not echo the code. The TUI observes the system clock during its normal repaint loop, asks the storage validity value for the remaining seconds, and refreshes at the exact boundary without treating repainting as user activity.