From 871e97a89f0276e57c01f7692111fca42e819b59 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 27 Feb 2019 20:43:55 +0800 Subject: ed448 support (#4610) * ed448 support * move the changelog entry * flake8 --- tests/hazmat/primitives/test_ed448.py | 242 ++++++++++++++++++++++++++ tests/hazmat/primitives/test_serialization.py | 64 +++++++ 2 files changed, 306 insertions(+) create mode 100644 tests/hazmat/primitives/test_ed448.py (limited to 'tests/hazmat/primitives') diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py new file mode 100644 index 00000000..28d92c70 --- /dev/null +++ b/tests/hazmat/primitives/test_ed448.py @@ -0,0 +1,242 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.exceptions import InvalidSignature, _Reasons +from cryptography.hazmat.backends.interfaces import DHBackend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed448 import ( + Ed448PrivateKey, Ed448PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.ed448_supported(), + skip_message="Requires OpenSSL without Ed448 support" +) +@pytest.mark.requires_backend_interface(interface=DHBackend) +def test_ed448_unsupported(backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PublicKey.from_public_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.from_private_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestEd448Signing(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_sign_input(self, vector, backend): + if vector.get("context") is not None: + pytest.skip("ed448 contexts are not currently supported") + + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key.verify(signature, message) + + def test_invalid_signature(self, backend): + key = Ed448PrivateKey.generate() + signature = key.sign(b"test data") + with pytest.raises(InvalidSignature): + key.public_key().verify(signature, b"wrong data") + + with pytest.raises(InvalidSignature): + key.public_key().verify(b"0" * 64, b"test data") + + def test_generate(self, backend): + key = Ed448PrivateKey.generate() + assert key + assert key.public_key() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_pub_priv_bytes_raw(self, vector, backend): + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == sk + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key = Ed448PublicKey.from_public_bytes(pk) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = Ed448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, Ed448PrivateKey) + + def test_invalid_type_public_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PublicKey.from_public_bytes(object()) + + def test_invalid_type_private_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PrivateKey.from_private_bytes(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 58) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 58) + + def test_invalid_private_bytes(self, backend): + key = Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = Ed448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + def test_buffer_protocol(self, backend): + private_bytes = os.urandom(57) + key = Ed448PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + + def test_malleability(self, backend): + # This is a signature where r > the group order. It should be + # rejected to prevent signature malleability issues. This test can + # be removed when wycheproof grows ed448 vectors + public_bytes = binascii.unhexlify( + "fedb02a658d74990244d9d10cf338e977565cbbda6b24c716829ed6ee1e4f28cf" + "2620c052db8d878f6243bffc22242816c1aaa67d2f3603600" + ) + signature = binascii.unhexlify( + "0cc16ba24d69277f927c1554b0f08a2a711bbdd20b058ccc660d00ca13542a3ce" + "f9e5c44c54ab23a2eb14f947e167b990b080863e28b399380f30db6e54d5d1406" + "d23378ffde11b1fb81b2b438a3b8e8aa7f7f4e1befcc905023fab5a5465053844" + "f04cf0c1b51d84760f869588687f57500" + ) + key = Ed448PublicKey.from_public_bytes(public_bytes) + with pytest.raises(InvalidSignature): + key.verify(signature, b"8") diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 52074bf9..ce3a4943 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -1474,3 +1474,67 @@ class TestX25519Serialization(object): assert public_key.public_bytes( encoding, PublicFormat.SubjectPublicKeyInfo ) == data + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +class TestEd448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["Ed448", "ed448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["Ed448", "ed448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data -- cgit v1.2.3