aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2019-01-20 15:02:59 -0600
committerAlex Gaynor <alex.gaynor@gmail.com>2019-01-20 15:02:59 -0600
commita07b1f5463361570c3248c1096ffd8b3bff0bfa5 (patch)
tree66bc3e076557579ad062dea6a08a716519857b11
parent5fe88ea0500c6e418492f4b166c0d4a24e9632cc (diff)
downloadplatform_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.rst2
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst16
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst22
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py11
-rw-r--r--src/cryptography/hazmat/backends/openssl/ec.py57
-rw-r--r--src/cryptography/hazmat/primitives/serialization/base.py3
-rw-r--r--tests/hazmat/primitives/test_dh.py23
-rw-r--r--tests/hazmat/primitives/test_dsa.py22
-rw-r--r--tests/hazmat/primitives/test_ec.py59
-rw-r--r--tests/hazmat/primitives/test_rsa.py25
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)