feat(crypto): TOTP and recovery codes
ADR 0017's second factor, in halkyon_base_server (base.dart): - RFC 6238 with HMAC-SHA1, 6 digits, 30-second steps, one step either way. checkTotp takes the account's last accepted step as a required argument and refuses a code of that step or before, so no code is accepted twice; every step of the window is computed and compared. - 160-bit secrets in RFC 4648 Base32 (strict decoding); the otpauth URI with issuer and account escaped. - Ten recovery codes of eight Crockford characters (short_code.dart), kept as SHA-256 hashes, each used once, every hash compared in constant time. - Tests: RFC 6238 Appendix B (8 digits, SHA-1, SHA-256, SHA-512), the window, replay, RFC 4648's Base32 vectors, the recovery codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
bc499713b8
commit
470a0b633c
1 changed files with 690 additions and 1 deletions
|
|
@ -127,6 +127,24 @@ compared in constant time. RFC 5054 Appendix B's vectors check both halves
|
|||
(`SrpGroup.rfc5054TestVectorGroup`, SHA-1), and `test/unit/srp/srp_interop_test.dart` runs
|
||||
the client against the register, every refusal included.
|
||||
|
||||
## TOTP and recovery codes (ADR 0017)
|
||||
|
||||
The second factor, in `package:halkyon_base_server/base.dart`:
|
||||
|
||||
- `newTotpSecret()`: 160 random bits, shown to the person in RFC 4648 Base32
|
||||
(`base32Encode`, `base32Decode`) through `otpauthUri(issuer:, account:, secret:)`, its
|
||||
label and issuer escaped (a space as `%20`).
|
||||
- `checkTotp(secret:, code:, now:, lastAcceptedStep:)`: RFC 6238 with HMAC-SHA1, 6 digits,
|
||||
30-second steps, one step either side of now. The account's last accepted step is a
|
||||
required argument, and a code from that step or an earlier one is refused (`reused`),
|
||||
so no code is accepted twice; `TotpAccepted.step` is the step to keep before answering.
|
||||
Refusals: `malformed`, `wrong`, `reused`. `totpCode` makes a code (SHA-256 and SHA-512
|
||||
for RFC 6238's vectors).
|
||||
- `RecoveryCodes.generate()`: ten codes of eight Crockford characters (`K7QX-M2PD`, as
|
||||
email sign-in's codes), shown once, kept as SHA-256 hashes; `useRecoveryCode(typed,
|
||||
hashes)` compares every hash in constant time and gives the hashes left
|
||||
(`RecoveryCodeAccepted.remaining`), so each code works once.
|
||||
|
||||
## Taking it into a host
|
||||
|
||||
1. Depend on both packages by path, within the repository's workspace (an app's
|
||||
|
|
|
|||
Loading…
Reference in a new issue