Skip to content

Commit 8e3f653

Browse files
committed
add support for parsing implicit DER tags
1 parent 55d2b56 commit 8e3f653

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

src/ecdsa/der.py

+43
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,49 @@ def remove_constructed(string):
142142
return tag, body, rest
143143

144144

145+
def remove_implicit(string, exp_class="context-specific"):
146+
"""
147+
Removes an IMPLICIT tagged value from ``string`` following :term:`DER`.
148+
149+
:param bytes string: a byte string that can have one or more
150+
DER elements.
151+
:param str exp_class: the expected tag class of the implicitly
152+
encoded value. Possible values are: "context-specific", "application",
153+
and "private".
154+
:return: a tuple with first value being the tag without indicator bits,
155+
second being the raw bytes of the value and the third one being
156+
remaining bytes (or an empty string if there are none)
157+
:rtype: tuple(int,bytes,bytes)
158+
"""
159+
if exp_class not in ("context-specific", "application", "private"):
160+
raise ValueError("invalid `exp_class` value")
161+
if exp_class == "application":
162+
tag_class = 0b01000000
163+
elif exp_class == "context-specific":
164+
tag_class = 0b10000000
165+
else:
166+
assert exp_class == "private"
167+
tag_class = 0b11000000
168+
tag_mask = 0b11000000
169+
170+
s0 = str_idx_as_int(string, 0)
171+
172+
if (s0 & tag_mask) != tag_class:
173+
raise UnexpectedDER(
174+
"wanted class {0}, got 0x{1:02x} tag".format(exp_class, s0)
175+
)
176+
if s0 & 0b00100000 != 0:
177+
raise UnexpectedDER(
178+
"wanted type primitive, got 0x{0:02x} tag".format(s0)
179+
)
180+
181+
tag = s0 & 0x1F
182+
length, llen = read_length(string[1:])
183+
body = string[1 + llen : 1 + llen + length]
184+
rest = string[1 + llen + length :]
185+
return tag, body, rest
186+
187+
145188
def remove_sequence(string):
146189
if not string:
147190
raise UnexpectedDER("Empty string does not encode a sequence")

src/ecdsa/test_der.py

+68
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
remove_object,
2323
encode_oid,
2424
remove_constructed,
25+
remove_implicit,
2526
remove_octet_string,
2627
remove_sequence,
2728
)
@@ -396,6 +397,73 @@ def test_with_malformed_tag(self):
396397
self.assertIn("constructed tag", str(e.exception))
397398

398399

400+
class TestRemoveImplicit(unittest.TestCase):
401+
@classmethod
402+
def setUpClass(cls):
403+
cls.exp_tag = 6
404+
cls.exp_data = b"\x0a\x0b"
405+
# data with application tag class
406+
cls.data_application = b"\x46\x02\x0a\x0b"
407+
# data with context-specific tag class
408+
cls.data_context_specific = b"\x86\x02\x0a\x0b"
409+
# data with private tag class
410+
cls.data_private = b"\xc6\x02\x0a\x0b"
411+
412+
def test_simple(self):
413+
tag, body, rest = remove_implicit(self.data_context_specific)
414+
415+
self.assertEqual(tag, self.exp_tag)
416+
self.assertEqual(body, self.exp_data)
417+
self.assertEqual(rest, b"")
418+
419+
def test_wrong_expected_class(self):
420+
with self.assertRaises(ValueError) as e:
421+
remove_implicit(self.data_context_specific, "foobar")
422+
423+
self.assertIn("invalid `exp_class` value", str(e.exception))
424+
425+
def test_with_wrong_class(self):
426+
with self.assertRaises(UnexpectedDER) as e:
427+
remove_implicit(self.data_application)
428+
429+
self.assertIn(
430+
"wanted class context-specific, got 0x46 tag", str(e.exception)
431+
)
432+
433+
def test_with_application_class(self):
434+
tag, body, rest = remove_implicit(self.data_application, "application")
435+
436+
self.assertEqual(tag, self.exp_tag)
437+
self.assertEqual(body, self.exp_data)
438+
self.assertEqual(rest, b"")
439+
440+
def test_with_private_class(self):
441+
tag, body, rest = remove_implicit(self.data_private, "private")
442+
443+
self.assertEqual(tag, self.exp_tag)
444+
self.assertEqual(body, self.exp_data)
445+
self.assertEqual(rest, b"")
446+
447+
def test_with_data_following(self):
448+
extra_data = b"\x00\x01"
449+
450+
tag, body, rest = remove_implicit(
451+
self.data_context_specific + extra_data
452+
)
453+
454+
self.assertEqual(tag, self.exp_tag)
455+
self.assertEqual(body, self.exp_data)
456+
self.assertEqual(rest, extra_data)
457+
458+
def test_with_constructed(self):
459+
data = b"\xa6\x02\x0a\x0b"
460+
461+
with self.assertRaises(UnexpectedDER) as e:
462+
remove_implicit(data)
463+
464+
self.assertIn("wanted type primitive, got 0xa6 tag", str(e.exception))
465+
466+
399467
class TestRemoveOctetString(unittest.TestCase):
400468
def test_simple(self):
401469
data = b"\x04\x03\xaa\xbb\xcc"

0 commit comments

Comments
 (0)