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 annotations
6
7import abc
8
9from cryptography import utils
10from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
11from cryptography.hazmat.primitives._cipheralgorithm import (
12 BlockCipherAlgorithm,
13 CipherAlgorithm,
14)
15from cryptography.hazmat.primitives.ciphers import algorithms
16
17
18class Mode(metaclass=abc.ABCMeta):
19 @property
20 @abc.abstractmethod
21 def name(self) -> str:
22 """
23 A string naming this mode (e.g. "ECB", "CBC").
24 """
25
26 @abc.abstractmethod
27 def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
28 """
29 Checks that all the necessary invariants of this (mode, algorithm)
30 combination are met.
31 """
32
33
34class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta):
35 @property
36 @abc.abstractmethod
37 def initialization_vector(self) -> bytes:
38 """
39 The value of the initialization vector for this mode as bytes.
40 """
41
42
43class ModeWithTweak(Mode, metaclass=abc.ABCMeta):
44 @property
45 @abc.abstractmethod
46 def tweak(self) -> bytes:
47 """
48 The value of the tweak for this mode as bytes.
49 """
50
51
52class ModeWithNonce(Mode, metaclass=abc.ABCMeta):
53 @property
54 @abc.abstractmethod
55 def nonce(self) -> bytes:
56 """
57 The value of the nonce for this mode as bytes.
58 """
59
60
61class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta):
62 @property
63 @abc.abstractmethod
64 def tag(self) -> bytes | None:
65 """
66 The value of the tag supplied to the constructor of this mode.
67 """
68
69
70def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None:
71 if algorithm.key_size > 256 and algorithm.name == "AES":
72 raise ValueError(
73 "Only 128, 192, and 256 bit keys are allowed for this AES mode"
74 )
75
76
77def _check_iv_length(
78 self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm
79) -> None:
80 if len(self.initialization_vector) * 8 != algorithm.block_size:
81 raise ValueError(
82 "Invalid IV size ({}) for {}.".format(
83 len(self.initialization_vector), self.name
84 )
85 )
86
87
88def _check_nonce_length(
89 nonce: bytes, name: str, algorithm: CipherAlgorithm
90) -> None:
91 if not isinstance(algorithm, BlockCipherAlgorithm):
92 raise UnsupportedAlgorithm(
93 f"{name} requires a block cipher algorithm",
94 _Reasons.UNSUPPORTED_CIPHER,
95 )
96 if len(nonce) * 8 != algorithm.block_size:
97 raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.")
98
99
100def _check_iv_and_key_length(
101 self: ModeWithInitializationVector, algorithm: CipherAlgorithm
102) -> None:
103 if not isinstance(algorithm, BlockCipherAlgorithm):
104 raise UnsupportedAlgorithm(
105 f"{self} requires a block cipher algorithm",
106 _Reasons.UNSUPPORTED_CIPHER,
107 )
108 _check_aes_key_length(self, algorithm)
109 _check_iv_length(self, algorithm)
110
111
112class CBC(ModeWithInitializationVector):
113 name = "CBC"
114
115 def __init__(self, initialization_vector: bytes):
116 utils._check_byteslike("initialization_vector", initialization_vector)
117 self._initialization_vector = initialization_vector
118
119 @property
120 def initialization_vector(self) -> bytes:
121 return self._initialization_vector
122
123 validate_for_algorithm = _check_iv_and_key_length
124
125
126class XTS(ModeWithTweak):
127 name = "XTS"
128
129 def __init__(self, tweak: bytes):
130 utils._check_byteslike("tweak", tweak)
131
132 if len(tweak) != 16:
133 raise ValueError("tweak must be 128-bits (16 bytes)")
134
135 self._tweak = tweak
136
137 @property
138 def tweak(self) -> bytes:
139 return self._tweak
140
141 def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
142 if isinstance(algorithm, (algorithms.AES128, algorithms.AES256)):
143 raise TypeError(
144 "The AES128 and AES256 classes do not support XTS, please use "
145 "the standard AES class instead."
146 )
147
148 if algorithm.key_size not in (256, 512):
149 raise ValueError(
150 "The XTS specification requires a 256-bit key for AES-128-XTS"
151 " and 512-bit key for AES-256-XTS"
152 )
153
154
155class ECB(Mode):
156 name = "ECB"
157
158 validate_for_algorithm = _check_aes_key_length
159
160
161class OFB(ModeWithInitializationVector):
162 name = "OFB"
163
164 def __init__(self, initialization_vector: bytes):
165 utils._check_byteslike("initialization_vector", initialization_vector)
166 self._initialization_vector = initialization_vector
167
168 @property
169 def initialization_vector(self) -> bytes:
170 return self._initialization_vector
171
172 validate_for_algorithm = _check_iv_and_key_length
173
174
175class CFB(ModeWithInitializationVector):
176 name = "CFB"
177
178 def __init__(self, initialization_vector: bytes):
179 utils._check_byteslike("initialization_vector", initialization_vector)
180 self._initialization_vector = initialization_vector
181
182 @property
183 def initialization_vector(self) -> bytes:
184 return self._initialization_vector
185
186 validate_for_algorithm = _check_iv_and_key_length
187
188
189class CFB8(ModeWithInitializationVector):
190 name = "CFB8"
191
192 def __init__(self, initialization_vector: bytes):
193 utils._check_byteslike("initialization_vector", initialization_vector)
194 self._initialization_vector = initialization_vector
195
196 @property
197 def initialization_vector(self) -> bytes:
198 return self._initialization_vector
199
200 validate_for_algorithm = _check_iv_and_key_length
201
202
203class CTR(ModeWithNonce):
204 name = "CTR"
205
206 def __init__(self, nonce: bytes):
207 utils._check_byteslike("nonce", nonce)
208 self._nonce = nonce
209
210 @property
211 def nonce(self) -> bytes:
212 return self._nonce
213
214 def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
215 _check_aes_key_length(self, algorithm)
216 _check_nonce_length(self.nonce, self.name, algorithm)
217
218
219class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag):
220 name = "GCM"
221 _MAX_ENCRYPTED_BYTES = (2**39 - 256) // 8
222 _MAX_AAD_BYTES = (2**64) // 8
223
224 def __init__(
225 self,
226 initialization_vector: bytes,
227 tag: bytes | None = None,
228 min_tag_length: int = 16,
229 ):
230 # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive
231 # This is a sane limit anyway so we'll enforce it here.
232 utils._check_byteslike("initialization_vector", initialization_vector)
233 if len(initialization_vector) < 8 or len(initialization_vector) > 128:
234 raise ValueError(
235 "initialization_vector must be between 8 and 128 bytes (64 "
236 "and 1024 bits)."
237 )
238 self._initialization_vector = initialization_vector
239 if tag is not None:
240 utils._check_bytes("tag", tag)
241 if min_tag_length < 4:
242 raise ValueError("min_tag_length must be >= 4")
243 if len(tag) < min_tag_length:
244 raise ValueError(
245 "Authentication tag must be {} bytes or longer.".format(
246 min_tag_length
247 )
248 )
249 self._tag = tag
250 self._min_tag_length = min_tag_length
251
252 @property
253 def tag(self) -> bytes | None:
254 return self._tag
255
256 @property
257 def initialization_vector(self) -> bytes:
258 return self._initialization_vector
259
260 def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
261 _check_aes_key_length(self, algorithm)
262 if not isinstance(algorithm, BlockCipherAlgorithm):
263 raise UnsupportedAlgorithm(
264 "GCM requires a block cipher algorithm",
265 _Reasons.UNSUPPORTED_CIPHER,
266 )
267 block_size_bytes = algorithm.block_size // 8
268 if self._tag is not None and len(self._tag) > block_size_bytes:
269 raise ValueError(
270 "Authentication tag cannot be more than {} bytes.".format(
271 block_size_bytes
272 )
273 )