File: test_EC.py

package info (click to toggle)
python-jose 3.3.0%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 528 kB
  • sloc: python: 4,020; makefile: 162; sh: 6
file content (199 lines) | stat: -rw-r--r-- 6,905 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import json

from jose.backends import ECKey
from jose.constants import ALGORITHMS
from jose.exceptions import JOSEError, JWKError

try:
    import ecdsa

    from jose.backends.ecdsa_backend import ECDSAECKey
except ImportError:
    ECDSAECKey = ecdsa = None

try:
    from cryptography.hazmat.backends import default_backend as CryptographyBackend
    from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc

    from jose.backends.cryptography_backend import CryptographyECKey
except ImportError:
    CryptographyECKey = CryptographyEc = CryptographyBackend = None

import pytest

private_key = """-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOiSs10XnBlfykk5zsJRmzYybKdMlGniSJcssDvUcF6DoAoGCCqGSM49
AwEHoUQDQgAE7gb4edKJ7ul9IgomCdcOebQTZ8qktqtBfRKboa71CfEKzBruUi+D
WkG0HJWIORlPbvXME+DRh6G/yVOKnTm88Q==
-----END EC PRIVATE KEY-----"""

# Private key generated using NIST256p curve
TOO_SHORT_PRIVATE_KEY = b"""\
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMlUyYGOpjV4bbW0C9FKS2zkspD0L/5vJLnr6sJoLdc+oAoGCCqGSM49
AwEHoUQDQgAE6TDUNj5QXl+RKdZvBV+cg7Td6cJRB+Ta8XAhIuCAzonq0Ix//1+C
pNSsy11sIKmMl61YJzxvZ6WkNluBmkDPCQ==
-----END EC PRIVATE KEY-----
"""

# ES256 signatures generated to test conversion logic
DER_SIGNATURE = (
    b"0F\x02!\x00\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3"
    b"\x9d\xdf \xd1\xbc\xedK\x01\x87:}\xf2\x02!\x00\xb2shTA\x00\x1a\x13~\xba"
    b"J\xdb\xeem\x12\x1e\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e"
)
RAW_SIGNATURE = (
    b"\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3\x9d\xdf "
    b"\xd1\xbc\xedK\x01\x87:}\xf2\xb2shTA\x00\x1a\x13~\xbaJ\xdb\xeem\x12\x1e"
    b"\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e"
)


def _backend_exception_types():
    """Build the backend exception types based on available backends."""
    if None not in (ECDSAECKey, ecdsa):
        yield ECDSAECKey, ecdsa.BadDigestError

    if CryptographyECKey is not None:
        yield CryptographyECKey, TypeError


@pytest.mark.ecdsa
@pytest.mark.skipif(None in (ECDSAECKey, ecdsa), reason="python-ecdsa backend not available")
def test_key_from_ecdsa():
    key = ecdsa.SigningKey.from_pem(private_key)
    assert not ECKey(key, ALGORITHMS.ES256).is_public()


@pytest.mark.cryptography
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
@pytest.mark.parametrize(
    "algorithm, expected_length", ((ALGORITHMS.ES256, 32), (ALGORITHMS.ES384, 48), (ALGORITHMS.ES512, 66))
)
def test_cryptography_sig_component_length(algorithm, expected_length):
    # Put mapping inside here to avoid more complex handling for test runs that do not have pyca/cryptography
    mapping = {
        ALGORITHMS.ES256: CryptographyEc.SECP256R1,
        ALGORITHMS.ES384: CryptographyEc.SECP384R1,
        ALGORITHMS.ES512: CryptographyEc.SECP521R1,
    }
    key = CryptographyECKey(
        CryptographyEc.generate_private_key(mapping[algorithm](), backend=CryptographyBackend()), algorithm
    )
    assert key._sig_component_length() == expected_length


@pytest.mark.cryptography
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
def test_cryptograhy_der_to_raw():
    key = CryptographyECKey(private_key, ALGORITHMS.ES256)
    assert key._der_to_raw(DER_SIGNATURE) == RAW_SIGNATURE


@pytest.mark.cryptography
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
def test_cryptograhy_raw_to_der():
    key = CryptographyECKey(private_key, ALGORITHMS.ES256)
    assert key._raw_to_der(RAW_SIGNATURE) == DER_SIGNATURE


class TestECAlgorithm:
    def test_key_from_pem(self):
        assert not ECKey(private_key, ALGORITHMS.ES256).is_public()

    def test_to_pem(self):
        key = ECKey(private_key, ALGORITHMS.ES256)
        assert not key.is_public()
        assert key.to_pem().strip() == private_key.strip().encode("utf-8")

        public_pem = key.public_key().to_pem()
        assert ECKey(public_pem, ALGORITHMS.ES256).is_public()

    @pytest.mark.parametrize("Backend,ExceptionType", _backend_exception_types())
    def test_key_too_short(self, Backend, ExceptionType):
        key = Backend(TOO_SHORT_PRIVATE_KEY, ALGORITHMS.ES512)
        with pytest.raises(ExceptionType):
            key.sign(b"foo")

    def test_get_public_key(self):
        key = ECKey(private_key, ALGORITHMS.ES256)
        pubkey = key.public_key()
        pubkey2 = pubkey.public_key()
        assert pubkey == pubkey2

    def test_string_secret(self):
        key = "secret"
        with pytest.raises(JOSEError):
            ECKey(key, ALGORITHMS.ES256)

    def test_object(self):
        key = object()
        with pytest.raises(JOSEError):
            ECKey(key, ALGORITHMS.ES256)

    def test_invalid_algorithm(self):
        with pytest.raises(JWKError):
            ECKey(private_key, "nonexistent")

        with pytest.raises(JWKError):
            ECKey({"kty": "bla"}, ALGORITHMS.ES256)

    def test_EC_jwk(self):
        key = {
            "kty": "EC",
            "kid": "bilbo.baggins@hobbiton.example",
            "use": "sig",
            "crv": "P-521",
            "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
            "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
            "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt",
        }

        assert not ECKey(key, ALGORITHMS.ES512).is_public()

        del key["d"]

        # We are now dealing with a public key.
        assert ECKey(key, ALGORITHMS.ES512).is_public()

        del key["x"]

        # This key is missing a required parameter.
        with pytest.raises(JWKError):
            ECKey(key, ALGORITHMS.ES512)

    def test_verify(self):
        key = ECKey(private_key, ALGORITHMS.ES256)
        msg = b"test"
        signature = key.sign(msg)
        public_key = key.public_key()

        assert bool(public_key.verify(msg, signature))
        assert not bool(public_key.verify(msg, b"not a signature"))

    def assert_parameters(self, as_dict, private):
        assert isinstance(as_dict, dict)

        # Public parameters should always be there.
        assert "x" in as_dict
        assert "y" in as_dict
        assert "crv" in as_dict

        assert "kty" in as_dict
        assert as_dict["kty"] == "EC"

        if private:
            # Private parameters as well
            assert "d" in as_dict

        else:
            # Private parameters should be absent
            assert "d" not in as_dict

        # as_dict should be serializable to JSON
        json.dumps(as_dict)

    def test_to_dict(self):
        key = ECKey(private_key, ALGORITHMS.ES256)
        self.assert_parameters(key.to_dict(), private=True)
        self.assert_parameters(key.public_key().to_dict(), private=False)