9b03a29b |
/* Copyright (C) CJ Affiliate
*
* You may use, distribute and modify this code under the
* terms of the GNU General Public License version 2 or
* later.
*
* You should have received a copy of the license with this
* file. If not, you will find a copy in the "LICENSE" file
* at https://github.com/cjdev/dual-control.
*/
#include "generator.h"
#include <iostream>
|
75b15a68 |
#include <openssl/hmac.h>
#include <openssl/evp.h>
|
c1080d50 |
#include "base32.h"
|
75b15a68 |
namespace
{
|
c1080d50 |
class hmac_failed_exception : public std::exception
{};
|
9b03a29b |
int ipow (int base, int exp)
{
int result = 1;
while (exp) {
if (exp & 1) {
result *= base;
}
exp >>= 1;
base *= base;
}
return result;
}
|
75b15a68 |
unsigned char *timeToBytes (unsigned long long time, unsigned char *data,
size_t data_size)
{
for (int idx = data_size - 1; idx > -1; idx--) {
unsigned char next_digit = time & 0xff;
data[idx] = next_digit;
time >>= 8;
}
return data;
}
|
9b3884f4 |
template <typename S>
unsigned long bytesToInt (const S &bytes)
|
9b03a29b |
{
unsigned long result = 0;
auto byteCount = bytes.size() - 1;
for (auto byte = bytes.cbegin(); byte < bytes.cend(); byte++, byteCount--) {
const uint8_t val = static_cast<uint8_t> (*byte);
result |= val << (byteCount * 8);
}
return result;
}
|
6a23c017 |
class token_generator_impl : public token_generator_ifc
|
9b03a29b |
{
|
75b15a68 |
private:
|
2e93eae5 |
const sys_time clock;
|
c1080d50 |
const unsigned int code_digits;
const base32 codec;
|
75b15a68 |
|
9b03a29b |
private:
|
75b15a68 |
std::string zero_fill (unsigned long result, int digits) const
|
9b03a29b |
{
|
75b15a68 |
std::ostringstream result_stream;
result_stream << std::setw (digits) << std::setfill ('0') << result;
return result_stream.str();
|
9b03a29b |
}
|
9b3884f4 |
unsigned long truncate (const octet_vector &mac) const
|
47f1fe7f |
{
|
9085c009 |
uint8_t offset = static_cast<uint8_t > (mac[19]) & static_cast<uint8_t>
(0x0f);
|
47f1fe7f |
|
9b3884f4 |
auto mac_begin = mac.begin();
auto subseq_begin = mac_begin + offset;
octet_vector offsetBytes (subseq_begin, subseq_begin + 4);
|
47f1fe7f |
return bytesToInt (offsetBytes) & 0x7fffffff;
}
|
c1080d50 |
std::string hotp (const std::vector<uint8_t> &key,
const unsigned char *data,
|
75b15a68 |
size_t data_size, const int digits=6) const
|
9b03a29b |
{
|
47f1fe7f |
// TODO: see if I can use sha256/etc. with google auth...
|
0d8b9a17 |
const unsigned char *digest = HMAC (EVP_sha1(), key.data(), key.size(),
data,
data_size, NULL, NULL);
|
47f1fe7f |
|
c1080d50 |
if (digest == nullptr) {
throw hmac_failed_exception();
}
|
9b3884f4 |
size_t sha1_output_size = 20;
// const_cast should be safe here because we generated the pointer
octet_vector digest_s = octet_vector (digest, digest+sha1_output_size);
|
47f1fe7f |
|
75b15a68 |
unsigned long result = truncate (digest_s) % ipow (10,digits);
|
9b03a29b |
|
75b15a68 |
return zero_fill (result, digits);
|
9b03a29b |
}
public:
|
2e93eae5 |
token_generator_impl (const sys_time clock,
|
75b15a68 |
const int code_digits) :
|
e57144d3 |
clock (clock), code_digits (code_digits)
|
6ab21073 |
{}
|
40fd6c82 |
std::string generate_token (const octet_vector &key) const override
|
9b03a29b |
{
|
e5bfbac8 |
// Assuming time is > 0, integer division produces the result we want.
|
75b15a68 |
const time_t &time_chunk = clock.time (nullptr) / 30;
unsigned char data[8] = {0,0,0,0,0,0,0,0};
timeToBytes (time_chunk, data, 8);
return hotp (key, data, 8, code_digits);
|
9b03a29b |
}
};
|
6a23c017 |
}
|
9b03a29b |
// Generator goes here....
|
6ab21073 |
totp_generator::totp_generator (
|
2e93eae5 |
const sys_time clock,
|
6ab21073 |
const int code_digits) :
|
e57144d3 |
delegate_ (std::make_shared<token_generator_impl> (clock, code_digits))
|
6ab21073 |
{}
|
0d8b9a17 |
|