DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
totp.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file totp.cpp
6 * @brief HMAC-SHA1 HOTP/TOTP (RFC 4226 / 6238) + base32 decode (pure).
7 *
8 * HMAC-SHA1 is built on the existing one-shot software SHA-1; the TOTP message is
9 * the 8-byte counter, so all working buffers are fixed and stack-local.
10 */
11
12#include "services/totp/totp.h"
13
14#if DETWS_ENABLE_TOTP
15
17#include <string.h>
18
19namespace
20{
21constexpr int BLOCK = 64; // SHA-1 block size
22
23// HMAC-SHA1 over an 8-byte message (the HOTP/TOTP counter).
24void hmac_sha1_8(const uint8_t *key, size_t keylen, const uint8_t msg[8], uint8_t out[SHA1_DIGEST_LEN])
25{
26 uint8_t k[BLOCK] = {0};
27 if (keylen > BLOCK)
28 {
29 uint8_t kh[SHA1_DIGEST_LEN];
30 sha1(key, keylen, kh);
31 memcpy(k, kh, SHA1_DIGEST_LEN);
32 }
33 else
34 {
35 memcpy(k, key, keylen);
36 }
37
38 uint8_t inner_in[BLOCK + 8];
39 for (int i = 0; i < BLOCK; i++)
40 inner_in[i] = k[i] ^ 0x36; // ipad
41 memcpy(inner_in + BLOCK, msg, 8);
42 uint8_t inner[SHA1_DIGEST_LEN];
43 sha1(inner_in, sizeof(inner_in), inner);
44
45 uint8_t outer_in[BLOCK + SHA1_DIGEST_LEN];
46 for (int i = 0; i < BLOCK; i++)
47 outer_in[i] = k[i] ^ 0x5c; // opad
48 memcpy(outer_in + BLOCK, inner, SHA1_DIGEST_LEN);
49 sha1(outer_in, sizeof(outer_in), out);
50}
51
52uint32_t pow10u(uint8_t n)
53{
54 uint32_t v = 1;
55 while (n--)
56 v *= 10;
57 return v;
58}
59} // namespace
60
61uint32_t detws_hotp(const uint8_t *key, size_t keylen, uint64_t counter, uint8_t digits)
62{
63 uint8_t msg[8];
64 for (int i = 7; i >= 0; i--)
65 {
66 msg[i] = (uint8_t)(counter & 0xFF);
67 counter >>= 8;
68 }
69 uint8_t mac[SHA1_DIGEST_LEN];
70 hmac_sha1_8(key, keylen, msg, mac);
71
72 int off = mac[SHA1_DIGEST_LEN - 1] & 0x0F; // dynamic truncation (RFC 4226 §5.3)
73 uint32_t bin = ((uint32_t)(mac[off] & 0x7F) << 24) | ((uint32_t)mac[off + 1] << 16) |
74 ((uint32_t)mac[off + 2] << 8) | (uint32_t)mac[off + 3];
75 return bin % pow10u(digits);
76}
77
78uint32_t detws_totp(const uint8_t *key, size_t keylen, uint64_t unix_time, uint32_t period, uint8_t digits)
79{
80 if (period == 0)
81 period = 30;
82 return detws_hotp(key, keylen, unix_time / period, digits);
83}
84
85bool detws_totp_verify(const uint8_t *key, size_t keylen, uint64_t unix_time, uint32_t code, uint32_t period,
86 uint8_t digits, int window)
87{
88 if (period == 0)
89 period = 30;
90 int64_t step = (int64_t)(unix_time / period);
91 for (int w = -window; w <= window; w++)
92 {
93 int64_t c = step + w;
94 if (c < 0)
95 continue;
96 if (detws_hotp(key, keylen, (uint64_t)c, digits) == code)
97 return true;
98 }
99 return false;
100}
101
102int detws_base32_decode(const char *b32, uint8_t *out, size_t cap)
103{
104 if (!b32 || !out)
105 return -1;
106 uint32_t buffer = 0;
107 int bits = 0;
108 size_t n = 0;
109 for (const char *p = b32; *p; p++)
110 {
111 char c = *p;
112 int val;
113 if (c >= 'A' && c <= 'Z')
114 val = c - 'A';
115 else if (c >= 'a' && c <= 'z')
116 val = c - 'a';
117 else if (c >= '2' && c <= '7')
118 val = c - '2' + 26;
119 else if (c == '=' || c == ' ' || c == '-')
120 continue; // padding / cosmetic separators
121 else
122 return -1; // invalid base32 character
123 buffer = (buffer << 5) | (uint32_t)val;
124 bits += 5;
125 if (bits >= 8)
126 {
127 bits -= 8;
128 if (n >= cap)
129 return -1;
130 out[n++] = (uint8_t)((buffer >> bits) & 0xFF);
131 }
132 }
133 return (int)n;
134}
135
136#endif // DETWS_ENABLE_TOTP
void sha1(const uint8_t *data, size_t len, uint8_t digest[SHA1_DIGEST_LEN])
Compute a SHA-1 digest over an arbitrary byte buffer.
Definition sha1.cpp:24
Software SHA-1 implementation - no platform dependencies.
#define SHA1_DIGEST_LEN
SHA-1 digest length in bytes.
Definition sha1.h:22
TOTP two-factor auth (RFC 6238) (DETWS_ENABLE_TOTP).