Skip to content

Commit 4137295

Browse files
committed
Migrate ChaCha20Poly1305 AEAD to Rust
1 parent ecf2129 commit 4137295

File tree

4 files changed

+147
-242
lines changed

4 files changed

+147
-242
lines changed

src/cryptography/hazmat/backends/openssl/aead.py

Lines changed: 12 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,24 @@
1414
AESCCM,
1515
AESGCM,
1616
AESOCB3,
17-
ChaCha20Poly1305,
1817
)
1918

20-
_AEADTypes = typing.Union[AESCCM, AESGCM, AESOCB3, ChaCha20Poly1305]
21-
22-
23-
def _is_evp_aead_supported_cipher(
24-
backend: Backend, cipher: _AEADTypes
25-
) -> bool:
26-
"""
27-
Checks whether the given cipher is supported through
28-
EVP_AEAD rather than the normal OpenSSL EVP_CIPHER API.
29-
"""
30-
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
31-
32-
return backend._lib.Cryptography_HAS_EVP_AEAD and isinstance(
33-
cipher, ChaCha20Poly1305
34-
)
19+
_AEADTypes = typing.Union[AESCCM, AESGCM, AESOCB3]
3520

3621

3722
def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool:
38-
if _is_evp_aead_supported_cipher(backend, cipher):
39-
return True
40-
else:
41-
cipher_name = _evp_cipher_cipher_name(cipher)
42-
if backend._fips_enabled and cipher_name not in backend._fips_aead:
43-
return False
44-
return (
45-
backend._lib.EVP_get_cipherbyname(cipher_name) != backend._ffi.NULL
46-
)
23+
cipher_name = _evp_cipher_cipher_name(cipher)
24+
if backend._fips_enabled and cipher_name not in backend._fips_aead:
25+
return False
26+
return backend._lib.EVP_get_cipherbyname(cipher_name) != backend._ffi.NULL
4727

4828

4929
def _aead_create_ctx(
5030
backend: Backend,
5131
cipher: _AEADTypes,
5232
key: bytes,
5333
):
54-
if _is_evp_aead_supported_cipher(backend, cipher):
55-
return _evp_aead_create_ctx(backend, cipher, key)
56-
else:
57-
return _evp_cipher_create_ctx(backend, cipher, key)
34+
return _evp_cipher_create_ctx(backend, cipher, key)
5835

5936

6037
def _encrypt(
@@ -66,14 +43,9 @@ def _encrypt(
6643
tag_length: int,
6744
ctx: typing.Any = None,
6845
) -> bytes:
69-
if _is_evp_aead_supported_cipher(backend, cipher):
70-
return _evp_aead_encrypt(
71-
backend, cipher, nonce, data, associated_data, tag_length, ctx
72-
)
73-
else:
74-
return _evp_cipher_encrypt(
75-
backend, cipher, nonce, data, associated_data, tag_length, ctx
76-
)
46+
return _evp_cipher_encrypt(
47+
backend, cipher, nonce, data, associated_data, tag_length, ctx
48+
)
7749

7850

7951
def _decrypt(
@@ -85,132 +57,10 @@ def _decrypt(
8557
tag_length: int,
8658
ctx: typing.Any = None,
8759
) -> bytes:
88-
if _is_evp_aead_supported_cipher(backend, cipher):
89-
return _evp_aead_decrypt(
90-
backend, cipher, nonce, data, associated_data, tag_length, ctx
91-
)
92-
else:
93-
return _evp_cipher_decrypt(
94-
backend, cipher, nonce, data, associated_data, tag_length, ctx
95-
)
96-
97-
98-
def _evp_aead_create_ctx(
99-
backend: Backend,
100-
cipher: _AEADTypes,
101-
key: bytes,
102-
tag_len: typing.Optional[int] = None,
103-
):
104-
aead_cipher = _evp_aead_get_cipher(backend, cipher)
105-
assert aead_cipher is not None
106-
key_ptr = backend._ffi.from_buffer(key)
107-
tag_len = (
108-
backend._lib.EVP_AEAD_DEFAULT_TAG_LENGTH
109-
if tag_len is None
110-
else tag_len
111-
)
112-
ctx = backend._lib.Cryptography_EVP_AEAD_CTX_new(
113-
aead_cipher, key_ptr, len(key), tag_len
114-
)
115-
backend.openssl_assert(ctx != backend._ffi.NULL)
116-
ctx = backend._ffi.gc(ctx, backend._lib.EVP_AEAD_CTX_free)
117-
return ctx
118-
119-
120-
def _evp_aead_get_cipher(backend: Backend, cipher: _AEADTypes):
121-
from cryptography.hazmat.primitives.ciphers.aead import (
122-
ChaCha20Poly1305,
60+
return _evp_cipher_decrypt(
61+
backend, cipher, nonce, data, associated_data, tag_length, ctx
12362
)
12463

125-
# Currently only ChaCha20-Poly1305 is supported using this API
126-
assert isinstance(cipher, ChaCha20Poly1305)
127-
return backend._lib.EVP_aead_chacha20_poly1305()
128-
129-
130-
def _evp_aead_encrypt(
131-
backend: Backend,
132-
cipher: _AEADTypes,
133-
nonce: bytes,
134-
data: bytes,
135-
associated_data: typing.List[bytes],
136-
tag_length: int,
137-
ctx: typing.Any,
138-
) -> bytes:
139-
assert ctx is not None
140-
141-
aead_cipher = _evp_aead_get_cipher(backend, cipher)
142-
assert aead_cipher is not None
143-
144-
out_len = backend._ffi.new("size_t *")
145-
# max_out_len should be in_len plus the result of
146-
# EVP_AEAD_max_overhead.
147-
max_out_len = len(data) + backend._lib.EVP_AEAD_max_overhead(aead_cipher)
148-
out_buf = backend._ffi.new("uint8_t[]", max_out_len)
149-
data_ptr = backend._ffi.from_buffer(data)
150-
nonce_ptr = backend._ffi.from_buffer(nonce)
151-
aad = b"".join(associated_data)
152-
aad_ptr = backend._ffi.from_buffer(aad)
153-
154-
res = backend._lib.EVP_AEAD_CTX_seal(
155-
ctx,
156-
out_buf,
157-
out_len,
158-
max_out_len,
159-
nonce_ptr,
160-
len(nonce),
161-
data_ptr,
162-
len(data),
163-
aad_ptr,
164-
len(aad),
165-
)
166-
backend.openssl_assert(res == 1)
167-
encrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:]
168-
return encrypted_data
169-
170-
171-
def _evp_aead_decrypt(
172-
backend: Backend,
173-
cipher: _AEADTypes,
174-
nonce: bytes,
175-
data: bytes,
176-
associated_data: typing.List[bytes],
177-
tag_length: int,
178-
ctx: typing.Any,
179-
) -> bytes:
180-
if len(data) < tag_length:
181-
raise InvalidTag
182-
183-
assert ctx is not None
184-
185-
out_len = backend._ffi.new("size_t *")
186-
# max_out_len should at least in_len
187-
max_out_len = len(data)
188-
out_buf = backend._ffi.new("uint8_t[]", max_out_len)
189-
data_ptr = backend._ffi.from_buffer(data)
190-
nonce_ptr = backend._ffi.from_buffer(nonce)
191-
aad = b"".join(associated_data)
192-
aad_ptr = backend._ffi.from_buffer(aad)
193-
194-
res = backend._lib.EVP_AEAD_CTX_open(
195-
ctx,
196-
out_buf,
197-
out_len,
198-
max_out_len,
199-
nonce_ptr,
200-
len(nonce),
201-
data_ptr,
202-
len(data),
203-
aad_ptr,
204-
len(aad),
205-
)
206-
207-
if res == 0:
208-
backend._consume_errors()
209-
raise InvalidTag
210-
211-
decrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:]
212-
return decrypted_data
213-
21464

21565
_ENCRYPT = 1
21666
_DECRYPT = 0
@@ -221,12 +71,9 @@ def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes:
22171
AESCCM,
22272
AESGCM,
22373
AESOCB3,
224-
ChaCha20Poly1305,
22574
)
22675

227-
if isinstance(cipher, ChaCha20Poly1305):
228-
return b"chacha20-poly1305"
229-
elif isinstance(cipher, AESCCM):
76+
if isinstance(cipher, AESCCM):
23077
return f"aes-{len(cipher._key) * 8}-ccm".encode("ascii")
23178
elif isinstance(cipher, AESOCB3):
23279
return f"aes-{len(cipher._key) * 8}-ocb".encode("ascii")

src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44

55
import typing
66

7+
class ChaCha20Poly1305:
8+
def __init__(self, key: bytes) -> None: ...
9+
@staticmethod
10+
def generate_key() -> bytes: ...
11+
def encrypt(
12+
self,
13+
nonce: bytes,
14+
data: bytes,
15+
associated_data: typing.Optional[bytes],
16+
) -> bytes: ...
17+
def decrypt(
18+
self,
19+
nonce: bytes,
20+
data: bytes,
21+
associated_data: typing.Optional[bytes],
22+
) -> bytes: ...
23+
724
class AESSIV:
825
def __init__(self, key: bytes) -> None: ...
926
@staticmethod

src/cryptography/hazmat/primitives/ciphers/aead.py

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from cryptography import exceptions, utils
1111
from cryptography.hazmat.backends.openssl import aead
1212
from cryptography.hazmat.backends.openssl.backend import backend
13-
from cryptography.hazmat.bindings._rust import FixedPool
1413
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
1514

1615
__all__ = [
@@ -21,82 +20,10 @@
2120
"AESSIV",
2221
]
2322

23+
ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305
2424
AESSIV = rust_openssl.aead.AESSIV
2525

2626

27-
class ChaCha20Poly1305:
28-
_MAX_SIZE = 2**31 - 1
29-
30-
def __init__(self, key: bytes):
31-
if not backend.aead_cipher_supported(self):
32-
raise exceptions.UnsupportedAlgorithm(
33-
"ChaCha20Poly1305 is not supported by this version of OpenSSL",
34-
exceptions._Reasons.UNSUPPORTED_CIPHER,
35-
)
36-
utils._check_byteslike("key", key)
37-
38-
if len(key) != 32:
39-
raise ValueError("ChaCha20Poly1305 key must be 32 bytes.")
40-
41-
self._key = key
42-
self._pool = FixedPool(self._create_fn)
43-
44-
@classmethod
45-
def generate_key(cls) -> bytes:
46-
return os.urandom(32)
47-
48-
def _create_fn(self):
49-
return aead._aead_create_ctx(backend, self, self._key)
50-
51-
def encrypt(
52-
self,
53-
nonce: bytes,
54-
data: bytes,
55-
associated_data: typing.Optional[bytes],
56-
) -> bytes:
57-
if associated_data is None:
58-
associated_data = b""
59-
60-
if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
61-
# This is OverflowError to match what cffi would raise
62-
raise OverflowError(
63-
"Data or associated data too long. Max 2**31 - 1 bytes"
64-
)
65-
66-
self._check_params(nonce, data, associated_data)
67-
with self._pool.acquire() as ctx:
68-
return aead._encrypt(
69-
backend, self, nonce, data, [associated_data], 16, ctx
70-
)
71-
72-
def decrypt(
73-
self,
74-
nonce: bytes,
75-
data: bytes,
76-
associated_data: typing.Optional[bytes],
77-
) -> bytes:
78-
if associated_data is None:
79-
associated_data = b""
80-
81-
self._check_params(nonce, data, associated_data)
82-
with self._pool.acquire() as ctx:
83-
return aead._decrypt(
84-
backend, self, nonce, data, [associated_data], 16, ctx
85-
)
86-
87-
def _check_params(
88-
self,
89-
nonce: bytes,
90-
data: bytes,
91-
associated_data: bytes,
92-
) -> None:
93-
utils._check_byteslike("nonce", nonce)
94-
utils._check_byteslike("data", data)
95-
utils._check_byteslike("associated_data", associated_data)
96-
if len(nonce) != 12:
97-
raise ValueError("Nonce must be 12 bytes")
98-
99-
10027
class AESCCM:
10128
_MAX_SIZE = 2**31 - 1
10229

0 commit comments

Comments
 (0)