Module: Smartcard::Gp::Asn1Ber

Defined in:
lib/smartcard/gp/asn1_ber.rb

Overview

Logic for encoding and decoding ASN.1-BER data as specified in X.690-0207.

Class Method Summary collapse

Class Method Details

.decode(data, offset = 0, length = data.length - offset) ⇒ Object

Decodes a sequence of TLVs (tag-length-value).

Returns an array with one element for each TLV in the sequence. See decode_tlv for the format of each array element.



107
108
109
110
111
112
113
114
115
# File 'lib/smartcard/gp/asn1_ber.rb', line 107

def self.decode(data, offset = 0, length = data.length - offset)
  sequence = []
  loop do
    break if offset >= length
    offset, tlv = decode_tlv data, offset
    sequence << tlv
  end
  sequence
end

.decode_length(data, offset) ⇒ Object

Decodes a TLV length.

Args:

data:: the array to decode from
offset:: the position of the first byte containing the length

Returns the offset of the first byte after the length, and the length. The returned value might be :indefinite if the encoding uses the indefinite length.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/smartcard/gp/asn1_ber.rb', line 51

def self.decode_length(data, offset)
  return (offset + 1), data[offset] if (data[offset] & 0x80) == 0
  len_bytes = (data[offset] & 0x7F)
  return (offset + 1), :indefinite if len_bytes == 0
  length = 0
  len_bytes.times do
    offset += 1
    length = (length << 8) | data[offset]
  end
  return (offset + 1), length
end

.decode_tag(data, offset) ⇒ Object

Decodes a TLV tag (the data type).

Args:

data:: the array to decode from
offset:: the position of the first byte containing the tag

Returns the offset of the first byte after the tag, and the tag information. Tag information is a hash with the following keys.

:class:: the tag's class (symbol, named after X690-0207)
:primitive:: if +false+, the tag's value is a sequence of TLVs
:number:: the tag's number


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/smartcard/gp/asn1_ber.rb', line 24

def self.decode_tag(data, offset)
  class_bits = data[offset] >> 6 
  tag_class = [:universal, :application, :context, :private][class_bits]
  tag_primitive = (data[offset] & 0x20) == 0
  tag_number = (data[offset] & 0x1F)
  if tag_number == 0x1F
    tag_number = 0
    loop do      
      offset += 1
      tag_number <<= 7
      tag_number |= (data[offset] & 0x7F)
      break if (data[offset] & 0x80) == 0
    end
  end
  return (offset + 1), { :class => tag_class, :primitive => tag_primitive,
                         :number => tag_number }
end

.decode_tlv(data, offset) ⇒ Object

Decodes a TLV (tag-length-value).

Returns a hash that contains tag and value information. See decode_tag for the keys containing the tag information. Value information is contained in the :value: tag.



94
95
96
97
98
99
100
101
# File 'lib/smartcard/gp/asn1_ber.rb', line 94

def self.decode_tlv(data, offset)
  offset, tag = decode_tag data, offset
  offset, length = decode_length data, offset
  offset, value = decode_value data, offset, length
  
  tag[:value] = tag[:primitive] ? map_value(value, tag) : decode(value)
  return offset, tag
end

.decode_value(data, offset, length) ⇒ Object

Decodes a TLV value.

Args:

data:: the array to decode from
offset:: the position of the first byte containing the length

Returns the offset of the first byte after the value, and the value.



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/smartcard/gp/asn1_ber.rb', line 71

def self.decode_value(data, offset, length)    
  return offset + length, data[offset, length] unless length == :indefinite
  
  length = 0
  loop do
    raise 'Unterminated data' if offset + length + 2 > data.length
    break if data[offset + length, 2] == [0, 0]
    length += 1
  end
  return (offset + length + 2), data[offset, length]
end

.encode(tlvs) ⇒ Object

Encodes a sequence of TLVs (tag-length-value).

Args
tlvs

an array of hashes to be encoded as TLV

Returns an array of byte values.



176
177
178
# File 'lib/smartcard/gp/asn1_ber.rb', line 176

def self.encode(tlvs)
  tlvs.map { |tlv| encode_tlv tlv }.flatten
end

.encode_length(length) ⇒ Object

Encodes a TLV length (the length of the data).

Args
length

the length to be encoded (number of :indefinite)

Returns an array of byte values.



147
148
149
150
151
152
153
154
155
156
# File 'lib/smartcard/gp/asn1_ber.rb', line 147

def self.encode_length(length)
  return [0x80] if length == :indefinite
  return [length] if length < 0x80
  length_bytes = []
  while length > 0
    length_bytes << (length & 0xFF)
    length >>= 8
  end
  [0x80 | length_bytes.length] + length_bytes.reverse
end

.encode_tag(tag) ⇒ Object

Encodes a TLV tag (the data type).

Args:

tag:: a hash with the keys produced by decode_tag.

Returns an array of byte values.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/smartcard/gp/asn1_ber.rb', line 123

def self.encode_tag(tag)
  tag_classes = { :universal => 0, :application => 1, :context => 2,
                  :private => 3 } 
  tag_lead = (tag_classes[tag[:class]] << 6) | (tag[:primitive] ? 0x00 : 0x20)
  return [tag_lead | tag[:number]] if tag[:number] < 0x1F
  
  number_bytes, number = [], tag[:number]
  first = true
  while number != 0
    byte = (number & 0x7F)
    number >>= 7
    byte |= 0x80 unless first
    first = false
    number_bytes << byte
  end
  [tag_lead | 0x1F] + number_bytes.reverse
end

.encode_tlv(tlv) ⇒ Object

Encodes a TLV (tag-length-value).

Args
tlv

hash with tag and value information, to be encoeded as TLV; see decode_tlv for the hash keys encoding the tag and value

Returns an array of byte values.



165
166
167
168
# File 'lib/smartcard/gp/asn1_ber.rb', line 165

def self.encode_tlv(tlv)
  value = tlv[:primitive] ? tlv[:value] : encode(tlv[:value])
  [encode_tag(tlv), encode_length(value.length), value].flatten
end

.map_value(value, tag) ⇒ Object

Maps a TLV value with a known tag to a Ruby data type.



84
85
86
87
# File 'lib/smartcard/gp/asn1_ber.rb', line 84

def self.map_value(value, tag)
  # TODO(costan): map primitive types if necessary
  value
end

.visit(tlvs, tag_path = [], &block) ⇒ Object

Visitor pattern for decoded TLVs.

Args:

tlvs:: the TLVs to visit
tag_path:: internal, do not use

Yields: |tag_path, value| tag_path lists the numeric tags for the current value’s tag, and all the parents’ tags.



188
189
190
191
192
193
194
195
196
# File 'lib/smartcard/gp/asn1_ber.rb', line 188

def self.visit(tlvs, tag_path = [], &block)
  tlvs.each do |tlv|
    tag_number = encode_tag(tlv).inject { |acc, v| (acc << 8) | v }
    new_tag_path = tag_path + [tag_number]
    yield new_tag_path, tlv[:value]
    next if tlv[:primitive]
    visit tlv[:value], new_tag_path, &block
  end
end