diff options
author | Jack Lloyd <[email protected]> | 2017-05-13 12:54:23 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-05-19 16:47:48 -0400 |
commit | ef2c04db178d0610352a27219e7b61b5169b826b (patch) | |
tree | f9231aa1191b81eef8bdcea256e1d07926f3681e /src/lib/misc/hotp | |
parent | dd2c8aa1707e59844ef4a30f01983b9ee5fe60fa (diff) |
Add HOTP (RFC 4226) and TOTP (RFC 6238)
Diffstat (limited to 'src/lib/misc/hotp')
-rw-r--r-- | src/lib/misc/hotp/hotp.cpp | 62 | ||||
-rw-r--r-- | src/lib/misc/hotp/hotp.h | 52 | ||||
-rw-r--r-- | src/lib/misc/hotp/info.txt | 9 | ||||
-rw-r--r-- | src/lib/misc/hotp/totp.cpp | 63 | ||||
-rw-r--r-- | src/lib/misc/hotp/totp.h | 56 |
5 files changed, 242 insertions, 0 deletions
diff --git a/src/lib/misc/hotp/hotp.cpp b/src/lib/misc/hotp/hotp.cpp new file mode 100644 index 000000000..f07c11c9f --- /dev/null +++ b/src/lib/misc/hotp/hotp.cpp @@ -0,0 +1,62 @@ +/* +* HOTP +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/hotp.h> + +namespace Botan { + +HOTP::HOTP(const SymmetricKey& key, const std::string& hash_algo, size_t digits) + { + if(digits == 6) + m_digit_mod = 1000000; + else if(digits == 7) + m_digit_mod = 10000000; + else if(digits == 8) + m_digit_mod = 100000000; + else + throw Invalid_Argument("Invalid HOTP digits"); + + /* + RFC 4228 only supports SHA-1 but TOTP allows SHA-256 and SHA-512 + and some HOTP libs support one or both as extensions + */ + if(hash_algo == "SHA-1") + m_mac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-1)"); + else if(hash_algo == "SHA-256") + m_mac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); + else if(hash_algo == "SHA-512") + m_mac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-512)"); + else + throw Invalid_Argument("Unsupported HOTP hash function"); + + m_mac->set_key(key); + } + +uint32_t HOTP::generate_hotp(uint64_t counter) + { + uint8_t counter8[8] = { 0 }; + store_be(counter, counter8); + m_mac->update(counter8, sizeof(counter8)); + const secure_vector<uint8_t> mac = m_mac->final(); + + const size_t offset = mac[mac.size()-1] & 0x0F; + const uint32_t code = load_be<uint32_t>(mac.data() + offset, 0) & 0x7FFFFFFF; + return code % m_digit_mod; + } + +std::pair<bool,uint64_t> HOTP::verify_hotp(uint32_t otp, uint64_t starting_counter, size_t resync_range) + { + for(size_t i = 0; i <= resync_range; ++i) + { + if(generate_hotp(starting_counter + i) == otp) + return std::make_pair(true, starting_counter + i + 1); + } + return std::make_pair(false, starting_counter); + } + +} + diff --git a/src/lib/misc/hotp/hotp.h b/src/lib/misc/hotp/hotp.h new file mode 100644 index 000000000..cc222e5c0 --- /dev/null +++ b/src/lib/misc/hotp/hotp.h @@ -0,0 +1,52 @@ +/* +* HOTP +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_HOTP_H__ +#define BOTAN_HOTP_H__ + +#include <botan/mac.h> + +namespace Botan { + +/** +* HOTP one time passwords (RFC 4226) +*/ +class BOTAN_DLL HOTP + { + public: + /** + * @param key the secret key shared between client and server + * @param hash_algo the hash algorithm to use, should be SHA-1 or SHA-256 + * @param digits the number of digits in the OTP (must be 6, 7, or 8) + */ + HOTP(const SymmetricKey& key, const std::string& hash_algo = "SHA-1", size_t digits = 6); + + /** + * Generate the HOTP for a particular counter value + * @warning if the counter value is repeated the OTP ceases to be one-time + */ + uint32_t generate_hotp(uint64_t counter); + + /** + * Check an OTP value using a starting counter and a resync range + * @param otp the client provided OTP + * @param starting_counter the server's guess as to the current counter state + * @param resync_range if 0 then only HOTP(starting_counter) is accepted + * If larger than 0, up to resync_range values after HOTP are also checked. + * @return (valid,next_counter). If the OTP does not validate, always + * returns (false,starting_counter). Otherwise returns (true,next_counter) + * where next_counter is at most starting_counter + resync_range + 1 + */ + std::pair<bool,uint64_t> verify_hotp(uint32_t otp, uint64_t starting_counter, size_t resync_range = 0); + private: + std::unique_ptr<MessageAuthenticationCode> m_mac; + uint32_t m_digit_mod; + }; + +} + +#endif diff --git a/src/lib/misc/hotp/info.txt b/src/lib/misc/hotp/info.txt new file mode 100644 index 000000000..ad74686c3 --- /dev/null +++ b/src/lib/misc/hotp/info.txt @@ -0,0 +1,9 @@ +<defines> +HOTP -> 20170513 +TOTP -> 20170519 +</defines> + +<requires> +hmac +utils +</requires> diff --git a/src/lib/misc/hotp/totp.cpp b/src/lib/misc/hotp/totp.cpp new file mode 100644 index 000000000..c3203c32a --- /dev/null +++ b/src/lib/misc/hotp/totp.cpp @@ -0,0 +1,63 @@ +/* +* TOTP +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/totp.h> +#include <botan/calendar.h> + +namespace Botan { + +TOTP::TOTP(const SymmetricKey& key, const std::string& hash_algo, + size_t digits, size_t time_step) + : m_hotp(key, hash_algo, digits) + , m_time_step(time_step) + , m_unix_epoch(calendar_point(1970, 1, 1, 0, 0, 0).to_std_timepoint()) + { + /* + * Technically any time step except 0 is valid, but 30 is typical + * and over 5 minutes seems unlikely. + */ + if(m_time_step == 0 || m_time_step > 300) + throw Invalid_Argument("Invalid TOTP time step"); + } + +uint32_t TOTP::generate_totp(std::chrono::system_clock::time_point current_time) + { + const uint64_t unix_time = + std::chrono::duration_cast<std::chrono::seconds>(current_time - m_unix_epoch).count(); + return this->generate_totp(unix_time); + } + +uint32_t TOTP::generate_totp(uint64_t unix_time) + { + return m_hotp.generate_hotp(unix_time / m_time_step); + } + +bool TOTP::verify_totp(uint32_t otp, std::chrono::system_clock::time_point current_time, + size_t clock_drift_accepted) + { + const uint64_t unix_time = + std::chrono::duration_cast<std::chrono::seconds>(current_time - m_unix_epoch).count(); + return verify_totp(otp, unix_time, clock_drift_accepted); + } + +bool TOTP::verify_totp(uint32_t otp, uint64_t unix_time, + size_t clock_drift_accepted) + { + uint64_t t = unix_time / m_time_step; + + for(size_t i = 0; i <= clock_drift_accepted; ++i) + { + if(m_hotp.generate_hotp(t-i) == otp) + { + return true; + } + } + + return false; + } + +} diff --git a/src/lib/misc/hotp/totp.h b/src/lib/misc/hotp/totp.h new file mode 100644 index 000000000..767c7cc5a --- /dev/null +++ b/src/lib/misc/hotp/totp.h @@ -0,0 +1,56 @@ +/* +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TOTP_H__ +#define BOTAN_TOTP_H__ + +#include <botan/hotp.h> +#include <chrono> + +namespace Botan { + +/** +* TOTP (time based) one time passwords (RFC 6238) +*/ +class BOTAN_DLL TOTP + { + public: + /** + * @param key the secret key shared between client and server + * @param hash_algo the hash algorithm to use, should be SHA-1, SHA-256 or SHA-512 + * @param digits the number of digits in the OTP (must be 6, 7, or 8) + * @param time_step granularity of OTP in seconds + */ + TOTP(const SymmetricKey& key, const std::string& hash_algo = "SHA-1", + size_t digits = 6, size_t time_step = 30); + + /** + * Convert the provided time_point to a Unix timestamp and call generate_totp + */ + uint32_t generate_totp(std::chrono::system_clock::time_point time_point); + + /** + * Generate the OTP cooresponding the the provided "Unix timestamp" (ie + * number of seconds since midnight Jan 1, 1970) + */ + uint32_t generate_totp(uint64_t unix_time); + + bool verify_totp(uint32_t otp, + std::chrono::system_clock::time_point time, + size_t clock_drift_accepted = 0); + + bool verify_totp(uint32_t otp, uint64_t unix_time, + size_t clock_drift_accepted = 0); + + private: + HOTP m_hotp; + size_t m_time_step; + std::chrono::system_clock::time_point m_unix_epoch; + }; + +} + +#endif |