rasn1 0.6.6 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4acb42db867027d85bfd208aaaee5906af7fe4fdcd92327055f66187415e959f
4
- data.tar.gz: 52cbb1f7306006fb0c03e3e00911d52a474a78c994e2a8e5618046356de43117
3
+ metadata.gz: f4eb9957878c25e54a394fee02aab5a42aa1201ad6250f7f6a2a3992128c87d6
4
+ data.tar.gz: 3b3d3c85d619ee7f78923d7f7c839874f329087b0b1a4094839b059daf94c2ec
5
5
  SHA512:
6
- metadata.gz: ce57ddfbbed465bce5fefb9c7c8b91593e09e4a9cc3797e467e5eb0f482964f1f2bbed3a9053da1f590fddb18e846a1a608351594d667459f59a0f5218294480
7
- data.tar.gz: 93943756f8b526d6467d834976eda477bacd777565d4d46723c050aab5e909b78c5e3f74539999d0cf3ab3c60f316582fc8060814003936df02c09e78b4d7308
6
+ metadata.gz: 9eb501aacf629355e56a4103dfc3e354bfa035c031a23f2dcc61f8add191a13960cd64ea68db36340694f3b20aba316c0088ebe91943d795405b11489be9342e
7
+ data.tar.gz: 115cef9248e5251a24ec0bb6d6e977084cfb4ff78118a7f5be31ee8fea05a0c831b935fa72c18bdc08e71bb729a85cff2b394f4a84aafd56245f70908f0a4dc6
data/README.md CHANGED
@@ -15,13 +15,22 @@ gem 'rasn1'
15
15
 
16
16
  And then execute:
17
17
 
18
- $ bundle
18
+ $ bundle install
19
19
 
20
20
  Or install it yourself as:
21
21
 
22
22
  $ gem install rasn1
23
23
 
24
- ## Usage
24
+ ## Simple usage
25
+
26
+ To decode a DER/BER string without checking a model, do:
27
+
28
+ ```ruby
29
+ decoded_der = RASN1.parse(der_string)
30
+ decoded_ber = RASN1.parse(ber_string, ber: true)
31
+ ```
32
+
33
+ ## Advanced usage
25
34
  All examples below will be based on:
26
35
 
27
36
  ```
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rasn1/version'
2
4
  require 'rasn1/types'
3
5
  require 'rasn1/model'
@@ -5,7 +7,6 @@ require 'rasn1/model'
5
7
  # Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
6
8
  # @author Sylvain Daubert
7
9
  module RASN1
8
-
9
10
  # Base error class
10
11
  class Error < StandardError; end
11
12
 
@@ -26,10 +27,10 @@ module RASN1
26
27
  # CHOICE error: #chosen not set
27
28
  class ChoiceError < RASN1::Error
28
29
  def message
29
- "CHOICE #@name: #chosen not set"
30
+ "CHOICE #{@name}: #chosen not set"
30
31
  end
31
32
  end
32
-
33
+
33
34
  # Parse a DER/BER string without checking a model
34
35
  # @note If you want to check ASN.1 grammary, you should define a {Model}
35
36
  # and use {Model#parse}.
@@ -42,15 +43,15 @@ module RASN1
42
43
  # @return [Types::Base]
43
44
  def self.parse(der, ber: false)
44
45
  root = nil
45
- while der.size > 0
46
- type = Types.tag2type(der[0].ord)
46
+ until der.empty?
47
+ type = Types.id2type(der)
47
48
  type.parse!(der, ber: ber)
48
49
  root = type if root.nil?
49
50
 
50
51
  if [Types::Sequence, Types::Set].include? type.class
51
52
  subder = type.value
52
53
  ary = []
53
- while subder.size > 0
54
+ until subder.empty?
54
55
  ary << self.parse(subder)
55
56
  subder = subder[ary.last.to_der.size..-1]
56
57
  end
@@ -1,5 +1,6 @@
1
- module RASN1
1
+ # frozen_string_literal: true
2
2
 
3
+ module RASN1
3
4
  # @abstract
4
5
  # {Model} class is a base class to define ASN.1 models.
5
6
  # == Create a simple ASN.1 model
@@ -58,8 +59,9 @@ module RASN1
58
59
  # this method.
59
60
  # @author Sylvain Daubert
60
61
  class Model
61
-
62
62
  class << self
63
+ # @return [Hash]
64
+ attr_reader :options
63
65
 
64
66
  # Use another model in this model
65
67
  # @param [String,Symbol] name
@@ -106,16 +108,16 @@ module RASN1
106
108
  # @param [Symbol,String] name name of object in model
107
109
  # @param [Hash] options
108
110
  # @see Types::Choice#initialize
109
- %w(sequence set choice).each do |type|
111
+ %w[sequence set choice].each do |type|
110
112
  class_eval "def #{type}(name, options={})\n" \
111
113
  " options.merge!(name: name)\n" \
112
- " proc = Proc.new do |opts|\n" \
114
+ " proc = proc do |opts|\n" \
113
115
  " Types::#{type.capitalize}.new(options.merge(opts))\n" \
114
116
  " end\n" \
115
117
  " @root = [name, proc]\n" \
116
118
  " @root << options[:content] unless options[:content].nil?\n" \
117
119
  " @root\n" \
118
- "end"
120
+ 'end'
119
121
  end
120
122
 
121
123
  # @method sequence_of(name, type, options)
@@ -128,15 +130,15 @@ module RASN1
128
130
  # @param [Model, Types::Base] type type for SET OF
129
131
  # @param [Hash] options
130
132
  # @see Types::SetOf#initialize
131
- %w(sequence set).each do |type|
133
+ %w[sequence set].each do |type|
132
134
  klass_name = "Types::#{type.capitalize}Of"
133
135
  class_eval "def #{type}_of(name, type, options={})\n" \
134
136
  " options.merge!(name: name)\n" \
135
- " proc = Proc.new do |opts|\n" \
137
+ " proc = proc do |opts|\n" \
136
138
  " #{klass_name}.new(type, options.merge(opts))\n" \
137
139
  " end\n" \
138
140
  " @root = [name, proc]\n" \
139
- "end"
141
+ 'end'
140
142
  end
141
143
 
142
144
  # @method boolean(name, options)
@@ -185,14 +187,15 @@ module RASN1
185
187
  # @see Types::IA5String#initialize
186
188
  Types.primitives.each do |prim|
187
189
  next if prim == Types::ObjectId
190
+
188
191
  method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
189
192
  class_eval "def #{method_name}(name, options={})\n" \
190
193
  " options.merge!(name: name)\n" \
191
- " proc = Proc.new do |opts|\n" \
192
- " #{prim.to_s}.new(options.merge(opts))\n" \
194
+ " proc = proc do |opts|\n" \
195
+ " #{prim}.new(options.merge(opts))\n" \
193
196
  " end\n" \
194
197
  " @root = [name, proc]\n" \
195
- "end"
198
+ 'end'
196
199
  end
197
200
 
198
201
  # @param [Symbol,String] name name of object in model
@@ -201,8 +204,8 @@ module RASN1
201
204
  # +Object#object_id+.
202
205
  # @see Types::ObjectId#initialize
203
206
  def objectid(name, options={})
204
- options.merge!(name: name)
205
- proc = Proc.new { |opts| Types::ObjectId.new(options.merge(opts)) }
207
+ options[:name] = name
208
+ proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
206
209
  @root = [name, proc]
207
210
  end
208
211
 
@@ -210,15 +213,16 @@ module RASN1
210
213
  # @param [Hash] options
211
214
  # @see Types::Any#initialize
212
215
  def any(name, options={})
213
- options.merge!(name: name)
214
- proc = Proc.new { |opts| Types::Any.new(options.merge(opts)) }
216
+ options[:name] = name
217
+ proc = proc { |opts| Types::Any.new(options.merge(opts)) }
215
218
  @root = [name, proc]
216
219
  end
217
220
 
218
221
  # Give type name (aka class name)
219
222
  # @return [String]
220
223
  def type
221
- return @type if @type
224
+ return @type if defined? @type
225
+
222
226
  @type = self.to_s.gsub(/.*::/, '')
223
227
  end
224
228
 
@@ -254,7 +258,8 @@ module RASN1
254
258
  # @param [Object] value
255
259
  # @return [Object] value
256
260
  def []=(name, value)
257
- raise Error, "cannot set value for a Model" if @elements[name].is_a? Model
261
+ raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
262
+
258
263
  @elements[name].value = value
259
264
  end
260
265
 
@@ -302,6 +307,32 @@ module RASN1
302
307
  @elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber)
303
308
  end
304
309
 
310
+ # @overload value
311
+ # Get value of root element
312
+ # @return [Object,nil]
313
+ # @overload value(name)
314
+ # Direct access to the value of +name+ (nested) element of model.
315
+ # @param [String,Symbol] name
316
+ # @return [Object,nil]
317
+ # @return [Object,nil]
318
+ def value(name=nil, *args)
319
+ if name.nil?
320
+ @elements[@root].value
321
+ else
322
+ elt = by_name(name)
323
+
324
+ unless args.empty?
325
+ elt = elt.root if elt.is_a?(Model)
326
+ args.each do |arg|
327
+ elt = elt.root if elt.is_a?(Model)
328
+ elt = elt[arg]
329
+ end
330
+ end
331
+
332
+ elt.value
333
+ end
334
+ end
335
+
305
336
  # Delegate some methods to root element
306
337
  # @param [Symbol] meth
307
338
  def method_missing(meth, *args)
@@ -312,6 +343,11 @@ module RASN1
312
343
  end
313
344
  end
314
345
 
346
+ # @return [Boolean]
347
+ def respond_to_missing?(meth, *)
348
+ @elements[@root].respond_to?(meth) || super
349
+ end
350
+
315
351
  # @return [String]
316
352
  def inspect(level=0)
317
353
  ' ' * level + "(#{type}) #{root.inspect(-level)}"
@@ -324,14 +360,29 @@ module RASN1
324
360
  (other.class == self.class) && (other.to_der == self.to_der)
325
361
  end
326
362
 
327
- private
363
+ protected
364
+
365
+ # Give a (nested) element from its name
366
+ # @param [String, Symbol] name
367
+ # @return [Model, Types::Base]
368
+ def by_name(name)
369
+ elt = self[name]
370
+ return elt unless elt.nil?
371
+
372
+ @elements.each_key do |subelt_name|
373
+ if self[subelt_name].is_a?(Model)
374
+ elt = self[subelt_name][name]
375
+ return elt unless elt.nil?
376
+ end
377
+ end
328
378
 
329
- def is_composed?(el)
330
- [Types::Sequence, Types::Set].include? el.class
379
+ nil
331
380
  end
332
381
 
333
- def is_of?(el)
334
- [Types::SequenceOf, Types::SetOf].include? el.class
382
+ private
383
+
384
+ def composed?(elt)
385
+ [Types::Sequence, Types::Set].include? elt.class
335
386
  end
336
387
 
337
388
  def get_type(proc_or_class, options={})
@@ -347,50 +398,47 @@ module RASN1
347
398
  root = self.class.class_eval { @root }
348
399
  @root = root[0]
349
400
  @elements = {}
350
- @elements[@root] = get_type(root[1], self.class.class_eval { @options } || {})
401
+ @elements[@root] = get_type(root[1], self.class.options || {})
351
402
  root
352
403
  end
353
404
 
354
- def set_elements(name, el, content=nil)
355
- if content.is_a? Array
356
- @elements[name].value = content.map do |name2, proc_or_class, content2|
357
- subel = get_type(proc_or_class)
358
- @elements[name2] = subel
359
- if is_composed?(subel) and content2.is_a? Array
360
- set_elements(name2, proc_or_class, content2)
361
- end
362
- subel
363
- end
405
+ def set_elements(name, _elt, content=nil)
406
+ return unless content.is_a? Array
407
+
408
+ @elements[name].value = content.map do |name2, proc_or_class, content2|
409
+ subel = get_type(proc_or_class)
410
+ @elements[name2] = subel
411
+ set_elements(name2, proc_or_class, content2) if composed?(subel) && content2.is_a?(Array)
412
+ subel
364
413
  end
365
414
  end
366
415
 
367
416
  def initialize_elements(obj, args)
368
417
  args.each do |name, value|
369
- if obj[name]
370
- if value.is_a? Hash
371
- if obj[name].is_a? Model
372
- initialize_elements obj[name], value
373
- else
374
- raise ArgumentError, "element #{name}: may only pass a Hash for Model elements"
375
- end
376
- elsif value.is_a? Array
377
- composed = if obj[name].is_a? Model
378
- obj[name].root
379
- else
380
- obj[name]
381
- end
382
- if composed.of_type.is_a? Model
383
- value.each do |el|
384
- composed << initialize_elements(composed.of_type.class.new, el)
385
- end
386
- else
387
- value.each do |el|
388
- obj[name] << el
389
- end
418
+ next unless obj[name]
419
+
420
+ case value
421
+ when Hash
422
+ raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model
423
+
424
+ initialize_elements obj[name], value
425
+ when Array
426
+ composed = if obj[name].is_a? Model
427
+ obj[name].root
428
+ else
429
+ obj[name]
430
+ end
431
+ if composed.of_type.is_a? Model
432
+ value.each do |el|
433
+ composed << initialize_elements(composed.of_type.class.new, el)
390
434
  end
391
435
  else
392
- obj[name].value = value
436
+ value.each do |el|
437
+ obj[name] << el
438
+ end
393
439
  end
440
+ else
441
+ obj[name].value = value
394
442
  end
395
443
  end
396
444
  end
@@ -408,10 +456,11 @@ module RASN1
408
456
  end
409
457
  when Types::Sequence
410
458
  seq = my_element.value.map do |el|
411
- next if el.optional? and el.value.nil?
412
- name = el.is_a?(Model) ? @elements.key(el) : el.name
413
- [name, private_to_h(el)]
414
- end
459
+ next if el.optional? && el.value.nil?
460
+
461
+ name = el.is_a?(Model) ? @elements.key(el) : el.name
462
+ [name, private_to_h(el)]
463
+ end
415
464
  seq.compact!
416
465
  Hash[seq]
417
466
  else
@@ -1,46 +1,75 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  # This modules is a namesapce for all ASN.1 type classes.
3
5
  # @author Sylvain Daubert
4
6
  module Types
5
-
6
7
  # Give all primitive types
7
8
  # @return [Array<Types::Primitive>]
8
9
  def self.primitives
9
- @primitives ||= self.constants.map { |c| Types.const_get(c) }.
10
- select { |klass| klass < Primitive }
10
+ @primitives ||= self.constants.map { |c| Types.const_get(c) }
11
+ .select { |klass| klass < Primitive }
11
12
  end
12
13
 
13
14
  # Give all constructed types
14
15
  # @return [Array<Types::Constructed>]
15
16
  def self.constructed
16
- @constructed ||= self.constants.map { |c| Types.const_get(c) }.
17
- select { |klass| klass < Constructed }
17
+ @constructed ||= self.constants.map { |c| Types.const_get(c) }
18
+ .select { |klass| klass < Constructed }
19
+ end
20
+
21
+ # @private
22
+ # Decode a DER string to extract identifier octets.
23
+ # @param [String] der
24
+ # @return [Array] Return ASN.1 class as Symbol, contructed/primitive as Symbol,
25
+ # ID and size of identifier octets
26
+ def self.decode_identifier_octets(der)
27
+ first_octet = der[0].unpack1('C')
28
+ asn1_class = Types::Base::CLASSES.key(first_octet & Types::Base::CLASS_MASK)
29
+ pc = (first_octet & Types::Constructed::ASN1_PC).positive? ? :constructed : :primitive
30
+ id = first_octet & Types::Base::MULTI_OCTETS_ID
31
+
32
+ size = if id == Types::Base::MULTI_OCTETS_ID
33
+ id = 0
34
+ 1.upto(der.size - 1) do |i|
35
+ octet = der[i].unpack1('C')
36
+ id = (id << 7) | (octet & 0x7f)
37
+ break i + 1 if (octet & 0x80).zero?
38
+ end
39
+ else
40
+ 1
41
+ end
42
+
43
+ [asn1_class, pc, id, size]
18
44
  end
19
45
 
20
- # Give ASN.1 type from an integer. If +tag+ is unknown, return a {Types::Base}
46
+ # Give ASN.1 type from a DER string. If ID is unknown, return a {Types::Base}
21
47
  # object.
22
- # @param [Integer] tag
48
+ # @param [String] der
23
49
  # @return [Types::Base]
24
50
  # @raise [ASN1Error] +tag+ is out of range
25
- def self.tag2type(tag)
26
- raise ASN1Error, "tag is out of range" if tag > 0xff
27
-
28
- if !defined? @tag2types
51
+ def self.id2type(der)
52
+ # Define a cache for well-known ASN.1 types
53
+ unless defined? @id2types
29
54
  constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
30
55
  primitives = self.primitives - [Types::Enumerated]
31
- ary = [primitives, constructed].flatten.map do |type|
32
- next unless type.const_defined? :TAG
33
- [type::TAG, type]
56
+ ary = (primitives + constructed).map do |type|
57
+ next unless type.const_defined? :ID
58
+
59
+ [type::ID, type]
34
60
  end
35
- @tag2types = Hash[ary]
36
- @tag2types.default = Types::Base
61
+ @id2types = Hash[ary]
62
+ @id2types.default = Types::Base
63
+ @id2types.freeze
37
64
  end
38
65
 
39
- klass = @tag2types[tag & 0xdf] # Remove CONSTRUCTED bit
40
- is_constructed = (tag & 0x20) == 0x20
41
- asn1class = Types::Base::CLASSES.key(tag & 0xc0)
66
+ asn1class, pc, id, = self.decode_identifier_octets(der)
67
+ # cache_id: check versus class and 5 LSB bits
68
+ cache_id = der.unpack1('C') & 0xdf
69
+ klass = cache_id < Types::Base::MULTI_OCTETS_ID ? @id2types[id] : Types::Base
70
+ is_constructed = (pc == :constructed)
42
71
  options = { class: asn1class, constructed: is_constructed }
43
- options[:tag_value] = (tag & 0x1f) if klass == Types::Base
72
+ options[:tag_value] = id if klass == Types::Base
44
73
  klass.new(options)
45
74
  end
46
75
  end