diff options
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | docs/hazmat/primitives/padding.rst | 45 | ||||
-rw-r--r-- | src/_cffi_src/hazmat_src/padding.c | 24 | ||||
-rw-r--r-- | src/_cffi_src/hazmat_src/padding.h | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/padding.py | 172 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_padding.py | 92 | ||||
-rw-r--r-- | tests/hypothesis/test_padding.py | 13 |
7 files changed, 301 insertions, 48 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aad8d934..63dab998 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changelog .. note:: This version is not yet released and is under active development. +* Added support for padding ANSI X.923 with + :class:`~cryptography.hazmat.primitives.padding.ANSIX923`. * Deprecated support for OpenSSL 0.9.8. Support will be removed in ``cryptography`` 1.4. * Added support for the :class:`~cryptography.x509.PolicyConstraints` diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index a60f5ac8..0b76327e 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -54,6 +54,49 @@ multiple of the block size. provider. +.. class:: ANSIX923(block_size) + + .. versionadded:: 1.3 + + `ANSI X.923`_ padding works by appending ``N-1`` bytes with the value of + ``0`` and a last byte with the value of ``chr(N)``, where ``N`` is the + number of bytes required to make the final block of data the same size as + the block size. A simple example of padding is: + + .. doctest:: + + >>> padder = padding.ANSIX923(128).padder() + >>> padded_data = padder.update(b"11111111111111112222222222") + >>> padded_data + '1111111111111111' + >>> padded_data += padder.finalize() + >>> padded_data + '11111111111111112222222222\x00\x00\x00\x00\x00\x06' + >>> unpadder = padding.ANSIX923(128).unpadder() + >>> data = unpadder.update(padded_data) + >>> data + '1111111111111111' + >>> data + unpadder.finalize() + '11111111111111112222222222' + + :param block_size: The size of the block in bits that the data is being + padded to. + :raises ValueError: Raised if block size is not a multiple of 8 or is not + between 0 and 256. + + .. method:: padder() + + :returns: A padding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + provider + + .. method:: unpadder() + + :returns: An unpadding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + provider. + + .. class:: PaddingContext When calling ``padder()`` or ``unpadder()`` the result will conform to the @@ -82,3 +125,5 @@ multiple of the block size. :raises TypeError: Raised if data is not bytes. :raises ValueError: When trying to remove padding from incorrectly padded data. + +.. _`ANSI X.923`: https://en.wikipedia.org/wiki/Padding_%28cryptography%29#ANSI_X.923 diff --git a/src/_cffi_src/hazmat_src/padding.c b/src/_cffi_src/hazmat_src/padding.c index 570bad9f..1a0c869d 100644 --- a/src/_cffi_src/hazmat_src/padding.c +++ b/src/_cffi_src/hazmat_src/padding.c @@ -37,3 +37,27 @@ uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, /* Now check the low bit to see if it's set */ return (mismatch & 1) == 0; } + +uint8_t Cryptography_check_ansix923_padding(const uint8_t *data, + uint8_t block_len) { + uint8_t i; + uint8_t pad_size = data[block_len - 1]; + uint8_t mismatch = 0; + /* Skip the first one with the pad size */ + for (i = 1; i < block_len; i++) { + unsigned int mask = Cryptography_constant_time_lt(i, pad_size); + uint8_t b = data[block_len - 1 - i]; + mismatch |= (mask & b); + } + + /* Check to make sure the pad_size was within the valid range. */ + mismatch |= ~Cryptography_constant_time_lt(0, pad_size); + mismatch |= Cryptography_constant_time_lt(block_len, pad_size); + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; +} diff --git a/src/_cffi_src/hazmat_src/padding.h b/src/_cffi_src/hazmat_src/padding.h index 4d218b1a..fb023c17 100644 --- a/src/_cffi_src/hazmat_src/padding.h +++ b/src/_cffi_src/hazmat_src/padding.h @@ -3,3 +3,4 @@ // repository for complete details. uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); +uint8_t Cryptography_check_ansix923_padding(const uint8_t *, uint8_t); diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index f6491eb7..77fb8f83 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -28,14 +28,75 @@ class PaddingContext(object): """ -class PKCS7(object): - def __init__(self, block_size): - if not (0 <= block_size < 256): - raise ValueError("block_size must be in range(0, 256).") +def _byte_padding_check(block_size): + if not (0 <= block_size < 256): + raise ValueError("block_size must be in range(0, 256).") + + if block_size % 8 != 0: + raise ValueError("block_size must be a multiple of 8.") + + +def _byte_padding_update(buffer_, data, block_size): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + + buffer_ += data + + finished_blocks = len(buffer_) // (block_size // 8) + + result = buffer_[:finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8):] + + return buffer_, result + + +def _byte_padding_pad(buffer_, block_size, paddingfn): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + pad_size = block_size // 8 - len(buffer_) + return buffer_ + paddingfn(pad_size) + + +def _byte_unpadding_update(buffer_, data, block_size): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") - if block_size % 8 != 0: - raise ValueError("block_size must be a multiple of 8.") + buffer_ += data + finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) + + result = buffer_[:finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8):] + + return buffer_, result + + +def _byte_unpadding_check(buffer_, block_size, checkfn): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if len(buffer_) != block_size // 8: + raise ValueError("Invalid padding bytes.") + + valid = checkfn(buffer_, block_size // 8) + + if not valid: + raise ValueError("Invalid padding bytes.") + + pad_size = six.indexbytes(buffer_, -1) + return buffer_[:-pad_size] + + +class PKCS7(object): + def __init__(self, block_size): + _byte_padding_check(block_size) self.block_size = block_size def padder(self): @@ -53,27 +114,16 @@ class _PKCS7PaddingContext(object): self._buffer = b"" def update(self, data): - if self._buffer is None: - raise AlreadyFinalized("Context was already finalized.") - - if not isinstance(data, bytes): - raise TypeError("data must be bytes.") - - self._buffer += data - - finished_blocks = len(self._buffer) // (self.block_size // 8) - - result = self._buffer[:finished_blocks * (self.block_size // 8)] - self._buffer = self._buffer[finished_blocks * (self.block_size // 8):] - + self._buffer, result = _byte_padding_update( + self._buffer, data, self.block_size) return result - def finalize(self): - if self._buffer is None: - raise AlreadyFinalized("Context was already finalized.") + def _padding(self, size): + return six.int2byte(size) * size - pad_size = self.block_size // 8 - len(self._buffer) - result = self._buffer + six.int2byte(pad_size) * pad_size + def finalize(self): + result = _byte_padding_pad( + self._buffer, self.block_size, self._padding) self._buffer = None return result @@ -86,39 +136,67 @@ class _PKCS7UnpaddingContext(object): self._buffer = b"" def update(self, data): - if self._buffer is None: - raise AlreadyFinalized("Context was already finalized.") + self._buffer, result = _byte_unpadding_update( + self._buffer, data, self.block_size) + return result + + def finalize(self): + result = _byte_unpadding_check( + self._buffer, self.block_size, + lib.Cryptography_check_pkcs7_padding) + self._buffer = None + return result - if not isinstance(data, bytes): - raise TypeError("data must be bytes.") - self._buffer += data +class ANSIX923(object): + def __init__(self, block_size): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self): + return _ANSIX923PaddingContext(self.block_size) + + def unpadder(self): + return _ANSIX923UnpaddingContext(self.block_size) - finished_blocks = max( - len(self._buffer) // (self.block_size // 8) - 1, - 0 - ) - result = self._buffer[:finished_blocks * (self.block_size // 8)] - self._buffer = self._buffer[finished_blocks * (self.block_size // 8):] +@utils.register_interface(PaddingContext) +class _ANSIX923PaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" + def update(self, data): + self._buffer, result = _byte_padding_update( + self._buffer, data, self.block_size) return result + def _padding(self, size): + return six.int2byte(0) * (size - 1) + six.int2byte(size) + def finalize(self): - if self._buffer is None: - raise AlreadyFinalized("Context was already finalized.") + result = _byte_padding_pad( + self._buffer, self.block_size, self._padding) + self._buffer = None + return result - if len(self._buffer) != self.block_size // 8: - raise ValueError("Invalid padding bytes.") - valid = lib.Cryptography_check_pkcs7_padding( - self._buffer, self.block_size // 8 - ) +@utils.register_interface(PaddingContext) +class _ANSIX923UnpaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" - if not valid: - raise ValueError("Invalid padding bytes.") + def update(self, data): + self._buffer, result = _byte_unpadding_update( + self._buffer, data, self.block_size) + return result - pad_size = six.indexbytes(self._buffer, -1) - res = self._buffer[:-pad_size] + def finalize(self): + result = _byte_unpadding_check( + self._buffer, self.block_size, + lib.Cryptography_check_ansix923_padding) self._buffer = None - return res + return result diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index 392ea737..e934c0ac 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -99,3 +99,95 @@ class TestPKCS7(object): unpadder.update(b"") with pytest.raises(AlreadyFinalized): unpadder.finalize() + + +class TestANSIX923(object): + @pytest.mark.parametrize("size", [127, 4096, -2]) + def test_invalid_block_size(self, size): + with pytest.raises(ValueError): + padding.ANSIX923(size) + + @pytest.mark.parametrize(("size", "padded"), [ + (128, b"1111"), + (128, b"1111111111111111"), + (128, b"111111111111111\x06"), + (128, b"1111111111\x06\x06\x06\x06\x06\x06"), + (128, b""), + (128, b"\x06" * 6), + (128, b"\x00" * 16), + ]) + def test_invalid_padding(self, size, padded): + unpadder = padding.ANSIX923(size).unpadder() + with pytest.raises(ValueError): + unpadder.update(padded) + unpadder.finalize() + + def test_non_bytes(self): + padder = padding.ANSIX923(128).padder() + with pytest.raises(TypeError): + padder.update(u"abc") + unpadder = padding.ANSIX923(128).unpadder() + with pytest.raises(TypeError): + unpadder.update(u"abc") + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ( + 128, + b"1" * 16, + b"1" * 16 + b"\x00" * 15 + b"\x10", + ), + ( + 128, + b"1" * 17, + b"1" * 17 + b"\x00" * 14 + b"\x0F", + ) + ]) + def test_pad(self, size, unpadded, padded): + padder = padding.ANSIX923(size).padder() + result = padder.update(unpadded) + result += padder.finalize() + assert result == padded + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ]) + def test_unpad(self, size, unpadded, padded): + unpadder = padding.ANSIX923(size).unpadder() + result = unpadder.update(padded) + result += unpadder.finalize() + assert result == unpadded + + def test_use_after_finalize(self): + padder = padding.ANSIX923(128).padder() + b = padder.finalize() + with pytest.raises(AlreadyFinalized): + padder.update(b"") + with pytest.raises(AlreadyFinalized): + padder.finalize() + + unpadder = padding.ANSIX923(128).unpadder() + unpadder.update(b) + unpadder.finalize() + with pytest.raises(AlreadyFinalized): + unpadder.update(b"") + with pytest.raises(AlreadyFinalized): + unpadder.finalize() diff --git a/tests/hypothesis/test_padding.py b/tests/hypothesis/test_padding.py index 21c9a234..29d726f1 100644 --- a/tests/hypothesis/test_padding.py +++ b/tests/hypothesis/test_padding.py @@ -5,7 +5,7 @@ from hypothesis import given from hypothesis.strategies import binary, integers -from cryptography.hazmat.primitives.padding import PKCS7 +from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 @given(integers(min_value=1, max_value=31), binary()) @@ -19,3 +19,14 @@ def test_pkcs7(block_size, data): padded = padder.update(data) + padder.finalize() assert unpadder.update(padded) + unpadder.finalize() == data + + +@given(integers(min_value=1, max_value=31), binary()) +def test_ansix923(block_size, data): + a = ANSIX923(block_size=block_size * 8) + padder = a.padder() + unpadder = a.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data |