Skip to content

[P1][Correctness/Security] Reject invalid Base32 secrets and TOTP/HOTP parameters instead of generating altered codes #331

Description

@baixiangcpp

Summary

The TOTP/HOTP generator silently normalizes invalid secrets and accepts invalid numeric parameters. It can therefore display a plausible OTP derived from different data than the user entered.

For an authentication utility, invalid input must fail closed and explain the exact validation error; it must not silently change the key or counter.

Confirmed defects

1. Invalid Base32 characters are silently deleted

base32Decode() currently performs:

const clean = str.toUpperCase().replace(/[^A-Z2-7]/g, "")

This removes every character outside the Base32 alphabet without reporting an error. For example, these inputs are treated as the same secret:

JBSWY3DPEHPK3PXP
JBSWY3DPEHPK3PXP0

The trailing 0 is a common transcription error, but it is discarded and the tool generates a code for the shorter key. Other punctuation, Unicode characters, or misplaced padding are handled the same way.

2. A zero/invalid TOTP period can generate a misleading code

The period input is assigned with Number(event.target.value) and is not validated before use. Clearing the field produces 0.

generateTOTP() then calculates:

const counter = Math.floor(time / period)

With period 0, the counter becomes Infinity; the byte-building loop coerces it through bitwise operations and can produce an all-zero counter rather than rejecting it. The UI countdown becomes NaN, while the OTP itself can still look valid.

3. HOTP counters are not constrained to valid integer precision

The HOTP counter accepts arbitrary JavaScript numbers. Negative, fractional, non-finite, and values above Number.MAX_SAFE_INTEGER cannot be represented as a correct 64-bit moving factor by the current loop, but no validation error is shown.

4. Errors collapse to the literal ERROR

All failures are caught and rendered as ERROR, with no localized explanation and no field-level indication of what must be corrected.

Why this matters

A wrong OTP is indistinguishable from clock skew, account mismatch, or an invalid provisioning secret. Silent normalization can waste recovery time and may lead users to trust a code generated from a key they did not actually provide.

This tool handles authentication secrets, so correctness and explicit failure semantics are security-sensitive.

Recommended implementation

Extract the OTP engine and validation into a testable logic.ts module.

  • Parse Base32 strictly.
  • Explicitly decide which presentation characters are allowed (for example, trimming surrounding whitespace and optionally ignoring separators such as spaces/hyphens).
  • Accept = padding only in valid trailing positions, or document an unpadded-only policy.
  • Reject all other characters and reject an empty decoded key.
  • Validate TOTP period as a finite positive integer.
  • Validate HOTP counter as a non-negative safe integer, or implement the counter with bigint for the full RFC 4226 range.
  • Return structured validation errors and disable copy/refresh actions while invalid.

Acceptance criteria

  • A secret containing 0, 1, punctuation, Unicode, or invalid/misplaced padding is rejected rather than altered.
  • Supported separators/whitespace behavior is explicit and covered by tests.
  • Empty or zero-length decoded secrets are rejected.
  • TOTP period must be a finite positive integer within a documented range.
  • HOTP counter must be a non-negative integer within the implemented precision/range.
  • Invalid values never produce a visible OTP or a copyable result.
  • Validation errors are localized and identify the invalid field.
  • RFC 4226 HOTP and RFC 6238 TOTP test vectors pass for supported digits/periods.
  • Boundary tests cover counter rollover, large counters, blank number inputs, and period changes.

Relevant files

  • src/features/tools/totp-generator/page.tsx
  • new/extracted src/features/tools/totp-generator/logic.ts
  • new unit/component tests for the TOTP generator

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions