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 binascii
8
9import pytest
10
11from cryptography.exceptions import AlreadyFinalized, _Reasons
12from cryptography.hazmat.backends.interfaces import CipherBackend
13from cryptography.hazmat.primitives.ciphers import (
14    Cipher,
15    algorithms,
16    base,
17    modes,
18)
19
20from .utils import (
21    generate_aead_exception_test,
22    generate_aead_tag_exception_test,
23)
24from ...doubles import DummyCipherAlgorithm, DummyMode
25from ...utils import raises_unsupported_algorithm
26
27
28@pytest.mark.requires_backend_interface(interface=CipherBackend)
29class TestCipher(object):
30    def test_creates_encryptor(self, backend):
31        cipher = Cipher(
32            algorithms.AES(binascii.unhexlify(b"0" * 32)),
33            modes.CBC(binascii.unhexlify(b"0" * 32)),
34            backend,
35        )
36        assert isinstance(cipher.encryptor(), base.CipherContext)
37
38    def test_creates_decryptor(self, backend):
39        cipher = Cipher(
40            algorithms.AES(binascii.unhexlify(b"0" * 32)),
41            modes.CBC(binascii.unhexlify(b"0" * 32)),
42            backend,
43        )
44        assert isinstance(cipher.decryptor(), base.CipherContext)
45
46    def test_instantiate_with_non_algorithm(self, backend):
47        algorithm = object()
48        with pytest.raises(TypeError):
49            Cipher(algorithm, mode=None, backend=backend)
50
51
52@pytest.mark.requires_backend_interface(interface=CipherBackend)
53class TestCipherContext(object):
54    def test_use_after_finalize(self, backend):
55        cipher = Cipher(
56            algorithms.AES(binascii.unhexlify(b"0" * 32)),
57            modes.CBC(binascii.unhexlify(b"0" * 32)),
58            backend,
59        )
60        encryptor = cipher.encryptor()
61        encryptor.update(b"a" * 16)
62        encryptor.finalize()
63        with pytest.raises(AlreadyFinalized):
64            encryptor.update(b"b" * 16)
65        with pytest.raises(AlreadyFinalized):
66            encryptor.finalize()
67        decryptor = cipher.decryptor()
68        decryptor.update(b"a" * 16)
69        decryptor.finalize()
70        with pytest.raises(AlreadyFinalized):
71            decryptor.update(b"b" * 16)
72        with pytest.raises(AlreadyFinalized):
73            decryptor.finalize()
74
75    def test_use_update_into_after_finalize(self, backend):
76        cipher = Cipher(
77            algorithms.AES(binascii.unhexlify(b"0" * 32)),
78            modes.CBC(binascii.unhexlify(b"0" * 32)),
79            backend,
80        )
81        encryptor = cipher.encryptor()
82        encryptor.update(b"a" * 16)
83        encryptor.finalize()
84        with pytest.raises(AlreadyFinalized):
85            buf = bytearray(31)
86            encryptor.update_into(b"b" * 16, buf)
87
88    def test_unaligned_block_encryption(self, backend):
89        cipher = Cipher(
90            algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.ECB(), backend
91        )
92        encryptor = cipher.encryptor()
93        ct = encryptor.update(b"a" * 15)
94        assert ct == b""
95        ct += encryptor.update(b"a" * 65)
96        assert len(ct) == 80
97        ct += encryptor.finalize()
98        decryptor = cipher.decryptor()
99        pt = decryptor.update(ct[:3])
100        assert pt == b""
101        pt += decryptor.update(ct[3:])
102        assert len(pt) == 80
103        assert pt == b"a" * 80
104        decryptor.finalize()
105
106    @pytest.mark.parametrize("mode", [DummyMode(), None])
107    def test_nonexistent_cipher(self, backend, mode):
108        cipher = Cipher(DummyCipherAlgorithm(), mode, backend)
109        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER):
110            cipher.encryptor()
111
112        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER):
113            cipher.decryptor()
114
115    def test_incorrectly_padded(self, backend):
116        cipher = Cipher(
117            algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16), backend
118        )
119        encryptor = cipher.encryptor()
120        encryptor.update(b"1")
121        with pytest.raises(ValueError):
122            encryptor.finalize()
123
124        decryptor = cipher.decryptor()
125        decryptor.update(b"1")
126        with pytest.raises(ValueError):
127            decryptor.finalize()
128
129
130@pytest.mark.supported(
131    only_if=lambda backend: backend.cipher_supported(
132        algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12)
133    ),
134    skip_message="Does not support AES GCM",
135)
136@pytest.mark.requires_backend_interface(interface=CipherBackend)
137class TestAEADCipherContext(object):
138    test_aead_exceptions = generate_aead_exception_test(
139        algorithms.AES,
140        modes.GCM,
141    )
142    test_aead_tag_exceptions = generate_aead_tag_exception_test(
143        algorithms.AES,
144        modes.GCM,
145    )
146
147
148@pytest.mark.requires_backend_interface(interface=CipherBackend)
149class TestModeValidation(object):
150    def test_cbc(self, backend):
151        with pytest.raises(ValueError):
152            Cipher(
153                algorithms.AES(b"\x00" * 16),
154                modes.CBC(b"abc"),
155                backend,
156            )
157
158    def test_ofb(self, backend):
159        with pytest.raises(ValueError):
160            Cipher(
161                algorithms.AES(b"\x00" * 16),
162                modes.OFB(b"abc"),
163                backend,
164            )
165
166    def test_cfb(self, backend):
167        with pytest.raises(ValueError):
168            Cipher(
169                algorithms.AES(b"\x00" * 16),
170                modes.CFB(b"abc"),
171                backend,
172            )
173
174    def test_cfb8(self, backend):
175        with pytest.raises(ValueError):
176            Cipher(
177                algorithms.AES(b"\x00" * 16),
178                modes.CFB8(b"abc"),
179                backend,
180            )
181
182    def test_ctr(self, backend):
183        with pytest.raises(ValueError):
184            Cipher(
185                algorithms.AES(b"\x00" * 16),
186                modes.CTR(b"abc"),
187                backend,
188            )
189
190    def test_gcm(self):
191        with pytest.raises(ValueError):
192            modes.GCM(b"")
193
194
195class TestModesRequireBytes(object):
196    def test_cbc(self):
197        with pytest.raises(TypeError):
198            modes.CBC([1] * 16)
199
200    def test_cfb(self):
201        with pytest.raises(TypeError):
202            modes.CFB([1] * 16)
203
204    def test_cfb8(self):
205        with pytest.raises(TypeError):
206            modes.CFB8([1] * 16)
207
208    def test_ofb(self):
209        with pytest.raises(TypeError):
210            modes.OFB([1] * 16)
211
212    def test_ctr(self):
213        with pytest.raises(TypeError):
214            modes.CTR([1] * 16)
215
216    def test_gcm_iv(self):
217        with pytest.raises(TypeError):
218            modes.GCM([1] * 16)
219
220    def test_gcm_tag(self):
221        with pytest.raises(TypeError):
222            modes.GCM(b"\x00" * 16, [1] * 16)
223