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,21 @@
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.
This cose is based on MacOSX code from taken from https://github.com/ekscrypto/Base32/
which itself is under the Unlicense Mhttps://unlicense.org>.
This code was based on code by Dave Poirier date 12-06-14 who released this
as "Public Domain"
The test vectors where taken from the https://www.rfc-editor.org/rfc/rfc4648
and are Copyright (C) The Internet Society (2006).

View File

@@ -0,0 +1,60 @@
# Arduino libary for Base32 (rfc4648) decoding.
## Traditional C interface
### base32decode : decode a \0 terminated base32 encoded string.
int base32decode(
const char * encoded,
unsigned char * decodedBytes,
size_t maxbuf
);
#### inputs:
encoded \0 terminated char buffer with encoded string
decodedBytes outputbuffer (or NULL)
maxbuff size of the output buffer
#### outputs:
returns the decoded byte array in decodedBytes and the length. Or if
decodedBytes==NULL, will just return the length needed; regardless of
the value of maxbuff.
If the size of maxbuff allows it - a terminating \0 is added (but not
including in the length returned) - as very often the decoded data
itself is actually again a string.
## C++/INO/Arduino# tring interface
### base32decodeToString - decode a String into a decoded String
int base32decodeToString(String encoded, String &decoded);
#### inputs:
encoded String with the encoded base32 value
&decoded returned string (if any)
#### outputs:
Will return the length of the decoded string or a negative
value on error.
# Example
Typical use:
String in = "IFZGI5LJNZXSAUTVNRSXU===";
String out;
int r = base32decodeToString(in, out);
if (r < 0) {
Serial.println("Could not decode the string");
return;
};
Serial.print("Decoded: ");
Serial.println(out);

View File

@@ -0,0 +1,127 @@
#include <limits.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <Base32-Decode.h>
void example1() {
String in, out;
in = "IFZGI5LJNZXSAUTVNRSXU===";
int r = base32decodeToString(in, out);
if (r < 0) {
Serial.println("Could not decode string");
return;
}
Serial.print("Decoded: ");
Serial.println(out);
}
void example2() {
const char * in = "IFZGI5LJNZXSAUTVNRSXU===";
size_t maxout = strlen(in); // we know that the encoded string is as long, or shorter than the decoded string.
char out[maxout];
int r = base32decode(in, (unsigned char*) out, maxout);
if (r < 0) {
Serial.println("Could not decode string");
return;
}
Serial.print("Decoded: ");
Serial.println(out);
}
void example3() {
const char * in = "IFZGI5LJNZXSAUTVNRSXU===";
// figure out the lenght we're going to get
//
int maxout = base32decode(in, NULL, 0);
// keep room for an terminating \0
maxout += 1;
// declare just enough memory
char out[maxout];
int r = base32decode(in, (unsigned char*) out, maxout);
if (r < 0) {
Serial.println("Could not decode string");
return;
}
Serial.print("Decoded: ");
Serial.println(out);
}
// RFC 4648 test vectors - https://www.rfc-editor.org/rfc/rfc4648 section 10
void runalltests() {
typedef struct testvector_t {
char *out;
char *in;
} testvector_t;
testvector_t testvectors[] = {
// normal with padding
{ (char *) "", (char *)""},
{ (char *) "f", (char *)"MY======"},
{ (char *) "fo", (char *)"MZXQ===="},
{ (char *) "foo", (char *)"MZXW6==="},
{ (char *) "foob", (char *)"MZXW6YQ="},
{ (char *) "fooba", (char *)"MZXW6YTB"},
{ (char *) "foobar", (char *)"MZXW6YTBOI======"},
// careless without the normal padding (but happens a lot)
{ (char *) "f", (char *)"MY"},
{ (char *) "fo", (char *) "MZXQ"},
{ (char *) "foo", (char *)"MZXW6"},
{ (char *) "foob", (char *)"MZXW6YQ"},
{ (char *) "fooba", (char *)"MZXW6YTB"},
{ (char *)"foobar", (char *)"MZXW6YTBOI"},
// wrong case.
{ (char *) "f", (char *)"my"},
{ (char *) "fo", (char *)"mzxq"},
{ (char *) "foo", (char *)"mzxw6"},
{ (char *) "foob", (char *)"mzxw6yq"},
{ (char *) "fooba", (char *)"mzxw6ytb"},
{ (char *)"foobar", (char *)"mzxw6ytboi"},
// acidental crufft (not in the RFC)
{ (char *)"", (char *)" "},
{ (char *)"", (char *)" "},
{ (char *)"foobar", (char *)" mzx w6 yt b o i"},
{ (char *)"foobar", (char *)" m zx w6 yt b o i"},
{ (char *)"foobar", (char *)"mzx\tw6ytboi"},
{ (char *)"foobar", (char *)"mzxw6\nytboi"},
{ (char *)"foobar", (char *)"mzxw6 ytb oi "}
};
for (int i = 0; i < sizeof(testvectors) / sizeof(testvector_t); i++) {
unsigned char buff[1024];
int ret = base32decode(testvectors[i].in, buff, sizeof(buff));
Serial.printf("test %d: %s -> '%s' == '%s' (size %d)\n", i + 1, testvectors[i].in, buff, testvectors[i].out, ret);
assert(ret == strlen(testvectors[i].out));
assert(strcmp((char *)buff, testvectors[i].out) == 0);
printf("test: %d ok\n\n", i + 1);
}
Serial.println("==\nAll test passed\n\n");
}
void setup() {
Serial.begin(119200);
while (!Serial) delay(10);
Serial.println("\n\n" __FILE__ " started");
// runalltests();
example1();
example2();
example3();
}
void loop() {
delay(10000);
}

View File

@@ -0,0 +1,12 @@
name=Base32-Decode
version=1.0.1
author=Dirk-Willem van Gulik et.al.
license=ASLv2, Unlicense, Public-Domain
maintainer=Dirk-Willem van Gulik <dirkx@webweaving.org>
sentence=Base32 decoder; able to handle both binary and string encoded data.
paragraph=RFC4648 Base32 decoder; handles both binary and string encoded data. With a char/unsigned-char interface as well as a String interface.
category=Communication
url=https://github.com/dirkx/Arduino-Base32-Decode
architectures=*
depends=
includes=Base32-Decode.h

View File

@@ -0,0 +1,174 @@
#include <Arduino.h>
#include <limits.h>
#include <string.h>
#include "Base32-Decode.h"
// Code and table taken from https://github.com/ekscrypto/Base32/
// under the Unlincense https://unlicense.org> and also by
// Dave Poirier on 12-06-14 who released this as "Public Domain"
//
int base32decode(const char * encoded, unsigned char * decoded, size_t maxbuf) {
#define __ 255
static char decodingTable[256] = {
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0x00 - 0x0F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0x10 - 0x1F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0x20 - 0x2F
__, __, 26, 27, 28, 29, 30, 31, __, __, __, __, __, 0, __, __, // 0x30 - 0x3F
__, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x40 - 0x4F
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, __, // 0x50 - 0x5F
__, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x60 - 0x6F
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, __, // 0x70 - 0x7F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0x80 - 0x8F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0x90 - 0x9F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xA0 - 0xAF
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xB0 - 0xBF
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xC0 - 0xCF
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xD0 - 0xDF
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xE0 - 0xEF
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0xF0 - 0xFF
};
size_t encodedLength = strlen(encoded);
// strip any trailing padding.
while (encodedLength && encoded[encodedLength - 1] == '=') encodedLength--;
int blocks = (encodedLength + 7) >> 3;
int expectedDataLength = blocks * 5;
if (decoded == NULL)
return expectedDataLength + 1; // for terminating 0
if (maxbuf <= expectedDataLength)
return -1;
unsigned char encodedByte1, encodedByte2, encodedByte3, encodedByte4;
unsigned char encodedByte5, encodedByte6, encodedByte7, encodedByte8;
unsigned int encodedToProcess = encodedLength;
unsigned int encodedBaseIndex = 0;
unsigned int decodedBaseIndex = 0;
unsigned char block[8] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned int blockIndex = 0;
unsigned char c;
while ( encodedToProcess-- >= 1 ) {
c = encoded[encodedBaseIndex++];
if ( c == '=' ) break; // padding...
c = decodingTable[c];
if ( c == __ ) continue; // skip anything we do not know.
block[blockIndex++] = c;
if ( blockIndex == 8 ) {
encodedByte1 = block[0];
encodedByte2 = block[1];
encodedByte3 = block[2];
encodedByte4 = block[3];
encodedByte5 = block[4];
encodedByte6 = block[5];
encodedByte7 = block[6];
encodedByte8 = block[7];
decoded[decodedBaseIndex + 0] = ((encodedByte1 << 3) & 0xF8) | ((encodedByte2 >> 2) & 0x07);
decoded[decodedBaseIndex + 1] = ((encodedByte2 << 6) & 0xC0) | ((encodedByte3 << 1) & 0x3E) | ((encodedByte4 >> 4) & 0x01);
decoded[decodedBaseIndex + 2] = ((encodedByte4 << 4) & 0xF0) | ((encodedByte5 >> 1) & 0x0F);
decoded[decodedBaseIndex + 3] = ((encodedByte5 << 7) & 0x80) | ((encodedByte6 << 2) & 0x7C) | ((encodedByte7 >> 3) & 0x03);
decoded[decodedBaseIndex + 4] = ((encodedByte7 << 5) & 0xE0) | (encodedByte8 & 0x1F);
decodedBaseIndex += 5;
blockIndex = 0;
}
}
encodedByte7 = 0;
encodedByte6 = 0;
encodedByte5 = 0;
encodedByte4 = 0;
encodedByte3 = 0;
encodedByte2 = 0;
if (blockIndex > 6)
encodedByte7 = block[6];
if (blockIndex > 5)
encodedByte6 = block[5];
if (blockIndex > 4)
encodedByte5 = block[4];
if (blockIndex > 3)
encodedByte4 = block[3];
if (blockIndex > 2)
encodedByte3 = block[2];
if (blockIndex > 1)
encodedByte2 = block[1];
if (blockIndex > 0) {
encodedByte1 = block[0];
decoded[decodedBaseIndex + 0] = ((encodedByte1 << 3) & 0xF8) | ((encodedByte2 >> 2) & 0x07);
decoded[decodedBaseIndex + 1] = ((encodedByte2 << 6) & 0xC0) | ((encodedByte3 << 1) & 0x3E) | ((encodedByte4 >> 4) & 0x01);
decoded[decodedBaseIndex + 2] = ((encodedByte4 << 4) & 0xF0) | ((encodedByte5 >> 1) & 0x0F);
decoded[decodedBaseIndex + 3] = ((encodedByte5 << 7) & 0x80) | ((encodedByte6 << 2) & 0x7C) | ((encodedByte7 >> 3) & 0x03);
decoded[decodedBaseIndex + 4] = ((encodedByte7 << 5) & 0xE0);
};
static int paddingAdjustment[8] = {0, 1, 1, 1, 2, 3, 3, 4};
decodedBaseIndex += paddingAdjustment[blockIndex];
// ensure null terminated if there is space.
if (decodedBaseIndex < maxbuf)
decoded[decodedBaseIndex] = 0;
return decodedBaseIndex;
}
int base32decodeToString(String encoded, String &decoded) {
size_t maxlen = encoded.length() * 5 / 8 + 1;
char * buff = new char[maxlen];
int ret = base32decode(encoded.c_str(), (unsigned char*) buff, maxlen);
if (ret >= 0)
decoded = String(buff);
return ret;
}
#ifdef TEST_BASE32
#include <assert.h>
#include <stdio.h>
int main(int a, char **b) {
typedef struct testvector_t {
char *out;
char *in;
} testvector_t;
// RFC 4648 test vectors - https://www.rfc-editor.org/rfc/rfc4648 section 10
testvector_t testvectors[] = {
// normal with padding
{"", ""},
{"f", "MY======"},
{ "fo", "MZXQ===="},
{"foo", "MZXW6==="},
{"foob", "MZXW6YQ="},
{"fooba", "MZXW6YTB"},
{"foobar", "MZXW6YTBOI======"},
// careless without
{"f", "MY"},
{"fo", "MZXQ"},
{"foo", "MZXW6"},
{"foob", "MZXW6YQ"},
{"fooba", "MZXW6YTB"},
{ "foobar", "MZXW6YTBOI"},
// wrong case.
{"f", "my"},
{"fo", "mzxq"},
{"foo", "mzxw6"},
{"foob", "mzxw6yq"},
{"fooba", "mzxw6ytb"},
{ "foobar", "mzxw6ytboi"}
};
for (int i = 0; i < sizeof(testvectors) / sizeof(testvector_t); i++) {
char buff[1024];
int ret = base32decode(testvectors[i].in, buff, sizeof(buff));
printf("test %d: %s -> '%s' == '%s' (size %d)\n", i + 1, testvectors[i].in, buff, testvectors[i].out, ret);
assert(ret == strlen(testvectors[i].out));
assert(strcmp(buff, testvectors[i].out) == 0);
printf("test %d ok\n", i + 1);
}
return 0;
}
#endif

View File

@@ -0,0 +1,30 @@
#ifndef BASE32_DECODE_H
#define BASE32_DECODE_H
/* base32decode - decode a \0 terminated base32 encoded string.
*
* encoded \0 terminated char buffer with encoded string
* decodedBytes outputbuffer (or NULL)
* maxbuff size of the output buffer
*
* returns the decoded byte array in decodedBytes and the length. Or if
* decodedBytes==NULL, will just return the length needed; regardless of
* the value of maxbuff.
*
* If the size of maxbuff allows it - a terminating \0 is added (but not
* including in the length returned) - as very often the decoded data
* itself is actually again a string.
*/
extern int base32decode(const char * encoded, unsigned char * decodedBytes, size_t maxbuf);
/* base32decodeToString - decode a String into a decoded String
*
* encoded String with the encoded base32 value
* &decoded returned string (if any)
*
* Will return the length of the decoded string or a negative
* value on error.
*/
extern int base32decodeToString(String encoded, String &decoded);
#endif

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