Skip to content

Commit f909288

Browse files
rewrite .traverse in ruby
1 parent 6cbc9e0 commit f909288

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
@@ -548,11 +548,24 @@ def decode_all(data)
548548
objs
549549
end
550550

551+
def traverse(der, &blk)
552+
raise LocalJumpError unless blk
553+
554+
_, remaining = decode0(der, &blk)
555+
556+
unless remaining.nil? || remaining.empty?
557+
total_read = der.size - remaining.size
558+
raise ASN1Error, "Type mismatch. Total bytes read: #{total_read} Bytes available: #{remaining.size} Offset: #{total_read}"
559+
end
560+
561+
nil
562+
end
563+
551564
def decode(data)
552565
decode0(data).first
553566
end
554567

555-
def decode0(data)
568+
def decode0(data, depth = 0, offset = 0, &block)
556569
data = data.to_der if data.respond_to?(:to_der)
557570

558571
first_byte, length = data.unpack('CC')
@@ -593,30 +606,50 @@ def decode0(data)
593606
data[no_id_idx + 1..-1]
594607
end
595608

609+
hlength = no_id_idx + length_bytes
610+
596611
if is_constructed
597-
decode_cons(tag_class, id, length, value, is_indefinite_length)
612+
decode_cons(tag_class, id, hlength, length, value, is_indefinite_length, depth, offset, &block)
598613
else
599-
decode_prim(tag_class, id, length, value)
614+
decode_prim(tag_class, id, hlength, length, value, depth, offset, &block)
600615
end
601616
end
602617

603-
def decode_cons(tag_class, id, length, data, is_indefinite_length)
604-
remaining = data[length..-1]
605-
data = data[0, length]
618+
def decode_cons(tag_class, id, hlength, length, data, is_indefinite_length, depth, offset, &block)
619+
datalen = data.size
620+
621+
if is_indefinite_length
622+
remaining = nil
623+
624+
else
625+
remaining = data[length..-1]
626+
data = data[0, length]
627+
628+
if length > datalen
629+
raise ASN1Error, "too long"
630+
end
631+
end
632+
633+
traverse0(depth, offset, hlength, length == 0x80 ? 0 : length, true, tag_class, id, &block) if block
634+
635+
offset += hlength
606636

607637
objs = []
608638
has_eoc = false
609-
until data.empty?
610-
obj, data = decode0(data)
639+
until data.nil? || data.empty?
640+
datalen = data.size
641+
642+
obj, data = decode0(data, depth + 1, offset, &block)
643+
644+
offset += datalen
645+
offset -= data.size if data
611646

612647
case obj
613648
when EndOfContent
614649
has_eoc = true
615650

616651
break if is_indefinite_length
617652

618-
# next if is_indefinite_length
619-
620653
objs << obj
621654

622655
break
@@ -654,10 +687,14 @@ def decode_cons(tag_class, id, length, data, is_indefinite_length)
654687
return obj, remaining
655688
end
656689

657-
def decode_prim(tag_class, id, length, data)
690+
def decode_prim(tag_class, id, hlength, length, data, depth, offset, &block)
658691
remaining = data[length..-1]
659692
data = data[0, length]
660693

694+
traverse0(depth, offset, hlength, length, false, tag_class, id, &block) if block
695+
696+
offset += hlength
697+
661698
obj = if tag_class == :UNIVERSAL
662699
case id
663700
when 0 # EOC
@@ -772,5 +809,26 @@ def decode_prim(tag_class, id, length, data)
772809

773810
return obj, remaining
774811
end
812+
813+
def traverse0(depth, offset, hlength, length, is_constructed, tag_class, id, &block)
814+
elems = [
815+
depth, offset,
816+
hlength, length,
817+
is_constructed,
818+
tag_class,
819+
id
820+
]
821+
822+
arity = block.arity
823+
if arity == 1
824+
block.call(elems)
825+
else
826+
if arity < elems.size
827+
elems = elems[0, arity]
828+
end
829+
830+
yield elems
831+
end
832+
end
775833
end
776834
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)