Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ChaCha20 with LibreSSL #9194

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/_cffi_src/build_openssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"asn1",
"bignum",
"bio",
"chacha",
"cmac",
"crypto",
"dh",
Expand Down
40 changes: 40 additions & 0 deletions src/_cffi_src/openssl/chacha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

from __future__ import annotations

INCLUDES = """
#if CRYPTOGRAPHY_IS_LIBRESSL
#include <openssl/chacha.h>
#endif"""

TYPES = """
static const long Cryptography_HAS_CHACHA20_API;
"""

FUNCTIONS = """
void Cryptography_CRYPTO_chacha_20(uint8_t *, const uint8_t *, size_t,
const uint8_t[32], const uint8_t[8],
uint64_t);
"""

CUSTOMIZATIONS = """
#if CRYPTOGRAPHY_IS_LIBRESSL
static const long Cryptography_HAS_CHACHA20_API = 1;
#else
static const long Cryptography_HAS_CHACHA20_API = 0;
#endif

#if CRYPTOGRAPHY_IS_LIBRESSL
void Cryptography_CRYPTO_chacha_20(uint8_t *out, const uint8_t *in,
size_t in_len, const uint8_t key[32],
const uint8_t nonce[8], uint64_t counter) {
CRYPTO_chacha_20(out, in, in_len, key, nonce, counter);
}
#else
void (*Cryptography_CRYPTO_chacha_20)(uint8_t *, const uint8_t *, size_t,
const uint8_t[32], const uint8_t[8],
uint64_t) = NULL;
#endif
"""
16 changes: 13 additions & 3 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.backends.openssl import aead
from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
from cryptography.hazmat.backends.openssl.ciphers import (
_CipherContext,
algorithms,
create_cipher_context,
)
from cryptography.hazmat.backends.openssl.cmac import _CMACContext
from cryptography.hazmat.backends.openssl.rsa import (
_RSAPrivateKey,
Expand Down Expand Up @@ -226,6 +230,12 @@ def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
return self.hash_supported(algorithm)

def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool:
# ChaCha20 is supported in LibreSSL by a different API than OpenSSL,
# so checking for the corresponding EVP_CIPHER is not useful
if (self._lib.CRYPTOGRAPHY_IS_LIBRESSL) and isinstance(
cipher, algorithms.ChaCha20
):
return True
if self._fips_enabled:
# FIPS mode requires AES. TripleDES is disallowed/deprecated in
# FIPS 140-3.
Expand Down Expand Up @@ -319,12 +329,12 @@ def _register_default_ciphers(self) -> None:
def create_symmetric_encryption_ctx(
self, cipher: CipherAlgorithm, mode: Mode
) -> _CipherContext:
return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT)
return create_cipher_context(self, cipher, mode, encrypt=True)

def create_symmetric_decryption_ctx(
self, cipher: CipherAlgorithm, mode: Mode
) -> _CipherContext:
return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT)
return create_cipher_context(self, cipher, mode, encrypt=False)

def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
return self.hmac_supported(algorithm)
Expand Down
89 changes: 88 additions & 1 deletion src/cryptography/hazmat/backends/openssl/ciphers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from __future__ import annotations

import abc
import typing

from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons
Expand All @@ -14,7 +15,93 @@
from cryptography.hazmat.backends.openssl.backend import Backend


class _CipherContext:
class _CipherContext(metaclass=abc.ABCMeta):
@abc.abstractmethod
def update(self, data: bytes) -> bytes:
"""
Processes the provided bytes through the cipher and returns the results
as bytes.
"""

@abc.abstractmethod
def update_into(self, data: bytes, buf: bytes) -> int:
"""
Processes the provided bytes and writes the resulting data into the
provided buffer. Returns the number of bytes written.
"""

@abc.abstractmethod
def finalize(self) -> bytes:
"""
Returns the results of processing the final block as bytes.
"""


def create_cipher_context(
backend: Backend, cipher, mode, encrypt: bool
) -> _CipherContext:
if (
isinstance(cipher, algorithms.ChaCha20)
and backend._lib.Cryptography_HAS_CHACHA20_API
):
return _CipherContextChaCha(backend, cipher)
else:
operation = (
_CipherContextEVP._ENCRYPT
if encrypt
else _CipherContextEVP._DECRYPT
)
return _CipherContextEVP(backend, cipher, mode, operation)


class _CipherContextChaCha(_CipherContext):
"""
Cipher context specific to ChaCha20 under LibreSSL
"""

def __init__(self, backend: Backend, cipher) -> None:
assert isinstance(cipher, algorithms.ChaCha20)
assert backend._lib.Cryptography_HAS_CHACHA20_API
self._backend = backend
self._cipher = cipher

# The ChaCha20 stream cipher. The key length is 256 bits, the IV is
# 128 bits long. The first 64 bits consists of a counter in
# little-endian order followed by a 64 bit nonce.
self._counter = int.from_bytes(cipher.nonce[:8], byteorder="little")
self._iv_nonce = cipher.nonce[8:]

def update(self, data: bytes) -> bytes:
buf = bytearray(len(data))
n = self.update_into(data, buf)
return bytes(buf[:n])

def update_into(self, data: bytes, buf: bytes) -> int:
total_data_len = len(data)
if len(buf) < total_data_len:
raise ValueError(
"buffer must be at least {} bytes for this "
"payload".format(len(data))
)

baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True)
baseinbuf = self._backend._ffi.from_buffer(data)

self._backend._lib.Cryptography_CRYPTO_chacha_20(
baseoutbuf,
baseinbuf,
total_data_len,
self._backend._ffi.from_buffer(self._cipher.key),
self._backend._ffi.from_buffer(self._iv_nonce),
self._counter,
)
return total_data_len

def finalize(self) -> bytes:
return b""


class _CipherContextEVP(_CipherContext):
_ENCRYPT = 1
_DECRYPT = 0
_MAX_CHUNK_SIZE = 2**30 - 1
Expand Down
Loading