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
8import typing
9
10from cryptography import utils
11from cryptography.exceptions import AlreadyFinalized
12from cryptography.hazmat.bindings._rust import (
13 PKCS7PaddingContext,
14 PKCS7UnpaddingContext,
15 check_ansix923_padding,
16)
17
18
19class PaddingContext(metaclass=abc.ABCMeta):
20 @abc.abstractmethod
21 def update(self, data: bytes) -> bytes:
22 """
23 Pads the provided bytes and returns any available data as bytes.
24 """
25
26 @abc.abstractmethod
27 def finalize(self) -> bytes:
28 """
29 Finalize the padding, returns bytes.
30 """
31
32
33def _byte_padding_check(block_size: int) -> None:
34 if not (0 <= block_size <= 2040):
35 raise ValueError("block_size must be in range(0, 2041).")
36
37 if block_size % 8 != 0:
38 raise ValueError("block_size must be a multiple of 8.")
39
40
41def _byte_padding_update(
42 buffer_: bytes | None, data: bytes, block_size: int
43) -> tuple[bytes, bytes]:
44 if buffer_ is None:
45 raise AlreadyFinalized("Context was already finalized.")
46
47 utils._check_byteslike("data", data)
48
49 buffer_ += bytes(data)
50
51 finished_blocks = len(buffer_) // (block_size // 8)
52
53 result = buffer_[: finished_blocks * (block_size // 8)]
54 buffer_ = buffer_[finished_blocks * (block_size // 8) :]
55
56 return buffer_, result
57
58
59def _byte_padding_pad(
60 buffer_: bytes | None,
61 block_size: int,
62 paddingfn: typing.Callable[[int], bytes],
63) -> bytes:
64 if buffer_ is None:
65 raise AlreadyFinalized("Context was already finalized.")
66
67 pad_size = block_size // 8 - len(buffer_)
68 return buffer_ + paddingfn(pad_size)
69
70
71def _byte_unpadding_update(
72 buffer_: bytes | None, data: bytes, block_size: int
73) -> tuple[bytes, bytes]:
74 if buffer_ is None:
75 raise AlreadyFinalized("Context was already finalized.")
76
77 utils._check_byteslike("data", data)
78
79 buffer_ += bytes(data)
80
81 finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0)
82
83 result = buffer_[: finished_blocks * (block_size // 8)]
84 buffer_ = buffer_[finished_blocks * (block_size // 8) :]
85
86 return buffer_, result
87
88
89def _byte_unpadding_check(
90 buffer_: bytes | None,
91 block_size: int,
92 checkfn: typing.Callable[[bytes], int],
93) -> bytes:
94 if buffer_ is None:
95 raise AlreadyFinalized("Context was already finalized.")
96
97 if len(buffer_) != block_size // 8:
98 raise ValueError("Invalid padding bytes.")
99
100 valid = checkfn(buffer_)
101
102 if not valid:
103 raise ValueError("Invalid padding bytes.")
104
105 pad_size = buffer_[-1]
106 return buffer_[:-pad_size]
107
108
109class PKCS7:
110 def __init__(self, block_size: int):
111 _byte_padding_check(block_size)
112 self.block_size = block_size
113
114 def padder(self) -> PaddingContext:
115 return PKCS7PaddingContext(self.block_size)
116
117 def unpadder(self) -> PaddingContext:
118 return PKCS7UnpaddingContext(self.block_size)
119
120
121PaddingContext.register(PKCS7PaddingContext)
122PaddingContext.register(PKCS7UnpaddingContext)
123
124
125class ANSIX923:
126 def __init__(self, block_size: int):
127 _byte_padding_check(block_size)
128 self.block_size = block_size
129
130 def padder(self) -> PaddingContext:
131 return _ANSIX923PaddingContext(self.block_size)
132
133 def unpadder(self) -> PaddingContext:
134 return _ANSIX923UnpaddingContext(self.block_size)
135
136
137class _ANSIX923PaddingContext(PaddingContext):
138 _buffer: bytes | None
139
140 def __init__(self, block_size: int):
141 self.block_size = block_size
142 # TODO: more copies than necessary, we should use zero-buffer (#193)
143 self._buffer = b""
144
145 def update(self, data: bytes) -> bytes:
146 self._buffer, result = _byte_padding_update(
147 self._buffer, data, self.block_size
148 )
149 return result
150
151 def _padding(self, size: int) -> bytes:
152 return bytes([0]) * (size - 1) + bytes([size])
153
154 def finalize(self) -> bytes:
155 result = _byte_padding_pad(
156 self._buffer, self.block_size, self._padding
157 )
158 self._buffer = None
159 return result
160
161
162class _ANSIX923UnpaddingContext(PaddingContext):
163 _buffer: bytes | None
164
165 def __init__(self, block_size: int):
166 self.block_size = block_size
167 # TODO: more copies than necessary, we should use zero-buffer (#193)
168 self._buffer = b""
169
170 def update(self, data: bytes) -> bytes:
171 self._buffer, result = _byte_unpadding_update(
172 self._buffer, data, self.block_size
173 )
174 return result
175
176 def finalize(self) -> bytes:
177 result = _byte_unpadding_check(
178 self._buffer,
179 self.block_size,
180 check_ansix923_padding,
181 )
182 self._buffer = None
183 return result