blob: aaff9f591f787fb3fc7e96e4d689e5f236d240cd [file] [log] [blame]
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function
import unittest
import os
from datetime import datetime, timedelta
from asn1crypto import core, util
from .unittest_data import data_decorator, data
from ._unittest_compat import patch
patch()
tests_root = os.path.dirname(__file__)
fixtures_dir = os.path.join(tests_root, 'fixtures')
class NamedBits(core.BitString):
_map = {
0: 'zero',
1: 'one',
2: 'two',
3: 'three',
4: 'four',
6: 'six',
7: 'seven',
}
class SequenceOfInts(core.SequenceOf):
_child_spec = core.Integer
class SequenceAny(core.SequenceOf):
_child_spec = core.Any
class Seq(core.Sequence):
_fields = [
('id', core.ObjectIdentifier),
('value', core.Any),
]
_oid_pair = ('id', 'value')
_oid_specs = {
'1.2.3': core.Integer,
'2.3.4': core.OctetString,
}
class CopySeq(core.Sequence):
_fields = [
('name', core.UTF8String),
('pair', Seq),
]
class NestSeqAny(core.Sequence):
_fields = [
('id', core.ObjectIdentifier),
('value', core.Any),
]
_oid_pair = ('id', 'value')
_oid_specs = {
'3.4.5': Seq,
}
class NestSeqExplicit(core.Sequence):
_fields = [
('id', core.ObjectIdentifier),
('value', NamedBits),
]
_oid_pair = ('id', 'value')
_oid_specs = {
'3.4.5': Seq,
}
class Enum(core.Enumerated):
_map = {
0: 'a',
1: 'b',
}
class ExplicitFieldDefault(core.Sequence):
_fields = [
('bits', NamedBits),
('seq', Seq, {'explicit': 2, 'default': {'id': '1.2.3', 'value': 10}}),
]
class NumChoice(core.Choice):
_alternatives = [
('one', core.Integer, {'explicit': 0}),
('two', core.Integer, {'implicit': 1}),
('three', core.Integer, {'explicit': 2}),
]
class NumChoiceOldApi(core.Choice):
_alternatives = [
('one', core.Integer, {'tag_type': 'explicit', 'tag': 0}),
('two', core.Integer, {'tag_type': 'implicit', 'tag': 1}),
('three', core.Integer, {'tag_type': 'explicit', 'tag': 2}),
]
class SeqChoice(core.Choice):
_alternatives = [
('one', CopySeq, {'explicit': 0}),
('two', CopySeq, {'implicit': 1}),
]
class SeqChoiceOldApi(core.Choice):
_alternatives = [
('one', CopySeq, {'tag_type': 'explicit', 'tag': 0}),
('two', CopySeq, {'tag_type': 'implicit', 'tag': 1}),
]
class ChoiceChoice(core.Choice):
_alternatives = [
('num', NumChoice, {'explicit': 0}),
('seq', SeqChoice, {'explicit': 1}),
]
class CCSeq(core.Sequence):
_fields = [
('cc', ChoiceChoice)
]
class ExplicitField(core.Sequence):
_fields = [
('field', NumChoice, {'tag_type': 'explicit', 'tag': 0}),
]
class ExplicitFieldOldApi(core.Sequence):
_fields = [
('field', NumChoiceOldApi, {'explicit': 0}),
]
class SetTest(core.Set):
_fields = [
('two', core.Integer, {'tag_type': 'implicit', 'tag': 2}),
('one', core.Integer, {'tag_type': 'implicit', 'tag': 1}),
]
class SetTestOldApi(core.Set):
_fields = [
('two', core.Integer, {'implicit': 2}),
('one', core.Integer, {'implicit': 1}),
]
class SetOfTest(core.SetOf):
_child_spec = core.Integer
class ConcatTest(core.Concat):
_child_specs = [Seq, core.Integer]
class IntegerConcats(core.Concat):
_child_specs = [core.Integer, core.Integer]
class MyOids(core.ObjectIdentifier):
_map = {
'1.2.3': 'abc',
'4.5.6': 'def',
}
class ApplicationTaggedInteger(core.Integer):
# This class attribute may be a 2-element tuple of integers,
# or a tuple of 2-element tuple of integers. The first form
# will be converted to the second form the first time an
# object of this type is constructed.
explicit = ((1, 10), )
class ApplicationTaggedInner(core.Sequence):
"""
TESTCASE DEFINITIONS EXPLICIT TAGS ::=
BEGIN
INNERSEQ ::= SEQUENCE {
innernumber [21] INTEGER
}
INNER ::= [APPLICATION 20] INNERSEQ
"""
explicit = (1, 20)
_fields = [
('innernumber', core.Integer, {'explicit': 21}),
]
class ApplicationTaggedOuter(core.Sequence):
"""
OUTERSEQ ::= SEQUENCE {
outernumber [11] INTEGER,
inner [12] INNER
}
OUTER ::= [APPLICATION 10] OUTERSEQ
END
"""
explicit = (1, 10)
_fields = [
('outernumber', core.Integer, {'explicit': 11}),
('inner', ApplicationTaggedInner, {'explicit': 12}),
]
class SpcPeImageFlags(core.BitString):
_map = {
0: "includeResources",
1: "includeDebugInfo",
2: "includeImportAddressTable",
}
class SpcSerializedObject(core.Sequence):
_fields = [
("classId", core.OctetString),
("serializedData", core.OctetString),
]
class SpcString(core.Choice):
_alternatives = [
("unicode", core.BMPString, {"implicit": 0}),
("ascii", core.IA5String, {"implicit": 1}),
]
class SpcLink(core.Choice):
_alternatives = [
("url", core.IA5String, {"implicit": 0}),
("moniker", SpcSerializedObject, {"implicit": 1}),
("file", SpcString, {"explicit": 2})
]
class SpcPeImageData(core.Sequence):
_fields = [
("flags", SpcPeImageFlags, {"default": "includeResources"}),
("file", SpcLink, {"explicit": 0})
]
class UTF8Sequence(core.Sequence):
_fields = [
("string", core.UTF8String)
]
class NestedUTF8Sequence(core.Sequence):
_fields = [
("seq", UTF8Sequence)
]
@data_decorator
class CoreTests(unittest.TestCase):
def test_large_tag_encode(self):
# https://misc.daniel-marschall.de/asn.1/oid_facts.html
v = core.Primitive(tag=31, contents=b'')
self.assertEqual(b'\x1f\x1f\x00', v.dump())
v = core.Primitive(tag=36, contents=b'')
self.assertEqual(b'\x1f\x24\x00', v.dump())
# One extra byte
v = core.Primitive(
class_="application",
method="constructed",
tag=73,
contents=b''
)
self.assertEqual(b'\x7f\x49\x00', v.dump())
# Two extra bytes
v = core.Primitive(
class_="application",
method="constructed",
tag=201,
contents=b''
)
self.assertEqual(b'\x7f\x81\x49\x00', v.dump())
# Three extra bytes
v = core.Primitive(
class_="application",
method="constructed",
tag=16384,
contents=b''
)
self.assertEqual(b'\x7f\x81\x80\x00\x00', v.dump())
def test_manual_construction(self):
v = core.Asn1Value(
class_="application",
method="constructed",
tag=1,
contents=b''
)
self.assertEqual(b'\x61\x00', v.dump())
def test_sequence_spec(self):
seq = Seq()
seq['id'] = '1.2.3'
self.assertEqual(core.Integer, seq.spec('value'))
seq['id'] = '2.3.4'
self.assertEqual(core.OctetString, seq.spec('value'))
def test_sequence_of_spec(self):
seq = SequenceAny()
self.assertEqual(core.Any, seq.spec())
@staticmethod
def compare_primitive_info():
return (
(core.ObjectIdentifier('1.2.3'), core.ObjectIdentifier('1.2.3'), True),
(core.Integer(1), Enum(1), False),
(core.Integer(1), core.Integer(1, implicit=5), True),
(core.Integer(1), core.Integer(1, explicit=5), True),
(core.Integer(1), core.Integer(2), False),
(core.OctetString(b''), core.OctetString(b''), True),
(core.OctetString(b''), core.OctetString(b'1'), False),
(core.OctetString(b''), core.OctetBitString(b''), False),
(core.ParsableOctetString(b'12'), core.OctetString(b'12'), True),
(core.ParsableOctetBitString(b'12'), core.OctetBitString(b'12'), True),
(core.UTF8String('12'), core.UTF8String('12'), True),
(core.UTF8String('12'), core.UTF8String('1'), False),
(core.UTF8String('12'), core.IA5String('12'), False),
)
@data('compare_primitive_info')
def compare_primitive(self, one, two, equal):
if equal:
self.assertEqual(one, two)
else:
self.assertNotEqual(one, two)
@staticmethod
def integer_info():
return (
(0, b'\x02\x01\x00'),
(255, b'\x02\x02\x00\xFF'),
(128, b'\x02\x02\x00\x80'),
(127, b'\x02\x01\x7F'),
(-127, b'\x02\x01\x81'),
(-127, b'\x02\x01\x81'),
(32768, b'\x02\x03\x00\x80\x00'),
(-32768, b'\x02\x02\x80\x00'),
(-32769, b'\x02\x03\xFF\x7F\xFF'),
)
@data('integer_info')
def integer(self, native, der_bytes):
i = core.Integer(native)
self.assertEqual(der_bytes, i.dump())
self.assertEqual(native, core.Integer.load(der_bytes).native)
@staticmethod
def utctime_info():
return (
(datetime(2030, 12, 31, 8, 30, 0, tzinfo=util.timezone.utc), b'\x17\x0D301231083000Z'),
(datetime(2049, 12, 31, 8, 30, 0, tzinfo=util.timezone.utc), b'\x17\x0D491231083000Z'),
(datetime(1950, 12, 31, 8, 30, 0, tzinfo=util.timezone.utc), b'\x17\x0D501231083000Z'),
(datetime(2018, 10, 20, 7, 35, 4, tzinfo=util.timezone(timedelta(hours=7, minutes=40))),
b'\x17\x0D181019235504Z'),
)
@data('utctime_info')
def utctime(self, native, der_bytes):
u = core.UTCTime(native)
self.assertEqual(der_bytes, u.dump())
self.assertEqual(native, core.UTCTime.load(der_bytes).native)
def test_utctime_errors(self):
with self.assertRaises(ValueError):
# is not aware
core.UTCTime(datetime.fromtimestamp(1234567890))
with self.assertRaises(ValueError):
# Is pre 1950
core.UTCTime(datetime(1910, 6, 22, 11, 33, 44, tzinfo=util.timezone.utc))
with self.assertRaises(ValueError):
# Is past 2050
core.UTCTime(datetime(2106, 2, 7, 6, 28, 16, tzinfo=util.timezone.utc))
@staticmethod
def generalized_time_info():
def tz(hours, minutes=0):
return util.create_timezone(timedelta(hours=hours, minutes=minutes))
return (
(b'\x18\x1520180405062426.0+0200', datetime(2018, 4, 5, 6, 24, 26, 0, tz(2)), b'\x18\x0f20180405042426Z'),
(b'\x18\x0f2018062419-1355', datetime(2018, 6, 24, 19, 0, 0, 0, tz(-13, -55)), b'\x18\x0f20180625085500Z'),
(b'\x18\x0d2018062419-13', datetime(2018, 6, 24, 19, 0, 0, 0, tz(-13)), b'\x18\x0f20180625080000Z'),
(b'\x18\x0b2018062419Z', datetime(2018, 6, 24, 19, 0, 0, 0, tz(0)), b'\x18\x0f20180624190000Z'),
(b'\x18\x122018062419.15+0345', datetime(2018, 6, 24, 19, 9, 0, 0, tz(3, 45)), b'\x18\x0f20180624152400Z'),
(
b'\x18\x13201806241957,433+02',
datetime(2018, 6, 24, 19, 57, 25, 980000, tz(2)),
b'\x18\x1220180624175725.98Z',
),
(
b'\x18\x1620180624195724.215999Z',
datetime(2018, 6, 24, 19, 57, 24, 215999, tz(0)),
b'\x18\x1620180624195724.215999Z',
),
(
b'\x18\x150000022910.31337-0815',
util.extended_datetime(0, 2, 29, 10, 18, 48, 132000, tz(-8, -15)),
b'\x18\x1300000229183348.132Z',
),
(b'\x18\x1520180624195724.215999', datetime(2018, 6, 24, 19, 57, 24, 215999), None),
(b'\x18\x0a2018062419', datetime(2018, 6, 24, 19, 0, 0, 0), None),
)
@data('generalized_time_info')
def generalized_time(self, ber_bytes, native, der_bytes):
decoded = core.GeneralizedTime.load(ber_bytes)
self.assertEqual(decoded.native, native)
self.assertEqual(decoded.native.tzinfo, native.tzinfo)
if der_bytes is not None:
encoded = core.GeneralizedTime(native).dump()
self.assertEqual(encoded, der_bytes)
decoded2 = core.GeneralizedTime.load(encoded)
self.assertEqual(decoded2.native, native)
else:
with self.assertRaises(ValueError):
encoded = core.GeneralizedTime(native).dump()
@staticmethod
def type_info():
return (
('universal/object_identifier.der', core.ObjectIdentifier, '1.2.840.113549.1.1.1'),
)
@data('type_info')
def parse_universal_type(self, input_filename, type_class, native):
with open(os.path.join(fixtures_dir, input_filename), 'rb') as f:
der = f.read()
parsed = type_class.load(der)
self.assertEqual(native, parsed.native)
self.assertEqual(der, parsed.dump(force=True))
def test_int_to_bit_tuple(self):
self.assertEqual((), core._int_to_bit_tuple(0, 0))
self.assertEqual((0,), core._int_to_bit_tuple(0, 1))
self.assertEqual((1,), core._int_to_bit_tuple(1, 1))
self.assertEqual((0, 0), core._int_to_bit_tuple(0, 2))
self.assertEqual((0, 1), core._int_to_bit_tuple(1, 2))
self.assertEqual((0, 0, 1), core._int_to_bit_tuple(1, 3))
self.assertEqual((0, 1, 0), core._int_to_bit_tuple(2, 3))
self.assertEqual((1, 0, 1), core._int_to_bit_tuple(5, 3))
with self.assertRaises(ValueError):
core._int_to_bit_tuple(9, 3)
with self.assertRaises(ValueError):
core._int_to_bit_tuple(-9, 5)
@staticmethod
def bit_string_info():
return (
((0, 1, 1), b'\x03\x02\x05\x60'),
((0, 1, 1, 0, 0, 0, 0, 0), b'\x03\x02\x00\x60'),
((0, 0, 0, 0, 0, 0, 0, 0), b'\x03\x02\x00\x00'),
((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), b'\x03\x03\x00\x00\x01'),
)
@data('bit_string_info')
def bit_string(self, native, der_bytes):
bs = core.BitString(native)
self.assertEqual(der_bytes, bs.dump())
self.assertEqual(native, core.BitString.load(der_bytes).native)
def test_bit_string_load_dump(self):
bs = core.BitString.load(b'\x03\x01\x00')
self.assertEqual(tuple(), bs.native)
self.assertEqual(b'\x03\x01\x00', bs.dump(True))
@staticmethod
def bit_string_error_values():
return (
# unused bits in empty bit string
(b'\x03\x01\x05',),
# too many unused bits
(b'\x03\x03\x0e\x0c\x00',),
# chunk with unused bits is not last chunk
(b'\x23\x80\x03\x02\x01\xfe\x03\x02\x00\x55\x00\x00',),
)
@data('bit_string_error_values')
def bit_string_errors(self, enc_bytes):
with self.assertRaises(ValueError):
core.BitString.load(enc_bytes).native
def test_cast(self):
a = core.OctetBitString(b'\x00\x01\x02\x03')
self.assertEqual(b'\x00\x01\x02\x03', a.native)
b = a.cast(core.BitString)
self.assertIsInstance(b, core.BitString)
self.assertEqual(
(
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1
),
b.native
)
c = a.cast(core.IntegerBitString)
self.assertIsInstance(c, core.IntegerBitString)
self.assertEqual(66051, c.native)
def test_load(self):
i = core.load(b'\x02\x01\x00')
self.assertIsInstance(i, core.Integer)
self.assertEqual(0, i.native)
def test_load_wrong_type(self):
with self.assertRaises(TypeError):
core.load('\x02\x01\x00')
@staticmethod
def truncated_der_byte_strings():
return (
(b'',),
(b'\x30',),
(b'\x30\x03\x02\x00\x02',),
)
@data('truncated_der_byte_strings')
def truncated(self, der_bytes):
with self.assertRaises(ValueError):
core.load(der_bytes).native
def test_strict(self):
with self.assertRaises(ValueError):
core.load(b'\x02\x01\x00\x00', strict=True)
def test_strict_on_class(self):
with self.assertRaises(ValueError):
core.Integer.load(b'\x02\x01\x00\x00', strict=True)
def test_strict_concat(self):
with self.assertRaises(ValueError):
IntegerConcats.load(b'\x02\x01\x00\x02\x01\x00\x00', strict=True)
def test_strict_choice(self):
with self.assertRaises(ValueError):
NumChoice.load(b'\xA0\x03\x02\x01\x00\x00', strict=True)
with self.assertRaises(ValueError):
NumChoiceOldApi.load(b'\xA0\x03\x02\x01\x00\x00', strict=True)
def test_choice_parse_return(self):
nc = NumChoice.load(b'\xA0\x03\x02\x01\x00\x00')
nc._parsed = None
self.assertEqual(0, nc.parse().native)
def test_sequece_choice_choice(self):
CCSeq({
'cc': ChoiceChoice(
'num',
NumChoice('one', core.Integer(0))
)
})
def test_bit_string_item_access(self):
named = core.BitString()
named[0] = True
self.assertEqual(False, named[2])
self.assertEqual(False, named[1])
self.assertEqual(True, named[0])
@staticmethod
def mapped_bit_string_info():
return (
(
(0, 1, 1),
b'\x03\x02\x05\x60',
set(['one', 'two'])
),
(
(0,),
b'\x03\x01\x00',
set()
),
(
set(['one', 'two']),
b'\x03\x02\x05\x60',
set(['one', 'two'])
)
)
@data('mapped_bit_string_info')
def mapped_bit_string(self, input_native, der_bytes, native):
named = NamedBits(input_native)
self.assertEqual(der_bytes, named.dump())
self.assertEqual(native, NamedBits.load(der_bytes).native)
def test_mapped_bit_string_item_access(self):
named = NamedBits()
named['one'] = True
self.assertEqual(False, named['two'])
self.assertEqual(True, named['one'])
self.assertEqual(True, 'one' in named.native)
def test_mapped_bit_string_unset_bit(self):
named = NamedBits(set(['one', 'two']))
named['one'] = False
self.assertEqual(True, named['two'])
self.assertEqual(set(['two']), named.native)
def test_mapped_bit_string_sparse(self):
named = NamedBits((0, 0, 0, 0, 0, 1))
self.assertEqual(False, named['two'])
self.assertEqual(True, named[5])
self.assertEqual(True, 5 in named.native)
def test_mapped_bit_string_numeric(self):
named = NamedBits()
named[1] = True
self.assertEqual(True, named['one'])
self.assertEqual(set(['one']), named.native)
def test_get_sequence_value(self):
seq = SequenceOfInts([1, 2])
self.assertEqual(2, seq[1].native)
def test_replace_sequence_value(self):
seq = SequenceOfInts([1, 2])
self.assertEqual([1, 2], seq.native)
seq[0] = 5
self.assertEqual([5, 2], seq.native)
def test_add_to_end_sequence_value(self):
seq = SequenceOfInts([1, 2])
self.assertEqual([1, 2], seq.native)
seq[2] = 5
self.assertEqual([1, 2, 5], seq.native)
seq.append(6)
self.assertEqual([1, 2, 5, 6], seq.native)
def test_delete_sequence_value(self):
seq = SequenceOfInts([1, 2])
self.assertEqual([1, 2], seq.native)
del seq[0]
self.assertEqual([2], seq.native)
def test_sequence_any_asn1value(self):
seq = SequenceAny()
seq.append(core.Integer(5))
self.assertEqual([5], seq.native)
def test_sequence_any_native_value(self):
seq = SequenceAny()
with self.assertRaises(ValueError):
seq.append(5)
def test_copy(self):
a = core.Integer(200)
b = a.copy()
self.assertNotEqual(id(a), id(b))
self.assertEqual(a.contents, b.contents)
self.assertEqual(a.dump(), b.dump())
def test_copy_mutable(self):
a = CopySeq({'name': 'foo', 'pair': {'id': '1.2.3', 'value': 5}})
# Cache the native representation so it is copied during the copy operation
a.native
b = a.copy()
self.assertNotEqual(id(a), id(b))
self.assertNotEqual(id(a['pair']), id(b['pair']))
self.assertEqual(a.contents, b.contents)
self.assertEqual(a.dump(), b.dump())
self.assertEqual(a['pair']['value'].native, b['pair']['value'].native)
a['pair']['value'] = 6
self.assertNotEqual(a['pair']['value'].native, b['pair']['value'].native)
a.native['pair']['value'] = 6
self.assertNotEqual(a.native['pair']['value'], b.native['pair']['value'])
self.assertNotEqual(a.contents, b.contents)
self.assertNotEqual(a.dump(), b.dump())
def test_explicit_tag_header(self):
val = NumChoice.load(b'\xa0\x03\x02\x01\x00')
self.assertEqual(b'\xa0\x03\x02\x01', val.chosen._header)
self.assertEqual(b'\x00', val.chosen.contents)
val2 = NumChoiceOldApi.load(b'\xa0\x03\x02\x01\x00')
self.assertEqual(b'\xa0\x03\x02\x01', val2.chosen._header)
self.assertEqual(b'\x00', val2.chosen.contents)
def test_explicit_field_default(self):
val = ExplicitFieldDefault.load(b'\x30\x0f\x03\x02\x06@\xa2\x090\x07\x06\x02*\x03\x02\x01\x01')
self.assertEqual(set(['one']), val['bits'].native)
self.assertEqual(
util.OrderedDict([
('id', '1.2.3'),
('value', 1)
]),
val['seq'].native
)
def test_explicit_header_field_choice(self):
der = b'\x30\x07\xa0\x05\xa0\x03\x02\x01\x00'
val = ExplicitField.load(der)
self.assertEqual(0, val['field'].chosen.native)
self.assertEqual(der, val.dump(force=True))
val2 = ExplicitFieldOldApi.load(der)
self.assertEqual(0, val2['field'].chosen.native)
self.assertEqual(der, val2.dump(force=True))
def test_retag(self):
a = core.Integer(200)
b = a.retag('explicit', 0)
self.assertNotEqual(id(a), id(b))
self.assertEqual(a.contents, b.contents)
self.assertNotEqual(a.dump(), b.dump())
def test_untag(self):
a = core.Integer(200, explicit=0)
b = a.untag()
self.assertNotEqual(id(a), id(b))
self.assertEqual(a.contents, b.contents)
self.assertNotEqual(a.dump(), b.dump())
def test_choice_dict_name(self):
a = CopySeq({'name': 'foo', 'pair': {'id': '1.2.3', 'value': 5}})
choice = SeqChoice({'one': a})
self.assertEqual('one', choice.name)
with self.assertRaises(ValueError):
SeqChoice({})
with self.assertRaises(ValueError):
SeqChoice({'one': a, 'two': a})
choice2 = SeqChoiceOldApi({'one': a})
self.assertEqual('one', choice2.name)
with self.assertRaises(ValueError):
SeqChoiceOldApi({})
with self.assertRaises(ValueError):
SeqChoiceOldApi({'one': a, 'two': a})
def test_choice_tuple_name(self):
a = CopySeq({'name': 'foo', 'pair': {'id': '1.2.3', 'value': 5}})
choice = SeqChoice(('one', a))
self.assertEqual('one', choice.name)
with self.assertRaises(ValueError):
SeqChoice(('one',))
with self.assertRaises(ValueError):
SeqChoice(('one', a, None))
choice2 = SeqChoiceOldApi(('one', a))
self.assertEqual('one', choice2.name)
with self.assertRaises(ValueError):
SeqChoiceOldApi(('one',))
with self.assertRaises(ValueError):
SeqChoiceOldApi(('one', a, None))
def test_load_invalid_choice(self):
with self.assertRaises(ValueError):
NumChoice.load(b'\x02\x01\x00')
with self.assertRaises(ValueError):
NumChoiceOldApi.load(b'\x02\x01\x00')
def test_fix_tagging_choice(self):
correct = core.Integer(200, explicit=2)
choice = NumChoice(
name='three',
value=core.Integer(200, explicit=1)
)
self.assertEqual(correct.dump(), choice.dump())
self.assertEqual(correct.explicit, choice.chosen.explicit)
choice2 = NumChoiceOldApi(
name='three',
value=core.Integer(200, explicit=1)
)
self.assertEqual(correct.dump(), choice2.dump())
self.assertEqual(correct.explicit, choice2.chosen.explicit)
def test_copy_choice_mutate(self):
a = CopySeq({'name': 'foo', 'pair': {'id': '1.2.3', 'value': 5}})
choice = SeqChoice(
name='one',
value=a
)
choice.dump()
choice_copy = choice.copy()
choice.chosen['name'] = 'bar'
self.assertNotEqual(choice.chosen['name'], choice_copy.chosen['name'])
choice2 = SeqChoiceOldApi(
name='one',
value=a
)
choice2.dump()
choice2_copy = choice2.copy()
choice2.chosen['name'] = 'bar'
self.assertNotEqual(choice2.chosen['name'], choice2_copy.chosen['name'])
def test_dump_ber_indefinite(self):
# A simple primitive type that is indefinite-length-encoded will be
# automatically re-encoded to DER encoding
data = b'\x2C\x80\x0C\x03foo\x00\x00'
v = core.UTF8String.load(data)
self.assertEqual(True, v._indefinite)
self.assertEqual('foo', v.native)
self.assertEqual(b'\x0C\x03foo', v.dump())
# In this case the indefinite length items are nested, and the
# top-level item is fixed-length, so it won't get automatically
# re-encoded
data = b'\x30\x0d\x30\x80\x2C\x80\x0C\x03foo\x00\x00\x00\x00'
v = NestedUTF8Sequence.load(data)
self.assertEqual(data, v.dump())
# Here both the top-level and the nested encoding will get fixed since
# the top-level being indefinitely triggers a full re-encoding
data = b'\x30\x80\x30\x09\x2C\x80\x0C\x03foo\x00\x00\x00\x00'
v = NestedUTF8Sequence.load(data)
self.assertEqual(b'\x30\x07\x30\x05\x0C\x03foo', v.dump())
def test_copy_indefinite(self):
v = core.BitString.load(b'\x23\x80\x03\x02\x00\x04\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(3, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual((0, 0, 0, 0, 0, 1, 0, 0), v2.native)
self.assertEqual(b'\x03\x02\x00\x04', v2.dump())
v = core.OctetBitString.load(b'\x23\x80\x03\x02\x00\x04\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(3, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(b'\x04', v2.native)
self.assertEqual(b'\x03\x02\x00\x04', v2.dump())
v = core.ParsableOctetBitString.load(b'\x23\x80\x03\x04\x00\x02\x01\x04\x00\x00')
self.assertEqual(4, v.parsed.native)
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(3, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(4, v2.parsed.native)
self.assertEqual(b'\x03\x04\x00\x02\x01\x04', v2.dump())
v = core.IntegerBitString.load(b'\x23\x80\x03\x02\x00\x04\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(3, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(4, v2.native)
self.assertEqual(b'\x03\x02\x00\x04', v2.dump())
v = core.OctetString.load(b'\x24\x80\x04\x03foo\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(4, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(b'foo', v2.native)
self.assertEqual(b'\x04\x03foo', v2.dump())
v = core.IntegerOctetString.load(b'\x24\x80\x04\x01\x04\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(4, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(4, v2.native)
self.assertEqual(b'\x04\x01\x04', v2.dump())
v = core.ParsableOctetString.load(b'\x24\x80\x04\x03\x02\x01\x04\x00\x00')
self.assertEqual(4, v.parsed.native)
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(4, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual(4, v2.parsed.native)
self.assertEqual(b'\x02\x01\x04', v2.__bytes__())
self.assertEqual(b'\x04\x03\x02\x01\x04', v2.dump())
v = core.UTF8String.load(b'\x2C\x80\x0C\x03foo\x00\x00')
self.assertEqual(True, v._indefinite)
v2 = v.copy()
self.assertEqual(0, v2.method)
self.assertEqual(12, v2.tag)
self.assertEqual(False, v2._indefinite)
self.assertEqual('foo', v2.native)
self.assertEqual(b'\x0C\x03foo', v2.dump())
def test_concat(self):
child1 = Seq({
'id': '1.2.3',
'value': 1
})
child2 = core.Integer(0)
parent = ConcatTest([
child1,
child2
])
self.assertEqual(child1, parent[0])
self.assertEqual(child2, parent[1])
self.assertEqual(child1.dump() + child2.dump(), parent.dump())
def test_oid_map_unmap(self):
self.assertEqual('abc', MyOids.map('1.2.3'))
self.assertEqual('def', MyOids.map('4.5.6'))
self.assertEqual('7.8.9', MyOids.map('7.8.9'))
self.assertEqual('1.2.3', MyOids.unmap('abc'))
self.assertEqual('4.5.6', MyOids.unmap('def'))
self.assertEqual('7.8.9', MyOids.unmap('7.8.9'))
with self.assertRaises(ValueError):
MyOids.unmap('no_such_mapping')
def test_oid_dotted_native(self):
self.assertEqual('abc', MyOids('1.2.3').native)
self.assertEqual('1.2.3', MyOids('1.2.3').dotted)
self.assertEqual('abc', MyOids('abc').native)
self.assertEqual('1.2.3', MyOids('abc').dotted)
def test_dump_set(self):
st = SetTest({'two': 2, 'one': 1})
self.assertEqual(b'1\x06\x81\x01\x01\x82\x01\x02', st.dump())
def test_dump_set_of(self):
st = SetOfTest([3, 2, 1])
self.assertEqual(b'1\x09\x02\x01\x01\x02\x01\x02\x02\x01\x03', st.dump())
def test_indefinite_length_octet_string(self):
data = b'$\x80\x04\x02\x01\x01\x04\x01\x01\x00\x00'
a = core.OctetString.load(data)
self.assertEqual(b'\x01\x01\x01', a.native)
self.assertEqual(b'\x01\x01\x01', a.__bytes__())
self.assertEqual(1, a.method)
# Test copying moves internal state
self.assertEqual(a._bytes, a.copy()._bytes)
def test_indefinite_length_octet_string_2(self):
data = b'$\x80\x04\r\x8d\xff\xf0\x98\x076\xaf\x93nB:\xcf\xcc\x04\x15' \
b'\x92w\xf7\xf0\xe4y\xff\xc7\xdc3\xb2\xd0={\x1a\x18mDr\xaaI\x00\x00'
a = core.OctetString.load(data)
self.assertEqual(
b'\x8d\xff\xf0\x98\x076\xaf\x93nB:\xcf\xcc\x92w\xf7\xf0\xe4y\xff\xc7\xdc3\xb2\xd0={\x1a\x18mDr\xaaI',
a.native
)
def test_nested_indefinite_length_octet_string(self):
data = b'\x24\x80\x24\x80\x24\x80\x04\x00\x00\x00\x00\x00\x00\x00'
a = core.load(data)
self.assertEqual(b'', a.native)
self.assertEqual(b'', a.__bytes__())
self.assertEqual(1, a.method)
self.assertEqual(b'\x04\x00', a.dump(force=True))
# Test copying moves internal state
self.assertEqual(a._bytes, a.copy()._bytes)
def test_indefinite_length_integer_octet_string(self):
data = b'$\x80\x04\x02\x01\x01\x04\x01\x01\x00\x00'
a = core.IntegerOctetString.load(data)
self.assertEqual(65793, a.native)
self.assertEqual(1, a.method)
self.assertEqual(b'\x01\x01\x01', a.cast(core.OctetString).native)
def test_indefinite_length_parsable_octet_string(self):
data = b'$\x80\x04\x02\x04\x01\x04\x01\x01\x00\x00'
a = core.ParsableOctetString.load(data)
self.assertEqual(b'\x04\x01\x01', a.parsed.dump())
self.assertEqual(b'\x04\x01\x01', a.__bytes__())
self.assertEqual(1, a.method)
self.assertEqual(b'\x01', a.parsed.native)
self.assertEqual(b'\x01', a.native)
self.assertEqual(b'\x04\x01\x01', a.cast(core.OctetString).native)
# Test copying moves internal state
self.assertEqual(a._bytes, a.copy()._bytes)
self.assertEqual(a._parsed, a.copy()._parsed)
def test_indefinite_length_utf8string(self):
data = b'\x2C\x80\x0C\x02\x61\x62\x0C\x01\x63\x00\x00'
a = core.UTF8String.load(data)
self.assertEqual('abc', a.native)
self.assertEqual('abc', a.__unicode__())
self.assertEqual(1, a.method)
# Ensure a forced re-encoding is proper DER
self.assertEqual(b'\x0C\x03\x61\x62\x63', a.dump(force=True))
# Test copying moves internal state
self.assertEqual(a._unicode, a.copy()._unicode)
def test_indefinite_length_bit_string(self):
data = b'#\x80\x03\x02\x00\x01\x03\x02\x02\x04\x00\x00'
a = core.BitString.load(data)
self.assertEqual((0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1), a.native)
self.assertEqual((0, 0), a.unused_bits)
# Example from X.690 §8.6.4.2
prim = core.BitString.load(b'\x03\x07\x04\x0A\x3B\x5F\x29\x1C\xD0')
self.assertEqual((0, 0, 0, 0), prim.unused_bits)
indef = core.BitString.load(b'\x23\x80\x03\x03\x00\x0a\x3b\x03\x05\x04\x5f\x29\x1c\xd0\x00\x00')
self.assertEqual(prim.native, indef.native)
self.assertEqual(core._int_to_bit_tuple(0x0A3B5F291CD, 44), indef.native)
self.assertEqual((0, 0, 0, 0), indef.unused_bits)
unused = core.BitString.load(b'\x23\x80\x03\x03\x00\x0a\x3b\x03\x05\x04\x5f\x29\x1c\xdd\x00\x00')
self.assertEqual(indef.native, unused.native)
self.assertEqual((1, 1, 0, 1), unused.unused_bits)
unused.set(indef.native)
self.assertEqual(indef.native, unused.native)
self.assertEqual((0, 0, 0, 0), unused.unused_bits)
def test_integer_bit_string(self):
a = core.IntegerBitString.load(b'\x03\x02\x04\xcb')
self.assertEqual(12, a.native)
self.assertEqual((1, 0, 1, 1), a.unused_bits)
b = a.copy()
self.assertEqual(12, b.native)
self.assertEqual((1, 0, 1, 1), b.unused_bits)
a.set(56)
self.assertEqual((), a.unused_bits)
self.assertEqual(56, a.native)
self.assertEqual(b'\x03\x02\x00\x38', a.dump())
with self.assertRaises(TypeError):
a.set('badtype')
with self.assertRaises(ValueError):
core.IntegerBitString(-1)
def test_indefinite_length_integer_bit_string(self):
data = b'#\x80\x03\x02\x00\x01\x03\x02\x00\x04\x00\x00'
a = core.IntegerBitString.load(data)
self.assertEqual(260, a.native)
self.assertEqual((), a.unused_bits)
a = core.IntegerBitString.load(b'\x23\x80\x00\x00')
self.assertEqual(0, a.native)
self.assertEqual((), a.unused_bits)
a = core.IntegerBitString.load(b'\x23\x80\x03\x01\x00\x03\x03\x03\x03\x03\x00\x00')
self.assertEqual(96, a.native)
self.assertEqual((0, 1, 1), a.unused_bits)
a.set(56)
self.assertEqual((), a.unused_bits)
self.assertEqual(56, a.native)
self.assertEqual(b'\x03\x02\x00\x38', a.dump())
@data('bit_string_error_values')
def integer_bit_string_errors(self, enc_bytes):
with self.assertRaises(ValueError):
core.IntegerBitString.load(enc_bytes).native
def test_octet_bit_string(self):
a = core.OctetBitString.load(b'\x03\x02\x04\xcb')
self.assertEqual(b'\xc0', a.native)
self.assertEqual((1, 0, 1, 1), a.unused_bits)
a.set(b'\x38')
self.assertEqual((), a.unused_bits)
self.assertEqual(b'\x38', a.native)
self.assertEqual(b'\x03\x02\x00\x38', a.dump())
with self.assertRaises(TypeError):
a.set('badtype')
def test_indefinite_length_octet_bit_string(self):
data = b'#\x80\x03\x02\x00\x01\x03\x02\x00\x04\x00\x00'
a = core.OctetBitString.load(data)
self.assertEqual(b'\x01\x04', a.native)
self.assertEqual(b'\x01\x04', a.__bytes__())
# Test copying moves internal state
self.assertEqual(a._bytes, a.copy()._bytes)
# octet bit string with unused bits
a = core.OctetBitString.load(b'\x23\x80\x03\x05\x05\x74\x65\x73\x74\x00\x00')
self.assertEqual(b'\x74\x65\x73\x60', a.native)
self.assertEqual((1, 0, 1, 0, 0), a.unused_bits)
a.set(b'\x38')
self.assertEqual((), a.unused_bits)
self.assertEqual(b'\x38', a.native)
self.assertEqual(b'\x03\x02\x00\x38', a.dump())
@data('bit_string_error_values')
def octet_bit_string_errors(self, enc_bytes):
with self.assertRaises(ValueError):
core.OctetBitString.load(enc_bytes).native
def test_indefinite_length_parsable_octet_bit_string(self):
data = b'#\x80\x03\x03\x00\x0C\x02\x03\x03\x00\x61\x62\x00\x00'
a = core.ParsableOctetBitString.load(data)
self.assertEqual(b'\x0C\x02\x61\x62', a.parsed.dump())
self.assertEqual(b'\x0C\x02\x61\x62', a.__bytes__())
self.assertEqual('ab', a.parsed.native)
self.assertEqual('ab', a.native)
# Test copying moves internal state
self.assertEqual(a._bytes, a.copy()._bytes)
self.assertEqual(a._parsed, a.copy()._parsed)
with self.assertRaises(ValueError):
# parsable octet bit string with unused bits
core.ParsableOctetBitString.load(b'\x23\x80\x03\x03\x04\x02\x00\x03\x03\x04\x12\xa0\x00\x00').native
def test_integer_octet_string(self):
v = core.IntegerOctetString(10)
self.assertEqual(10, v.native)
with self.assertRaises(TypeError):
core.IntegerOctetString('0')
with self.assertRaises(ValueError):
core.IntegerOctetString(-1)
def test_explicit_application_tag(self):
data = b'\x6a\x81\x03\x02\x01\x00'
ati = ApplicationTaggedInteger.load(data)
self.assertEqual(((1, 10),), ati.explicit)
self.assertEqual(0, ati.class_)
self.assertEqual(2, ati.tag)
self.assertEqual(0, ati.native)
# The output encoding is DER, whereas the input was not, so
# the length encoding changes from long form to short form
self.assertEqual(b'\x6a\x03\x02\x01\x00', ati.dump(force=True))
def test_required_field(self):
with self.assertRaisesRegexp(ValueError, '"id" is missing from structure'):
Seq({'value': core.Integer(5)}).dump()
def test_explicit_application_tag_nested(self):
# tag = [APPLICATION 10] constructed; length = 18
# OUTER SEQUENCE: tag = [UNIVERSAL 16] constructed; length = 16
# outernumber : tag = [11] constructed; length = 3
# INTEGER: tag = [UNIVERSAL 2] primitive; length = 1
# 23
# inner : tag = [12] constructed; length = 9
# tag = [APPLICATION 20] constructed; length = 7
# INNER SEQUENCE: tag = [UNIVERSAL 16] constructed; length = 5
# innernumber : tag = [21] constructed; length = 3
# INTEGER: tag = [UNIVERSAL 2] primitive; length = 1
# 42
der = (
b'\x6A\x12\x30\x10\xAB\x03\x02\x01\x17\xAC\x09\x74'
b'\x07\x30\x05\xB5\x03\x02\x01\x2A'
)
ato = ApplicationTaggedOuter.load(der)
self.assertEqual(((1, 10),), ato.explicit)
self.assertEqual(0, ato.class_)
self.assertEqual(16, ato.tag)
self.assertEqual(1, ato.method)
onum = ato['outernumber']
self.assertEqual(((2, 11),), onum.explicit)
self.assertEqual(0, onum.class_)
self.assertEqual(2, onum.tag)
self.assertEqual(0, onum.method)
self.assertEqual(23, onum.native)
ati = ato['inner']
self.assertEqual(((1, 20), (2, 12)), ati.explicit)
self.assertEqual(0, ati.class_)
self.assertEqual(16, ati.tag)
self.assertEqual(util.OrderedDict([('innernumber', 42)]), ati.native)
inum = ati['innernumber']
self.assertEqual(((2, 21),), inum.explicit)
self.assertEqual(0, inum.class_)
self.assertEqual(2, inum.tag)
self.assertEqual(0, inum.method)
self.assertEqual(42, inum.native)
self.assertEqual(der, ato.dump(force=True))
def test_sequence_choice_field_by_tuple(self):
val = ExplicitField({'field': ('one', 32)})
self.assertEqual('one', val['field'].name)
self.assertEqual(32, val['field'].chosen.native)
def test_sequence_choice_field_by_dict(self):
val = ExplicitField({'field': {'two': 32}})
self.assertEqual('two', val['field'].name)
self.assertEqual(32, val['field'].chosen.native)
def test_nested_explicit_tag_choice(self):
# Explicitly tagged values have a _header that contains
# the explicit tag and the header for the contained value.
# When parsing nested Choice values, it is necessary to not pull
# up the next Choice value's header, since Choice values
# themselves don't have their own header and it will result in
# duplication.
data = b'\x30\x09\x03\x01\x00\xa0\x04\xa2\x02\x80\x00'
image_data = SpcPeImageData.load(data, strict=True)
self.assertEqual(data[2:5], image_data['flags'].dump())
self.assertEqual(data[5:11], image_data['file'].dump())
self.assertEqual(data[5:7], image_data['file']._header)
self.assertEqual(data[7:11], image_data['file'].chosen.dump())
self.assertEqual(data[7:9], image_data['file'].chosen._header)
self.assertEqual(data[9:11], image_data['file'].chosen.chosen.dump())
self.assertEqual(data[9:11], image_data['file'].chosen.chosen._header)
image_data2 = SpcPeImageData.load(data, strict=True)
self.assertEqual(data[2:5], image_data2['flags'].dump(True))
self.assertEqual(data[5:11], image_data2['file'].dump(True))
self.assertEqual(data[5:7], image_data2['file']._header)
self.assertEqual(data[7:11], image_data2['file'].chosen.dump(True))
self.assertEqual(data[7:9], image_data2['file'].chosen._header)
self.assertEqual(data[9:11], image_data2['file'].chosen.chosen.dump(True))
self.assertEqual(data[9:11], image_data2['file'].chosen.chosen._header)
def test_choice_dump_header_native(self):
s = SpcString({'unicode': 'test'})
self.assertEqual(b'\x80\x08\x00t\x00e\x00s\x00t', s.dump())
self.assertEqual(b'', s._header)
self.assertEqual('test', s.native)
self.assertEqual(b'\x80\x08', s.chosen._header)
self.assertEqual('test', s.chosen.native)
link = SpcLink('file', {'unicode': 'test'})
self.assertEqual(b'\xa2\x0a\x80\x08\x00t\x00e\x00s\x00t', link.dump())
self.assertEqual(b'', link._header)
self.assertEqual('test', link.native)
self.assertEqual(b'\xa2\x0a', link.chosen._header)
self.assertEqual('test', link.chosen.native)
self.assertEqual(b'\x80\x08', link.chosen.chosen._header)
self.assertEqual('test', link.chosen.chosen.native)
def test_parse_broken_sequence_fields_repeatedly(self):
s = Seq.load(b'\x30\x06\x88\x00\x00\x00\x00\x00')
with self.assertRaises(ValueError):
s.native
with self.assertRaises(ValueError):
s.native
def test_parse_broken_sequenceof_children_repeatedly(self):
s = SequenceOfInts.load(b'\x30\x06\x88\x00\x00\x00\x00\x00')
with self.assertRaises(ValueError):
s.native
with self.assertRaises(ValueError):
s.native
def test_wrong_asn1value(self):
with self.assertRaises(TypeError):
Seq({
'id': core.Integer(1),
'value': 1
})
def test_wrong_asn1value2(self):
with self.assertRaises(TypeError):
CopySeq({
'name': core.UTF8String('Test'),
'pair': core.Integer(1)
})
def test_wrong_asn1value3(self):
with self.assertRaises(TypeError):
NestSeqAny({
'id': '3.4.5',
'value': core.Integer(1)
})
def test_wrong_asn1value4(self):
with self.assertRaises(TypeError):
NestSeqExplicit({
'id': '3.4.5',
'value': core.Integer(1)
})
def test_integer_octet_string_encoded_width(self):
a = core.IntegerOctetString(1)
self.assertEqual(1, a.native)
self.assertEqual(b'\x04\x01\x01', a.dump())
b = core.IntegerOctetString(1)
b.set_encoded_width(4)
self.assertEqual(1, b.native)
self.assertEqual(b'\x04\x04\x00\x00\x00\x01', b.dump())