• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 itertools
9import os
10
11import pytest
12
13from cryptography.hazmat.backends.interfaces import (
14    DERSerializationBackend,
15    DHBackend,
16    PEMSerializationBackend,
17)
18from cryptography.hazmat.primitives import serialization
19from cryptography.hazmat.primitives.asymmetric import dh
20from cryptography.utils import int_from_bytes
21
22from .fixtures_dh import FFDH3072_P
23from ...doubles import DummyKeySerializationEncryption
24from ...utils import load_nist_vectors, load_vectors_from_file
25
26# RFC 3526
27P_1536 = int(
28    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
29    "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
30    "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
31    "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
32    "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
33    "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
34    "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
35    "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
36    16,
37)
38
39
40def _skip_dhx_unsupported(backend, is_dhx):
41    if not is_dhx:
42        return
43    if not backend.dh_x942_serialization_supported():
44        pytest.skip("DH x9.42 serialization is not supported")
45
46
47def test_dh_parameternumbers():
48    params = dh.DHParameterNumbers(P_1536, 2)
49
50    assert params.p == P_1536
51    assert params.g == 2
52
53    with pytest.raises(TypeError):
54        dh.DHParameterNumbers(None, 2)
55
56    with pytest.raises(TypeError):
57        dh.DHParameterNumbers(P_1536, None)
58
59    with pytest.raises(TypeError):
60        dh.DHParameterNumbers(None, None)
61
62    with pytest.raises(ValueError):
63        dh.DHParameterNumbers(P_1536, 1)
64
65    # p too small
66    with pytest.raises(ValueError):
67        dh.DHParameterNumbers(65537, 2)
68
69    params = dh.DHParameterNumbers(P_1536, 7, 1245)
70
71    assert params.p == P_1536
72    assert params.g == 7
73    assert params.q == 1245
74
75    with pytest.raises(TypeError):
76        dh.DHParameterNumbers(P_1536, 2, "hello")
77
78
79def test_dh_numbers():
80    params = dh.DHParameterNumbers(P_1536, 2)
81
82    public = dh.DHPublicNumbers(1, params)
83
84    assert public.parameter_numbers is params
85    assert public.y == 1
86
87    with pytest.raises(TypeError):
88        dh.DHPublicNumbers(1, None)
89
90    with pytest.raises(TypeError):
91        dh.DHPublicNumbers(None, params)
92
93    private = dh.DHPrivateNumbers(1, public)
94
95    assert private.public_numbers is public
96    assert private.x == 1
97
98    with pytest.raises(TypeError):
99        dh.DHPrivateNumbers(1, None)
100
101    with pytest.raises(TypeError):
102        dh.DHPrivateNumbers(None, public)
103
104
105def test_dh_parameter_numbers_equality():
106    assert dh.DHParameterNumbers(P_1536, 2) == dh.DHParameterNumbers(P_1536, 2)
107    assert dh.DHParameterNumbers(P_1536, 7, 12345) == dh.DHParameterNumbers(
108        P_1536, 7, 12345
109    )
110    assert dh.DHParameterNumbers(P_1536 + 2, 2) != dh.DHParameterNumbers(
111        P_1536, 2
112    )
113    assert dh.DHParameterNumbers(P_1536, 2, 123) != dh.DHParameterNumbers(
114        P_1536, 2, 456
115    )
116    assert dh.DHParameterNumbers(P_1536, 5) != dh.DHParameterNumbers(P_1536, 2)
117    assert dh.DHParameterNumbers(P_1536, 2) != object()
118
119
120def test_dh_private_numbers_equality():
121    params = dh.DHParameterNumbers(P_1536, 2)
122    public = dh.DHPublicNumbers(1, params)
123    private = dh.DHPrivateNumbers(2, public)
124
125    assert private == dh.DHPrivateNumbers(2, public)
126    assert private != dh.DHPrivateNumbers(0, public)
127    assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params))
128    assert private != dh.DHPrivateNumbers(
129        2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5))
130    )
131    assert private != object()
132
133
134def test_dh_public_numbers_equality():
135    params = dh.DHParameterNumbers(P_1536, 2)
136    public = dh.DHPublicNumbers(1, params)
137
138    assert public == dh.DHPublicNumbers(1, params)
139    assert public != dh.DHPublicNumbers(0, params)
140    assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5))
141    assert public != object()
142
143
144@pytest.mark.requires_backend_interface(interface=DHBackend)
145class TestDH(object):
146    def test_small_key_generate_dh(self, backend):
147        with pytest.raises(ValueError):
148            dh.generate_parameters(2, 511, backend)
149
150    def test_unsupported_generator_generate_dh(self, backend):
151        with pytest.raises(ValueError):
152            dh.generate_parameters(7, 512, backend)
153
154    @pytest.mark.skip_fips(reason="non-FIPS parameters")
155    def test_dh_parameters_supported(self, backend):
156        valid_p = int(
157            b"907c7211ae61aaaba1825ff53b6cb71ac6df9f1a424c033f4a0a41ac42fad3a9"
158            b"bcfc7f938a269710ed69e330523e4039029b7900977c740990d46efed79b9bbe"
159            b"73505ae878808944ce4d9c6c52daecc0a87dc889c53499be93db8551ee685f30"
160            b"349bf1b443d4ebaee0d5e8b441a40d4e8178f8f612f657a5eb91e0a8e"
161            b"107755f",
162            16,
163        )
164        assert backend.dh_parameters_supported(valid_p, 5)
165        assert not backend.dh_parameters_supported(23, 22)
166
167    @pytest.mark.parametrize(
168        "vector",
169        load_vectors_from_file(
170            os.path.join("asymmetric", "DH", "rfc3526.txt"), load_nist_vectors
171        ),
172    )
173    def test_dh_parameters_allows_rfc3526_groups(self, backend, vector):
174        p = int_from_bytes(binascii.unhexlify(vector["p"]), "big")
175        if (
176            backend._fips_enabled
177            and p.bit_length() < backend._fips_dh_min_modulus
178        ):
179            pytest.skip("modulus too small for FIPS mode")
180
181        params = dh.DHParameterNumbers(p, int(vector["g"]))
182        param = params.parameters(backend)
183        key = param.generate_private_key()
184        # This confirms that a key generated with this group
185        # will pass DH_check when we serialize and de-serialize it via
186        # the Numbers path.
187        roundtripped_key = key.private_numbers().private_key(backend)
188        assert key.private_numbers() == roundtripped_key.private_numbers()
189
190    @pytest.mark.skip_fips(reason="non-FIPS parameters")
191    @pytest.mark.parametrize(
192        "vector",
193        load_vectors_from_file(
194            os.path.join("asymmetric", "DH", "RFC5114.txt"), load_nist_vectors
195        ),
196    )
197    def test_dh_parameters_supported_with_q(self, backend, vector):
198        assert backend.dh_parameters_supported(
199            int(vector["p"], 16), int(vector["g"], 16), int(vector["q"], 16)
200        )
201
202    @pytest.mark.skip_fips(reason="modulus too small for FIPS")
203    @pytest.mark.parametrize("with_q", [False, True])
204    def test_convert_to_numbers(self, backend, with_q):
205        if with_q:
206            vector = load_vectors_from_file(
207                os.path.join("asymmetric", "DH", "RFC5114.txt"),
208                load_nist_vectors,
209            )[0]
210            p = int(vector["p"], 16)
211            g = int(vector["g"], 16)
212            q = int(vector["q"], 16)
213        else:
214            parameters = backend.generate_dh_private_key_and_parameters(2, 512)
215
216            private = parameters.private_numbers()
217
218            p = private.public_numbers.parameter_numbers.p
219            g = private.public_numbers.parameter_numbers.g
220            q = None
221
222        params = dh.DHParameterNumbers(p, g, q)
223        public = dh.DHPublicNumbers(1, params)
224        private = dh.DHPrivateNumbers(2, public)
225
226        deserialized_params = params.parameters(backend)
227        deserialized_public = public.public_key(backend)
228        deserialized_private = private.private_key(backend)
229
230        assert isinstance(
231            deserialized_params, dh.DHParametersWithSerialization
232        )
233        assert isinstance(deserialized_public, dh.DHPublicKeyWithSerialization)
234        assert isinstance(
235            deserialized_private, dh.DHPrivateKeyWithSerialization
236        )
237
238    @pytest.mark.skip_fips(reason="FIPS requires specific parameters")
239    def test_numbers_unsupported_parameters(self, backend):
240        # p is set to P_1536 + 1 because when calling private_key we want it to
241        # fail the DH_check call OpenSSL does, but we specifically want it to
242        # fail such that we don't get a DH_NOT_SUITABLE_GENERATOR. We can cause
243        # this by making sure p is not prime.
244        params = dh.DHParameterNumbers(P_1536 + 1, 2)
245        public = dh.DHPublicNumbers(1, params)
246        private = dh.DHPrivateNumbers(2, public)
247
248        with pytest.raises(ValueError):
249            private.private_key(backend)
250
251    @pytest.mark.skip_fips(reason="FIPS requires key size >= 2048")
252    @pytest.mark.parametrize("with_q", [False, True])
253    def test_generate_dh(self, backend, with_q):
254        if with_q:
255            vector = load_vectors_from_file(
256                os.path.join("asymmetric", "DH", "RFC5114.txt"),
257                load_nist_vectors,
258            )[0]
259            p = int(vector["p"], 16)
260            g = int(vector["g"], 16)
261            q = int(vector["q"], 16)
262            parameters = dh.DHParameterNumbers(p, g, q).parameters(backend)
263            key_size = 1024
264        else:
265            generator = 2
266            key_size = 512
267
268            parameters = dh.generate_parameters(generator, key_size, backend)
269        assert isinstance(parameters, dh.DHParameters)
270
271        key = parameters.generate_private_key()
272        assert isinstance(key, dh.DHPrivateKey)
273        assert key.key_size == key_size
274
275        public = key.public_key()
276        assert isinstance(public, dh.DHPublicKey)
277        assert public.key_size == key_size
278
279        assert isinstance(parameters, dh.DHParametersWithSerialization)
280        parameter_numbers = parameters.parameter_numbers()
281        assert isinstance(parameter_numbers, dh.DHParameterNumbers)
282        assert parameter_numbers.p.bit_length() == key_size
283
284        assert isinstance(public, dh.DHPublicKeyWithSerialization)
285        assert isinstance(public.public_numbers(), dh.DHPublicNumbers)
286        assert isinstance(public.parameters(), dh.DHParameters)
287
288        assert isinstance(key, dh.DHPrivateKeyWithSerialization)
289        assert isinstance(key.private_numbers(), dh.DHPrivateNumbers)
290        assert isinstance(key.parameters(), dh.DHParameters)
291
292    def test_exchange(self, backend):
293        parameters = FFDH3072_P.parameters(backend)
294        assert isinstance(parameters, dh.DHParameters)
295
296        key1 = parameters.generate_private_key()
297        key2 = parameters.generate_private_key()
298
299        symkey1 = key1.exchange(key2.public_key())
300        assert symkey1
301        assert len(symkey1) == 3072 // 8
302
303        symkey2 = key2.exchange(key1.public_key())
304        assert symkey1 == symkey2
305
306    def test_exchange_algorithm(self, backend):
307        parameters = FFDH3072_P.parameters(backend)
308        key1 = parameters.generate_private_key()
309        key2 = parameters.generate_private_key()
310
311        shared_key_bytes = key2.exchange(key1.public_key())
312        symkey = int_from_bytes(shared_key_bytes, "big")
313
314        symkey_manual = pow(
315            key1.public_key().public_numbers().y,
316            key2.private_numbers().x,
317            parameters.parameter_numbers().p,
318        )
319
320        assert symkey == symkey_manual
321
322    @pytest.mark.skip_fips(reason="key_size too small for FIPS")
323    def test_symmetric_key_padding(self, backend):
324        """
325        This test has specific parameters that produce a symmetric key
326        In length 63 bytes instead 64. We make sure here that we add
327        padding to the key.
328        """
329        p = int(
330            "11859949538425015739337467917303613431031019140213666"
331            "129025407300654026585086345323066284800963463204246390"
332            "256567934582260424238844463330887962689642467123"
333        )
334        g = 2
335        y = int(
336            "32155788395534640648739966373159697798396966919821525"
337            "72238852825117261342483718574508213761865276905503199"
338            "969908098203345481366464874759377454476688391248"
339        )
340        x = int(
341            "409364065449673443397833358558926598469347813468816037"
342            "268451847116982490733450463194921405069999008617231539"
343            "7147035896687401350877308899732826446337707128"
344        )
345        parameters = dh.DHParameterNumbers(p, g)
346        public = dh.DHPublicNumbers(y, parameters)
347        private = dh.DHPrivateNumbers(x, public)
348        key = private.private_key(backend)
349        symkey = key.exchange(public.public_key(backend))
350        assert len(symkey) == 512 // 8
351        assert symkey[:1] == b"\x00"
352
353    @pytest.mark.parametrize(
354        "vector",
355        load_vectors_from_file(
356            os.path.join("asymmetric", "DH", "bad_exchange.txt"),
357            load_nist_vectors,
358        ),
359    )
360    def test_bad_exchange(self, backend, vector):
361        if (
362            backend._fips_enabled
363            and int(vector["p1"]) < backend._fips_dh_min_modulus
364        ):
365            pytest.skip("modulus too small for FIPS mode")
366        parameters1 = dh.DHParameterNumbers(
367            int(vector["p1"]), int(vector["g"])
368        )
369        public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1)
370        private1 = dh.DHPrivateNumbers(int(vector["x1"]), public1)
371        key1 = private1.private_key(backend)
372        pub_key1 = key1.public_key()
373
374        parameters2 = dh.DHParameterNumbers(
375            int(vector["p2"]), int(vector["g"])
376        )
377        public2 = dh.DHPublicNumbers(int(vector["y2"]), parameters2)
378        private2 = dh.DHPrivateNumbers(int(vector["x2"]), public2)
379        key2 = private2.private_key(backend)
380        pub_key2 = key2.public_key()
381
382        if pub_key2.public_numbers().y >= parameters1.p:
383            with pytest.raises(ValueError):
384                key1.exchange(pub_key2)
385        else:
386            symkey1 = key1.exchange(pub_key2)
387            assert symkey1
388
389            symkey2 = key2.exchange(pub_key1)
390
391            assert symkey1 != symkey2
392
393    @pytest.mark.skip_fips(reason="key_size too small for FIPS")
394    def test_load_256bit_key_from_pkcs8(self, backend):
395        data = load_vectors_from_file(
396            os.path.join("asymmetric", "DH", "dh_key_256.pem"),
397            lambda pemfile: pemfile.read(),
398            mode="rb",
399        )
400        key = serialization.load_pem_private_key(data, None, backend)
401        assert key.key_size == 256
402
403    @pytest.mark.parametrize(
404        "vector",
405        load_vectors_from_file(
406            os.path.join("asymmetric", "DH", "vec.txt"), load_nist_vectors
407        ),
408    )
409    def test_dh_vectors(self, backend, vector):
410        if (
411            backend._fips_enabled
412            and int(vector["p"]) < backend._fips_dh_min_modulus
413        ):
414            pytest.skip("modulus too small for FIPS mode")
415
416        if int(vector["p"]).bit_length() < 512:
417            pytest.skip("DH keys less than 512 bits are unsupported")
418
419        parameters = dh.DHParameterNumbers(int(vector["p"]), int(vector["g"]))
420        public = dh.DHPublicNumbers(int(vector["y"]), parameters)
421        private = dh.DHPrivateNumbers(int(vector["x"]), public)
422        key = private.private_key(backend)
423        symkey = key.exchange(public.public_key(backend))
424
425        assert int_from_bytes(symkey, "big") == int(vector["k"], 16)
426
427    @pytest.mark.skip_fips(reason="non-FIPS parameters")
428    @pytest.mark.parametrize(
429        "vector",
430        load_vectors_from_file(
431            os.path.join("asymmetric", "DH", "RFC5114.txt"), load_nist_vectors
432        ),
433    )
434    def test_dh_vectors_with_q(self, backend, vector):
435        parameters = dh.DHParameterNumbers(
436            int(vector["p"], 16), int(vector["g"], 16), int(vector["q"], 16)
437        )
438        public1 = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters)
439        private1 = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public1)
440        public2 = dh.DHPublicNumbers(int(vector["ystatiut"], 16), parameters)
441        private2 = dh.DHPrivateNumbers(int(vector["xstatiut"], 16), public2)
442        key1 = private1.private_key(backend)
443        key2 = private2.private_key(backend)
444        symkey1 = key1.exchange(public2.public_key(backend))
445        symkey2 = key2.exchange(public1.public_key(backend))
446
447        assert int_from_bytes(symkey1, "big") == int(vector["z"], 16)
448        assert int_from_bytes(symkey2, "big") == int(vector["z"], 16)
449
450
451@pytest.mark.requires_backend_interface(interface=DHBackend)
452@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
453@pytest.mark.requires_backend_interface(interface=DERSerializationBackend)
454class TestDHPrivateKeySerialization(object):
455    @pytest.mark.parametrize(
456        ("encoding", "loader_func"),
457        [
458            [serialization.Encoding.PEM, serialization.load_pem_private_key],
459            [serialization.Encoding.DER, serialization.load_der_private_key],
460        ],
461    )
462    def test_private_bytes_unencrypted(self, backend, encoding, loader_func):
463        parameters = FFDH3072_P.parameters(backend)
464        key = parameters.generate_private_key()
465        serialized = key.private_bytes(
466            encoding,
467            serialization.PrivateFormat.PKCS8,
468            serialization.NoEncryption(),
469        )
470        loaded_key = loader_func(serialized, None, backend)
471        loaded_priv_num = loaded_key.private_numbers()
472        priv_num = key.private_numbers()
473        assert loaded_priv_num == priv_num
474
475    @pytest.mark.parametrize(
476        ("encoding", "fmt"),
477        [
478            (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8),
479            (serialization.Encoding.DER, serialization.PrivateFormat.Raw),
480            (serialization.Encoding.Raw, serialization.PrivateFormat.Raw),
481            (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8),
482        ],
483    )
484    def test_private_bytes_rejects_invalid(self, encoding, fmt, backend):
485        parameters = FFDH3072_P.parameters(backend)
486        key = parameters.generate_private_key()
487        with pytest.raises(ValueError):
488            key.private_bytes(encoding, fmt, serialization.NoEncryption())
489
490    @pytest.mark.skip_fips(reason="non-FIPS parameters")
491    @pytest.mark.parametrize(
492        ("key_path", "loader_func", "encoding", "is_dhx"),
493        [
494            (
495                os.path.join("asymmetric", "DH", "dhkey.pem"),
496                serialization.load_pem_private_key,
497                serialization.Encoding.PEM,
498                False,
499            ),
500            (
501                os.path.join("asymmetric", "DH", "dhkey.der"),
502                serialization.load_der_private_key,
503                serialization.Encoding.DER,
504                False,
505            ),
506            (
507                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"),
508                serialization.load_pem_private_key,
509                serialization.Encoding.PEM,
510                True,
511            ),
512            (
513                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"),
514                serialization.load_der_private_key,
515                serialization.Encoding.DER,
516                True,
517            ),
518        ],
519    )
520    def test_private_bytes_match(
521        self, key_path, loader_func, encoding, is_dhx, backend
522    ):
523        _skip_dhx_unsupported(backend, is_dhx)
524        key_bytes = load_vectors_from_file(
525            key_path, lambda pemfile: pemfile.read(), mode="rb"
526        )
527        key = loader_func(key_bytes, None, backend)
528        serialized = key.private_bytes(
529            encoding,
530            serialization.PrivateFormat.PKCS8,
531            serialization.NoEncryption(),
532        )
533        assert serialized == key_bytes
534
535    @pytest.mark.skip_fips(reason="non-FIPS parameters")
536    @pytest.mark.parametrize(
537        ("key_path", "loader_func", "vec_path", "is_dhx"),
538        [
539            (
540                os.path.join("asymmetric", "DH", "dhkey.pem"),
541                serialization.load_pem_private_key,
542                os.path.join("asymmetric", "DH", "dhkey.txt"),
543                False,
544            ),
545            (
546                os.path.join("asymmetric", "DH", "dhkey.der"),
547                serialization.load_der_private_key,
548                os.path.join("asymmetric", "DH", "dhkey.txt"),
549                False,
550            ),
551            (
552                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"),
553                serialization.load_pem_private_key,
554                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
555                True,
556            ),
557            (
558                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"),
559                serialization.load_der_private_key,
560                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
561                True,
562            ),
563        ],
564    )
565    def test_private_bytes_values(
566        self, key_path, loader_func, vec_path, is_dhx, backend
567    ):
568        _skip_dhx_unsupported(backend, is_dhx)
569        key_bytes = load_vectors_from_file(
570            key_path, lambda pemfile: pemfile.read(), mode="rb"
571        )
572        vec = load_vectors_from_file(vec_path, load_nist_vectors)[0]
573        key = loader_func(key_bytes, None, backend)
574        private_numbers = key.private_numbers()
575        assert private_numbers.x == int(vec["x"], 16)
576        assert private_numbers.public_numbers.y == int(vec["y"], 16)
577        assert private_numbers.public_numbers.parameter_numbers.g == int(
578            vec["g"], 16
579        )
580        assert private_numbers.public_numbers.parameter_numbers.p == int(
581            vec["p"], 16
582        )
583        if "q" in vec:
584            assert private_numbers.public_numbers.parameter_numbers.q == int(
585                vec["q"], 16
586            )
587        else:
588            assert private_numbers.public_numbers.parameter_numbers.q is None
589
590    def test_private_bytes_traditional_openssl_invalid(self, backend):
591        parameters = FFDH3072_P.parameters(backend)
592        key = parameters.generate_private_key()
593        with pytest.raises(ValueError):
594            key.private_bytes(
595                serialization.Encoding.PEM,
596                serialization.PrivateFormat.TraditionalOpenSSL,
597                serialization.NoEncryption(),
598            )
599
600    def test_private_bytes_invalid_encoding(self, backend):
601        parameters = FFDH3072_P.parameters(backend)
602        key = parameters.generate_private_key()
603        with pytest.raises(TypeError):
604            key.private_bytes(
605                "notencoding",
606                serialization.PrivateFormat.PKCS8,
607                serialization.NoEncryption(),
608            )
609
610    def test_private_bytes_invalid_format(self, backend):
611        parameters = FFDH3072_P.parameters(backend)
612        key = parameters.generate_private_key()
613        with pytest.raises(ValueError):
614            key.private_bytes(
615                serialization.Encoding.PEM,
616                "invalidformat",
617                serialization.NoEncryption(),
618            )
619
620    def test_private_bytes_invalid_encryption_algorithm(self, backend):
621        parameters = FFDH3072_P.parameters(backend)
622        key = parameters.generate_private_key()
623        with pytest.raises(TypeError):
624            key.private_bytes(
625                serialization.Encoding.PEM,
626                serialization.PrivateFormat.PKCS8,
627                "notanencalg",
628            )
629
630    def test_private_bytes_unsupported_encryption_type(self, backend):
631        parameters = FFDH3072_P.parameters(backend)
632        key = parameters.generate_private_key()
633        with pytest.raises(ValueError):
634            key.private_bytes(
635                serialization.Encoding.PEM,
636                serialization.PrivateFormat.PKCS8,
637                DummyKeySerializationEncryption(),
638            )
639
640
641@pytest.mark.requires_backend_interface(interface=DHBackend)
642@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
643@pytest.mark.requires_backend_interface(interface=DERSerializationBackend)
644class TestDHPublicKeySerialization(object):
645    @pytest.mark.parametrize(
646        ("encoding", "loader_func"),
647        [
648            [serialization.Encoding.PEM, serialization.load_pem_public_key],
649            [serialization.Encoding.DER, serialization.load_der_public_key],
650        ],
651    )
652    def test_public_bytes(self, backend, encoding, loader_func):
653        parameters = FFDH3072_P.parameters(backend)
654        key = parameters.generate_private_key().public_key()
655        serialized = key.public_bytes(
656            encoding, serialization.PublicFormat.SubjectPublicKeyInfo
657        )
658        loaded_key = loader_func(serialized, backend)
659        loaded_pub_num = loaded_key.public_numbers()
660        pub_num = key.public_numbers()
661        assert loaded_pub_num == pub_num
662
663    @pytest.mark.parametrize(
664        ("key_path", "loader_func", "encoding", "is_dhx"),
665        [
666            (
667                os.path.join("asymmetric", "DH", "dhpub.pem"),
668                serialization.load_pem_public_key,
669                serialization.Encoding.PEM,
670                False,
671            ),
672            (
673                os.path.join("asymmetric", "DH", "dhpub.der"),
674                serialization.load_der_public_key,
675                serialization.Encoding.DER,
676                False,
677            ),
678            (
679                os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"),
680                serialization.load_pem_public_key,
681                serialization.Encoding.PEM,
682                True,
683            ),
684            (
685                os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"),
686                serialization.load_der_public_key,
687                serialization.Encoding.DER,
688                True,
689            ),
690        ],
691    )
692    def test_public_bytes_match(
693        self, key_path, loader_func, encoding, is_dhx, backend
694    ):
695        _skip_dhx_unsupported(backend, is_dhx)
696        key_bytes = load_vectors_from_file(
697            key_path, lambda pemfile: pemfile.read(), mode="rb"
698        )
699        pub_key = loader_func(key_bytes, backend)
700        serialized = pub_key.public_bytes(
701            encoding,
702            serialization.PublicFormat.SubjectPublicKeyInfo,
703        )
704        assert serialized == key_bytes
705
706    @pytest.mark.parametrize(
707        ("key_path", "loader_func", "vec_path", "is_dhx"),
708        [
709            (
710                os.path.join("asymmetric", "DH", "dhpub.pem"),
711                serialization.load_pem_public_key,
712                os.path.join("asymmetric", "DH", "dhkey.txt"),
713                False,
714            ),
715            (
716                os.path.join("asymmetric", "DH", "dhpub.der"),
717                serialization.load_der_public_key,
718                os.path.join("asymmetric", "DH", "dhkey.txt"),
719                False,
720            ),
721            (
722                os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"),
723                serialization.load_pem_public_key,
724                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
725                True,
726            ),
727            (
728                os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"),
729                serialization.load_der_public_key,
730                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
731                True,
732            ),
733        ],
734    )
735    def test_public_bytes_values(
736        self, key_path, loader_func, vec_path, is_dhx, backend
737    ):
738        _skip_dhx_unsupported(backend, is_dhx)
739        key_bytes = load_vectors_from_file(
740            key_path, lambda pemfile: pemfile.read(), mode="rb"
741        )
742        vec = load_vectors_from_file(vec_path, load_nist_vectors)[0]
743        pub_key = loader_func(key_bytes, backend)
744        public_numbers = pub_key.public_numbers()
745        assert public_numbers.y == int(vec["y"], 16)
746        assert public_numbers.parameter_numbers.g == int(vec["g"], 16)
747        assert public_numbers.parameter_numbers.p == int(vec["p"], 16)
748        if "q" in vec:
749            assert public_numbers.parameter_numbers.q == int(vec["q"], 16)
750        else:
751            assert public_numbers.parameter_numbers.q is None
752
753    def test_public_bytes_invalid_encoding(self, backend):
754        parameters = FFDH3072_P.parameters(backend)
755        key = parameters.generate_private_key().public_key()
756        with pytest.raises(TypeError):
757            key.public_bytes(
758                "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo
759            )
760
761    def test_public_bytes_pkcs1_unsupported(self, backend):
762        parameters = FFDH3072_P.parameters(backend)
763        key = parameters.generate_private_key().public_key()
764        with pytest.raises(ValueError):
765            key.public_bytes(
766                serialization.Encoding.PEM, serialization.PublicFormat.PKCS1
767            )
768
769
770@pytest.mark.requires_backend_interface(interface=DHBackend)
771@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
772@pytest.mark.requires_backend_interface(interface=DERSerializationBackend)
773class TestDHParameterSerialization(object):
774    @pytest.mark.parametrize(
775        ("encoding", "loader_func"),
776        [
777            [serialization.Encoding.PEM, serialization.load_pem_parameters],
778            [serialization.Encoding.DER, serialization.load_der_parameters],
779        ],
780    )
781    def test_parameter_bytes(self, backend, encoding, loader_func):
782        parameters = FFDH3072_P.parameters(backend)
783        serialized = parameters.parameter_bytes(
784            encoding, serialization.ParameterFormat.PKCS3
785        )
786        loaded_key = loader_func(serialized, backend)
787        loaded_param_num = loaded_key.parameter_numbers()
788        assert loaded_param_num == parameters.parameter_numbers()
789
790    @pytest.mark.parametrize(
791        ("param_path", "loader_func", "encoding", "is_dhx"),
792        [
793            (
794                os.path.join("asymmetric", "DH", "dhp.pem"),
795                serialization.load_pem_parameters,
796                serialization.Encoding.PEM,
797                False,
798            ),
799            (
800                os.path.join("asymmetric", "DH", "dhp.der"),
801                serialization.load_der_parameters,
802                serialization.Encoding.DER,
803                False,
804            ),
805            (
806                os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"),
807                serialization.load_pem_parameters,
808                serialization.Encoding.PEM,
809                True,
810            ),
811            (
812                os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"),
813                serialization.load_der_parameters,
814                serialization.Encoding.DER,
815                True,
816            ),
817        ],
818    )
819    def test_parameter_bytes_match(
820        self, param_path, loader_func, encoding, backend, is_dhx
821    ):
822        _skip_dhx_unsupported(backend, is_dhx)
823        param_bytes = load_vectors_from_file(
824            param_path, lambda pemfile: pemfile.read(), mode="rb"
825        )
826        parameters = loader_func(param_bytes, backend)
827        serialized = parameters.parameter_bytes(
828            encoding,
829            serialization.ParameterFormat.PKCS3,
830        )
831        assert serialized == param_bytes
832
833    @pytest.mark.parametrize(
834        ("param_path", "loader_func", "vec_path", "is_dhx"),
835        [
836            (
837                os.path.join("asymmetric", "DH", "dhp.pem"),
838                serialization.load_pem_parameters,
839                os.path.join("asymmetric", "DH", "dhkey.txt"),
840                False,
841            ),
842            (
843                os.path.join("asymmetric", "DH", "dhp.der"),
844                serialization.load_der_parameters,
845                os.path.join("asymmetric", "DH", "dhkey.txt"),
846                False,
847            ),
848            (
849                os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"),
850                serialization.load_pem_parameters,
851                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
852                True,
853            ),
854            (
855                os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"),
856                serialization.load_der_parameters,
857                os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
858                True,
859            ),
860        ],
861    )
862    def test_public_bytes_values(
863        self, param_path, loader_func, vec_path, backend, is_dhx
864    ):
865        _skip_dhx_unsupported(backend, is_dhx)
866        key_bytes = load_vectors_from_file(
867            param_path, lambda pemfile: pemfile.read(), mode="rb"
868        )
869        vec = load_vectors_from_file(vec_path, load_nist_vectors)[0]
870        parameters = loader_func(key_bytes, backend)
871        parameter_numbers = parameters.parameter_numbers()
872        assert parameter_numbers.g == int(vec["g"], 16)
873        assert parameter_numbers.p == int(vec["p"], 16)
874        if "q" in vec:
875            assert parameter_numbers.q == int(vec["q"], 16)
876        else:
877            assert parameter_numbers.q is None
878
879    @pytest.mark.parametrize(
880        ("encoding", "fmt"),
881        [
882            (
883                serialization.Encoding.Raw,
884                serialization.PublicFormat.SubjectPublicKeyInfo,
885            ),
886            (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1),
887        ]
888        + list(
889            itertools.product(
890                [
891                    serialization.Encoding.Raw,
892                    serialization.Encoding.X962,
893                    serialization.Encoding.PEM,
894                    serialization.Encoding.DER,
895                ],
896                [
897                    serialization.PublicFormat.Raw,
898                    serialization.PublicFormat.UncompressedPoint,
899                    serialization.PublicFormat.CompressedPoint,
900                ],
901            )
902        ),
903    )
904    def test_public_bytes_rejects_invalid(self, encoding, fmt, backend):
905        parameters = FFDH3072_P.parameters(backend)
906        key = parameters.generate_private_key().public_key()
907        with pytest.raises(ValueError):
908            key.public_bytes(encoding, fmt)
909
910    def test_parameter_bytes_invalid_encoding(self, backend):
911        parameters = FFDH3072_P.parameters(backend)
912        with pytest.raises(TypeError):
913            parameters.parameter_bytes(
914                "notencoding", serialization.ParameterFormat.PKCS3
915            )
916
917    def test_parameter_bytes_invalid_format(self, backend):
918        parameters = FFDH3072_P.parameters(backend)
919        with pytest.raises(ValueError):
920            parameters.parameter_bytes(serialization.Encoding.PEM, "notformat")
921
922    def test_parameter_bytes_openssh_unsupported(self, backend):
923        parameters = FFDH3072_P.parameters(backend)
924        with pytest.raises(TypeError):
925            parameters.parameter_bytes(
926                serialization.Encoding.OpenSSH,
927                serialization.ParameterFormat.PKCS3,
928            )
929