Skip to content

Commit eb1e8d2

Browse files
rewrite .traverse in ruby
1 parent 795a736 commit eb1e8d2

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

lib/openssl/asn1.rb

Lines changed: 47 additions & 4 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,8 +595,34 @@ def decode0(data)
582595
data[no_id_idx + 1..-1]
583596
end
584597

598+
if block
599+
elems = [
600+
depth, offset,
601+
# header_length
602+
no_id_idx + length_bytes,
603+
# length
604+
length,
605+
is_constructed,
606+
tag_class,
607+
id
608+
]
609+
610+
arity = block.arity
611+
if arity == 1
612+
block.call(elems)
613+
else
614+
if arity < elems.size
615+
elems = elems[0, arity]
616+
end
617+
618+
block.call(*elems)
619+
end
620+
end
621+
622+
offset += no_id_idx + length_bytes
623+
585624
if is_constructed
586-
decode_cons(tag_class, id, length, value, is_indefinite_length)
625+
decode_cons(tag_class, id, length, value, is_indefinite_length, depth, offset, &block)
587626
else
588627
if is_indefinite_length
589628
raise ASN1Error, "invalid length" if PRIMITIVE_TAG_IDS.include?(id)
@@ -592,14 +631,18 @@ def decode0(data)
592631
end
593632
end
594633

595-
def decode_cons(tag_class, id, length, data, is_indefinite_length)
634+
def decode_cons(tag_class, id, length, data, is_indefinite_length, depth, offset, &block)
596635
remaining = data[length..-1]
597636
data = data[0, length]
598637

599638
objs = []
600639
has_eoc = false
601640
until data.empty?
602-
obj, data = decode0(data)
641+
datalen = data.size
642+
643+
obj, data = decode0(data, depth + 1, offset, &block)
644+
645+
offset += datalen - data.size
603646

604647
case obj
605648
when EndOfContent

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)