ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
totp.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file totp.h
6 * @brief Time-based one-time passwords, RFC 6238 over RFC 4226 HOTP (PROTOCORE_ENABLE_TOTP).
7 *
8 * RFC 6238 sec 4.2: "TOTP = HOTP(K, T), where T is an integer and represents the number of time
9 * steps between the initial counter time T0 and the current Unix time", with
10 * "T = (Current Unix time - T0) / X" under the floor function. X is the time step in seconds and T0
11 * the Unix time step counting starts at, both system parameters of RFC 6238 sec 4.1.
12 *
13 * The value under it is RFC 4226 sec 5.2 "HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))": the counter C is
14 * hashed high-order byte first, the DT function of RFC 4226 sec 5.3 takes a 31-bit window out of
15 * the 20-byte MAC, and that number is reduced mod 10^Digit. The MAC is RFC 2104 sec 2
16 * H(K XOR opad, H(K XOR ipad, C)) over the platform's SHA-1.
17 *
18 * The verifier walks the drift window of RFC 6238 sec 6: an OTP is accepted when it matches the
19 * receipt time step or any step within a set number of steps forward or backward of it. The
20 * provisioning secret arrives as base32 text (RFC 4648 sec 6) and decodes into the K every other
21 * call is keyed by.
22 *
23 * A caller sets the members a call takes, invokes it through ::Totp, and reads the outcome off the
24 * same handle. The module exports one symbol, @ref Totp; everything in totp.c has internal linkage.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_TOTP_H
31#define PROTOCORE_TOTP_H
32
33#include "protocore_config.h" // the entry point: protocore_types.h for the widths
34
35#if PROTOCORE_ENABLE_TOTP
36
38
39/** @brief X: the time step in seconds a zero @c step.x takes (RFC 6238 sec 4.1). */
40#define PROTOCORE_TOTP_X_DEFAULT 30u
41
42/** @brief Digit: the shortest OTP the algorithm extracts, and what a zero @c digit takes (RFC 4226 sec 5.3). */
43#define PROTOCORE_TOTP_DIGIT_MIN 6u
44
45/** @brief RFC 4226 sec 5.1 C and RFC 6238 sec 4.1 T0/X: the moving factor one OTP is computed for. */
46typedef struct
47{
48 uint64_t counter; ///< C: the counter HOTP hashes, high-order byte first (RFC 4226 sec 5.2)
49 uint64_t unix_time; ///< the current Unix time T is taken from (RFC 6238 sec 4.2)
50 uint64_t t0; ///< T0: the Unix time step counting starts at; 0 is the epoch (RFC 6238 sec 4.1)
51 uint32_t x; ///< X: the time step in seconds; 0 takes ::PROTOCORE_TOTP_X_DEFAULT (RFC 6238 sec 4.1)
52} TotpStepArgs;
53
54/** @brief RFC 6238 sec 6: the OTP a validation judges and the clock drift it accepts around it. */
55typedef struct
56{
57 uint32_t otp; ///< the OTP value submitted for validation
58 int32_t drift; ///< time steps of out-of-synch taken forward and backward; negative matches nothing
59} TotpValidateArgs;
60
61/** @brief RFC 4648 sec 6: the base32 provisioning secret and the buffer a decode fills with K. */
62typedef struct
63{
64 const char *b32; ///< the base32 text a decode reads
65 uint8_t *out; ///< where the decoded K bytes land
66 size_t cap; ///< how many bytes that buffer holds
67} TotpSecretArgs;
68
69/**
70 * @brief One-time passwords: HOTP over a counter, TOTP over a time step, and the base32 secret.
71 *
72 * No storage member: every call reads only the members set on this handle and writes only its
73 * results back. The throttling counter of RFC 4226 sec 7.3 and the "MUST NOT accept the second
74 * attempt of the OTP after the successful validation" record of RFC 6238 sec 5.2 are the caller's
75 * to keep, so there is no table, window or counter here to hold them.
76 *
77 * @var TotpNs::k K: the shared secret every OTP is keyed by (RFC 4226 sec 5.1)
78 * @var TotpNs::keylen how many bytes K holds; over 64 it is replaced by H(K) (RFC 2104 sec 2)
79 * @var TotpNs::digit Digit: how many digits the OTP carries; 0 takes ::PROTOCORE_TOTP_DIGIT_MIN
80 * @var TotpNs::step C, and the T0/X/Unix time that fix T: set @c step.counter for a
81 * counter-based code, @c step.unix_time / @c step.t0 / @c step.x for a
82 * time-based one
83 * @var TotpNs::check the submitted OTP and the drift steps a validation walks around T
84 * @var TotpNs::secret the base32 text a decode reads and the buffer it writes K into
85 * @var TotpNs::ok a validation's true/false verdict
86 * @var TotpNs::u32 D: the OTP a generate produced, in 0...10^Digit-1 (RFC 4226 sec 5.3)
87 * @var TotpNs::i32 the number of K bytes a decode wrote, or -1 on a rejected character or a
88 * buffer too small
89 * @var TotpNs::hotp HOTP(K,C) for @c step.counter (RFC 4226 sec 5.3)
90 * @var TotpNs::totp HOTP(K,T) for the time step T names (RFC 6238 sec 4.2)
91 * @var TotpNs::verify match @c check.otp against T and the steps around it (RFC 6238 sec 6)
92 * @var TotpNs::base32_decode base32 text to the K bytes it stands for (RFC 4648 sec 6)
93 */
94typedef struct
95{
96 const uint8_t *k; ///< K: the shared secret every OTP is keyed by (RFC 4226 sec 5.1)
97 size_t keylen; ///< how many bytes K holds
98 uint8_t digit; ///< Digit: how many digits the OTP carries (RFC 4226 sec 5.1)
99 TotpStepArgs step; ///< the moving factor an OTP is computed for
100 TotpValidateArgs check; ///< what a validation judges, and the drift around it
101 TotpSecretArgs secret; ///< the base32 secret and where a decode writes K
102 proto_bool ok;
103 uint32_t u32;
104 int32_t i32;
105} TotpVars;
106
107/** @brief The operands and the outcome. */
108extern TotpVars TotpV;
109
110/** @brief The entries. */
111typedef struct
112{
113 void (*const hotp)(uint8_t *work);
114 void (*const totp)(uint8_t *work);
115 void (*const verify)(uint8_t *work);
116 void (*const base32_decode)(uint8_t *work);
117} TotpNs;
118
119// What the table binds, defined once in the .c and taking one parameter each: everything
120// else an entry needs is an operand in TotpV or a region of the borrow at a fixed offset.
121void protocore_totp_hotp(uint8_t *work);
122void protocore_totp_totp(uint8_t *work);
123void protocore_totp_verify(uint8_t *work);
124void protocore_totp_base32_decode(uint8_t *work);
125
126// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
127// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
128// `Totp.hotp(work)` resolves to a named function and becomes a DIRECT call. An extern table
129// leaves the call indirect and the symbol live at every level, -O2 -flto included.
130static const TotpNs Totp __attribute__((unused)) = {
131 .hotp = protocore_totp_hotp,
132 .totp = protocore_totp_totp,
133 .verify = protocore_totp_verify,
134 .base32_decode = protocore_totp_base32_decode,
135};
136
138
139#endif // PROTOCORE_ENABLE_TOTP
140
141#endif // PROTOCORE_TOTP_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97