ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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
13
14#if PC_ENABLE_TOTP
15
16#include "crypto/hash/sha1.h"
17#include <string.h>
18
19namespace
20{
21#define PC_BLOCK 64 // SHA-1 block size
22
23// HMAC-SHA1 over an 8-byte message (the HOTP/TOTP counter).
24void pc_hmac_sha1_8(const uint8_t *key, size_t keylen, const uint8_t msg[8], uint8_t out[PC_SHA1_DIGEST_LEN])
25{
26 uint8_t k[PC_BLOCK] = {0};
27 if (keylen > PC_BLOCK)
28 {
29 uint8_t kh[PC_SHA1_DIGEST_LEN];
30 pc_sha1(key, keylen, kh);
31 memcpy(k, kh, PC_SHA1_DIGEST_LEN);
32 }
33 else
34 {
35 memcpy(k, key, keylen);
36 }
37
38 uint8_t inner_in[PC_BLOCK + 8];
39 for (int i = 0; i < PC_BLOCK; i++)
40 {
41 inner_in[i] = k[i] ^ 0x36; // ipad
42 }
43 memcpy(inner_in + PC_BLOCK, msg, 8);
44 uint8_t inner[PC_SHA1_DIGEST_LEN];
45 pc_sha1(inner_in, sizeof(inner_in), inner);
46
47 uint8_t outer_in[PC_BLOCK + PC_SHA1_DIGEST_LEN];
48 for (int i = 0; i < PC_BLOCK; i++)
49 {
50 outer_in[i] = k[i] ^ 0x5c; // opad
51 }
52 memcpy(outer_in + PC_BLOCK, inner, PC_SHA1_DIGEST_LEN);
53 pc_sha1(outer_in, sizeof(outer_in), out);
54}
55
56uint32_t pow10u(uint8_t n)
57{
58 uint32_t v = 1;
59 while (n--)
60 {
61 v *= 10;
62 }
63 return v;
64}
65} // namespace
66
67uint32_t pc_hotp(const uint8_t *key, size_t keylen, uint64_t counter, uint8_t digits)
68{
69 uint8_t msg[8];
70 for (int i = 7; i >= 0; i--)
71 {
72 msg[i] = (uint8_t)(counter & 0xFF);
73 counter >>= 8;
74 }
75 uint8_t mac[PC_SHA1_DIGEST_LEN];
76 pc_hmac_sha1_8(key, keylen, msg, mac);
77
78 int off = mac[PC_SHA1_DIGEST_LEN - 1] & 0x0F; // dynamic truncation (RFC 4226 ยง5.3)
79 uint32_t bin = ((uint32_t)(mac[off] & 0x7F) << 24) | ((uint32_t)mac[off + 1] << 16) |
80 ((uint32_t)mac[off + 2] << 8) | (uint32_t)mac[off + 3];
81 return bin % pow10u(digits);
82}
83
84uint32_t pc_totp(const uint8_t *key, size_t keylen, uint64_t unix_time, uint32_t period, uint8_t digits)
85{
86 if (period == 0)
87 {
88 period = 30;
89 }
90 return pc_hotp(key, keylen, unix_time / period, digits);
91}
92
93bool pc_totp_verify(const uint8_t *key, size_t keylen, uint64_t unix_time, uint32_t code, uint32_t period,
94 uint8_t digits, int window)
95{
96 if (period == 0)
97 {
98 period = 30;
99 }
100 int64_t step = (int64_t)(unix_time / period);
101 for (int w = -window; w <= window; w++)
102 {
103 int64_t c = step + w;
104 if (c < 0)
105 {
106 continue;
107 }
108 if (pc_hotp(key, keylen, (uint64_t)c, digits) == code)
109 {
110 return true;
111 }
112 }
113 return false;
114}
115
116int pc_base32_decode(const char *b32, uint8_t *out, size_t cap)
117{
118 if (!b32 || !out)
119 {
120 return -1;
121 }
122 uint32_t buffer = 0;
123 int bits = 0;
124 size_t n = 0;
125 for (const char *p = b32; *p; p++)
126 {
127 char c = *p;
128 int val;
129 if (c >= 'A' && c <= 'Z')
130 {
131 val = c - 'A';
132 }
133 else if (c >= 'a' && c <= 'z')
134 {
135 val = c - 'a';
136 }
137 else if (c >= '2' && c <= '7')
138 {
139 val = c - '2' + 26;
140 }
141 else if (c == '=' || c == ' ' || c == '-')
142 {
143 continue; // padding / cosmetic separators
144 }
145 else
146 {
147 return -1; // invalid base32 character
148 }
149 buffer = (buffer << 5) | (uint32_t)val;
150 bits += 5;
151 if (bits >= 8)
152 {
153 bits -= 8;
154 if (n >= cap)
155 {
156 return -1;
157 }
158 out[n++] = (uint8_t)((buffer >> bits) & 0xFF);
159 }
160 }
161 return (int)n;
162}
163
164#endif // PC_ENABLE_TOTP
PC_CRYPTO_HOT void pc_sha1(const uint8_t *data, size_t len, uint8_t digest[PC_SHA1_DIGEST_LEN])
Compute a SHA-1 digest over an arbitrary byte buffer.
Definition sha1.cpp:26
SHA-1 (FIPS 180-4) - one-shot digest.
#define PC_SHA1_DIGEST_LEN
SHA-1 digest length in bytes.
Definition sha1.h:23
TOTP two-factor auth (RFC 6238) (PC_ENABLE_TOTP).