def test_code_from_hash_with_alternate_lengths(self): """Test Otp.code_from_hash(). Try with alternate code lengths. """ cut = HOTP() # code_length 1 # should_be = ("4", "2", "2", "9", "4", "6", "2", "3", "1", "9") for i in range(0, 10): code = cut.code_from_hash(self.expected[i][2], 1) self.assertEqual(should_be[i], code) # code_length 9 # should_be = ( "284755224", "094287082", "137359152", "726969429", "640338314", "868254676", "918287922", "082162583", "673399871", "645520489") for i in range(0, 10): code = cut.code_from_hash(self.expected[i][2], 9) self.assertEqual(should_be[i], code)
def test_convert_base32_bad_chars(self): """Test Otp.convert_base32_secret_key(). Check that an input base32 encoded string that is not encoded correctly throws the expected exception. """ cut = HOTP() # First be certain the method works with a correct base32-encoded # input string # in_string = "ABCDEFGH" cut.convert_base32_secret_key(in_string) # Then check that it throws the expected exception with an # incorrectly coded input string. # # lower case not acceptable in_string = "abcdefgh" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string) # numbers outside 2-7 not acceptable in_string = "ABCDEFG1" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string) # padding character elsewhere than end of string in_string = "ABCD=FGH" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string)
def test_generate_code_from_time_period_not_numeric(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to non-numeric period. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time(self.secret, period="abc")
def reference_generate_code_from_time(self, secret_key): """Reference implementation of generate_code_from_time method. A reference/alternate implementation of Otp.generate_code_from_time() which is to be used to generate expected values for unit tests. Returns: A tuple containing: * The time-based OTP, as a string of digits. * The integer number of seconds remaining in the current interval. """ import time import datetime from hashlib import sha1 import hmac cut = HOTP() # message := current Unix time ÷ 30 # local_now = datetime.datetime.now() seconds_now = time.mktime(local_now.timetuple()) intervals = seconds_now // 30 remaining_seconds = seconds_now - (intervals * 30) message = cut.num_to_counter(intervals) # hash := HMAC-SHA1(key, message) # hmac = hmac.new(secret_key, message, sha1) hash = hmac.hexdigest() # offset := last nibble of hash # offset = int("0" + hash[-1], 16) offset *= 2 # truncated_hash := hash[offset..offset+3] # (that is 4 bytes starting at the offset) # truncated_hash = hash[offset: offset + (4 * 2)] # Set the first bit of truncated_hash to zero # (remove the most significant bit) # new_high_order_byte = hex( int(truncated_hash[0:2], 16) & int('7F', 16))[2:] new_high_order_byte = \ "0" * (2 - len(new_high_order_byte)) + new_high_order_byte truncated_hash = new_high_order_byte + truncated_hash[2:] # code := truncated_hash mod 1000000 # int_hash = int(truncated_hash, 16) code = int_hash % 1000000 # pad code with 0 until length of code is 6 # code_string = str(code) code_string = "0" * (6 - len(code_string)) + code_string # return code # return code_string, int(30 - remaining_seconds)
def test_generate_code_from_counter_secret_wrong_type(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to wrong secret type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_counter(1.234, self.expected[0][0])
def test_code_from_hash_bad_hash(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(TypeError): cut.code_from_hash("abc")
def test_generate_code_from_time_secret_wrong_type(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to wrong secret type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_time(1.234)
def test_generate_code_from_time_period_wrong_type(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to wrong period type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_time(self.secret, period=(6, 3))
def test_code_from_hash_long_code_length(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(ValueError): cut.code_from_hash(self.expected[0][2], 11)
def reference_generate_code_from_time(self, secret_key): """Reference implementation of generate_code_from_time method. A reference/alternate implementation of Otp.generate_code_from_time() which is to be used to generate expected values for unit tests. Returns: A tuple containing: * The time-based OTP, as a string of digits. * The integer number of seconds remaining in the current interval. """ import time import datetime from hashlib import sha1 import hmac cut = HOTP() # message := current Unix time ÷ 30 # local_now = datetime.datetime.now() seconds_now = time.mktime(local_now.timetuple()) intervals = seconds_now // 30 remaining_seconds = seconds_now - (intervals * 30) message = cut.num_to_counter(intervals) # hash := HMAC-SHA1(key, message) # hmac = hmac.new(secret_key, message, sha1) hash = hmac.hexdigest() # offset := last nibble of hash # offset = int("0" + hash[-1], 16) offset *= 2 # truncated_hash := hash[offset..offset+3] # (that is 4 bytes starting at the offset) # truncated_hash = hash[offset:offset + (4 * 2)] # Set the first bit of truncated_hash to zero # (remove the most significant bit) # new_high_order_byte = hex( int(truncated_hash[0:2], 16) & int('7F', 16))[2:] new_high_order_byte = \ "0" * (2 - len(new_high_order_byte)) + new_high_order_byte truncated_hash = new_high_order_byte + truncated_hash[2:] # code := truncated_hash mod 1000000 # int_hash = int(truncated_hash, 16) code = int_hash % 1000000 # pad code with 0 until length of code is 6 # code_string = str(code) code_string = "0" * (6 - len(code_string)) + code_string # return code # return code_string, int(30 - remaining_seconds)
def test_code_from_hash_bad_hash(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(TypeError): cut.code_from_hash("abc")
def test_code_from_hash_wrong_length(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(ValueError): cut.code_from_hash(bytes.fromhex("abcdef"))
def test_generate_code_from_counter_counter_bad(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to invalid counter value. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter(self.secret, -1)
def test_code_from_hash_long_code_length(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(ValueError): cut.code_from_hash(self.expected[0][2], 11)
def test_generate_code_from_counter_counter_wrong_type(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to wrong counter type. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter(self.secret, "abcdefgh")
def test_generate_code_from_time_secret_wrong_type(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to wrong secret type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_time(1.234)
def test_code_from_hash_wrong_length(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() with self.assertRaises(ValueError): cut.code_from_hash(bytes.fromhex("abcdef"))
def test_generate_code_from_counter_code_length_not_numeric(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to non-numeric code_length. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter( self.secret, self.expected[0][0], code_length="abc")
def test_generate_code_from_counter_code_length_wrong_type(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to wrong code_length type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_counter( self.secret, self.expected[0][0], code_length=(6, 3))
def test_generate_code_from_counter_counter_bad(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to invalid counter value. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter( self.secret, -1)
def test_generate_code_from_counter_counter_wrong_type(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to wrong counter type. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter( self.secret, "abcdefgh")
def test_generate_code_from_counter_secret_wrong_type(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to wrong secret type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_counter( 1.234, self.expected[0][0])
def test_hash_from_hmac_not_20bytes(self): """Test Otp.hash_from_hmac(). Check that HMAC length validation is performed. """ cut = HOTP() hmac = bytes.fromhex("ff0102030405060708090a0b0c0d0e0f101112") with self.assertRaises(ValueError): cut.hash_from_hmac(hmac)
def test_code_from_hash(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() for i in range(0, 10): code = cut.code_from_hash(self.expected[i][2]) self.assertEqual(self.expected[i][3], code)
def test_counter_from_time_period_wrong_type(self): """Test Otp.counter_from_time(). Check that providing a period that is not numeric or convertable to numeric raises the appropriate exception. """ cut = HOTP() with self.assertRaises(TypeError): cut.counter_from_time(period=(6, 3))
def test_hash_from_hmac_not_byte_string(self): """Test Otp.hash_from_hmac(). Check that HMAC type (byte string) validation is performed. """ cut = HOTP() hmac = bytearray.fromhex("ff0102030405060708090a0b0c0d0e0f101112f0") with self.assertRaises(TypeError): cut.hash_from_hmac(hmac)
def test_generate_hmac(self): """Test Otp.generate_hmac(). Check that the RFC4226 test cases work for generate_hmac(). """ cut = HOTP() for i in range(0, 10): hmac = cut.generate_hmac(self.secret, self.expected[i][0]) self.assertEqual(self.expected[i][1], hmac)
def test_generate_code_from_time_period_wrong_type(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to wrong period type. """ cut = HOTP() with self.assertRaises(TypeError): cut.generate_code_from_time( self.secret, period=(6, 3))
def test_generate_hmac(self): """Test Otp.generate_hmac(). Check that the RFC4226 test cases work for generate_hmac(). """ cut = HOTP() for i in range(0, 10): hmac = cut.generate_hmac(self.secret, self.expected[i][0]) self.assertEqual(self.expected[i][1], hmac)
def test_generate_code_from_time_secret_wrong_base32_length(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to secret string that is invalid base32 encoding (too long, bad padding). """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQAAA")
def test_hash_from_hmac_not_20bytes(self): """Test Otp.hash_from_hmac(). Check that HMAC length validation is performed. """ cut = HOTP() hmac = bytes.fromhex("ff0102030405060708090a0b0c0d0e0f101112") with self.assertRaises(ValueError): cut.hash_from_hmac(hmac)
def test_generate_code_from_time_period_not_numeric(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to non-numeric period. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time( self.secret, period="abc")
def test_counter_from_time_period_wrong_type(self): """Test Otp.counter_from_time(). Check that providing a period that is not numeric or convertable to numeric raises the appropriate exception. """ cut = HOTP() with self.assertRaises(TypeError): cut.counter_from_time(period=(6, 3))
def test_code_from_hash(self): """Test Otp.code_from_hash(). Check that the RFC4226 test cases work for code_from_hash() """ cut = HOTP() for i in range(0, 10): code = cut.code_from_hash(self.expected[i][2]) self.assertEqual(self.expected[i][3], code)
def test_hash_from_hmac_not_byte_string(self): """Test Otp.hash_from_hmac(). Check that HMAC type (byte string) validation is performed. """ cut = HOTP() hmac = bytearray.fromhex("ff0102030405060708090a0b0c0d0e0f101112f0") with self.assertRaises(TypeError): cut.hash_from_hmac(hmac)
def test_generate_code_from_time_secret_bad_base32(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to secret string that is invalid base32 encoding. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time("GEZDGNBVGY1TQOJQGEZDGNBVGY1TQOJQ")
def test_convert_base32_bad_chars(self): """Test Otp.convert_base32_secret_key(). Check that an input base32 encoded string that is not encoded correctly throws the expected exception. """ cut = HOTP() # First be certain the method works with a correct base32-encoded # input string # in_string = "ABCDEFGH" cut.convert_base32_secret_key(in_string) # Then check that it throws the expected exception with an # incorrectly coded input string. # # lower case not acceptable in_string = "abcdefgh" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string) # numbers outside 2-7 not acceptable in_string = "ABCDEFG1" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string) # padding character elsewhere than end of string in_string = "ABCD=FGH" with self.assertRaises(ValueError): cut.convert_base32_secret_key(in_string)
def test_generate_hmac_counter_not_byte_string(self): """Test Otp.generate_hmac(). Check that a counter that is other than a byte string. """ cut = HOTP() # If the counter byte string is less than 8 bytes # with self.assertRaises(TypeError): cut.generate_hmac(self.secret, "1234")
def test_num_to_counter_too_large(self): """Test Otp.num_to_counter(). Check that an exception is raised if a very value is passed to num_to_counter(). """ cut = HOTP() num = 2**64 with self.assertRaises(ValueError): cut.num_to_counter(num)
def test_generate_hmac_secret_not_byte_string(self): """Test Otp.generate_hmac(). Check that a counter that is other than a byte string. """ cut = HOTP() # If the counter byte string is less than 8 bytes # with self.assertRaises(TypeError): cut.generate_hmac("1234567890", self.expected[1][0])
def test_generate_code_from_time_secret_bad_base32(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to secret string that is invalid base32 encoding. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time( "GEZDGNBVGY1TQOJQGEZDGNBVGY1TQOJQ")
def test_generate_code_from_counter_counter_long(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to counter byte string that is too short. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter( self.secret, bytes.fromhex("010203040506070809"))
def test_num_to_counter_negative(self): """Test Otp.num_to_counter(). Check that an exception is raised if a negative value is passed to num_to_counter(). """ cut = HOTP() num = -1 with self.assertRaises(ValueError): cut.num_to_counter(num)
def test_generate_code_from_time_secret_wrong_base32_length(self): """Test Otp.generate_code_from_time(). Check for appropriate exception to secret string that is invalid base32 encoding (too long, bad padding). """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_time( "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQAAA")
def test_hash_from_hmac(self): """Test Otp.hash_from_hmac(). Check that expected truncated hash values are produced. """ cut = HOTP() for i in range(0, 10): hash = cut.hash_from_hmac(self.expected[i][1]) self.assertEqual(self.expected[i][2], hash) return
def test_num_to_counter_too_large(self): """Test Otp.num_to_counter(). Check that an exception is raised if a very value is passed to num_to_counter(). """ cut = HOTP() num = 2**64 with self.assertRaises(ValueError): cut.num_to_counter(num)
def test_num_to_counter_not_number(self): """Test Otp.num_to_counter(). Check that an exception is raised if a non-numeric value is passed to num_to_counter(). """ cut = HOTP() num = "abcd" with self.assertRaises(ValueError): cut.num_to_counter(num)
def test_generate_code_from_counter_counter_long(self): """Test Otp.generate_code_from_counter(). Check for appropriate exception to counter byte string that is too short. """ cut = HOTP() with self.assertRaises(ValueError): cut.generate_code_from_counter(self.secret, bytes.fromhex("010203040506070809"))
def test_hmac_from_counter(self): """Test Otp.generate_hmac(). Check that expected HMAC-SHA-1 digest values are produced. """ cut = HOTP() for i in range(0, 10): counter = cut.num_to_counter(i) actual_hmac = cut.generate_hmac(self.secret, counter) self.assertEqual(self.expected[i][1], actual_hmac)
def test_num_to_counter_negative(self): """Test Otp.num_to_counter(). Check that an exception is raised if a negative value is passed to num_to_counter(). """ cut = HOTP() num = -1 with self.assertRaises(ValueError): cut.num_to_counter(num)
def test_hash_from_hmac_clear_high_bit(self): """Test Otp.hash_from_hmac(). Check that the high order bit is cleared in the truncated hash. """ cut = HOTP() hmac = bytes.fromhex("ff0102030405060708090a0b0c0d0e0f101112f0") expected = bytes.fromhex("7f010203") hash = cut.hash_from_hmac(hmac) self.assertEqual(expected, hash)
def test_hmac_from_counter(self): """Test Otp.generate_hmac(). Check that expected HMAC-SHA-1 digest values are produced. """ cut = HOTP() for i in range(0, 10): counter = cut.num_to_counter(i) actual_hmac = cut.generate_hmac(self.secret, counter) self.assertEqual(self.expected[i][1], actual_hmac)
def test_hash_from_hmac_clear_high_bit(self): """Test Otp.hash_from_hmac(). Check that the high order bit is cleared in the truncated hash. """ cut = HOTP() hmac = bytes.fromhex("ff0102030405060708090a0b0c0d0e0f101112f0") expected = bytes.fromhex("7f010203") hash = cut.hash_from_hmac(hmac) self.assertEqual(expected, hash)
def test_generate_hmac_counter_not_byte_string(self): """Test Otp.generate_hmac(). Check that a counter that is other than a byte string. """ cut = HOTP() # If the counter byte string is less than 8 bytes # with self.assertRaises(TypeError): cut.generate_hmac(self.secret, "1234")
def test_generate_hmac_secret_not_byte_string(self): """Test Otp.generate_hmac(). Check that a counter that is other than a byte string. """ cut = HOTP() # If the counter byte string is less than 8 bytes # with self.assertRaises(TypeError): cut.generate_hmac("1234567890", self.expected[1][0])
def test_hash_from_hmac(self): """Test Otp.hash_from_hmac(). Check that expected truncated hash values are produced. """ cut = HOTP() for i in range(0, 10): hash = cut.hash_from_hmac(self.expected[i][1]) self.assertEqual(self.expected[i][2], hash) return
def test_num_to_counter_not_number(self): """Test Otp.num_to_counter(). Check that an exception is raised if a non-numeric value is passed to num_to_counter(). """ cut = HOTP() num = "abcd" with self.assertRaises(ValueError): cut.num_to_counter(num)