1# This file is dual licensed under the terms of the Apache License, Version 2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 3# for complete details. 4 5from __future__ import absolute_import, division, print_function 6 7import pytest 8 9from cryptography.exceptions import _Reasons 10from cryptography.hazmat.backends.interfaces import HMACBackend 11from cryptography.hazmat.primitives import hashes 12from cryptography.hazmat.primitives.twofactor import InvalidToken 13from cryptography.hazmat.primitives.twofactor.totp import TOTP 14 15from ....utils import ( 16 load_nist_vectors, 17 load_vectors_from_file, 18 raises_unsupported_algorithm, 19) 20 21vectors = load_vectors_from_file("twofactor/rfc-6238.txt", load_nist_vectors) 22 23 24@pytest.mark.requires_backend_interface(interface=HMACBackend) 25class TestTOTP(object): 26 @pytest.mark.supported( 27 only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), 28 skip_message="Does not support HMAC-SHA1.", 29 ) 30 @pytest.mark.parametrize( 31 "params", [i for i in vectors if i["mode"] == b"SHA1"] 32 ) 33 def test_generate_sha1(self, backend, params): 34 secret = params["secret"] 35 time = int(params["time"]) 36 totp_value = params["totp"] 37 38 totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) 39 assert totp.generate(time) == totp_value 40 41 @pytest.mark.supported( 42 only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), 43 skip_message="Does not support HMAC-SHA256.", 44 ) 45 @pytest.mark.parametrize( 46 "params", [i for i in vectors if i["mode"] == b"SHA256"] 47 ) 48 def test_generate_sha256(self, backend, params): 49 secret = params["secret"] 50 time = int(params["time"]) 51 totp_value = params["totp"] 52 53 totp = TOTP(secret, 8, hashes.SHA256(), 30, backend) 54 assert totp.generate(time) == totp_value 55 56 @pytest.mark.supported( 57 only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), 58 skip_message="Does not support HMAC-SHA512.", 59 ) 60 @pytest.mark.parametrize( 61 "params", [i for i in vectors if i["mode"] == b"SHA512"] 62 ) 63 def test_generate_sha512(self, backend, params): 64 secret = params["secret"] 65 time = int(params["time"]) 66 totp_value = params["totp"] 67 68 totp = TOTP(secret, 8, hashes.SHA512(), 30, backend) 69 assert totp.generate(time) == totp_value 70 71 @pytest.mark.supported( 72 only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), 73 skip_message="Does not support HMAC-SHA1.", 74 ) 75 @pytest.mark.parametrize( 76 "params", [i for i in vectors if i["mode"] == b"SHA1"] 77 ) 78 def test_verify_sha1(self, backend, params): 79 secret = params["secret"] 80 time = int(params["time"]) 81 totp_value = params["totp"] 82 83 totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) 84 85 assert totp.verify(totp_value, time) is None 86 87 @pytest.mark.supported( 88 only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), 89 skip_message="Does not support HMAC-SHA256.", 90 ) 91 @pytest.mark.parametrize( 92 "params", [i for i in vectors if i["mode"] == b"SHA256"] 93 ) 94 def test_verify_sha256(self, backend, params): 95 secret = params["secret"] 96 time = int(params["time"]) 97 totp_value = params["totp"] 98 99 totp = TOTP(secret, 8, hashes.SHA256(), 30, backend) 100 101 assert totp.verify(totp_value, time) is None 102 103 @pytest.mark.supported( 104 only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), 105 skip_message="Does not support HMAC-SHA512.", 106 ) 107 @pytest.mark.parametrize( 108 "params", [i for i in vectors if i["mode"] == b"SHA512"] 109 ) 110 def test_verify_sha512(self, backend, params): 111 secret = params["secret"] 112 time = int(params["time"]) 113 totp_value = params["totp"] 114 115 totp = TOTP(secret, 8, hashes.SHA512(), 30, backend) 116 117 assert totp.verify(totp_value, time) is None 118 119 def test_invalid_verify(self, backend): 120 secret = b"12345678901234567890" 121 time = 59 122 123 totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) 124 125 with pytest.raises(InvalidToken): 126 totp.verify(b"12345678", time) 127 128 def test_floating_point_time_generate(self, backend): 129 secret = b"12345678901234567890" 130 time = 59.1 131 132 totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) 133 134 assert totp.generate(time) == b"94287082" 135 136 def test_get_provisioning_uri(self, backend): 137 secret = b"12345678901234567890" 138 totp = TOTP(secret, 6, hashes.SHA1(), 30, backend=backend) 139 140 assert totp.get_provisioning_uri("Alice Smith", None) == ( 141 "otpauth://totp/Alice%20Smith?digits=6&secret=GEZDGNBVG" 142 "Y3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&period=30" 143 ) 144 145 assert totp.get_provisioning_uri("Alice Smith", "World") == ( 146 "otpauth://totp/World:Alice%20Smith?digits=6&secret=GEZ" 147 "DGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=World" 148 "&period=30" 149 ) 150 151 def test_buffer_protocol(self, backend): 152 key = bytearray(b"a long key with lots of entropy goes here") 153 totp = TOTP(key, 8, hashes.SHA512(), 30, backend) 154 time = 60 155 assert totp.generate(time) == b"53049576" 156 157 158def test_invalid_backend(): 159 secret = b"12345678901234567890" 160 161 pretend_backend = object() 162 163 with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): 164 TOTP(secret, 8, hashes.SHA1(), 30, pretend_backend) 165