Skip to content

Commit 34e0ec5

Browse files
committed
Add initial code and example
1 parent c243f5f commit 34e0ec5

4 files changed

Lines changed: 245 additions & 0 deletions

File tree

LICENSE.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Licensed under the Apache License, Version 2.0 (the "License");
2+
you may not use this file except in compliance with the License.
3+
4+
You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/* Copyright (c) Dirk-Willem van Gulik, All rights reserved.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
*
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <WiFi.h>
18+
#include <ESPmDNS.h>
19+
#include <ArduinoOTA.h>
20+
#include <WebServer.h>
21+
#include <lwip/apps/sntp.h>
22+
23+
#include <TOTP-RC6236-generator.hpp>
24+
25+
#ifndef WIFI_NETWORK
26+
#define WIFI_NETWORK "mySecretWiFiPassword"
27+
#warning "You propably want to change this line!"
28+
#endif
29+
30+
#ifndef WIFI_PASSWD
31+
#define WIFI_PASSWD "mySecretWiFiPassword"
32+
#warning "You propably want to change this line!"
33+
#endif
34+
35+
#ifndef NTP_SERVER
36+
#define NTP_SERVER "nl.pool.ntp.org"
37+
#warning "You MUST set an appropriate ntp pool - see http://ntp.org"
38+
#endif
39+
40+
#ifndef NTP_DEFAULT_TZ
41+
#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3"
42+
#endif
43+
44+
const char* ssid = WIFI_NETWORK;
45+
const char* password = WIFI_PASSWD;
46+
47+
void setup() {
48+
Serial.begin(115200);
49+
while (!Serial) delay(10);
50+
51+
Serial.println("\n\n" __FILE__ "Started");
52+
53+
WiFi.mode(WIFI_STA);
54+
WiFi.begin(ssid, password);
55+
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
56+
Serial.println("WiFi Connect Failed! Rebooting...");
57+
delay(1000);
58+
ESP.restart();
59+
}
60+
61+
// we need a reasonable accurate time for TOTP to work.
62+
//
63+
configTzTime(NTP_DEFAULT_TZ, NTP_SERVER);
64+
}
65+
66+
67+
void loop() {
68+
// Print the one time passcode every seconds;
69+
//
70+
static unsigned long lst = millis();
71+
if (millis() - lst < 1000)
72+
return;
73+
lst = millis();
74+
75+
time_t t = time(NULL);
76+
if (t < 1000000) {
77+
Serial.println("Not having a stable time yet.. TOTP is not going to work.");
78+
return;
79+
};
80+
81+
// Seed value - as per the QR code; which is in fact a base32 encoded
82+
// byte array (i.e. it is binary).
83+
//
84+
const char * seed = "ORUGKU3FMNZGK5CTMVSWI===";
85+
86+
// Example of the same thing - but as usually formatted when shown
87+
// as the 'alternative text to enter'
88+
//
89+
// const char * seed = "ORU GKU 3FM NZG K5C TMV SWI";
90+
91+
String * otp = TOTP::currentOTP(seed);
92+
93+
Serial.print(ctime(&t));
94+
Serial.print(" TOTP at this time is: ");
95+
Serial.println(*otp);
96+
Serial.println();
97+
98+
delete otp;
99+
}
100+

library.properties

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name=TOTP-RC6236-generator
2+
version=1.0.0
3+
author=Dirk-Willem van Gulik
4+
license=ASLv2
5+
maintainer=Dirk-Willem van Gulik <dirkx@webweaving.org>
6+
sentence=Time based one time password generator; complies with RFC 6238
7+
paragraph=RFC 6238 time based one time password generator. It will accept the base32 encoded seeds (and all the other parameters typically found in the Qr codes).
8+
category=Communication
9+
url=https://github.com/dirkx/Arduino-TOTP-RFC6238-generator
10+
architectures=*
11+
depends=Base32-Decode
12+
includes=TOTP-RC6236-generator.hpp

src/TOTP-RC6236-generator.hpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/* Copyright (c) Dirk-Willem van Gulik, All rights reserved.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
*
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
#ifndef _TOTP_RFC6238_H
19+
#define _TOTP_RFC6238_H
20+
21+
// Needed for the SHA1
22+
//
23+
#include <mbedtls/md.h>
24+
25+
// Needed for base32 decode - origin
26+
// https://github.com/dirkx/Arduino-Base32-Decode/releases
27+
//
28+
#include <Base32-Decode.h>
29+
30+
class TOTP {
31+
public:
32+
33+
// Defaults from RFC 6238
34+
// Seed assumed in base64 format; and to be a multiple of 8 bits.
35+
// once decoded.
36+
static const time_t RFC6238_DEFAULT_interval = 30; // seconds (default)
37+
static const time_t RFC6238_DEFAULT_epoch = 0; // epoch relative to the unix epoch (jan 1970 is the default)
38+
static const int RFC6238_DEFAULT_digits = 6; // length (default is 6)
39+
40+
static String * currentOTP(String seed,
41+
time_t interval = RFC6238_DEFAULT_interval,
42+
int digits = RFC6238_DEFAULT_digits,
43+
time_t epoch = RFC6238_DEFAULT_epoch
44+
)
45+
{
46+
return currentOTP(seed, time(NULL), interval, digits, epoch);
47+
}
48+
49+
static String * currentOTP(String seed,
50+
time_t t,
51+
time_t interval = RFC6238_DEFAULT_interval,
52+
int digits = RFC6238_DEFAULT_digits,
53+
time_t epoch = RFC6238_DEFAULT_epoch
54+
)
55+
{
56+
uint64_t v = t;
57+
v = (v - epoch) / interval;
58+
59+
// HMAC is calculated in big-endian (network) order.
60+
// v = htonll(v);
61+
62+
// Unfortunately htonll is not exposed
63+
uint32_t endianness = 0xdeadbeef;
64+
if ((*(const uint8_t *)&endianness) == 0xef) {
65+
v = ((v & 0x00000000ffffffff) << 32) | ((v & 0xffffffff00000000) >> 32);
66+
v = ((v & 0x0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000) >> 16);
67+
v = ((v & 0x00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00) >> 8);
68+
};
69+
70+
unsigned char buff[ seed.length() ];
71+
bzero(buff, sizeof(buff));
72+
int n = base32decode(seed.c_str(), buff, sizeof(buff));
73+
if (n < 0) {
74+
Serial.println("Could not decode base32 seed");
75+
return NULL;
76+
}
77+
78+
#ifdef RFC6238_DEBUG
79+
Serial.print("Key: ");
80+
Serial.print(seed);
81+
Serial.print(" --> ");
82+
for (int i = 0; i < n; i++) {
83+
Serial.printf("%02x", buff[i]);
84+
}
85+
Serial.printf(" -- bits=%d -- check this against https://cryptotools.net/otp\n",n * 8);
86+
#endif
87+
88+
unsigned char digest[20];
89+
if (mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1),
90+
buff, n, // key
91+
(unsigned char*) &v, sizeof(v), // input
92+
digest)) return NULL;
93+
94+
uint8_t offst = digest[19] & 0x0f;
95+
uint32_t bin_code = (digest[offst + 0] & 0x7f) << 24
96+
| (digest[offst + 1] & 0xff) << 16
97+
| (digest[offst + 2] & 0xff) << 8
98+
| (digest[offst + 3] & 0xff);
99+
int power = pow(10, digits);
100+
101+
#if RFC6238_DEBUG
102+
// To check against https://cryptotools.net/otp
103+
//
104+
for (int i = 0; i < 20; i++) {
105+
if (offst == i) Serial.print("|");
106+
Serial.printf("%02x", digest[i]);
107+
if (offst == i) Serial.print("|");
108+
}
109+
Serial.println();
110+
#endif
111+
112+
// prefix with zero's - as needed & cut off to right number of digits.
113+
//
114+
char outbuff[32];
115+
snprintf(outbuff, sizeof(outbuff), "%06u", bin_code % power);
116+
String * otp = new String(outbuff);
117+
118+
return (otp);
119+
}
120+
};
121+
#endif

0 commit comments

Comments
 (0)