git.fiddlerwoaroof.com
Raw Blame History
/* 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>

#ifdef CRYPTOPP_NAME
/// Prefixed with __ to avoid symbol conflicts with Cryptopp
#define __CRYPTOPP_BASE32_H <CRYPTOPP_NAME/base32.h>
#define __CRYPTOPP_HEX_H <cryptopp/hex.h>
#define __CRYPTOPP_HMAC_H <cryptopp/hmac.h>
#include __CRYPTOPP_BASE32_H
#include __CRYPTOPP_HEX_H
#include __CRYPTOPP_HMAC_H
#else
#error "CRYPTOPP_NAME not defined"
#endif

namespace {
int ipow (int base, int exp)
{
    int result = 1;

    while (exp) {
        if (exp & 1) {
            result *= base;
        }

        exp >>= 1;
        base *= base;
    }

    return result;
}

unsigned long bytesToInt (const std::string &bytes)
{
    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;
}

time_t time_step (const time_t time, const int step)
{
    // Time is > 0 so division produces the result we want.
    return time / step;
}

class token_generator_impl : public token_generator_ifc
{
private:
    unsigned long truncate (const std::string &mac) const
    {
        uint8_t offset = static_cast<uint8_t > (mac[19]) & static_cast<uint8_t>
                         (0x0f);
        std::string  offsetBytes = mac.substr (offset, 4);
        return bytesToInt (offsetBytes) & 0x7fffffff;
    }

    unsigned long hotp (const CryptoPP::SecByteBlock &key,
                        const CryptoPP::Integer &counter) const
    {
        std::string mac;

        CryptoPP::SecByteBlock counter_bytes (8);
        // Do I know that 8 is sufficient here? . . .
        counter.Encode (counter_bytes.BytePtr(), 8, CryptoPP::Integer::UNSIGNED);

        CryptoPP::HMAC<CryptoPP::SHA1> hmac (key, key.size());

        CryptoPP::StringSink  *stringSink = new CryptoPP::StringSink (mac);
        CryptoPP::HashFilter  *hashFilter = new CryptoPP::HashFilter (hmac,
                stringSink);
        CryptoPP::StringSource ss2 (counter_bytes, counter_bytes.size(), true,
                                    hashFilter);

        unsigned long result = truncate (mac);

        result = result % ipow (10, code_digits);
        return result;
    }

    CryptoPP::SecByteBlock generate_key (unsigned int size)
    const
    {
        CryptoPP::AutoSeededRandomPool prng;

        CryptoPP::SecByteBlock key (size);
        prng.GenerateBlock (key, key.size());
        return key;
    }

    const sys_time &clock;
    unsigned int code_digits;
    const std::shared_ptr<CryptoPP::SecByteBlock> key;
public:
    token_generator_impl (const sys_time &clock,
          const std::string &key_c,
          const int code_digits) :
        clock (clock), code_digits (code_digits),
        key (std::make_shared<CryptoPP::SecByteBlock> (CryptoPP::SecByteBlock (
                    reinterpret_cast<const unsigned char *> (key_c.c_str()), key_c.size())))
    {}

    std::string generate_token () const override
    {
        const CryptoPP::Integer &time = clock.time (nullptr);
        int time_step_size = 30;
        CryptoPP::Integer current_step = time_step (time.ConvertToLong(),
                                         time_step_size);
        long otp = hotp (*key, current_step);
        std::ostringstream is;
        is << std::setfill ('0') << std::setw (6)<< otp;
        return is.str();
    }
};
}

// Generator goes here....

totp_generator::totp_generator (
    const sys_time &clock,
    const std::string &key_c,
    const int code_digits) :
    delegate_ (std::make_shared<token_generator_impl> (clock, key_c, code_digits))
{}