diff options
author | Philipp Gesang <phg@phi-gamma.net> | 2017-05-02 15:28:33 +0200 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2017-05-02 08:28:33 -0500 |
commit | 2e84daa8e2a3bdcb52750b0589e2ee7ee0fd17ec (patch) | |
tree | f9852f1d16b6fa11f524c46343b179c661bebac9 /src | |
parent | cb94281f5b788f583f5f8a5b689dc9dce321ff8e (diff) | |
download | cryptography-2e84daa8e2a3bdcb52750b0589e2ee7ee0fd17ec.tar.gz cryptography-2e84daa8e2a3bdcb52750b0589e2ee7ee0fd17ec.tar.bz2 cryptography-2e84daa8e2a3bdcb52750b0589e2ee7ee0fd17ec.zip |
postpone GCM authentication tag requirement until finalization (#3421)
* postpone GCM authentication tag requirement until finalization
Add a .finalize_with_tag() variant of the .finalize() function of
the GCM context. At the same time, do not enforce the requirement
of supplying the tag with the mode ctor. This facilitates
streamed decryption when the MAC is appended to the ciphertext
and cannot be efficiently retrieved ahead of decryption.
According to the GCM spec (section 7.2: “Algorithm for the
Authenticated Decryption Function”), the tag itself is not needed
until the ciphertext has been decrypted.
Addresses #3380
Signed-off-by: Philipp Gesang <philipp.gesang@intra2net.com>
* disallow delayed GCM tag passing for legacy OpenSSL
Old versions of Ubuntu supported by Cryptography ship a v1.0.1 of
OpenSSL which is no longer supported by upstream. This library
seems to cause erratic test failures with the delayed GCM tag
functionality which are not reproducible outside the CI.
Unfortunately OpenSSL v1.0.1 does not even document the required
API (``EVP_EncryptInit(3)``) so there is no by-the-book fix.
For backends of version 1.0.1 and earlier, verify the GCM tag
at the same stage as before.
Also, indicate to the user that late passing of GCM tags is
unsupported by throwing ``NotImplementedError`` for these backend
versions if
- the method ``finalize_with_tag()`` is invoked, or
- the mode ctor is called without passing a tag.
Unit tests have been adapted to account for different backend
versions.
Diffstat (limited to 'src')
4 files changed, 64 insertions, 23 deletions
diff --git a/src/cryptography/hazmat/backends/commoncrypto/ciphers.py b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py index b59381cb..85ec9e76 100644 --- a/src/cryptography/hazmat/backends/commoncrypto/ciphers.py +++ b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py @@ -213,11 +213,15 @@ class _GCMCipherContext(object): self._backend._check_cipher_response(res) self._backend._release_cipher_ctx(self._ctx) self._tag = self._backend._ffi.buffer(tag_buf)[:] - if (self._operation == self._backend._lib.kCCDecrypt and - not constant_time.bytes_eq( - self._tag[:len(self._mode.tag)], self._mode.tag - )): - raise InvalidTag + if self._operation == self._backend._lib.kCCDecrypt: + if self._mode.tag is None: + raise ValueError( + "Authentication tag must be provided when decrypting." + ) + if not constant_time.bytes_eq( + self._tag[:len(self._mode.tag)], self._mode.tag + ): + raise InvalidTag return b"" def authenticate_additional_data(self, data): diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index 0e0918af..b6058150 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -78,7 +78,13 @@ class _CipherContext(object): len(iv_nonce), self._backend._ffi.NULL ) self._backend.openssl_assert(res != 0) - if operation == self._DECRYPT: + if operation == self._DECRYPT and \ + self._backend.openssl_version_number() < 0x10002000: + if mode.tag is None: + raise NotImplementedError( + "delayed passing of GCM tag requires OpenSSL >= 1.0.2." + " To use this feature please update OpenSSL" + ) res = self._backend._lib.EVP_CIPHER_CTX_ctrl( ctx, self._backend._lib.EVP_CTRL_GCM_SET_TAG, len(mode.tag), mode.tag @@ -134,6 +140,20 @@ class _CipherContext(object): if isinstance(self._mode, modes.GCM): self.update(b"") + if self._operation == self._DECRYPT and \ + isinstance(self._mode, modes.ModeWithAuthenticationTag) and \ + self._backend.openssl_version_number() >= 0x10002000: + tag = self._mode.tag + if tag is None: + raise ValueError( + "Authentication tag must be provided when decrypting." + ) + res = self._backend._lib.EVP_CIPHER_CTX_ctrl( + self._ctx, self._backend._lib.EVP_CTRL_GCM_SET_TAG, + len(tag), tag + ) + self._backend.openssl_assert(res != 0) + buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes) outlen = self._backend._ffi.new("int *") res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index e9d55a10..9e0d0051 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -76,6 +76,16 @@ class AEADCipherContext(object): @six.add_metaclass(abc.ABCMeta) +class AEADDecryptionContext(object): + @abc.abstractmethod + def finalize_with_tag(self, tag): + """ + Returns the results of processing the final block as bytes and allows + delayed passing of the authentication tag. + """ + + +@six.add_metaclass(abc.ABCMeta) class AEADEncryptionContext(object): @abc.abstractproperty def tag(self): @@ -115,11 +125,6 @@ class Cipher(object): return self._wrap_ctx(ctx, encrypt=True) def decryptor(self): - if isinstance(self.mode, modes.ModeWithAuthenticationTag): - if self.mode.tag is None: - raise ValueError( - "Authentication tag must be provided when decrypting." - ) ctx = self._backend.create_symmetric_decryption_ctx( self.algorithm, self.mode ) @@ -169,6 +174,7 @@ class _CipherContext(object): @utils.register_interface(AEADCipherContext) @utils.register_interface(CipherContext) +@utils.register_interface(AEADDecryptionContext) class _AEADCipherContext(object): def __init__(self, ctx): self._ctx = ctx @@ -214,6 +220,16 @@ class _AEADCipherContext(object): self._ctx = None return data + def finalize_with_tag(self, tag): + if self._ctx._backend.name == "openssl" and \ + self._ctx._backend.openssl_version_number() < 0x10002000: + raise NotImplementedError( + "finalize_with_tag requires OpenSSL >= 1.0.2. To use this " + "method please update OpenSSL" + ) + self._ctx._mode._set_tag(tag) + return self.finalize() + def authenticate_additional_data(self, data): if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index 802e544a..5b28157a 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -161,21 +161,22 @@ class GCM(object): # len(initialization_vector) must in [1, 2 ** 64), but it's impossible # to actually construct a bytes object that large, so we don't check # for it - if min_tag_length < 4: - raise ValueError("min_tag_length must be >= 4") - if tag is not None and len(tag) < min_tag_length: - raise ValueError( - "Authentication tag must be {0} bytes or longer.".format( - min_tag_length) - ) - if not isinstance(initialization_vector, bytes): raise TypeError("initialization_vector must be bytes") - - if tag is not None and not isinstance(tag, bytes): - raise TypeError("tag must be bytes or None") - self._initialization_vector = initialization_vector + self._set_tag(tag, min_tag_length) + + def _set_tag(self, tag, min_tag_length=16): + if tag is not None: + if not isinstance(tag, bytes): + raise TypeError("tag must be bytes or None") + if min_tag_length < 4: + raise ValueError("min_tag_length must be >= 4") + if len(tag) < min_tag_length: + raise ValueError( + "Authentication tag must be {0} bytes or longer.".format( + min_tag_length) + ) self._tag = tag tag = utils.read_only_property("_tag") |