From ef2c04db178d0610352a27219e7b61b5169b826b Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Sat, 13 May 2017 12:54:23 -0400 Subject: Add HOTP (RFC 4226) and TOTP (RFC 6238) --- src/tests/data/otp/hotp.vec | 127 ++++++++++++++++++++++++++++++++++++++++++ src/tests/data/otp/totp.vec | 19 +++++++ src/tests/test_otp.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 src/tests/data/otp/hotp.vec create mode 100644 src/tests/data/otp/totp.vec create mode 100644 src/tests/test_otp.cpp (limited to 'src/tests') diff --git a/src/tests/data/otp/hotp.vec b/src/tests/data/otp/hotp.vec new file mode 100644 index 000000000..60e3d1665 --- /dev/null +++ b/src/tests/data/otp/hotp.vec @@ -0,0 +1,127 @@ +[SHA-1] + +# From RFC 4226 + +Key = 3132333435363738393031323334353637383930 +Digits = 6 + +Counter = 0 +OTP = 755224 + +Counter = 1 +OTP = 287082 + +Counter = 2 +OTP = 359152 + +Counter = 3 +OTP = 969429 + +Counter = 4 +OTP = 338314 + +Counter = 5 +OTP = 254676 + +Counter = 6 +OTP = 287922 + +Counter = 7 +OTP = 162583 + +Counter = 8 +OTP = 399871 + +Counter = 9 +OTP = 520489 + +# Long digit tests + +Digits = 7 + +Counter = 7 +OTP = 2162583 + +Counter = 8 +OTP = 3399871 + +Digits = 8 + +Counter = 7 +OTP = 82162583 + +Counter = 8 +OTP = 73399871 + +# From RFC 6238 + +Key = 3132333435363738393031323334353637383930 +Digits = 8 + +Counter = 1 +OTP = 94287082 + +Counter = 37037036 +OTP = 07081804 + +Counter = 37037037 +OTP = 14050471 + +Counter = 41152263 +OTP = 89005924 + +Counter = 66666666 +OTP = 69279037 + +Counter = 666666666 +OTP = 65353130 + +[SHA-256] + +# From RFC 6238 + +Key = 3132333435363738393031323334353637383930313233343536373839303132 +Digits = 8 + +Counter = 1 +OTP = 46119246 + +Counter = 37037036 +OTP = 68084774 + +Counter = 37037037 +OTP = 67062674 + +Counter = 41152263 +OTP = 91819424 + +Counter = 66666666 +OTP = 90698825 + +Counter = 666666666 +OTP = 77737706 + +[SHA-512] + +# From RFC 6238 + +Key = 31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334 +Digits = 8 + +Counter = 1 +OTP = 90693936 + +Counter = 37037036 +OTP = 25091201 + +Counter = 37037037 +OTP = 99943326 + +Counter = 41152263 +OTP = 93441116 + +Counter = 66666666 +OTP = 38618901 + +Counter = 666666666 +OTP = 47863826 diff --git a/src/tests/data/otp/totp.vec b/src/tests/data/otp/totp.vec new file mode 100644 index 000000000..2247b6f14 --- /dev/null +++ b/src/tests/data/otp/totp.vec @@ -0,0 +1,19 @@ +[SHA-1] + +# From RFC 6238 + +Key = 3132333435363738393031323334353637383930 +Digits = 8 +Timestep = 30 + +Timestamp = 1970-01-01T00:00:59 +OTP = 94287082 + +Timestamp = 2005-03-18T01:58:29 +OTP = 07081804 + +Timestamp = 2009-02-13T23:31:30 +OTP = 89005924 + +Timestamp = 2033-05-18:03:33:20 +OTP = 69279037 diff --git a/src/tests/test_otp.cpp b/src/tests/test_otp.cpp new file mode 100644 index 000000000..aa899f764 --- /dev/null +++ b/src/tests/test_otp.cpp @@ -0,0 +1,132 @@ +/* +* OTP tests +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_HOTP) + #include +#endif + +#if defined(BOTAN_HAS_TOTP) + #include + #include +#endif + +namespace Botan_Tests { + +#if defined(BOTAN_HAS_HOTP) + +class HOTP_KAT_Tests : public Text_Based_Test + { + public: + HOTP_KAT_Tests() + : Text_Based_Test("otp/hotp.vec", "Key,Digits,Counter,OTP") + {} + + bool clear_between_callbacks() const override { return false; } + + Test::Result run_one_test(const std::string& hash_algo, const VarMap& vars) override + { + Test::Result result("HOTP " + hash_algo); + + const std::vector key = get_req_bin(vars, "Key"); + const size_t otp = get_req_sz(vars, "OTP"); + const size_t counter = get_req_sz(vars, "Counter"); + const size_t digits = get_req_sz(vars, "Digits"); + + Botan::HOTP hotp(key, hash_algo, digits); + + result.test_eq("OTP", hotp.generate_hotp(counter), otp); + + std::pair otp_res = hotp.verify_hotp(otp, counter, 0); + result.test_eq("OTP verify result", otp_res.first, true); + result.test_eq("OTP verify next counter", otp_res.second, counter + 1); + + // Test invalid OTP + otp_res = hotp.verify_hotp(otp + 1, counter, 0); + result.test_eq("OTP verify result", otp_res.first, false); + result.test_eq("OTP verify next counter", otp_res.second, counter); + + // Test invalid OTP with long range + otp_res = hotp.verify_hotp(otp + 1, counter, 100); + result.test_eq("OTP verify result", otp_res.first, false); + result.test_eq("OTP verify next counter", otp_res.second, counter); + + // Test valid OTP with long range + otp_res = hotp.verify_hotp(otp, counter - 90, 100); + result.test_eq("OTP verify result", otp_res.first, true); + result.test_eq("OTP verify next counter", otp_res.second, counter + 1); + + return result; + } + }; + +BOTAN_REGISTER_TEST("hotp", HOTP_KAT_Tests); + +#endif + +#if defined(BOTAN_HAS_TOTP) + +class TOTP_KAT_Tests : public Text_Based_Test + { + public: + TOTP_KAT_Tests() + : Text_Based_Test("otp/totp.vec", "Key,Digits,Timestep,Timestamp,OTP") + {} + + bool clear_between_callbacks() const override { return false; } + + Test::Result run_one_test(const std::string& hash_algo, const VarMap& vars) override + { + Test::Result result("TOTP " + hash_algo); + + const std::vector key = get_req_bin(vars, "Key"); + const size_t otp = get_req_sz(vars, "OTP"); + const size_t digits = get_req_sz(vars, "Digits"); + const size_t timestep = get_req_sz(vars, "Timestep"); + const std::string timestamp = get_req_str(vars, "Timestamp"); + + Botan::TOTP totp(key, hash_algo, digits, timestep); + + std::chrono::system_clock::time_point time = from_timestring(timestamp); + std::chrono::system_clock::time_point later_time = time + std::chrono::seconds(timestep); + std::chrono::system_clock::time_point too_late = time + std::chrono::seconds(2*timestep); + + result.test_eq("TOTP generate", totp.generate_totp(time), otp); + + result.test_eq("TOTP verify valid", totp.verify_totp(otp, time, 0), true); + result.test_eq("TOTP verify invalid", totp.verify_totp(otp ^ 1, time, 0), false); + result.test_eq("TOTP verify time slip", totp.verify_totp(otp, later_time, 0), false); + result.test_eq("TOTP verify time slip allowed", totp.verify_totp(otp, later_time, 1), true); + result.test_eq("TOTP verify time slip out of range", totp.verify_totp(otp, too_late, 1), false); + + return result; + } + + private: + std::chrono::system_clock::time_point from_timestring(const std::string& time_str) + { + if(time_str.size() != 19) + throw std::invalid_argument("Invalid TOTP timestamp string " + time_str); + // YYYY-MM-DDTHH:MM:SS + // 0123456789012345678 + const uint32_t year = Botan::to_u32bit(time_str.substr(0, 4)); + const uint32_t month = Botan::to_u32bit(time_str.substr(5, 2)); + const uint32_t day = Botan::to_u32bit(time_str.substr(8, 2)); + const uint32_t hour = Botan::to_u32bit(time_str.substr(11, 2)); + const uint32_t minute = Botan::to_u32bit(time_str.substr(14, 2)); + const uint32_t second = Botan::to_u32bit(time_str.substr(17, 2)); + return Botan::calendar_point(year, month, day, hour, minute, second).to_std_timepoint(); + } + }; + +BOTAN_REGISTER_TEST("totp", TOTP_KAT_Tests); +#endif + +} + + -- cgit v1.2.3 From 9761cd53a695c17ad444ada6f0a00fb9ad5a1256 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 22 May 2017 12:43:10 -0400 Subject: Doc and 32-bit build fixes --- doc/manual/contents.rst | 1 + doc/manual/otp.rst | 3 +++ src/tests/test_otp.cpp | 14 +++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src/tests') diff --git a/doc/manual/contents.rst b/doc/manual/contents.rst index 20bd855a0..23fa218b2 100644 --- a/doc/manual/contents.rst +++ b/doc/manual/contents.rst @@ -30,6 +30,7 @@ Contents compression pkcs11 tpm + otp ffi python cli diff --git a/doc/manual/otp.rst b/doc/manual/otp.rst index 1be117478..5c49c4533 100644 --- a/doc/manual/otp.rst +++ b/doc/manual/otp.rst @@ -29,6 +29,9 @@ client, so it should be treated with the same security consideration as would be given to any other symmetric key or plaintext password. .. cpp:class:: HOTP + + Implement counter-based OTP + .. cpp:function:: HOTP(const SymmetricKey& key, const std::string& hash_algo = "SHA-1", size_t digits = 6) Initialize an HOTP instance with a secret key (specific to each client), diff --git a/src/tests/test_otp.cpp b/src/tests/test_otp.cpp index aa899f764..480065194 100644 --- a/src/tests/test_otp.cpp +++ b/src/tests/test_otp.cpp @@ -35,7 +35,7 @@ class HOTP_KAT_Tests : public Text_Based_Test const std::vector key = get_req_bin(vars, "Key"); const size_t otp = get_req_sz(vars, "OTP"); - const size_t counter = get_req_sz(vars, "Counter"); + const uint64_t counter = get_req_sz(vars, "Counter"); const size_t digits = get_req_sz(vars, "Digits"); Botan::HOTP hotp(key, hash_algo, digits); @@ -44,28 +44,28 @@ class HOTP_KAT_Tests : public Text_Based_Test std::pair otp_res = hotp.verify_hotp(otp, counter, 0); result.test_eq("OTP verify result", otp_res.first, true); - result.test_eq("OTP verify next counter", otp_res.second, counter + 1); + result.confirm("OTP verify next counter", otp_res.second == counter + 1); // Test invalid OTP otp_res = hotp.verify_hotp(otp + 1, counter, 0); result.test_eq("OTP verify result", otp_res.first, false); - result.test_eq("OTP verify next counter", otp_res.second, counter); + result.confirm("OTP verify next counter", otp_res.second == counter); // Test invalid OTP with long range otp_res = hotp.verify_hotp(otp + 1, counter, 100); result.test_eq("OTP verify result", otp_res.first, false); - result.test_eq("OTP verify next counter", otp_res.second, counter); + result.confirm("OTP verify next counter", otp_res.second == counter); // Test valid OTP with long range otp_res = hotp.verify_hotp(otp, counter - 90, 100); result.test_eq("OTP verify result", otp_res.first, true); - result.test_eq("OTP verify next counter", otp_res.second, counter + 1); + result.confirm("OTP verify next counter", otp_res.second == counter + 1); return result; } }; -BOTAN_REGISTER_TEST("hotp", HOTP_KAT_Tests); +BOTAN_REGISTER_TEST("otp_hotp", HOTP_KAT_Tests); #endif @@ -124,7 +124,7 @@ class TOTP_KAT_Tests : public Text_Based_Test } }; -BOTAN_REGISTER_TEST("totp", TOTP_KAT_Tests); +BOTAN_REGISTER_TEST("otp_totp", TOTP_KAT_Tests); #endif } -- cgit v1.2.3