1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import absolute_import, division, print_function
6
7import binascii
8import os
9
10import pytest
11
12from cryptography.exceptions import InvalidSignature, _Reasons
13from cryptography.hazmat.primitives import serialization
14from cryptography.hazmat.primitives.asymmetric.ed25519 import (
15    Ed25519PrivateKey,
16    Ed25519PublicKey,
17)
18
19from ...utils import load_vectors_from_file, raises_unsupported_algorithm
20
21
22def load_ed25519_vectors(vector_data):
23    """
24    djb's ed25519 vectors are structured as a colon delimited array:
25        0: secret key (32 bytes) + public key (32 bytes)
26        1: public key (32 bytes)
27        2: message (0+ bytes)
28        3: signature + message (64+ bytes)
29    """
30    data = []
31    for line in vector_data:
32        secret_key, public_key, message, signature, _ = line.split(":")
33        secret_key = secret_key[0:64]
34        signature = signature[0:128]
35        data.append(
36            {
37                "secret_key": secret_key,
38                "public_key": public_key,
39                "message": message,
40                "signature": signature,
41            }
42        )
43    return data
44
45
46@pytest.mark.supported(
47    only_if=lambda backend: not backend.ed25519_supported(),
48    skip_message="Requires OpenSSL without Ed25519 support",
49)
50def test_ed25519_unsupported(backend):
51    with raises_unsupported_algorithm(
52        _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
53    ):
54        Ed25519PublicKey.from_public_bytes(b"0" * 32)
55
56    with raises_unsupported_algorithm(
57        _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
58    ):
59        Ed25519PrivateKey.from_private_bytes(b"0" * 32)
60
61    with raises_unsupported_algorithm(
62        _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
63    ):
64        Ed25519PrivateKey.generate()
65
66
67@pytest.mark.supported(
68    only_if=lambda backend: backend.ed25519_supported(),
69    skip_message="Requires OpenSSL with Ed25519 support",
70)
71class TestEd25519Signing(object):
72    @pytest.mark.parametrize(
73        "vector",
74        load_vectors_from_file(
75            os.path.join("asymmetric", "Ed25519", "sign.input"),
76            load_ed25519_vectors,
77        ),
78    )
79    def test_sign_verify_input(self, vector, backend):
80        sk = binascii.unhexlify(vector["secret_key"])
81        pk = binascii.unhexlify(vector["public_key"])
82        message = binascii.unhexlify(vector["message"])
83        signature = binascii.unhexlify(vector["signature"])
84        private_key = Ed25519PrivateKey.from_private_bytes(sk)
85        computed_sig = private_key.sign(message)
86        assert computed_sig == signature
87        public_key = private_key.public_key()
88        assert (
89            public_key.public_bytes(
90                serialization.Encoding.Raw, serialization.PublicFormat.Raw
91            )
92            == pk
93        )
94        public_key.verify(signature, message)
95
96    def test_invalid_signature(self, backend):
97        key = Ed25519PrivateKey.generate()
98        signature = key.sign(b"test data")
99        with pytest.raises(InvalidSignature):
100            key.public_key().verify(signature, b"wrong data")
101
102        with pytest.raises(InvalidSignature):
103            key.public_key().verify(b"0" * 64, b"test data")
104
105    def test_generate(self, backend):
106        key = Ed25519PrivateKey.generate()
107        assert key
108        assert key.public_key()
109
110    def test_load_public_bytes(self, backend):
111        public_key = Ed25519PrivateKey.generate().public_key()
112        public_bytes = public_key.public_bytes(
113            serialization.Encoding.Raw, serialization.PublicFormat.Raw
114        )
115        public_key2 = Ed25519PublicKey.from_public_bytes(public_bytes)
116        assert public_bytes == public_key2.public_bytes(
117            serialization.Encoding.Raw, serialization.PublicFormat.Raw
118        )
119
120    def test_invalid_type_public_bytes(self, backend):
121        with pytest.raises(TypeError):
122            Ed25519PublicKey.from_public_bytes(object())
123
124    def test_invalid_type_private_bytes(self, backend):
125        with pytest.raises(TypeError):
126            Ed25519PrivateKey.from_private_bytes(object())
127
128    def test_invalid_length_from_public_bytes(self, backend):
129        with pytest.raises(ValueError):
130            Ed25519PublicKey.from_public_bytes(b"a" * 31)
131        with pytest.raises(ValueError):
132            Ed25519PublicKey.from_public_bytes(b"a" * 33)
133
134    def test_invalid_length_from_private_bytes(self, backend):
135        with pytest.raises(ValueError):
136            Ed25519PrivateKey.from_private_bytes(b"a" * 31)
137        with pytest.raises(ValueError):
138            Ed25519PrivateKey.from_private_bytes(b"a" * 33)
139
140    def test_invalid_private_bytes(self, backend):
141        key = Ed25519PrivateKey.generate()
142        with pytest.raises(ValueError):
143            key.private_bytes(
144                serialization.Encoding.Raw,
145                serialization.PrivateFormat.Raw,
146                None,
147            )
148
149        with pytest.raises(ValueError):
150            key.private_bytes(
151                serialization.Encoding.Raw,
152                serialization.PrivateFormat.PKCS8,
153                None,
154            )
155
156        with pytest.raises(ValueError):
157            key.private_bytes(
158                serialization.Encoding.PEM,
159                serialization.PrivateFormat.Raw,
160                serialization.NoEncryption(),
161            )
162
163    def test_invalid_public_bytes(self, backend):
164        key = Ed25519PrivateKey.generate().public_key()
165        with pytest.raises(ValueError):
166            key.public_bytes(
167                serialization.Encoding.Raw,
168                serialization.PublicFormat.SubjectPublicKeyInfo,
169            )
170
171        with pytest.raises(ValueError):
172            key.public_bytes(
173                serialization.Encoding.PEM, serialization.PublicFormat.PKCS1
174            )
175
176        with pytest.raises(ValueError):
177            key.public_bytes(
178                serialization.Encoding.PEM, serialization.PublicFormat.Raw
179            )
180
181    @pytest.mark.parametrize(
182        ("encoding", "fmt", "encryption", "passwd", "load_func"),
183        [
184            (
185                serialization.Encoding.PEM,
186                serialization.PrivateFormat.PKCS8,
187                serialization.BestAvailableEncryption(b"password"),
188                b"password",
189                serialization.load_pem_private_key,
190            ),
191            (
192                serialization.Encoding.DER,
193                serialization.PrivateFormat.PKCS8,
194                serialization.BestAvailableEncryption(b"password"),
195                b"password",
196                serialization.load_der_private_key,
197            ),
198            (
199                serialization.Encoding.PEM,
200                serialization.PrivateFormat.PKCS8,
201                serialization.NoEncryption(),
202                None,
203                serialization.load_pem_private_key,
204            ),
205            (
206                serialization.Encoding.DER,
207                serialization.PrivateFormat.PKCS8,
208                serialization.NoEncryption(),
209                None,
210                serialization.load_der_private_key,
211            ),
212        ],
213    )
214    def test_round_trip_private_serialization(
215        self, encoding, fmt, encryption, passwd, load_func, backend
216    ):
217        key = Ed25519PrivateKey.generate()
218        serialized = key.private_bytes(encoding, fmt, encryption)
219        loaded_key = load_func(serialized, passwd, backend)
220        assert isinstance(loaded_key, Ed25519PrivateKey)
221
222    def test_buffer_protocol(self, backend):
223        private_bytes = os.urandom(32)
224        key = Ed25519PrivateKey.from_private_bytes(bytearray(private_bytes))
225        assert (
226            key.private_bytes(
227                serialization.Encoding.Raw,
228                serialization.PrivateFormat.Raw,
229                serialization.NoEncryption(),
230            )
231            == private_bytes
232        )
233