This commit is contained in:
iranl
2024-05-24 22:05:42 +02:00
parent 79febfd14e
commit 69679dfeca
23 changed files with 1522 additions and 232 deletions

View File

@@ -0,0 +1,12 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,24 @@
# Arduino libary for TOTP generation
Example use:
// Seed value - as per the QR code; which is in fact a base32 encoded
// byte array (i.e. it is binary).
//
const char * seed = "ORUGKU3FMNZGK5CTMVSWI===";
// Example of the same thing - but as usually formatted when shown
// as the 'alternative text to enter'
//
// const char * seed = "ORU GKU 3FM NZG K5C TMV SWI";
String * otp = TOTP::currentOTP(seed);
Serial.print(ctime(&t));
Serial.print(" TOTP at this time is: ");
Serial.println(*otp);
Serial.println();
This assumes a normal RFC compliant TOTP. It is possible that the Qr code provides
different values for the interval (default is 30 seconds), epoch or the hash (sha1).
These can be passwd as optional arguments.

View File

@@ -0,0 +1,100 @@
/* Copyright (c) Dirk-Willem van Gulik, All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>
#include <lwip/apps/sntp.h>
#include <TOTP-generator.hpp>
#ifndef WIFI_NETWORK
#define WIFI_NETWORK "mySecretWiFiPassword"
#warning "You propably want to change this line!"
#endif
#ifndef WIFI_PASSWD
#define WIFI_PASSWD "mySecretWiFiPassword"
#warning "You propably want to change this line!"
#endif
#ifndef NTP_SERVER
#define NTP_SERVER "nl.pool.ntp.org"
#warning "You MUST set an appropriate ntp pool - see http://ntp.org"
#endif
#ifndef NTP_DEFAULT_TZ
#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3"
#endif
const char* ssid = WIFI_NETWORK;
const char* password = WIFI_PASSWD;
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("\n\n" __FILE__ "Started");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Connect Failed! Rebooting...");
delay(1000);
ESP.restart();
}
// we need a reasonable accurate time for TOTP to work.
//
configTzTime(NTP_DEFAULT_TZ, NTP_SERVER);
}
void loop() {
// Print the one time passcode every seconds;
//
static unsigned long lst = millis();
if (millis() - lst < 1000)
return;
lst = millis();
time_t t = time(NULL);
if (t < 1000000) {
Serial.println("Not having a stable time yet.. TOTP is not going to work.");
return;
};
// Seed value - as per the QR code; which is in fact a base32 encoded
// byte array (i.e. it is binary).
//
const char * seed = "ORUGKU3FMNZGK5CTMVSWI===";
// Example of the same thing - but as usually formatted when shown
// as the 'alternative text to enter'
//
// const char * seed = "ORU GKU 3FM NZG K5C TMV SWI";
String * otp = TOTP::currentOTP(seed);
Serial.print(ctime(&t));
Serial.print(" TOTP at this time is: ");
Serial.println(*otp);
Serial.println();
delete otp;
}

View File

@@ -0,0 +1,12 @@
name=TOTP-generator
version=1.0.1
author=Dirk-Willem van Gulik
license=ASLv2
maintainer=Dirk-Willem van Gulik <dirkx@webweaving.org>
sentence=Time based one time password generator; complies with RFC 6238
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).
category=Communication
url=https://github.com/dirkx/Arduino-TOTP-RFC6238-generator
architectures=*
depends=Base32-Decode
includes=TOTP-generator.hpp

View File

@@ -0,0 +1,121 @@
/* Copyright (c) Dirk-Willem van Gulik, All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _TOTP_RFC6238_H
#define _TOTP_RFC6238_H
// Needed for the SHA1
//
#include <mbedtls/md.h>
// Needed for base32 decode - origin
// https://github.com/dirkx/Arduino-Base32-Decode/releases
//
#include <Base32-Decode.h>
class TOTP {
public:
// Defaults from RFC 6238
// Seed assumed in base64 format; and to be a multiple of 8 bits.
// once decoded.
static const time_t RFC6238_DEFAULT_interval = 30; // seconds (default)
static const time_t RFC6238_DEFAULT_epoch = 0; // epoch relative to the unix epoch (jan 1970 is the default)
static const int RFC6238_DEFAULT_digits = 6; // length (default is 6)
static String * currentOTP(String seed,
time_t interval = RFC6238_DEFAULT_interval,
int digits = RFC6238_DEFAULT_digits,
time_t epoch = RFC6238_DEFAULT_epoch
)
{
return currentOTP(time(NULL), seed, interval, digits, epoch);
}
static String * currentOTP(time_t t,
String seed,
time_t interval = RFC6238_DEFAULT_interval,
int digits = RFC6238_DEFAULT_digits,
time_t epoch = RFC6238_DEFAULT_epoch
)
{
uint64_t v = t;
v = (v - epoch) / interval;
// HMAC is calculated in big-endian (network) order.
// v = htonll(v);
// Unfortunately htonll is not exposed
uint32_t endianness = 0xdeadbeef;
if ((*(const uint8_t *)&endianness) == 0xef) {
v = ((v & 0x00000000ffffffff) << 32) | ((v & 0xffffffff00000000) >> 32);
v = ((v & 0x0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000) >> 16);
v = ((v & 0x00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00) >> 8);
};
unsigned char buff[ seed.length() ];
bzero(buff, sizeof(buff));
int n = base32decode(seed.c_str(), buff, sizeof(buff));
if (n < 0) {
Serial.println("Could not decode base32 seed");
return NULL;
}
#ifdef RFC6238_DEBUG
Serial.print("Key: ");
Serial.print(seed);
Serial.print(" --> ");
for (int i = 0; i < n; i++) {
Serial.printf("%02x", buff[i]);
}
Serial.printf(" -- bits=%d -- check this against https://cryptotools.net/otp\n",n * 8);
#endif
unsigned char digest[20];
if (mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1),
buff, n, // key
(unsigned char*) &v, sizeof(v), // input
digest)) return NULL;
uint8_t offst = digest[19] & 0x0f;
uint32_t bin_code = (digest[offst + 0] & 0x7f) << 24
| (digest[offst + 1] & 0xff) << 16
| (digest[offst + 2] & 0xff) << 8
| (digest[offst + 3] & 0xff);
int power = pow(10, digits);
#if RFC6238_DEBUG
// To check against https://cryptotools.net/otp
//
for (int i = 0; i < 20; i++) {
if (offst == i) Serial.print("|");
Serial.printf("%02x", digest[i]);
if (offst == i) Serial.print("|");
}
Serial.println();
#endif
// prefix with zero's - as needed & cut off to right number of digits.
//
char outbuff[32];
snprintf(outbuff, sizeof(outbuff), "%06u", bin_code % power);
String * otp = new String(outbuff);
return (otp);
}
};
#endif

View File

@@ -0,0 +1,121 @@
/* Copyright (c) Dirk-Willem van Gulik, All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _TOTP_RFC6238_H
#define _TOTP_RFC6238_H
// Needed for the SHA1
//
#include <mbedtls/md.h>
// Needed for base32 decode - origin
// https://github.com/dirkx/Arduino-Base32-Decode/releases
//
#include <Base32-Decode.h>
class TOTP {
public:
// Defaults from RFC 6238
// Seed assumed in base64 format; and to be a multiple of 8 bits.
// once decoded.
static const time_t RFC6238_DEFAULT_interval = 30; // seconds (default)
static const time_t RFC6238_DEFAULT_epoch = 0; // epoch relative to the unix epoch (jan 1970 is the default)
static const int RFC6238_DEFAULT_digits = 6; // length (default is 6)
static String * currentOTP(String seed,
time_t interval = RFC6238_DEFAULT_interval,
int digits = RFC6238_DEFAULT_digits,
time_t epoch = RFC6238_DEFAULT_epoch
)
{
return currentOTP(seed, time(NULL), interval, digits, epoch);
}
static String * currentOTP(String seed,
time_t t,
time_t interval = RFC6238_DEFAULT_interval,
int digits = RFC6238_DEFAULT_digits,
time_t epoch = RFC6238_DEFAULT_epoch
)
{
uint64_t v = t;
v = (v - epoch) / interval;
// HMAC is calculated in big-endian (network) order.
// v = htonll(v);
// Unfortunately htonll is not exposed
uint32_t endianness = 0xdeadbeef;
if ((*(const uint8_t *)&endianness) == 0xef) {
v = ((v & 0x00000000ffffffff) << 32) | ((v & 0xffffffff00000000) >> 32);
v = ((v & 0x0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000) >> 16);
v = ((v & 0x00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00) >> 8);
};
unsigned char buff[ seed.length() ];
bzero(buff, sizeof(buff));
int n = base32decode(seed.c_str(), buff, sizeof(buff));
if (n < 0) {
Serial.println("Could not decode base32 seed");
return NULL;
}
#ifdef RFC6238_DEBUG
Serial.print("Key: ");
Serial.print(seed);
Serial.print(" --> ");
for (int i = 0; i < n; i++) {
Serial.printf("%02x", buff[i]);
}
Serial.printf(" -- bits=%d -- check this against https://cryptotools.net/otp\n",n * 8);
#endif
unsigned char digest[20];
if (mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1),
buff, n, // key
(unsigned char*) &v, sizeof(v), // input
digest)) return NULL;
uint8_t offst = digest[19] & 0x0f;
uint32_t bin_code = (digest[offst + 0] & 0x7f) << 24
| (digest[offst + 1] & 0xff) << 16
| (digest[offst + 2] & 0xff) << 8
| (digest[offst + 3] & 0xff);
int power = pow(10, digits);
#if RFC6238_DEBUG
// To check against https://cryptotools.net/otp
//
for (int i = 0; i < 20; i++) {
if (offst == i) Serial.print("|");
Serial.printf("%02x", digest[i]);
if (offst == i) Serial.print("|");
}
Serial.println();
#endif
// prefix with zero's - as needed & cut off to right number of digits.
//
char outbuff[32];
snprintf(outbuff, sizeof(outbuff), "%06u", bin_code % power);
String * otp = new String(outbuff);
return (otp);
}
};
#endif