Skip to content

Commit 9c261ac

Browse files
decoding DERs in plain ruby
1 parent 6fefe6a commit 9c261ac

File tree

1 file changed

+264
-6
lines changed

1 file changed

+264
-6
lines changed

lib/openssl/asn1.rb

Lines changed: 264 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -495,16 +495,274 @@ def take_default_tag(klass)
495495
take_default_tag(sklass)
496496
end
497497

498+
# :nodoc:
499+
TAG_CLASS_TYPES = {
500+
UNIVERSAL: 0x00,
501+
APPLICATION: 0x40,
502+
CONTEXT_SPECIFIC: 0x80,
503+
PRIVATE: 0xc0
504+
}
505+
private_constant :TAG_CLASS_TYPES
506+
498507
# from ossl_asn1.c : ossl_asn1_tag_class
499508
def take_asn1_tag_class(tag_class)
500-
case tag_class
501-
when :UNIVERSAL, nil then 0x00
502-
when :APPLICATION then 0x40
503-
when :CONTEXT_SPECIFIC then 0x80
504-
when :PRIVATE then 0xc0
505-
else
509+
tag_class ||= :UNIVERSAL
510+
511+
TAG_CLASS_TYPES.fetch(tag_class) do
506512
raise ASN1Error, "invalid tag class"
507513
end
508514
end
515+
516+
PRIMITIVE_TAG_IDS = [*(0..10), 12, *(18..28), 30]
517+
518+
def decode_all(data)
519+
data = data.to_der if data.respond_to?(:to_der)
520+
521+
datalen = data.size
522+
523+
objs = []
524+
525+
loop do
526+
obj, data = decode0(data)
527+
528+
if obj.nil?
529+
raise ASN1Error, "Type mismatch. Total bytes read: #{datalen} Bytes available: #{data.size} Offset: #{datalen - data.size}"
530+
end
531+
532+
objs << obj
533+
534+
break if data.nil? || data.empty?
535+
end
536+
537+
objs
538+
end
539+
540+
def decode(data)
541+
decode0(data).first
542+
end
543+
544+
def decode0(data)
545+
data = data.to_der if data.respond_to?(:to_der)
546+
547+
first_byte, length = data.unpack('CC')
548+
length_bytes = 1
549+
tag_class = TAG_CLASS_TYPES.key(first_byte & 0xc0) || :UNIVERSAL
550+
is_constructed = first_byte.anybits?(0x20)
551+
is_indefinite_length = length == 0x80 # indefinite length
552+
id = first_byte & 0x1f
553+
554+
no_id_idx = if id == 0x1f
555+
id = 0
556+
count = 1
557+
data[1..].each_byte do |byte|
558+
count += 1
559+
560+
id = (id << 7) | (byte & 0x7f)
561+
break if byte.nobits?(0x80)
562+
end
563+
length = data.getbyte(count)
564+
count
565+
else
566+
1
567+
end
568+
569+
value = if is_indefinite_length
570+
unless is_constructed
571+
raise ASN1Error, "invalid length" if PRIMITIVE_TAG_IDS.include?(id)
572+
end
573+
574+
data[no_id_idx + 1..-1]
575+
elsif length > 0x80
576+
# ASN.1 says this octet can't be 0xff
577+
raise ASN1Error, "invalid length" if length == 0xff
578+
length_bytes = length & 0x7f
579+
length = data[no_id_idx + 1, length_bytes].unpack('C*').reduce(0) { |len, b| (len << 8) | b }
580+
data[no_id_idx + length_bytes + 1..-1]
581+
else
582+
data[no_id_idx + 1..-1]
583+
end
584+
585+
if is_constructed
586+
decode_cons(tag_class, id, length, value, is_indefinite_length)
587+
else
588+
if is_indefinite_length
589+
raise ASN1Error, "invalid length" if PRIMITIVE_TAG_IDS.include?(id)
590+
end
591+
decode_prim(tag_class, id, length, value)
592+
end
593+
end
594+
595+
def decode_cons(tag_class, id, length, data, is_indefinite_length)
596+
remaining = data[length..-1]
597+
data = data[0, length]
598+
599+
objs = []
600+
has_eoc = false
601+
until data.empty?
602+
obj, data = decode0(data)
603+
604+
case obj
605+
when EndOfContent
606+
has_eoc = true
607+
608+
break if is_indefinite_length
609+
610+
# next if is_indefinite_length
611+
612+
objs << obj
613+
614+
break
615+
else
616+
objs << obj
617+
end
618+
end
619+
620+
if is_indefinite_length && !has_eoc
621+
raise ASN1Error, "missing EOC"
622+
end
623+
624+
obj = if tag_class == :UNIVERSAL
625+
case id
626+
when 16 # Sequence
627+
Sequence.new(objs, id, nil, tag_class)
628+
when 17 # Set
629+
Set.new(objs, id, nil, tag_class)
630+
else
631+
Constructive.new(objs, id, nil, tag_class)
632+
end
633+
else
634+
ASN1Data.new(objs, id, tag_class)
635+
end
636+
obj.indefinite_length = is_indefinite_length
637+
638+
if data && !data.empty?
639+
if remaining.nil?
640+
remaining = data
641+
else
642+
remaining << data
643+
end
644+
end
645+
646+
return obj, remaining
647+
end
648+
649+
def decode_prim(tag_class, id, length, data)
650+
remaining = data[length..-1]
651+
data = data[0, length]
652+
653+
obj = if tag_class == :UNIVERSAL
654+
case id
655+
when 0 # EOC
656+
if length != 0 || !data.empty?
657+
raise ASN1Error, "too long"
658+
end
659+
EndOfContent.new
660+
when 1 # BOOLEAN
661+
if length < 1
662+
raise ASN1Error, "invalid length for BOOLEAN"
663+
elsif length > 1
664+
raise ASN1Error, "too long"
665+
else
666+
Boolean.new(data != "\x00", id, nil, tag_class)
667+
end
668+
when 2 # INTEGER
669+
number = data.unpack('C*').reduce(0) { |len, b| (len << 8) | b }
670+
if data[0].ord[7] == 1
671+
number -= (1 << (8 * length))
672+
end
673+
OpenSSL::ASN1::Integer.new(number.to_bn, id, nil, tag_class)
674+
when 3 # BIT_STRING
675+
if data.empty?
676+
raise ASN1Error, "string too short"
677+
end
678+
unused = data.unpack1('C')
679+
if (unused > 7)
680+
raise ASN1Error, "invalid bit string bits left"
681+
end
682+
str = data.byteslice(1..-1) || ""
683+
BitString.new(str, id, nil, tag_class).tap do |b|
684+
b.unused_bits = unused
685+
end
686+
when 4 # OCTET_STRING
687+
OctetString.new(data, id, nil, tag_class)
688+
when 5 # NULL
689+
unless length.zero?
690+
raise ASN1Error, "null is wrong length"
691+
end
692+
693+
Null.new(nil, id, nil, tag_class)
694+
when 6 # OBJECT
695+
top, *codes = data.unpack("w*")
696+
697+
if top
698+
first = [2, top / 40].min
699+
second = top - first * 40
700+
codes = [first, second, *codes]
701+
else
702+
raise ASN1Error, "invalid object encoding"
703+
end
704+
705+
obj = ObjectId.new(codes.join("."), id, nil, tag_class)
706+
707+
if (sn = obj.sn)
708+
# on decoding, if there's a short name in the table, then
709+
# that's the value
710+
obj.value = sn
711+
end
712+
obj
713+
# when 7 # V_ASN1_OBJECT_DESCRIPTOR
714+
# when 8 # EXTERNAL
715+
# when 9 # REAL
716+
when 10 # ENUMERATED
717+
number = data.unpack('C*').reduce(0) { |len, b| (len << 8) | b }
718+
if data[0].ord[7] == 1
719+
number -= (1 << (8 * length))
720+
end
721+
OpenSSL::ASN1::Enumerated.new(number.to_bn, id, nil, tag_class)
722+
when 12 # UTF8String
723+
UTF8String.new(data, id, nil, tag_class)
724+
when 18
725+
NumericString.new(data, id, nil, tag_class)
726+
when 19
727+
PrintableString.new(data, id, nil, tag_class)
728+
when 20
729+
T61String.new(data, id, nil, tag_class)
730+
when 21
731+
VideotexString.new(data, id, nil, tag_class)
732+
when 22
733+
IA5String.new(data, id, nil, tag_class)
734+
when 23
735+
unless (c = /\A(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})(?<hour>\d{2})(?<min>\d{2})(?<sec>\d{2})Z\z/.match(data))
736+
raise ASN1Error, "too long"
737+
end
738+
year = c[:year].to_i
739+
year = year > 49 ? 1900 + year : 2000 + year
740+
time = Time.utc(year, c[:month], c[:day], c[:hour], c[:min], c[:sec])
741+
UTCTime.new(time, id, nil, tag_class)
742+
when 24
743+
unless (c = /\A(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})(?<hour>\d{2})(?<min>\d{2})(?<sec>\d{2})Z\z/.match(data))
744+
raise ASN1Error, "too long"
745+
end
746+
time = Time.utc(c[:year], c[:month], c[:day], c[:hour], c[:min], c[:sec])
747+
GeneralizedTime.new(time, id, nil, tag_class)
748+
when 25
749+
GraphicString.new(data, id, nil, tag_class)
750+
when 26
751+
ISO64String.new(data, id, nil, tag_class)
752+
when 27
753+
GeneralString.new(data, id, nil, tag_class)
754+
when 28
755+
UniversalString.new(data, id, nil, tag_class)
756+
when 30
757+
BMPString.new(data, id, nil, tag_class)
758+
else
759+
ASN1Data.new(data, id, tag_class)
760+
end
761+
else
762+
ASN1Data.new(data, id, tag_class)
763+
end
764+
765+
return obj, remaining
766+
end
509767
end
510768
end

0 commit comments

Comments
 (0)