1# Copyright 2013-2018 Donald Stufft and individual contributors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from __future__ import annotations
15
16from typing import Union, cast
17
18from nacl import exceptions as exc
19from nacl._sodium import ffi, lib
20from nacl.exceptions import ensure
21
22crypto_secretstream_xchacha20poly1305_ABYTES: int = (
23 lib.crypto_secretstream_xchacha20poly1305_abytes()
24)
25crypto_secretstream_xchacha20poly1305_HEADERBYTES: int = (
26 lib.crypto_secretstream_xchacha20poly1305_headerbytes()
27)
28crypto_secretstream_xchacha20poly1305_KEYBYTES: int = (
29 lib.crypto_secretstream_xchacha20poly1305_keybytes()
30)
31crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: int = (
32 lib.crypto_secretstream_xchacha20poly1305_messagebytes_max()
33)
34crypto_secretstream_xchacha20poly1305_STATEBYTES: int = (
35 lib.crypto_secretstream_xchacha20poly1305_statebytes()
36)
37
38
39crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: int = (
40 lib.crypto_secretstream_xchacha20poly1305_tag_message()
41)
42crypto_secretstream_xchacha20poly1305_TAG_PUSH: int = (
43 lib.crypto_secretstream_xchacha20poly1305_tag_push()
44)
45crypto_secretstream_xchacha20poly1305_TAG_REKEY: int = (
46 lib.crypto_secretstream_xchacha20poly1305_tag_rekey()
47)
48crypto_secretstream_xchacha20poly1305_TAG_FINAL: int = (
49 lib.crypto_secretstream_xchacha20poly1305_tag_final()
50)
51
52
53def crypto_secretstream_xchacha20poly1305_keygen() -> bytes:
54 """
55 Generate a key for use with
56 :func:`.crypto_secretstream_xchacha20poly1305_init_push`.
57
58 """
59 keybuf = ffi.new(
60 "unsigned char[]",
61 crypto_secretstream_xchacha20poly1305_KEYBYTES,
62 )
63 lib.crypto_secretstream_xchacha20poly1305_keygen(keybuf)
64 return ffi.buffer(keybuf)[:]
65
66
67class crypto_secretstream_xchacha20poly1305_state:
68 """
69 An object wrapping the crypto_secretstream_xchacha20poly1305 state.
70
71 """
72
73 __slots__ = ["rawbuf", "statebuf", "tagbuf"]
74
75 def __init__(self) -> None:
76 """Initialize a clean state object."""
77 ByteString = Union[bytes, bytearray, memoryview]
78 self.statebuf: ByteString = ffi.new(
79 "unsigned char[]",
80 crypto_secretstream_xchacha20poly1305_STATEBYTES,
81 )
82
83 self.rawbuf: ByteString | None = None
84 self.tagbuf: ByteString | None = None
85
86
87def crypto_secretstream_xchacha20poly1305_init_push(
88 state: crypto_secretstream_xchacha20poly1305_state, key: bytes
89) -> bytes:
90 """
91 Initialize a crypto_secretstream_xchacha20poly1305 encryption buffer.
92
93 :param state: a secretstream state object
94 :type state: crypto_secretstream_xchacha20poly1305_state
95 :param key: must be
96 :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
97 :type key: bytes
98 :return: header
99 :rtype: bytes
100
101 """
102 ensure(
103 isinstance(state, crypto_secretstream_xchacha20poly1305_state),
104 "State must be a crypto_secretstream_xchacha20poly1305_state object",
105 raising=exc.TypeError,
106 )
107 ensure(
108 isinstance(key, bytes),
109 "Key must be a bytes sequence",
110 raising=exc.TypeError,
111 )
112 ensure(
113 len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
114 "Invalid key length",
115 raising=exc.ValueError,
116 )
117
118 headerbuf = ffi.new(
119 "unsigned char []",
120 crypto_secretstream_xchacha20poly1305_HEADERBYTES,
121 )
122
123 rc = lib.crypto_secretstream_xchacha20poly1305_init_push(
124 state.statebuf, headerbuf, key
125 )
126 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
127
128 return ffi.buffer(headerbuf)[:]
129
130
131def crypto_secretstream_xchacha20poly1305_push(
132 state: crypto_secretstream_xchacha20poly1305_state,
133 m: bytes,
134 ad: bytes | None = None,
135 tag: int = crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
136) -> bytes:
137 """
138 Add an encrypted message to the secret stream.
139
140 :param state: a secretstream state object
141 :type state: crypto_secretstream_xchacha20poly1305_state
142 :param m: the message to encrypt, the maximum length of an individual
143 message is
144 :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX`.
145 :type m: bytes
146 :param ad: additional data to include in the authentication tag
147 :type ad: bytes or None
148 :param tag: the message tag, usually
149 :data:`.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE` or
150 :data:`.crypto_secretstream_xchacha20poly1305_TAG_FINAL`.
151 :type tag: int
152 :return: ciphertext
153 :rtype: bytes
154
155 """
156 ensure(
157 isinstance(state, crypto_secretstream_xchacha20poly1305_state),
158 "State must be a crypto_secretstream_xchacha20poly1305_state object",
159 raising=exc.TypeError,
160 )
161 ensure(isinstance(m, bytes), "Message is not bytes", raising=exc.TypeError)
162 ensure(
163 len(m) <= crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
164 "Message is too long",
165 raising=exc.ValueError,
166 )
167 ensure(
168 ad is None or isinstance(ad, bytes),
169 "Additional data must be bytes or None",
170 raising=exc.TypeError,
171 )
172
173 clen = len(m) + crypto_secretstream_xchacha20poly1305_ABYTES
174 if state.rawbuf is None or len(state.rawbuf) < clen:
175 state.rawbuf = ffi.new("unsigned char[]", clen)
176
177 if ad is None:
178 ad = ffi.NULL
179 adlen = 0
180 else:
181 adlen = len(ad)
182
183 rc = lib.crypto_secretstream_xchacha20poly1305_push(
184 state.statebuf,
185 state.rawbuf,
186 ffi.NULL,
187 m,
188 len(m),
189 ad,
190 adlen,
191 tag,
192 )
193 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
194
195 return ffi.buffer(state.rawbuf, clen)[:]
196
197
198def crypto_secretstream_xchacha20poly1305_init_pull(
199 state: crypto_secretstream_xchacha20poly1305_state,
200 header: bytes,
201 key: bytes,
202) -> None:
203 """
204 Initialize a crypto_secretstream_xchacha20poly1305 decryption buffer.
205
206 :param state: a secretstream state object
207 :type state: crypto_secretstream_xchacha20poly1305_state
208 :param header: must be
209 :data:`.crypto_secretstream_xchacha20poly1305_HEADERBYTES` long
210 :type header: bytes
211 :param key: must be
212 :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
213 :type key: bytes
214
215 """
216 ensure(
217 isinstance(state, crypto_secretstream_xchacha20poly1305_state),
218 "State must be a crypto_secretstream_xchacha20poly1305_state object",
219 raising=exc.TypeError,
220 )
221 ensure(
222 isinstance(header, bytes),
223 "Header must be a bytes sequence",
224 raising=exc.TypeError,
225 )
226 ensure(
227 len(header) == crypto_secretstream_xchacha20poly1305_HEADERBYTES,
228 "Invalid header length",
229 raising=exc.ValueError,
230 )
231 ensure(
232 isinstance(key, bytes),
233 "Key must be a bytes sequence",
234 raising=exc.TypeError,
235 )
236 ensure(
237 len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
238 "Invalid key length",
239 raising=exc.ValueError,
240 )
241
242 if state.tagbuf is None:
243 state.tagbuf = ffi.new("unsigned char *")
244
245 rc = lib.crypto_secretstream_xchacha20poly1305_init_pull(
246 state.statebuf, header, key
247 )
248 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
249
250
251def crypto_secretstream_xchacha20poly1305_pull(
252 state: crypto_secretstream_xchacha20poly1305_state,
253 c: bytes,
254 ad: bytes | None = None,
255) -> tuple[bytes, int]:
256 """
257 Read a decrypted message from the secret stream.
258
259 :param state: a secretstream state object
260 :type state: crypto_secretstream_xchacha20poly1305_state
261 :param c: the ciphertext to decrypt, the maximum length of an individual
262 ciphertext is
263 :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX` +
264 :data:`.crypto_secretstream_xchacha20poly1305_ABYTES`.
265 :type c: bytes
266 :param ad: additional data to include in the authentication tag
267 :type ad: bytes or None
268 :return: (message, tag)
269 :rtype: (bytes, int)
270
271 """
272 ensure(
273 isinstance(state, crypto_secretstream_xchacha20poly1305_state),
274 "State must be a crypto_secretstream_xchacha20poly1305_state object",
275 raising=exc.TypeError,
276 )
277 ensure(
278 state.tagbuf is not None,
279 (
280 "State must be initialized using "
281 "crypto_secretstream_xchacha20poly1305_init_pull"
282 ),
283 raising=exc.ValueError,
284 )
285 ensure(
286 isinstance(c, bytes),
287 "Ciphertext is not bytes",
288 raising=exc.TypeError,
289 )
290 ensure(
291 len(c) >= crypto_secretstream_xchacha20poly1305_ABYTES,
292 "Ciphertext is too short",
293 raising=exc.ValueError,
294 )
295 ensure(
296 len(c)
297 <= (
298 crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX
299 + crypto_secretstream_xchacha20poly1305_ABYTES
300 ),
301 "Ciphertext is too long",
302 raising=exc.ValueError,
303 )
304 ensure(
305 ad is None or isinstance(ad, bytes),
306 "Additional data must be bytes or None",
307 raising=exc.TypeError,
308 )
309
310 mlen = len(c) - crypto_secretstream_xchacha20poly1305_ABYTES
311 if state.rawbuf is None or len(state.rawbuf) < mlen:
312 state.rawbuf = ffi.new("unsigned char[]", mlen)
313
314 if ad is None:
315 ad = ffi.NULL
316 adlen = 0
317 else:
318 adlen = len(ad)
319
320 rc = lib.crypto_secretstream_xchacha20poly1305_pull(
321 state.statebuf,
322 state.rawbuf,
323 ffi.NULL,
324 state.tagbuf,
325 c,
326 len(c),
327 ad,
328 adlen,
329 )
330 ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
331
332 # Cast safety: we `ensure` above that `state.tagbuf is not None`.
333 return (
334 ffi.buffer(state.rawbuf, mlen)[:],
335 int(cast(bytes, state.tagbuf)[0]),
336 )
337
338
339def crypto_secretstream_xchacha20poly1305_rekey(
340 state: crypto_secretstream_xchacha20poly1305_state,
341) -> None:
342 """
343 Explicitly change the encryption key in the stream.
344
345 Normally the stream is re-keyed as needed or an explicit ``tag`` of
346 :data:`.crypto_secretstream_xchacha20poly1305_TAG_REKEY` is added to a
347 message to ensure forward secrecy, but this method can be used instead
348 if the re-keying is controlled without adding the tag.
349
350 :param state: a secretstream state object
351 :type state: crypto_secretstream_xchacha20poly1305_state
352
353 """
354 ensure(
355 isinstance(state, crypto_secretstream_xchacha20poly1305_state),
356 "State must be a crypto_secretstream_xchacha20poly1305_state object",
357 raising=exc.TypeError,
358 )
359 lib.crypto_secretstream_xchacha20poly1305_rekey(state.statebuf)