233 lines
6.0 KiB
Python
233 lines
6.0 KiB
Python
# 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 pytest
|
|
|
|
from cryptography.hazmat._der import (
|
|
DERReader,
|
|
INTEGER,
|
|
NULL,
|
|
OCTET_STRING,
|
|
SEQUENCE,
|
|
encode_der,
|
|
encode_der_integer,
|
|
)
|
|
|
|
|
|
def test_der_reader_basic():
|
|
reader = DERReader(b"123456789")
|
|
assert reader.read_byte() == ord(b"1")
|
|
assert reader.read_bytes(1).tobytes() == b"2"
|
|
assert reader.read_bytes(4).tobytes() == b"3456"
|
|
|
|
with pytest.raises(ValueError):
|
|
reader.read_bytes(4)
|
|
|
|
assert reader.read_bytes(3).tobytes() == b"789"
|
|
|
|
# The input is now empty.
|
|
with pytest.raises(ValueError):
|
|
reader.read_bytes(1)
|
|
with pytest.raises(ValueError):
|
|
reader.read_byte()
|
|
|
|
|
|
def test_der():
|
|
# This input is the following structure, using
|
|
# https://github.com/google/der-ascii
|
|
#
|
|
# SEQUENCE {
|
|
# SEQUENCE {
|
|
# NULL {}
|
|
# INTEGER { 42 }
|
|
# OCTET_STRING { "hello" }
|
|
# }
|
|
# }
|
|
der = b"\x30\x0e\x30\x0c\x05\x00\x02\x01\x2a\x04\x05\x68\x65\x6c\x6c\x6f"
|
|
reader = DERReader(der)
|
|
with pytest.raises(ValueError):
|
|
reader.check_empty()
|
|
|
|
with pytest.raises(ValueError):
|
|
with reader:
|
|
pass
|
|
|
|
with pytest.raises(ZeroDivisionError):
|
|
with DERReader(der):
|
|
raise ZeroDivisionError
|
|
|
|
# Parse the outer element.
|
|
outer = reader.read_element(SEQUENCE)
|
|
reader.check_empty()
|
|
assert outer.data.tobytes() == der[2:]
|
|
|
|
# Parse the outer element with read_any_element.
|
|
reader = DERReader(der)
|
|
tag, outer2 = reader.read_any_element()
|
|
reader.check_empty()
|
|
assert tag == SEQUENCE
|
|
assert outer2.data.tobytes() == der[2:]
|
|
|
|
# Parse the outer element with read_single_element.
|
|
outer3 = DERReader(der).read_single_element(SEQUENCE)
|
|
assert outer3.data.tobytes() == der[2:]
|
|
|
|
# read_single_element rejects trailing data.
|
|
with pytest.raises(ValueError):
|
|
DERReader(der + der).read_single_element(SEQUENCE)
|
|
|
|
# Continue parsing the structure.
|
|
inner = outer.read_element(SEQUENCE)
|
|
outer.check_empty()
|
|
|
|
# Parsing a missing optional element should work.
|
|
assert inner.read_optional_element(INTEGER) is None
|
|
|
|
null = inner.read_element(NULL)
|
|
null.check_empty()
|
|
|
|
# Parsing a present optional element should work.
|
|
integer = inner.read_optional_element(INTEGER)
|
|
assert integer.as_integer() == 42
|
|
|
|
octet_string = inner.read_element(OCTET_STRING)
|
|
assert octet_string.data.tobytes() == b"hello"
|
|
|
|
# Parsing a missing optional element should work when the input is empty.
|
|
inner.check_empty()
|
|
assert inner.read_optional_element(INTEGER) is None
|
|
|
|
# Re-encode the same structure.
|
|
der2 = encode_der(
|
|
SEQUENCE,
|
|
encode_der(
|
|
SEQUENCE,
|
|
encode_der(NULL),
|
|
encode_der(INTEGER, encode_der_integer(42)),
|
|
encode_der(OCTET_STRING, b"hello"),
|
|
),
|
|
)
|
|
assert der2 == der
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"length,header",
|
|
[
|
|
# Single-byte lengths.
|
|
(0, b"\x04\x00"),
|
|
(1, b"\x04\x01"),
|
|
(2, b"\x04\x02"),
|
|
(127, b"\x04\x7f"),
|
|
# Long-form lengths.
|
|
(128, b"\x04\x81\x80"),
|
|
(129, b"\x04\x81\x81"),
|
|
(255, b"\x04\x81\xff"),
|
|
(0x100, b"\x04\x82\x01\x00"),
|
|
(0x101, b"\x04\x82\x01\x01"),
|
|
(0xFFFF, b"\x04\x82\xff\xff"),
|
|
(0x10000, b"\x04\x83\x01\x00\x00"),
|
|
],
|
|
)
|
|
def test_der_lengths(length, header):
|
|
body = length * b"a"
|
|
der = header + body
|
|
|
|
reader = DERReader(der)
|
|
element = reader.read_element(OCTET_STRING)
|
|
reader.check_empty()
|
|
assert element.data.tobytes() == body
|
|
|
|
assert encode_der(OCTET_STRING, body) == der
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bad_input",
|
|
[
|
|
# The input ended before the tag.
|
|
b"",
|
|
# The input ended before the length.
|
|
b"\x30",
|
|
# The input ended before the second byte of the length.
|
|
b"\x30\x81",
|
|
# The input ended before the body.
|
|
b"\x30\x01",
|
|
# The length used long form when it should be short form.
|
|
b"\x30\x81\x01\x00",
|
|
# The length was not minimally-encoded.
|
|
b"\x30\x82\x00\x80" + (0x80 * b"a"),
|
|
# Indefinite-length encoding is not valid DER.
|
|
b"\x30\x80\x00\x00"
|
|
# Tag number (the bottom 5 bits) 31 indicates long form tags, which we
|
|
# do not support.
|
|
b"\x1f\x00",
|
|
b"\x9f\x00",
|
|
b"\xbf\x00",
|
|
b"\xff\x00",
|
|
],
|
|
)
|
|
def test_der_reader_bad_input(bad_input):
|
|
reader = DERReader(bad_input)
|
|
with pytest.raises(ValueError):
|
|
reader.read_any_element()
|
|
|
|
|
|
def test_der_reader_wrong_tag():
|
|
reader = DERReader(b"\x04\x00")
|
|
with pytest.raises(ValueError):
|
|
reader.read_element(SEQUENCE)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,der",
|
|
[
|
|
(0, b"\x00"),
|
|
(1, b"\x01"),
|
|
(2, b"\x02"),
|
|
(3, b"\x03"),
|
|
(127, b"\x7f"),
|
|
(128, b"\x00\x80"),
|
|
(
|
|
0x112233445566778899AABBCCDDEEFF,
|
|
b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff",
|
|
),
|
|
],
|
|
)
|
|
def test_integer(value, der):
|
|
assert encode_der_integer(value) == der
|
|
assert DERReader(der).as_integer() == value
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bad_input",
|
|
[
|
|
# Zero is encoded as b"\x00", not the empty string.
|
|
b"",
|
|
# Too many leading zeros.
|
|
b"\x00\x00",
|
|
b"\x00\x7f",
|
|
# Too many leading ones.
|
|
b"\xff\xff",
|
|
b"\xff\x80",
|
|
# Negative integers are not supported.
|
|
b"\x80",
|
|
b"\x81",
|
|
b"\x80\x00\x00",
|
|
b"\xff",
|
|
],
|
|
)
|
|
def test_invalid_integer(bad_input):
|
|
reader = DERReader(bad_input)
|
|
with pytest.raises(ValueError):
|
|
reader.as_integer()
|
|
|
|
|
|
def test_invalid_integer_encode():
|
|
with pytest.raises(ValueError):
|
|
encode_der_integer(-1)
|
|
|
|
with pytest.raises(ValueError):
|
|
encode_der_integer("not an integer")
|