Skip to content

Commit 18b3b7d

Browse files
committed
Corrects BitString decoding
Properly handles initial octet which stores the number of unused bits and removes the corresponding number of bits. Adds corresponding unit tests.
1 parent a7d95a9 commit 18b3b7d

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

src/asn1.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,8 @@ def _read_value(self, cls, nr, length): # type: (int, int, int) -> any
544544
value = self._decode_object_identifier(bytes_data)
545545
elif nr in (Numbers.PrintableString, Numbers.IA5String, Numbers.UTCTime):
546546
value = self._decode_printable_string(bytes_data)
547+
elif nr == Numbers.BitString:
548+
value = self._decode_bitstring(bytes_data)
547549
else:
548550
value = bytes_data
549551
return value
@@ -647,3 +649,28 @@ def _decode_object_identifier(bytes_data): # type: (bytes) -> str
647649
def _decode_printable_string(bytes_data): # type: (bytes) -> str
648650
"""Decode a printable string."""
649651
return bytes_data.decode('utf-8')
652+
653+
@staticmethod
654+
def _decode_bitstring(bytes_data): # type: (bytes) -> str
655+
"""Decode a bitstring."""
656+
if len(bytes_data) == 0:
657+
raise Error('ASN1 syntax error')
658+
659+
num_unused_bits = bytes_data[0]
660+
if not (0 <= num_unused_bits <= 7):
661+
raise Error('ASN1 syntax error')
662+
663+
if num_unused_bits == 0:
664+
return bytes_data[1:]
665+
666+
# Shift off unused bits
667+
remaining = bytearray(bytes_data[1:])
668+
bitmask = (1 << num_unused_bits) - 1
669+
removed_bits = 0
670+
671+
for i in range(len(remaining)):
672+
byte = int(remaining[i])
673+
remaining[i] = (byte >> num_unused_bits) | (removed_bits << num_unused_bits)
674+
removed_bits = byte & bitmask
675+
676+
return bytes(remaining)

tests/test_asn1.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,24 @@ def test_printable_string(self):
435435
tag, val = dec.read()
436436
assert val == u'foo'
437437

438+
def test_bitstring(self):
439+
buf = b'\x03\x04\x00\x12\x34\x56'
440+
dec = asn1.Decoder()
441+
dec.start(buf)
442+
tag = dec.peek()
443+
assert tag == (asn1.Numbers.BitString, asn1.Types.Primitive, asn1.Classes.Universal)
444+
tag, val = dec.read()
445+
assert val == b'\x12\x34\x56'
446+
447+
def test_bitstring_unused_bits(self):
448+
buf = b'\x03\x04\x04\x12\x34\x50'
449+
dec = asn1.Decoder()
450+
dec.start(buf)
451+
tag = dec.peek()
452+
assert tag == (asn1.Numbers.BitString, asn1.Types.Primitive, asn1.Classes.Universal)
453+
tag, val = dec.read()
454+
assert val == b'\x01\x23\x45'
455+
438456
def test_unicode_printable_string(self):
439457
buf = b'\x13\x05\x66\x6f\x6f\xc3\xa9'
440458
dec = asn1.Decoder()
@@ -703,6 +721,12 @@ def test_error_object_identifier_with_too_large_first_component(self):
703721
dec.start(buf)
704722
pytest.raises(asn1.Error, dec.read)
705723

724+
def test_error_bitstring_with_too_many_unused_bits(self):
725+
buf = b'\x03\x04\x08\x12\x34\x50'
726+
dec = asn1.Decoder()
727+
dec.start(buf)
728+
pytest.raises(asn1.Error, dec.read)
729+
706730
def test_big_negative_integer(self):
707731
buf = b'\x02\x10\xff\x7f\x2b\x3a\x4d\xea\x48\x1e\x1f\x37\x7b\xa8\xbd\x7f\xb0\x16'
708732
dec = asn1.Decoder()

0 commit comments

Comments
 (0)