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 pytest
8
9from cryptography.hazmat._der import (
10    DERReader,
11    INTEGER,
12    NULL,
13    OCTET_STRING,
14    SEQUENCE,
15    encode_der,
16    encode_der_integer,
17)
18
19
20def test_der_reader_basic():
21    reader = DERReader(b"123456789")
22    assert reader.read_byte() == ord(b"1")
23    assert reader.read_bytes(1).tobytes() == b"2"
24    assert reader.read_bytes(4).tobytes() == b"3456"
25
26    with pytest.raises(ValueError):
27        reader.read_bytes(4)
28
29    assert reader.read_bytes(3).tobytes() == b"789"
30
31    # The input is now empty.
32    with pytest.raises(ValueError):
33        reader.read_bytes(1)
34    with pytest.raises(ValueError):
35        reader.read_byte()
36
37
38def test_der():
39    # This input is the following structure, using
40    # https://github.com/google/der-ascii
41    #
42    # SEQUENCE {
43    #   SEQUENCE {
44    #     NULL {}
45    #     INTEGER { 42 }
46    #     OCTET_STRING { "hello" }
47    #   }
48    # }
49    der = b"\x30\x0e\x30\x0c\x05\x00\x02\x01\x2a\x04\x05\x68\x65\x6c\x6c\x6f"
50    reader = DERReader(der)
51    with pytest.raises(ValueError):
52        reader.check_empty()
53
54    with pytest.raises(ValueError):
55        with reader:
56            pass
57
58    with pytest.raises(ZeroDivisionError):
59        with DERReader(der):
60            raise ZeroDivisionError
61
62    # Parse the outer element.
63    outer = reader.read_element(SEQUENCE)
64    reader.check_empty()
65    assert outer.data.tobytes() == der[2:]
66
67    # Parse the outer element with read_any_element.
68    reader = DERReader(der)
69    tag, outer2 = reader.read_any_element()
70    reader.check_empty()
71    assert tag == SEQUENCE
72    assert outer2.data.tobytes() == der[2:]
73
74    # Parse the outer element with read_single_element.
75    outer3 = DERReader(der).read_single_element(SEQUENCE)
76    assert outer3.data.tobytes() == der[2:]
77
78    # read_single_element rejects trailing data.
79    with pytest.raises(ValueError):
80        DERReader(der + der).read_single_element(SEQUENCE)
81
82    # Continue parsing the structure.
83    inner = outer.read_element(SEQUENCE)
84    outer.check_empty()
85
86    # Parsing a missing optional element should work.
87    assert inner.read_optional_element(INTEGER) is None
88
89    null = inner.read_element(NULL)
90    null.check_empty()
91
92    # Parsing a present optional element should work.
93    integer = inner.read_optional_element(INTEGER)
94    assert integer.as_integer() == 42
95
96    octet_string = inner.read_element(OCTET_STRING)
97    assert octet_string.data.tobytes() == b"hello"
98
99    # Parsing a missing optional element should work when the input is empty.
100    inner.check_empty()
101    assert inner.read_optional_element(INTEGER) is None
102
103    # Re-encode the same structure.
104    der2 = encode_der(
105        SEQUENCE,
106        encode_der(
107            SEQUENCE,
108            encode_der(NULL),
109            encode_der(INTEGER, encode_der_integer(42)),
110            encode_der(OCTET_STRING, b"hello"),
111        ),
112    )
113    assert der2 == der
114
115
116@pytest.mark.parametrize(
117    "length,header",
118    [
119        # Single-byte lengths.
120        (0, b"\x04\x00"),
121        (1, b"\x04\x01"),
122        (2, b"\x04\x02"),
123        (127, b"\x04\x7f"),
124        # Long-form lengths.
125        (128, b"\x04\x81\x80"),
126        (129, b"\x04\x81\x81"),
127        (255, b"\x04\x81\xff"),
128        (0x100, b"\x04\x82\x01\x00"),
129        (0x101, b"\x04\x82\x01\x01"),
130        (0xFFFF, b"\x04\x82\xff\xff"),
131        (0x10000, b"\x04\x83\x01\x00\x00"),
132    ],
133)
134def test_der_lengths(length, header):
135    body = length * b"a"
136    der = header + body
137
138    reader = DERReader(der)
139    element = reader.read_element(OCTET_STRING)
140    reader.check_empty()
141    assert element.data.tobytes() == body
142
143    assert encode_der(OCTET_STRING, body) == der
144
145
146@pytest.mark.parametrize(
147    "bad_input",
148    [
149        # The input ended before the tag.
150        b"",
151        # The input ended before the length.
152        b"\x30",
153        # The input ended before the second byte of the length.
154        b"\x30\x81",
155        # The input ended before the body.
156        b"\x30\x01",
157        # The length used long form when it should be short form.
158        b"\x30\x81\x01\x00",
159        # The length was not minimally-encoded.
160        b"\x30\x82\x00\x80" + (0x80 * b"a"),
161        # Indefinite-length encoding is not valid DER.
162        b"\x30\x80\x00\x00"
163        # Tag number (the bottom 5 bits) 31 indicates long form tags, which we
164        # do not support.
165        b"\x1f\x00",
166        b"\x9f\x00",
167        b"\xbf\x00",
168        b"\xff\x00",
169    ],
170)
171def test_der_reader_bad_input(bad_input):
172    reader = DERReader(bad_input)
173    with pytest.raises(ValueError):
174        reader.read_any_element()
175
176
177def test_der_reader_wrong_tag():
178    reader = DERReader(b"\x04\x00")
179    with pytest.raises(ValueError):
180        reader.read_element(SEQUENCE)
181
182
183@pytest.mark.parametrize(
184    "value,der",
185    [
186        (0, b"\x00"),
187        (1, b"\x01"),
188        (2, b"\x02"),
189        (3, b"\x03"),
190        (127, b"\x7f"),
191        (128, b"\x00\x80"),
192        (
193            0x112233445566778899AABBCCDDEEFF,
194            b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff",
195        ),
196    ],
197)
198def test_integer(value, der):
199    assert encode_der_integer(value) == der
200    assert DERReader(der).as_integer() == value
201
202
203@pytest.mark.parametrize(
204    "bad_input",
205    [
206        # Zero is encoded as b"\x00", not the empty string.
207        b"",
208        # Too many leading zeros.
209        b"\x00\x00",
210        b"\x00\x7f",
211        # Too many leading ones.
212        b"\xff\xff",
213        b"\xff\x80",
214        # Negative integers are not supported.
215        b"\x80",
216        b"\x81",
217        b"\x80\x00\x00",
218        b"\xff",
219    ],
220)
221def test_invalid_integer(bad_input):
222    reader = DERReader(bad_input)
223    with pytest.raises(ValueError):
224        reader.as_integer()
225
226
227def test_invalid_integer_encode():
228    with pytest.raises(ValueError):
229        encode_der_integer(-1)
230
231    with pytest.raises(ValueError):
232        encode_der_integer("not an integer")
233