Skip to content

Commit

Permalink
Add support for ChaCha20 with LibreSSL
Browse files Browse the repository at this point in the history
  • Loading branch information
facutuesca committed Jul 7, 2023
1 parent 2d33543 commit fe7787e
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 4 deletions.
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

0 comments on commit fe7787e

Please sign in to comment.