Skip to content

Commit 0866f27

Browse files
rewrite .traverse in ruby
1 parent 9c261ac commit 0866f27

File tree

2 files changed

+125
-11
lines changed

2 files changed

+125
-11
lines changed

lib/openssl/asn1.rb

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -537,11 +537,24 @@ def decode_all(data)
537537
objs
538538
end
539539

540+
def traverse(der, &blk)
541+
raise LocalJumpError unless blk
542+
543+
_, remaining = decode0(der, &blk)
544+
545+
unless remaining.nil? || remaining.empty?
546+
total_read = der.size - remaining.size
547+
raise ASN1Error, "Type mismatch. Total bytes read: #{total_read} Bytes available: #{remaining.size} Offset: #{total_read}"
548+
end
549+
550+
nil
551+
end
552+
540553
def decode(data)
541554
decode0(data).first
542555
end
543556

544-
def decode0(data)
557+
def decode0(data, depth = 0, offset = 0, &block)
545558
data = data.to_der if data.respond_to?(:to_der)
546559

547560
first_byte, length = data.unpack('CC')
@@ -582,33 +595,53 @@ def decode0(data)
582595
data[no_id_idx + 1..-1]
583596
end
584597

598+
hlength = no_id_idx + length_bytes
599+
585600
if is_constructed
586-
decode_cons(tag_class, id, length, value, is_indefinite_length)
601+
decode_cons(tag_class, id, hlength, length, value, is_indefinite_length, depth, offset, &block)
587602
else
588603
if is_indefinite_length
589604
raise ASN1Error, "invalid length" if PRIMITIVE_TAG_IDS.include?(id)
590605
end
591-
decode_prim(tag_class, id, length, value)
606+
decode_prim(tag_class, id, hlength, length, value, depth, offset, &block)
592607
end
593608
end
594609

595-
def decode_cons(tag_class, id, length, data, is_indefinite_length)
596-
remaining = data[length..-1]
597-
data = data[0, length]
610+
def decode_cons(tag_class, id, hlength, length, data, is_indefinite_length, depth, offset, &block)
611+
datalen = data.size
612+
613+
if is_indefinite_length
614+
remaining = nil
615+
616+
else
617+
remaining = data[length..-1]
618+
data = data[0, length]
619+
620+
if length > datalen
621+
raise ASN1Error, "too long"
622+
end
623+
end
624+
625+
traverse0(depth, offset, hlength, length == 0x80 ? 0 : length, true, tag_class, id, &block) if block
626+
627+
offset += hlength
598628

599629
objs = []
600630
has_eoc = false
601-
until data.empty?
602-
obj, data = decode0(data)
631+
until data.nil? || data.empty?
632+
datalen = data.size
633+
634+
obj, data = decode0(data, depth + 1, offset, &block)
635+
636+
offset += datalen
637+
offset -= data.size if data
603638

604639
case obj
605640
when EndOfContent
606641
has_eoc = true
607642

608643
break if is_indefinite_length
609644

610-
# next if is_indefinite_length
611-
612645
objs << obj
613646

614647
break
@@ -646,10 +679,14 @@ def decode_cons(tag_class, id, length, data, is_indefinite_length)
646679
return obj, remaining
647680
end
648681

649-
def decode_prim(tag_class, id, length, data)
682+
def decode_prim(tag_class, id, hlength, length, data, depth, offset, &block)
650683
remaining = data[length..-1]
651684
data = data[0, length]
652685

686+
traverse0(depth, offset, hlength, length, false, tag_class, id, &block) if block
687+
688+
offset += hlength
689+
653690
obj = if tag_class == :UNIVERSAL
654691
case id
655692
when 0 # EOC
@@ -764,5 +801,26 @@ def decode_prim(tag_class, id, length, data)
764801

765802
return obj, remaining
766803
end
804+
805+
def traverse0(depth, offset, hlength, length, is_constructed, tag_class, id, &block)
806+
elems = [
807+
depth, offset,
808+
hlength, length,
809+
is_constructed,
810+
tag_class,
811+
id
812+
]
813+
814+
arity = block.arity
815+
if arity == 1
816+
block.call(elems)
817+
else
818+
if arity < elems.size
819+
elems = elems[0, arity]
820+
end
821+
822+
block.call(*elems)
823+
end
824+
end
767825
end
768826
end

test/openssl/test_asn1.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,62 @@ def test_decode_all
203203
end
204204
end
205205

206+
def test_traverse
207+
assert_raise(LocalJumpError) {
208+
OpenSSL::ASN1.traverse(B(%w{ 00 00 }))
209+
}
210+
# primitive
211+
expected = [[0, 0, 2, 1, false, :UNIVERSAL, 2]]
212+
received = []
213+
OpenSSL::ASN1.traverse(B((%w{ 02 01 00 }))) do |args|
214+
received << args
215+
end
216+
assert_equal expected, received
217+
218+
# asn1data
219+
expected = [[0, 0, 2, 0, false, :APPLICATION, 1]]
220+
received = []
221+
OpenSSL::ASN1.traverse(B((%w{ 41 00 }))) do |args|
222+
received << args
223+
end
224+
assert_equal expected, received
225+
#constructed
226+
expected = [
227+
[0, 0, 2, 7, true, :UNIVERSAL, 16],
228+
[1, 2, 2, 0, false, :UNIVERSAL, 5],
229+
[1, 4, 2, 0, true, :UNIVERSAL, 16],
230+
[1, 6, 2, 1, false, :UNIVERSAL, 4]
231+
]
232+
received = []
233+
OpenSSL::ASN1.traverse(B(%w{ 30 07 05 00 30 00 04 01 00 })) do |args|
234+
received << args
235+
end
236+
assert_equal expected, received
237+
# indefinite length
238+
expected = [
239+
[0, 0, 2, 7, true, :UNIVERSAL, 16],
240+
[1, 2, 2, 0, false, :UNIVERSAL, 5],
241+
[1, 4, 2, 0, true, :UNIVERSAL, 16],
242+
[1, 6, 2, 1, false, :UNIVERSAL, 4]
243+
]
244+
received = []
245+
OpenSSL::ASN1.traverse(B(%w{ 30 07 05 00 30 00 04 01 00 })) do |args|
246+
received << args
247+
end
248+
# multiple ders
249+
# it yields while traversing, and fails if there's more data beyond the first DER
250+
expected = [
251+
[0, 0, 2, 1, false, :UNIVERSAL, 2]
252+
]
253+
received = []
254+
assert_raise(OpenSSL::ASN1::ASN1Error) do
255+
OpenSSL::ASN1.traverse(B(%w{ 02 01 01 02 01 02 02 01 03 })) do |args|
256+
received << args
257+
end
258+
end
259+
assert_equal expected, received
260+
end
261+
206262
def test_object_id_register
207263
oid = "1.2.34.56789"
208264
pend "OID 1.2.34.56789 is already registered" if OpenSSL::ASN1::ObjectId(oid).sn

0 commit comments

Comments
 (0)