diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2019-01-20 15:02:59 -0600 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2019-01-20 15:02:59 -0600 |
commit | a07b1f5463361570c3248c1096ffd8b3bff0bfa5 (patch) | |
tree | 66bc3e076557579ad062dea6a08a716519857b11 | |
parent | 5fe88ea0500c6e418492f4b166c0d4a24e9632cc (diff) | |
download | platform_external_python_cryptography-a07b1f5463361570c3248c1096ffd8b3bff0bfa5.tar.gz platform_external_python_cryptography-a07b1f5463361570c3248c1096ffd8b3bff0bfa5.tar.bz2 platform_external_python_cryptography-a07b1f5463361570c3248c1096ffd8b3bff0bfa5.zip |
add support for encoding compressed points (#4638)
* add support for encoding compressed points
* review feedback
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/ec.rst | 16 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/serialization.rst | 22 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 11 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/ec.py | 57 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/serialization/base.py | 3 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dh.py | 23 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dsa.py | 22 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_ec.py | 59 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 25 |
10 files changed, 207 insertions, 33 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f5c802e..88e2aaf5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -49,6 +49,8 @@ Changelog additional serialization methods. Calling :meth:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey.public_bytes` with no arguments has been deprecated. +* Added support for encoding compressed and uncompressed points via + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`. .. _v2-4-2: diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index bd618551..a356dcaa 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -668,12 +668,20 @@ Key Interfaces .. method:: public_bytes(encoding, format) - Allows serialization of the key to bytes. Encoding ( - :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + Allows serialization of the key data to bytes. When encoding the public + key the encodings ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`) - are chosen to define the exact serialization. + are chosen to define the exact serialization. When encoding the point + the encoding + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.X962` + should be used with the formats ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.UncompressedPoint` + or + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.CompressedPoint` + ). :param encoding: A value from the :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. @@ -681,7 +689,7 @@ Key Interfaces :param format: A value from the :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. - :return bytes: Serialized key. + :return bytes: Serialized data. .. method:: verify(signature, data, signature_algorithm) diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 4c2e5f2a..87a6372c 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -536,6 +536,20 @@ Serialization Formats A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. + .. attribute:: CompressedPoint + + .. versionadded:: 2.5 + + A compressed elliptic curve public key as defined in ANSI X9.62 section + 4.3.6 (as well as `SEC 1 v2.0`_). + + .. attribute:: UncompressedPoint + + .. versionadded:: 2.5 + + An uncompressed elliptic curve public key as defined in ANSI X9.62 + section 4.3.6 (as well as `SEC 1 v2.0`_). + .. class:: ParameterFormat .. versionadded:: 2.0 @@ -594,6 +608,13 @@ Serialization Encodings A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. + .. attribute:: X962 + + .. versionadded:: 2.5 + + The format used by elliptic curve point encodings. This is a binary + format. + Serialization Encryption Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -626,3 +647,4 @@ Serialization Encryption Types .. _`PKCS3`: https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-3-diffie-hellman-key-agreement-standar.htm +.. _`SEC 1 v2.0`: http://www.secg.org/sec1-v2.pdf diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index ab0daa28..b5232ba0 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1690,6 +1690,10 @@ class Backend(object): "format must be an item from the PrivateFormat enum" ) + # X9.62 encoding is only valid for EC public keys + if encoding is serialization.Encoding.X962: + raise ValueError("X9.62 format is only valid for EC public keys") + # Raw format and encoding are only valid for X25519, Ed25519, X448, and # Ed448 keys. We capture those cases before this method is called so if # we see those enum values here it means the caller has passed them to @@ -1792,6 +1796,13 @@ class Backend(object): if not isinstance(encoding, serialization.Encoding): raise TypeError("encoding must be an item from the Encoding enum") + # Compressed/UncompressedPoint are only valid for EC keys and those + # cases are handled by the ECPublicKey public_bytes method before this + # method is called + if format in (serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint): + raise ValueError("Point formats are not valid for this key type") + # Raw format and encoding are only valid for X25519, Ed25519, X448, and # Ed448 keys. We capture those cases before this method is called so if # we see those enum values here it means the caller has passed them to diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index 852b4918..a8d69bdf 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -275,19 +275,62 @@ class _EllipticCurvePublicKey(object): curve=self._curve ) + def _encode_point(self, format): + if format is serialization.PublicFormat.CompressedPoint: + conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED + else: + assert format is serialization.PublicFormat.UncompressedPoint + conversion = self._backend._lib.POINT_CONVERSION_UNCOMPRESSED + + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + self._backend.openssl_assert(group != self._backend._ffi.NULL) + point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) + self._backend.openssl_assert(point != self._backend._ffi.NULL) + with self._backend._tmp_bn_ctx() as bn_ctx: + buflen = self._backend._lib.EC_POINT_point2oct( + group, point, conversion, self._backend._ffi.NULL, 0, bn_ctx + ) + self._backend.openssl_assert(buflen > 0) + buf = self._backend._ffi.new("char[]", buflen) + res = self._backend._lib.EC_POINT_point2oct( + group, point, conversion, buf, buflen, bn_ctx + ) + self._backend.openssl_assert(buflen == res) + + return self._backend._ffi.buffer(buf)[:] + def public_bytes(self, encoding, format): if format is serialization.PublicFormat.PKCS1: raise ValueError( "EC public keys do not support PKCS1 serialization" ) - return self._backend._public_key_bytes( - encoding, - format, - self, - self._evp_pkey, - None - ) + if ( + encoding is serialization.Encoding.X962 or + format is serialization.PublicFormat.CompressedPoint or + format is serialization.PublicFormat.UncompressedPoint + ): + if ( + encoding is not serialization.Encoding.X962 or + format not in ( + serialization.PublicFormat.CompressedPoint, + serialization.PublicFormat.UncompressedPoint + ) + ): + raise ValueError( + "X962 encoding must be used with CompressedPoint or " + "UncompressedPoint format" + ) + + return self._encode_point(format) + else: + return self._backend._public_key_bytes( + encoding, + format, + self, + self._evp_pkey, + None + ) def verify(self, signature, data, signature_algorithm): _check_signature_algorithm(signature_algorithm) diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 1670fd18..4218ea82 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -41,6 +41,7 @@ class Encoding(Enum): DER = "DER" OpenSSH = "OpenSSH" Raw = "Raw" + X962 = "ANSI X9.62" class PrivateFormat(Enum): @@ -54,6 +55,8 @@ class PublicFormat(Enum): PKCS1 = "Raw PKCS#1" OpenSSH = "OpenSSH" Raw = "Raw" + CompressedPoint = "X9.62 Compressed Point" + UncompressedPoint = "X9.62 Uncompressed Point" class ParameterFormat(Enum): diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index c63e520f..c667cd16 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import binascii +import itertools import os import pytest @@ -430,9 +431,10 @@ class TestDHPrivateKeySerialization(object): (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ] ) - def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): parameters = dh.generate_parameters(2, 512, backend) key = parameters.generate_private_key() with pytest.raises(ValueError): @@ -823,15 +825,26 @@ class TestDHParameterSerialization(object): @pytest.mark.parametrize( ("encoding", "fmt"), [ - (serialization.Encoding.Raw, serialization.PublicFormat.Raw), - (serialization.Encoding.PEM, serialization.PublicFormat.Raw), ( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo ), - ] + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) ) - def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): parameters = dh.generate_parameters(2, 512, backend) key = parameters.generate_private_key().public_key() with pytest.raises(ValueError): diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 5d2f1bd8..efd2239c 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -719,9 +719,10 @@ class TestDSASerialization(object): (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ] ) - def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): key = DSA_KEY_1024.private_key(backend) with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @@ -968,15 +969,26 @@ class TestDSAPEMPublicKeySerialization(object): @pytest.mark.parametrize( ("encoding", "fmt"), [ - (serialization.Encoding.Raw, serialization.PublicFormat.Raw), - (serialization.Encoding.PEM, serialization.PublicFormat.Raw), ( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo ), - ] + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) ) - def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 830d89a0..471ef267 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -711,9 +711,10 @@ class TestECSerialization(object): (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ] ) - def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): @@ -1001,13 +1002,27 @@ class TestEllipticCurvePEMPublicKeySerialization(object): @pytest.mark.parametrize( ("encoding", "fmt"), - [ - (serialization.Encoding.Raw, serialization.PublicFormat.Raw), - (serialization.Encoding.PEM, serialization.PublicFormat.Raw), - (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), - ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + ] + )) + list(itertools.product( + [serialization.Encoding.Raw], + [ + serialization.PublicFormat.SubjectPublicKeyInfo, + serialization.PublicFormat.PKCS1, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint, + ] + )) ) - def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = ec.generate_private_key(ec.SECP256R1(), backend).public_key() with pytest.raises(ValueError): @@ -1121,6 +1136,36 @@ class TestEllipticCurvePEMPublicKeySerialization(object): ec.SECP256R1(), unsupported_type ) + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "EC", "compressed_points.txt"), + load_nist_vectors + ) + ) + def test_serialize_point(self, vector, backend): + curve = { + b"SECP256R1": ec.SECP256R1(), + b"SECP256K1": ec.SECP256K1(), + }[vector["curve"]] + point = binascii.unhexlify(vector["point"]) + key = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) + key2 = ec.EllipticCurvePublicKey.from_encoded_point( + curve, + key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint + ) + ) + assert key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + assert key2.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) class TestECDSAVerification(object): diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 0c25bdbb..65d88f54 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -2067,9 +2067,10 @@ class TestRSAPrivateKeySerialization(object): (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ] ) - def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): key = RSA_KEY_2048.private_key(backend) with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @@ -2303,12 +2304,26 @@ class TestRSAPEMPublicKeySerialization(object): @pytest.mark.parametrize( ("encoding", "fmt"), [ - (serialization.Encoding.Raw, serialization.PublicFormat.Raw), - (serialization.Encoding.PEM, serialization.PublicFormat.Raw), + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), - ] + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) ) - def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): key = RSA_KEY_2048.private_key(backend).public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) |