#include #include #include #include "debug_auth.h" using namespace roro::debug; void setUp() {} void tearDown() {} static std::string hmacHex(const std::string& key, const std::string& message) { uint8_t mac[32]; hmacSha256(reinterpret_cast(key.data()), key.size(), reinterpret_cast(message.data()), message.size(), mac); return toHex(mac, sizeof mac); } // RFC 4231, test cases 1, 2 and 6. void test_hmac_matches_the_rfc_vectors() { TEST_ASSERT_EQUAL_STRING("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", hmacHex(std::string(20, '\x0b'), "Hi There").c_str()); TEST_ASSERT_EQUAL_STRING("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", hmacHex("Jefe", "what do ya want for nothing?").c_str()); TEST_ASSERT_EQUAL_STRING("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", hmacHex(std::string(131, '\xaa'), "Test Using Larger Than Block-Size Key - Hash Key First").c_str()); } void test_a_made_token_is_20_characters_without_lookalikes() { uint8_t zeros[kTokenRandom] = {}, ones[kTokenRandom], counting[kTokenRandom]; memset(ones, 0xff, sizeof ones); for (size_t i = 0; i < sizeof counting; i++) counting[i] = static_cast(i); TEST_ASSERT_EQUAL_STRING("00000000000000000000", makeToken(zeros).c_str()); TEST_ASSERT_EQUAL_STRING("ZZZZZZZZZZZZZZZZZZZZ", makeToken(ones).c_str()); TEST_ASSERT_EQUAL_STRING("000G40R40M30E209185G", makeToken(counting).c_str()); // as Python's reference gives for (char c : makeToken(counting)) TEST_ASSERT_NULL(strchr("ILOU", c)); TEST_ASSERT_TRUE(validToken(makeToken(counting))); } void test_a_typed_token_is_tidied() { TEST_ASSERT_EQUAL_STRING("K7QF3M2X9WBDHT4P6RNC", tidyToken("k7qf-3m2x 9wbd-HT4P-6rnc\n").c_str()); TEST_ASSERT_EQUAL_STRING("K7QF-3M2X-9WBD-HT4P-6RNC", groupToken("K7QF3M2X9WBDHT4P6RNC").c_str()); TEST_ASSERT_EQUAL_STRING("K7QF3M2X9WBDHT4P6RNC", tidyToken(groupToken("K7QF3M2X9WBDHT4P6RNC")).c_str()); // Misread as letters the alphabet doesn't have: taken for the digits they look like. TEST_ASSERT_EQUAL_STRING("1010", tidyToken("IOlo").c_str()); } void test_a_token_needs_16_to_64_printable_characters() { TEST_ASSERT_FALSE(validToken("")); TEST_ASSERT_FALSE(validToken("1234")); TEST_ASSERT_FALSE(validToken(std::string(15, 'A'))); TEST_ASSERT_TRUE(validToken(std::string(16, 'A'))); TEST_ASSERT_TRUE(validToken(std::string(64, 'A'))); TEST_ASSERT_FALSE(validToken(std::string(65, 'A'))); TEST_ASSERT_FALSE(validToken("AAAAAAAAAAAAAAA A")); // untidied: a space TEST_ASSERT_FALSE(validToken("aaaaaaaaaaaaaaaaaaaa")); // untidied: small letters TEST_ASSERT_FALSE(validToken("AAAAAAAAAAAAAAAAAAAO")); // untidied: an O nobody could ever match TEST_ASSERT_TRUE(validToken(tidyToken("hello-world-this-is-long"))); TEST_ASSERT_FALSE(validToken(std::string(16, '\x01'))); // The token of before, 32 hex digits from a file, still fits once tidied. TEST_ASSERT_TRUE(validToken(tidyToken("0f1e2d3c4b5a69788796a5b4c3d2e1f0"))); } // The same vector scripts/rdbg.py is checked against: Python's hmac.new(token, nonce, sha256). void test_the_answer_is_the_hmac_of_the_nonce() { uint8_t nonce[kNonceBytes]; for (size_t i = 0; i < sizeof nonce; i++) nonce[i] = static_cast(i); TEST_ASSERT_EQUAL_STRING("e1246c7e74d711cdb27d8d9346515829a01cf46d79fbbc1137885446f12ed2ba", answerFor("K7QF3M2X9WBDHT4P6RNC", nonce).c_str()); nonce[0] ^= 1; // another challenge, another answer: a recorded one is no use TEST_ASSERT_FALSE(answerFor("K7QF3M2X9WBDHT4P6RNC", nonce) == "e1246c7e74d711cdb27d8d9346515829a01cf46d79fbbc1137885446f12ed2ba"); } void test_same_text() { TEST_ASSERT_TRUE(sameText("abc", "abc")); TEST_ASSERT_FALSE(sameText("abc", "abd")); TEST_ASSERT_FALSE(sameText("ab", "abc")); TEST_ASSERT_FALSE(sameText("abcd", "abc")); TEST_ASSERT_FALSE(sameText("", "abc")); TEST_ASSERT_TRUE(sameText("", "")); } void test_five_wrong_answers_lock_for_a_minute() { AuthGate gate; for (int i = 0; i < 4; i++) { TEST_ASSERT_FALSE(gate.failed(1000 + i)); TEST_ASSERT_FALSE(gate.locked(1000 + i)); } TEST_ASSERT_TRUE(gate.failed(2000)); // the fifth starts the pause TEST_ASSERT_TRUE(gate.locked(2001)); TEST_ASSERT_FALSE(gate.failed(2002)); // nothing is counted meanwhile TEST_ASSERT_TRUE(gate.locked(2000 + AuthGate::kLockMs - 1)); TEST_ASSERT_FALSE(gate.locked(2000 + AuthGate::kLockMs)); TEST_ASSERT_EQUAL(0, gate.failures()); // and the count starts again } void test_a_right_answer_forgets_the_wrong_ones() { AuthGate gate; for (int i = 0; i < 4; i++) gate.failed(i); gate.succeeded(); for (int i = 0; i < 4; i++) TEST_ASSERT_FALSE(gate.failed(10 + i)); TEST_ASSERT_FALSE(gate.locked(20)); } void test_the_lock_holds_across_the_clock_wrap() { AuthGate gate; uint32_t t = 0xFFFFFFF0u; // 16 ms before millis() wraps for (int i = 0; i < 5; i++) gate.failed(t); TEST_ASSERT_TRUE(gate.locked(t + 100)); // after the wrap: still locked TEST_ASSERT_TRUE(gate.locked(t + AuthGate::kLockMs - 1)); TEST_ASSERT_FALSE(gate.locked(t + AuthGate::kLockMs)); } int main(int, char**) { UNITY_BEGIN(); RUN_TEST(test_hmac_matches_the_rfc_vectors); RUN_TEST(test_a_made_token_is_20_characters_without_lookalikes); RUN_TEST(test_a_typed_token_is_tidied); RUN_TEST(test_a_token_needs_16_to_64_printable_characters); RUN_TEST(test_the_answer_is_the_hmac_of_the_nonce); RUN_TEST(test_same_text); RUN_TEST(test_five_wrong_answers_lock_for_a_minute); RUN_TEST(test_a_right_answer_forgets_the_wrong_ones); RUN_TEST(test_the_lock_holds_across_the_clock_wrap); return UNITY_END(); }