rasn1 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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