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 #9209

Closed
wants to merge 1 commit into from

Conversation

facutuesca
Copy link
Contributor

This PR adds support for ChaCha20 with LibreSSL. Since cryptography's ChaCha20 uses OpenSSL's implementation (which uses a 64 bit counter + 64 bit nonce), and LibreSSL does the same, it is straightforward to use LibreSSL's API.

The only complication is that the current _CipherContext implementation assumes that the underlying cipher can be accessed through the EVP_CIPHER API. This is not the case for LibreSSL's ChaCha20, which has a separate CRYPTO_chacha_20() API for it.

In order to solve that, this PR makes _CipherContext an abstract class with two implementations, _CipherContextEVP (which is the previous _CipherContext) and _CipherContextChaCha, which is a simple context that is used only for the LibreSSL+ChaCha20 combination.

This means all of the code that uses _CipherContext remains the same, and we only have a single branching point isolated in the create_cipher_context() function, which selects the correct context depending on the cipher+backend.

It also leaves the option open in the future if we want to add BoringSSL's ChaCha20, since we can reuse the same _CipherContextChaCha for it (the API is almost the same, and we can deal with the differences in a C wrapper)

src/_cffi_src/openssl/chacha.py Show resolved Hide resolved
@@ -226,6 +229,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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (self._lib.CRYPTOGRAPHY_IS_LIBRESSL) and isinstance(
if self._lib.CRYPTOGRAPHY_IS_LIBRESSL and isinstance(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -14,7 +15,114 @@
from cryptography.hazmat.backends.openssl.backend import Backend


class _CipherContext:
class _CipherContext(metaclass=abc.ABCMeta):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a new interface required? This seems to match our existing public cipher context ABC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an interface for the backend cipher context, which needs a couple of methods not present in the existing public CipherContext ABC:

    @abc.abstractmethod
    def authenticate_additional_data(self, data: bytes) -> None:
        ...

    @abc.abstractmethod
    def finalize_with_tag(self, tag: bytes) -> bytes:
        ...

I think it also makes sense to have separate interfaces for public vs backend ctx, since the public context wraps the backend one:

    def _wrap_ctx(
        self, ctx: _BackendCipherContext, encrypt: bool
    ) -> typing.Union[
        AEADEncryptionContext, AEADDecryptionContext, CipherContext
    ]:
        if isinstance(self.mode, modes.ModeWithAuthenticationTag):
            if encrypt:
                return _AEADEncryptionContext(ctx)
            else:
                return _AEADDecryptionContext(ctx)
        else:
            return _CipherContext(ctx)

total_data_len,
self._backend._ffi.from_buffer(self._cipher.key),
self._backend._ffi.from_buffer(self._iv_nonce),
self._counter,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, shouldn't counter be advancing across multiple calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I missed that completely. I have rewritten the update_into() function as per our discussion

@facutuesca facutuesca force-pushed the chacha20-libressl branch 4 times, most recently from 1e90828 to 6cffe5a Compare August 8, 2023 14:58
@facutuesca
Copy link
Contributor Author

facutuesca commented Aug 8, 2023

The current CI failure is due to not having coverage of these lines (the counter rollover during 64-bit overflow). Once the test vectors from this PR are merged, the coverage should go to 100%

@facutuesca facutuesca requested a review from alex August 8, 2023 17:01
@alex
Copy link
Member

alex commented Aug 11, 2023

Ok. @reaperhulk has the TODO to review that one, once it merges I'll look here.

@facutuesca
Copy link
Contributor Author

Ok. @reaperhulk has the TODO to review that one, once it merges I'll look here.

@alex The test vectors PR was merged and now the coverage for this code is 100%

@alex
Copy link
Member

alex commented Sep 4, 2023

I now realize I'm doing this out of order, but did we ever talk to the LibreSSL maintainers about if they'd be interested in an EVP API for this?

@facutuesca
Copy link
Contributor Author

facutuesca commented Sep 4, 2023

I now realize I'm doing this out of order, but did we ever talk to the LibreSSL maintainers about if they'd be interested in an EVP API for this?

We haven't, I believe, because the main goal for these PRs was to improve BoringSSL feature parity, and this LibreSSL implementation was just a bonus due to the LibreSSL API being the same as BoringSSL's.

My idea was to follow up this PR with the BoringSSL implementation (which is trivial once this LibreSSL PR is merged). I don't know if we're currently interested in implementing the EVP API for ChaCha20 in LibreSSL (@woodruffw thoughts?)

@woodruffw
Copy link
Contributor

I don't know if we're currently interested in implementing the EVP API for ChaCha20 in LibreSSL

I believe that doing that implementation in LibreSSL would be out of scope for us. That being said, we can certainly raise it upstream and see if they'd be interested in exposing ChaCha20 via EVP_CIPHER.

Would you prefer we do that before moving forwards here @alex?

@botovq
Copy link
Contributor

botovq commented Oct 19, 2023

I must be missing something. EVP_chacha20 has existed in LibreSSL since forever (it was committed on May 1, 2014, not even a month into the fork's existence). Regrettably, we adjusted it to be compatible with OpenSSL for OpenSSH to be able to use the OpenSSL 1.1 API in early 2020, even if that is wrong per spec.

@facutuesca
Copy link
Contributor Author

I must be missing something. EVP_chacha20 has existed in LibreSSL since forever (it was committed on May 1, 2014, not even a month into the fork's existence).

Thanks for the clarification, I now see why I thought the EVP API was not available in LibreSSL: Turns out that cryptography uses EVP_get_cipherbyname() to get the EVP cipher using the corresponding name. When I tried this with LibreSSL, it seemed as if the cipher was not available.

However, the error was because the name for ChaCha20 in OpenSSL is different from LibreSSL. OpenSSL uses chacha20 and ChaCha20:

#define SN_chacha20             "ChaCha20"
#define LN_chacha20             "chacha20"
#define NID_chacha20            1019

Whereas LibresSSL uses chacha and ChaCha:

# ChaCha Stream Cipher
!Cname chacha20
			: ChaCha		: chacha

I'll close this PR and open a new one that conditionally changes the name according to the backend used.
@botovq Do you know why the names don't match?

cc @woodruffw

@botovq
Copy link
Contributor

botovq commented Oct 23, 2023 via email

@facutuesca
Copy link
Contributor Author

Closing in favor of #9758

@facutuesca facutuesca closed this Oct 24, 2023
@facutuesca facutuesca deleted the chacha20-libressl branch October 24, 2023 12:27
botovq pushed a commit to libressl/openbsd that referenced this pull request Oct 24, 2023
OpenSSL has the 20 in the long and short names, so add aliases to the
existing names to make things work. In particular, EVP_get_cipherbyname()
will now return EVP_chacha20() for both 'ChaCha20' and 'chacha20'.

Found by Facundo Tuesca when trying to add LibreSSL support for ChaCha20 in
pyca/cryptography#9209

ok jsing
bob-beck pushed a commit to openbsd/src that referenced this pull request Oct 24, 2023
OpenSSL has the 20 in the long and short names, so add aliases to the
existing names to make things work. In particular, EVP_get_cipherbyname()
will now return EVP_chacha20() for both 'ChaCha20' and 'chacha20'.

Found by Facundo Tuesca when trying to add LibreSSL support for ChaCha20 in
pyca/cryptography#9209

ok jsing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants