From ad298934f4b361dc33dfc9359eb627f9a7161729 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Tue, 24 Oct 2023 11:53:55 +0200 Subject: [PATCH] Add support for ChaCha20 in LibreSSL --- CHANGELOG.rst | 4 +++- .../hazmat/backends/openssl/backend.py | 8 ++++++- tests/hazmat/primitives/test_chacha20.py | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3940cb9e4e03..be5ba2070299 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -38,7 +38,9 @@ Changelog :meth:`~cryptography.x509.CertificateRevocationList.next_update`, :meth:`~cryptography.x509.CertificateRevocationList.last_update` in favor of the new timezone-aware variants mentioned above. - +* Added support for + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20` + on LibreSSL. .. _v41-0-4: diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index dd1ca9044937..733c31d47296 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -264,8 +264,14 @@ def _register_default_ciphers(self) -> None: self.register_cipher_adapter( TripleDES, ECB, GetCipherByName("des-ede3") ) + # ChaCha20 uses the Short Name "chacha20" in OpenSSL, but in LibreSSL + # it uses "chacha" self.register_cipher_adapter( - ChaCha20, type(None), GetCipherByName("chacha20") + ChaCha20, + type(None), + GetCipherByName( + "chacha" if self._lib.CRYPTOGRAPHY_IS_LIBRESSL else "chacha20" + ), ) self.register_cipher_adapter(AES, XTS, _get_xts_cipher) for mode_cls in [ECB, CBC, OFB, CFB, CTR]: diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index 314b0aa60666..7c52ad598d3c 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -69,3 +69,24 @@ def test_invalid_nonce(self): def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): algorithms.ChaCha20("0" * 32, b"0" * 16) # type:ignore[arg-type] + + def test_partial_blocks(self, backend): + # Test that partial blocks and counter increments are handled + # correctly. Successive calls to update should return the same + # as if the entire input was passed in a single call: + # update(pt[0:n]) + update(pt[n:m]) + update(pt[m:]) == update(pt) + key = bytearray(os.urandom(32)) + nonce = bytearray(os.urandom(16)) + cipher = Cipher(algorithms.ChaCha20(key, nonce), None, backend) + pt = bytearray(os.urandom(96 * 3)) + + enc_full = cipher.encryptor() + ct_full = enc_full.update(pt) + + enc_partial = cipher.encryptor() + len_partial = len(pt) // 3 + ct_partial_1 = enc_partial.update(pt[:len_partial]) + ct_partial_2 = enc_partial.update(pt[len_partial : len_partial * 2]) + ct_partial_3 = enc_partial.update(pt[len_partial * 2 :]) + + assert ct_full == ct_partial_1 + ct_partial_2 + ct_partial_3