rasn1 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66f3cf5edaaabbd17a920296e943f8e46f5de81c
4
+ data.tar.gz: a83fb189ba31da35c1dc1743a01675fb3ac270d1
5
+ SHA512:
6
+ metadata.gz: fa8459ba40745b387cdde298ff577707c63fd41ee4a2646b639878fcbb695638050910b94ec638ce7f010b8053140e30aa305a0634f3508c6fb8e6c7a8a337c7
7
+ data.tar.gz: a7bc698f83d23585dfd75b27cc46429c9f2ec84b6c36d77980fbb3d0423b8ead411cd9a700a4700b2164e117d247837d54771e66a5f8aaf03fa198b1336cb9b7
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *~
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rasn1.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Sylvain Daubert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Rasn1
2
+
3
+ Rasn1 will be a ruby ASN.1 library to encode, parse and decode ASN.1 data in DER format.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rasn1'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rasn1
20
+
21
+ ## Usage
22
+ All examples below will be based on:
23
+
24
+ ```
25
+ Record ::= SEQUENCE {
26
+ id INTEGER,
27
+ room [0] INTEGER OPTIONAL,
28
+ house [1] INTEGER DEFAULT 0
29
+ }
30
+ ```
31
+
32
+ ## Create a ASN.1 model
33
+
34
+ ```ruby
35
+ class Record < RASN1::Model
36
+ sequence :record,
37
+ content: [integer(:id),
38
+ integer(:room, implicit: 0, optional: true),
39
+ integer(:house, implicit: 1, default: 0)]
40
+ end
41
+ ```
42
+
43
+ More comple classes may be designed by nesting simple classes. For example:
44
+
45
+ ```ruby
46
+ class ComplexRecord < RASN1::Model
47
+ sequence :cplx_record,
48
+ content: [boolean(:bool),
49
+ octet_string(:data, explicit: 0),
50
+ Record]
51
+ end
52
+ ```
53
+
54
+ ## Parse a DER-encoded string
55
+ ```ruby
56
+ record = Record.parse(der_string)
57
+ record[:id] # => RASN1::Types::Integer
58
+ record[:id].value # => Integer
59
+ record[:id].to_i # => Integer
60
+ record[:id].asn1_class # => Symbol
61
+ record[:id].optional? # => false
62
+ record[:id].default # => nil
63
+ record[:room].optional # => true
64
+ record[:house].default # => 0
65
+
66
+ record[:id].to_der # => String
67
+ ```
68
+
69
+ ## Generate a DER-encoded string
70
+ ```ruby
71
+ record = Record.new(id: 12)
72
+ record[:id].to_i # => 12
73
+ record[:room] # => nil
74
+ record[:house] # => 0
75
+
76
+ # Set one value
77
+ record[:room] = 43
78
+ record[:room] # => 43
79
+
80
+ # Set mulitple values
81
+ record.set id: 124, house: 155
82
+
83
+ record.to_der # => String
84
+ ```
85
+
86
+ ## Contributing
87
+
88
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/rasn1.
89
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ RSpec::Core::RakeTask.new
6
+ YARD::Rake::YardocTask.new do |t|
7
+ t.files = ['lib/**/*.rb', '-', 'README.md', 'LICENSE']
8
+ t.options = %w(--no-private)
9
+ end
10
+
11
+ task :default => :spec
12
+
data/lib/rasn1.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'rasn1/version'
2
+ require 'rasn1/types'
3
+ require 'rasn1/model'
4
+
5
+ # Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
6
+ # @author Sylvain Daubert
7
+ module RASN1
8
+
9
+ # Base error class
10
+ class Error < StandardError; end
11
+
12
+ # ASN.1 encoding/decoding error
13
+ class ASN1Error < Error; end
14
+
15
+ # ASN.1 class error
16
+ class ClassError < Error
17
+ # @return [String]
18
+ def message
19
+ "Tag class should be a symbol: #{Types::Base::CLASSES.keys.join(', ')}"
20
+ end
21
+ end
22
+
23
+ # Enumerated error
24
+ class EnumeratedError < Error; end
25
+
26
+ # CHOICE error: #chosen not set
27
+ class ChoiceError < RASN1::Error
28
+ def message
29
+ "CHOICE #@name: #chosen not set"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,197 @@
1
+ module RASN1
2
+
3
+ # @abstract
4
+ # {Model} class is a base class to define ASN.1 models.
5
+ # == Create a simple ASN.1 model
6
+ # Given this ASN.1 example:
7
+ # Record ::= SEQUENCE {
8
+ # id INTEGER,
9
+ # room [0] IMPLICIT INTEGER OPTIONAL,
10
+ # house [1] EXPLICIT INTEGER DEFAULT 0
11
+ # }
12
+ # you may create your model like this:
13
+ # class Record < RASN1::Model
14
+ # sequence(:record,
15
+ # content: [integer(:id),
16
+ # integer(:room, implicit: 0, optional: true),
17
+ # integer(:house, explicit: 1, default: 0)])
18
+ # end
19
+ #
20
+ # === Parse a DER-encoded string
21
+ # record = Record.parse(der_string)
22
+ # record[:id] # => RASN1::Types::Integer
23
+ # record[:id].value # => Integer
24
+ # record[:id].to_i # => Integer
25
+ # record[:id].asn1_class # => Symbol
26
+ # record[:id].optional? # => false
27
+ # record[:id].default # => nil
28
+ # record[:room].optional # => true
29
+ # record[:house].default # => 0
30
+ #
31
+ # You may also parse a BER-encoded string this way:
32
+ # record = Record.parse(der_string, ber: true)
33
+ #
34
+ # === Generate a DER-encoded string
35
+ # record = Record.new(id: 12, room: 24)
36
+ # record.to_der
37
+ #
38
+ # == Create a more complex model
39
+ # Models may be nested. For example:
40
+ # class Record2 < RASN1::Model
41
+ # sequence(:record2,
42
+ # content: [boolean(:rented, default: false),
43
+ # Record])
44
+ # end
45
+ # Set values like this:
46
+ # record2 = Record2.new
47
+ # record2[:rented] = true
48
+ # record2[:record][:id] = 65537
49
+ # record2[:record][:room] = 43
50
+ # or like this:
51
+ # record2 = Record2.new(rented: true, record: { id: 65537, room: 43 })
52
+ # @author Sylvain Daubert
53
+ class Model
54
+
55
+ class << self
56
+
57
+ # Define a SEQUENCE type. Should be used to define a new subclass of {Model}.
58
+ # @param [Hash] options
59
+ # @option options [Array] :content
60
+ # @see Types::Sequence#initialize
61
+ # @return [Types::Sequence]
62
+ def sequence(name, options={})
63
+ @records ||= {}
64
+ @records[name] = Proc.new do
65
+ seq = Types::Sequence.new(name, options)
66
+ seq.value = options[:content] if options[:content]
67
+ seq
68
+ end
69
+ end
70
+
71
+ # define all class methods to instance a ASN.1 TAG
72
+ Types.primitives.each do |prim|
73
+ class_eval "def #{prim.type.downcase.gsub(/\s+/, '_')}(name, options={})\n" \
74
+ " Proc.new { #{prim.to_s}.new(name, options) }\n" \
75
+ "end"
76
+ end
77
+
78
+ # Parse a DER/BER encoded string
79
+ # @param [String] str
80
+ # @param [Boolean] ber accept BER encoding or not
81
+ # @return [Model]
82
+ def parse(str, ber: false)
83
+ model = new
84
+ model.parse! str, ber: ber
85
+ model
86
+ end
87
+ end
88
+
89
+ # Create a new instance of a {Model}
90
+ # @param [Hash] args
91
+ def initialize(args={})
92
+ set_elements
93
+ initialize_elements self, args
94
+ end
95
+
96
+ # Give access to element +name+ in model
97
+ # @param [String,Symbol] name
98
+ # @return [Types::Base]
99
+ def [](name)
100
+ @elements[name]
101
+ end
102
+
103
+ # Get name frm root type
104
+ # @return [String,Symbol]
105
+ def name
106
+ @elements[@root].name
107
+ end
108
+
109
+ # Return a hash image of model
110
+ # @return [Hash]
111
+ def to_h
112
+ { @root => private_to_h(@elements[@root]) }
113
+ end
114
+
115
+ # @return [String]
116
+ def to_der
117
+ @elements[@root].to_der
118
+ end
119
+
120
+ # Parse a DER/BER encoded string, and modify object in-place.
121
+ # @param [String] str
122
+ # @param [Boolean] ber accept BER encoding or not
123
+ # @return [Integer] number of parsed bytes
124
+ def parse!(str, ber: false)
125
+ @elements[@root].parse!(str, ber: ber)
126
+ end
127
+
128
+ private
129
+
130
+ def set_elements(element=nil)
131
+ if element.nil?
132
+ records = self.class.class_eval { @records }
133
+ @root = records.keys.first
134
+ @elements = {}
135
+ @elements[@root] = records[@root].call
136
+ if @elements[@root].value.is_a? Array
137
+ @elements[@root].value = @elements[@root].value.map do |subel|
138
+ se = case subel
139
+ when Proc
140
+ subel.call
141
+ when Class
142
+ subel.new
143
+ end
144
+ @elements[se.name] = se
145
+ set_elements se if se.is_a? Types::Sequence
146
+ se
147
+ end
148
+ end
149
+ else
150
+ element.value.map! do |subel|
151
+ se = case subel
152
+ when Proc
153
+ subel.call
154
+ when Class
155
+ subel.new
156
+ end
157
+ @elements[se.name] = se
158
+ set_elements se if se.is_a? Types::Sequence
159
+ se
160
+ end
161
+ end
162
+ end
163
+
164
+ def initialize_elements(obj, args)
165
+ args.each do |name, value|
166
+ if obj[name]
167
+ if value.is_a? Hash
168
+ if obj[name].is_a? Model
169
+ initialize_elements obj[name], value
170
+ else
171
+ raise ArgumentError, "element #{name}: may only pass a Hash for Sequence elements"
172
+ end
173
+ else
174
+ obj[name].value = value
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def private_to_h(element)
181
+ h = {}
182
+ element.value.each do |subel|
183
+ h[subel.name] = case subel
184
+ when Types::Sequence
185
+ private_to_h(subel)
186
+ when Model
187
+ subel.to_h[subel.name]
188
+ else
189
+ next if subel.value.nil? and subel.optional?
190
+ subel.value
191
+ end
192
+ end
193
+
194
+ h
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,37 @@
1
+ module RASN1
2
+ # This modules is a namesapce for all ASN.1 type classes.
3
+ # @author Sylvain Daubert
4
+ module Types
5
+
6
+ # Give all primitive types
7
+ # @return [Array<Types::Primitive>]
8
+ def self.primitives
9
+ self.constants.map { |c| Types.const_get(c) }.
10
+ select { |klass| klass < Primitive }
11
+ end
12
+
13
+ # Give all constructed types
14
+ # @return [Array<Types::Constructed>]
15
+ def self.constructed
16
+ self.constants.map { |c| Types.const_get(c) }.
17
+ select { |klass| klass < Constructed }
18
+ end
19
+ end
20
+ end
21
+
22
+ require_relative 'types/base'
23
+ require_relative 'types/primitive'
24
+ require_relative 'types/boolean'
25
+ require_relative 'types/integer'
26
+ require_relative 'types/bit_string'
27
+ require_relative 'types/octet_string'
28
+ require_relative 'types/null'
29
+ require_relative 'types/object_id'
30
+ require_relative 'types/enumerated'
31
+ require_relative 'types/utf8_string'
32
+ require_relative 'types/constructed'
33
+ require_relative 'types/sequence'
34
+ require_relative 'types/sequence_of'
35
+ require_relative 'types/set'
36
+ require_relative 'types/set_of'
37
+ require_relative 'types/choice'
@@ -0,0 +1,347 @@
1
+ module RASN1
2
+ module Types
3
+
4
+ # @abstract This is base class for all ASN.1 types.
5
+ #
6
+ # Subclasses SHOULD define:
7
+ # * a TAG constant defining ASN.1 tag number,
8
+ # * a private method {#value_to_der} converting its {#value} to DER,
9
+ # * a private method {#der_to_value} converting DER into {#value}.
10
+ #
11
+ # ==Define an optional value
12
+ # An optional value may be defined using +:optional+ key from {#initialize}:
13
+ # Integer.new(:int, optional: true)
14
+ # An optional value implies:
15
+ # * while parsing, if decoded tag is not optional expected tag, no {ASN1Error}
16
+ # is raised, and parser tries net tag,
17
+ # * while encoding, if {#value} is +nil+, this value is not encoded.
18
+ # ==Define a default value
19
+ # A default value may be defined using +:default+ key from {#initialize}:
20
+ # Integer.new(:int, default: 0)
21
+ # A default value implies:
22
+ # * while parsing, if decoded tag is not expected tag, no {ASN1Error} is raised
23
+ # and parser sets default value to this tag. Then parser tries nex tag,
24
+ # * while encoding, if {#value} is equal to default value, this value is not
25
+ # encoded.
26
+ # ==Define a tagged value
27
+ # ASN.1 permits to define tagged values.
28
+ # By example:
29
+ # -- context specific tag
30
+ # CType ::= [0] EXPLICIT INTEGER
31
+ # -- application specific tag
32
+ # AType ::= [APPLICATION 1] EXPLICIT INTEGER
33
+ # -- private tag
34
+ # PType ::= [PRIVATE 2] EXPLICIT INTEGER
35
+ # These types may be defined as:
36
+ # ctype = RASN1::Types::Integer.new(:ctype, explicit: 0) # with explicit, default #asn1_class is :context
37
+ # atype = RASN1::Types::Integer.new(:atype, explicit: 1, class: :application)
38
+ # ptype = RASN1::Types::Integer.new(:ptype, explicit: 2, class: :private)
39
+ #
40
+ # Implicit tagged values may also be defined:
41
+ # ctype_implicit = RASN1::Types::Integer.new(:ctype, implicit: 0)
42
+ # @author Sylvain Daubert
43
+ class Base
44
+ # Allowed ASN.1 tag classes
45
+ CLASSES = {
46
+ universal: 0x00,
47
+ application: 0x40,
48
+ context: 0x80,
49
+ private: 0xc0
50
+ }
51
+
52
+ # Maximum ASN.1 tag number
53
+ MAX_TAG = 0x1e
54
+
55
+ # Length value for indefinite length
56
+ INDEFINITE_LENGTH = 0x80
57
+
58
+ # @return [Symbol, String]
59
+ attr_reader :name
60
+ # @return [Symbol]
61
+ attr_reader :asn1_class
62
+ # @return [Object,nil] default value, if defined
63
+ attr_reader :default
64
+ # @return [Object]
65
+ attr_writer :value
66
+
67
+ # Get ASN.1 type
68
+ # @return [String]
69
+ def self.type
70
+ return @type if @type
71
+ @type = self.to_s.gsub(/.*::/, '').gsub(/([a-z0-9])([A-Z])/, '\1 \2').upcase
72
+ end
73
+
74
+
75
+ # @param [Symbol, String] name name for this tag in grammar
76
+ # @param [Hash] options
77
+ # @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+
78
+ # @option options [::Boolean] :optional define this tag as optional. Default
79
+ # is +false+
80
+ # @option options [Object] :default default value for DEFAULT tag
81
+ # @option options [Object] :value value to set
82
+ def initialize(name, options={})
83
+ @name = name
84
+
85
+ set_options options
86
+ end
87
+
88
+ # Used by +#dup+ and +#clone+. Deep copy @value.
89
+ def initialize_copy(other)
90
+ @value = @value.nil? ? nil : @value.dup
91
+ end
92
+
93
+ # Get value or default value
94
+ def value
95
+ if @value.nil?
96
+ @default
97
+ else
98
+ @value
99
+ end
100
+ end
101
+
102
+ # @return [::Boolean]
103
+ def optional?
104
+ @optional
105
+ end
106
+
107
+ # Say if this type is tagged or not
108
+ # @return [::Boolean]
109
+ def tagged?
110
+ !@tag.nil?
111
+ end
112
+
113
+ # Say if a tagged type is explicit
114
+ # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
115
+ # if explicit, else +false+
116
+ def explicit?
117
+ @tag.nil? ? @tag : @tag == :explicit
118
+ end
119
+
120
+ # Say if a tagged type is implicit
121
+ # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
122
+ # if implicit, else +false+
123
+ def implicit?
124
+ @tag.nil? ? @tag : @tag == :implicit
125
+ end
126
+
127
+ # @abstract This method SHOULD be partly implemented by subclasses, which
128
+ # SHOULD respond to +#value_to_der+.
129
+ # @return [String] DER-formated string
130
+ def to_der
131
+ if self.class.const_defined?('TAG')
132
+ build_tag
133
+ else
134
+ raise NotImplementedError, 'should be implemented by subclasses'
135
+ end
136
+ end
137
+
138
+ # @return [::Boolean] +true+ if this is a primitive type
139
+ def primitive?
140
+ if self.class < Primitive
141
+ true
142
+ else
143
+ false
144
+ end
145
+ end
146
+
147
+ # @return [::Boolean] +true+ if this is a constructed type
148
+ def constructed?
149
+ if self.class < Constructed
150
+ true
151
+ else
152
+ false
153
+ end
154
+ end
155
+
156
+ # Get ASN.1 type
157
+ # @return [String]
158
+ def type
159
+ self.class.type
160
+ end
161
+
162
+ # Get tag value
163
+ # @return [Integer]
164
+ def tag
165
+ (@tag_value || self.class::TAG) | CLASSES[@asn1_class] | self.class::ASN1_PC
166
+ end
167
+
168
+ # @abstract This method SHOULD be partly implemented by subclasses to parse
169
+ # data. Subclasses SHOULD respond to +#der_to_value+.
170
+ # Parse a DER string. This method updates object.
171
+ # @param [String] der DER string
172
+ # @param [Boolean] ber if +true+, accept BER encoding
173
+ # @return [Integer] total number of parsed bytes
174
+ # @raise [ASN1Error] error on parsing
175
+ def parse!(der, ber: false)
176
+ return 0 unless check_tag(der)
177
+
178
+ total_length, data = get_data(der, ber)
179
+ if explicit?
180
+ type = self.class.new(@name)
181
+ type.parse!(data)
182
+ @value = type.value
183
+ else
184
+ der_to_value(data, ber: ber)
185
+ end
186
+
187
+ total_length
188
+ end
189
+
190
+
191
+ private
192
+
193
+ def set_options(options)
194
+ set_class options[:class]
195
+ set_optional options[:optional]
196
+ set_default options[:default]
197
+ set_tag options
198
+ @value = options[:value]
199
+ end
200
+
201
+ def set_class(asn1_class)
202
+ case asn1_class
203
+ when nil
204
+ @asn1_class = :universal
205
+ when Symbol
206
+ if CLASSES.keys.include? asn1_class
207
+ @asn1_class = asn1_class
208
+ else
209
+ raise ClassError
210
+ end
211
+ else
212
+ raise ClassError
213
+ end
214
+ end
215
+
216
+ def set_optional(optional)
217
+ @optional = !!optional
218
+ end
219
+
220
+ def set_default(default)
221
+ @default = default
222
+ end
223
+
224
+ def set_tag(options)
225
+ if options[:explicit]
226
+ @tag = :explicit
227
+ @tag_value = options[:explicit]
228
+ elsif options[:implicit]
229
+ @tag = :implicit
230
+ @tag_value = options[:implicit]
231
+ end
232
+
233
+ @asn1_class = :context if @tag and @asn1_class == :universal
234
+ end
235
+
236
+ def build_tag?
237
+ !(!@default.nil? and (@value.nil? or @value == @default)) and
238
+ !(optional? and @value.nil?)
239
+ end
240
+
241
+ def build_tag
242
+ if build_tag?
243
+ if explicit?
244
+ v = self.class.new(nil)
245
+ v.value = @value
246
+ encoded_value = v.to_der
247
+ else
248
+ encoded_value = value_to_der
249
+ end
250
+ encode_tag << encode_size(encoded_value.size) << encoded_value
251
+ else
252
+ ''
253
+ end
254
+ end
255
+
256
+ def encode_tag
257
+ if (@tag_value || self.class::TAG) <= MAX_TAG
258
+ [tag].pack('C')
259
+ else
260
+ raise ASN1Error, 'multi-byte tag value are not supported'
261
+ end
262
+ end
263
+
264
+ def encode_size(size)
265
+ if size >= INDEFINITE_LENGTH
266
+ bytes = []
267
+ while size > 255
268
+ bytes << (size & 0xff)
269
+ size >>= 8
270
+ end
271
+ bytes << size
272
+ bytes.reverse!
273
+ bytes.unshift(INDEFINITE_LENGTH | bytes.size)
274
+ bytes.pack('C*')
275
+ else
276
+ [size].pack('C')
277
+ end
278
+ end
279
+
280
+ def check_tag(der)
281
+ tag = der[0, 1]
282
+ if tag != encode_tag
283
+ if optional?
284
+ @value = nil
285
+ elsif !@default.nil?
286
+ @value = @default
287
+ else
288
+ raise_tag_error(encode_tag, tag)
289
+ end
290
+ false
291
+ else
292
+ true
293
+ end
294
+ end
295
+
296
+ def get_data(der, ber)
297
+ length = der[1, 1].unpack('C').first
298
+ length_length = 0
299
+
300
+ if length == INDEFINITE_LENGTH
301
+ if primitive?
302
+ raise ASN1Error, "malformed #{type} TAG (#@name): indefinite length " \
303
+ "forbidden for primitive types"
304
+ else
305
+ if ber
306
+ raise NotImplementedError, "TAG #@name: indefinite length not " \
307
+ "supported yet"
308
+ else
309
+ raise ASN1Error, "TAG #@name: indefinite length forbidden in DER " \
310
+ "encoding"
311
+ end
312
+ end
313
+ elsif length < INDEFINITE_LENGTH
314
+ data = der[2, length]
315
+ else
316
+ length_length = length & 0x7f
317
+ length = der[2, length_length].unpack('C*').
318
+ reduce(0) { |len, b| (len << 8) | b }
319
+ data = der[2 + length_length, length]
320
+ end
321
+
322
+ total_length = 2 + length
323
+ total_length += length_length if length_length > 0
324
+
325
+ [total_length, data]
326
+ end
327
+
328
+ def raise_tag_error(expected_tag, tag)
329
+ msg = "Expected tag #{tag2name(expected_tag)} but get #{tag2name(tag)}"
330
+ msg << " for #@name"
331
+ raise ASN1Error, msg
332
+ end
333
+
334
+ def tag2name(tag)
335
+ return 'no tag' if tag.nil? or tag.empty?
336
+
337
+ itag = tag.unpack('C').first
338
+ name = CLASSES.key(itag & 0xc0).to_s.upcase
339
+ name << " #{itag & Constructed::ASN1_PC > 0 ? 'CONSTRUCTED' : 'PRIMITIVE'}"
340
+ type = Types.constants.map { |c| Types.const_get(c) }.
341
+ select { |klass| klass < Primitive || klass < Constructed }.
342
+ find { |klass| klass::TAG == itag & 0x1f }
343
+ name << " #{type.nil? ? "0x%02X" % (itag & 0x1f) : type.type }"
344
+ end
345
+ end
346
+ end
347
+ end