• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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