rasn1 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc6eb3e3649f88dae027153d6136d5e5969135c74a527d4abbc42a42e03e2e73
4
- data.tar.gz: a674238e2461230d7076ffb29250b3a222c229b7af484b586da25642ba87863a
3
+ metadata.gz: 552859c3f49caec5374c8db4459ad23e1f9c43e930b0d6511cab8f997c798d20
4
+ data.tar.gz: 29eaf2fc529b470c9bc6283efbaf82e580d5b4b86d5c46920ee4a882a18b0d72
5
5
  SHA512:
6
- metadata.gz: dba48f620835a6ce4c9b6764d1538a2f96a94f21a7c8aa39b587783ba08b5c745c918eb885276372b3ae215f904596dfb3b5eab818c1a7416131e898c9c220eb
7
- data.tar.gz: e397c4b1b78320a3b69e24aa9bbf46d29b04653b03aed76f9c906e5e47a03b027957d2d8d6a8f60793f13255b2492f5eb43e9f05a73d3e8dd9bde676cbbcdfe9
6
+ metadata.gz: 9e5df0e417db7e1f02accae9cc4f17b4a49975d1dc17b716dbf587bb3f5e8ca68ed842d22358d7de037041255c164df5a92c6ada52adba944f0ac635793a64b1
7
+ data.tar.gz: e193284ea13ca5f80a460b8b390839a0283a8424ca8201f24cbd6113919399fcd5b83a2fc39dd5b26925187e19930a1aceccb1485255de8301435e20a2c82761
data/lib/rasn1/errors.rb CHANGED
@@ -20,8 +20,14 @@ module RASN1
20
20
 
21
21
  # CHOICE error: #chosen not set
22
22
  class ChoiceError < RASN1::Error
23
+ # @param [Types::Base] object
24
+ def initialize(object)
25
+ @object = object
26
+ super()
27
+ end
28
+
23
29
  def message
24
- "CHOICE #{@name}: #chosen not set"
30
+ "CHOICE #{@object.name}: #chosen not set"
25
31
  end
26
32
  end
27
33
 
@@ -37,7 +43,12 @@ module RASN1
37
43
 
38
44
  # @return [String]
39
45
  def message
40
- "Constraint not verified on #{object.inspect}"
46
+ "Constraint not verified on #{@object.inspect}"
41
47
  end
42
48
  end
49
+
50
+ # Exception raised when model validation fails
51
+ # @since 0.12.0
52
+ class ModelValidationError < Error
53
+ end
43
54
  end
data/lib/rasn1/model.rb CHANGED
@@ -57,16 +57,42 @@ module RASN1
57
57
  #
58
58
  # == Delegation
59
59
  # {Model} may delegate some methods to its root element. Thus, if root element
60
- # is, for example, a {TypeInts::Choice}, model may delegate +#chosen+ and +#chosen_value+.
60
+ # is, for example, a {Types::Choice}, model may delegate +#chosen+ and +#chosen_value+.
61
61
  #
62
62
  # All methods defined by root may be delegated by model, unless model also defines
63
63
  # this method.
64
64
  # @author Sylvain Daubert
65
- class Model
65
+ # @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
66
+ class Model # rubocop:disable Metrics/ClassLength
66
67
  # @private
67
- Elem = Struct.new(:name, :proc_or_class, :content)
68
+ Elem = Struct.new(:name, :proc_or_class, :content) do
69
+ def initialize(name, proc_or_class, content)
70
+ if content.is_a?(Array)
71
+ duplicate_names = find_all_duplicate_names(content.map(&:name) + [name])
72
+ raise ModelValidationError, "Duplicate name #{duplicate_names.first} found" if duplicate_names.any?
73
+ end
74
+
75
+ super
76
+ end
77
+
78
+ private
79
+
80
+ # @param [Array<String>] names
81
+ # @return [Array<String>] The duplicate names found in the array
82
+ def find_all_duplicate_names(names)
83
+ names.group_by { |name| name }
84
+ .select { |_name, values| values.length > 1 }
85
+ .keys
86
+ end
87
+ end
68
88
 
69
89
  # @private
90
+ WrapElem = Struct.new(:element, :options) do
91
+ def name
92
+ "#{element.name}_wrapper"
93
+ end
94
+ end
95
+
70
96
  module Accel
71
97
  # @return [Hash]
72
98
  attr_reader :options
@@ -79,6 +105,15 @@ module RASN1
79
105
  @root = Elem.new(name, model_klass, nil)
80
106
  end
81
107
 
108
+ # Use a {Wrapper} around a {Types::Base} or a {Model} object
109
+ # @param [Types::Base,Model] element
110
+ # @param [Hash] options
111
+ # @return [WrapElem]
112
+ # @since 0.12
113
+ def wrapper(element, options={})
114
+ @root = WrapElem.new(element, options)
115
+ end
116
+
82
117
  # Update options of root element.
83
118
  # May be used when subclassing.
84
119
  # class Model1 < RASN1::Model
@@ -92,8 +127,13 @@ module RASN1
92
127
  # end
93
128
  # @param [Hash] options
94
129
  # @return [void]
130
+ # @since 0.12.0 may change name through +:name+
95
131
  def root_options(options)
96
132
  @options = options
133
+ return unless options.key?(:name)
134
+
135
+ @root = @root.dup
136
+ @root.name = options[:name]
97
137
  end
98
138
 
99
139
  # On inheritance, create +@root+ class variable
@@ -105,33 +145,38 @@ module RASN1
105
145
  klass.class_eval { @root = root }
106
146
  end
107
147
 
148
+ # @since 0.11.0
149
+ # @since 0.12.0 track source location on error (adfoster-r7)
108
150
  def define_type_accel_base(accel_name, klass)
109
- singleton_class.class_eval(
110
- "def #{accel_name}(name, options={})\n" \
111
- " options[:name] = name\n" \
112
- " proc = proc do |opts|\n" \
113
- " #{klass}.new(options.merge(opts))\n" \
114
- " end\n" \
115
- " @root = Elem.new(name, proc, options[:content])\n" \
116
- 'end'
117
- )
151
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
152
+ def #{accel_name}(name, options={}) # def sequence(name, type, options)
153
+ options[:name] = name
154
+ proc = proc do |opts|
155
+ #{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
156
+ end
157
+ @root = Elem.new(name, proc, options[:content])
158
+ end
159
+ EVAL
118
160
  end
119
161
 
162
+ # @since 0.11.0
163
+ # @since 0.12.0 track source location on error (adfoster-r7)
120
164
  def define_type_accel_of(accel_name, klass)
121
- singleton_class.class_eval(
122
- "def #{accel_name}_of(name, type, options={})\n" \
123
- " options[:name] = name\n" \
124
- " proc = proc do |opts|\n" \
125
- " #{klass}.new(type, options.merge(opts))\n" \
126
- " end\n" \
127
- " @root = Elem.new(name, proc, nil)\n" \
128
- 'end'
129
- )
165
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
166
+ def #{accel_name}_of(name, type, options={}) # def sequence_of(name, type, options)
167
+ options[:name] = name
168
+ proc = proc do |opts|
169
+ #{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
170
+ end
171
+ @root = Elem.new(name, proc, nil)
172
+ end
173
+ EVAL
130
174
  end
131
175
 
132
176
  # Define an accelarator to access a type in a model definition
133
177
  # @param [String] accel_name
134
178
  # @param [Class] klass
179
+ # @since 0.11.0
135
180
  def define_type_accel(accel_name, klass)
136
181
  if klass < Types::SequenceOf
137
182
  define_type_accel_of(accel_name, klass)
@@ -184,17 +229,20 @@ module RASN1
184
229
 
185
230
  extend Accel
186
231
 
187
- # @method sequence(name, options)
232
+ # @!method sequence(name, options)
233
+ # @!scope class
188
234
  # @param [Symbol,String] name name of object in model
189
235
  # @param [Hash] options
190
236
  # @return [Elem]
191
237
  # @see Types::Sequence#initialize
192
- # @method set(name, options)
238
+ # @!method set(name, options)
239
+ # @!scope class
193
240
  # @param [Symbol,String] name name of object in model
194
241
  # @param [Hash] options
195
242
  # @return [Elem]
196
243
  # @see Types::Set#initialize
197
- # @method choice(name, options)
244
+ # @!method choice(name, options)
245
+ # @!scope class
198
246
  # @param [Symbol,String] name name of object in model
199
247
  # @param [Hash] options
200
248
  # @return [Elem]
@@ -203,13 +251,15 @@ module RASN1
203
251
  self.define_type_accel_base(type, Types.const_get(type.capitalize))
204
252
  end
205
253
 
206
- # @method sequence_of(name, type, options)
254
+ # @!method sequence_of(name, type, options)
255
+ # @!scope class
207
256
  # @param [Symbol,String] name name of object in model
208
257
  # @param [Model, Types::Base] type type for SEQUENCE OF
209
258
  # @param [Hash] options
210
259
  # @return [Elem]
211
260
  # @see Types::SequenceOf#initialize
212
- # @method set_of(name, type, options)
261
+ # @!method set_of(name, type, options)
262
+ # @!scope class
213
263
  # @param [Symbol,String] name name of object in model
214
264
  # @param [Model, Types::Base] type type for SET OF
215
265
  # @param [Hash] options
@@ -219,57 +269,68 @@ module RASN1
219
269
  define_type_accel_of(type, Types.const_get("#{type.capitalize}Of"))
220
270
  end
221
271
 
222
- # @method boolean(name, options)
272
+ # @!method boolean(name, options)
273
+ # @!scope class
223
274
  # @param [Symbol,String] name name of object in model
224
275
  # @param [Hash] options
225
276
  # @return [Elem]
226
277
  # @see Types::Boolean#initialize
227
- # @method integer(name, options)
278
+ # @!method integer(name, options)
279
+ # @!scope class
228
280
  # @param [Symbol,String] name name of object in model
229
281
  # @param [Hash] options
230
282
  # @return [Elem]
231
283
  # @see Types::Integer#initialize
232
- # @method bit_string(name, options)
284
+ # @!method bit_string(name, options)
285
+ # @!scope class
233
286
  # @param [Symbol,String] name name of object in model
234
287
  # @param [Hash] options
235
288
  # @return [Elem]
236
289
  # @see Types::BitString#initialize
237
- # @method octet_string(name, options)
290
+ # @!method octet_string(name, options)
291
+ # @!scope class
238
292
  # @param [Symbol,String] name name of object in model
239
293
  # @param [Hash] options
240
294
  # @return [Elem]
241
295
  # @see Types::OctetString#initialize
242
- # @method null(name, options)
296
+ # @!method null(name, options)
297
+ # @!scope class
243
298
  # @param [Symbol,String] name name of object in model
244
299
  # @param [Hash] options
245
300
  # @return [Elem]
246
301
  # @see Types::Null#initialize
247
- # @method enumerated(name, options)
302
+ # @!method enumerated(name, options)
303
+ # @!scope class
248
304
  # @param [Symbol,String] name name of object in model
249
305
  # @param [Hash] options
250
306
  # @return [Elem]
251
307
  # @see Types::Enumerated#initialize
252
- # @method utf8_string(name, options)
308
+ # @!method utf8_string(name, options)
309
+ # @!scope class
253
310
  # @param [Symbol,String] name name of object in model
254
311
  # @param [Hash] options
255
312
  # @return [Elem]
256
313
  # @see Types::Utf8String#initialize
257
- # @method numeric_string(name, options)
314
+ # @!method numeric_string(name, options)
315
+ # @!scope class
258
316
  # @param [Symbol,String] name name of object in model
259
317
  # @param [Hash] options
260
318
  # @return [Elem]
261
319
  # @see Types::NumericString#initialize
262
- # @method printable_string(name, options)
320
+ # @!method printable_string(name, options)
321
+ # @!scope class
263
322
  # @param [Symbol,String] name name of object in model
264
323
  # @param [Hash] options
265
324
  # @return [Elem]
266
325
  # @see Types::PrintableString#initialize
267
- # @method visible_string(name, options)
326
+ # @!method visible_string(name, options)
327
+ # @!scope class
268
328
  # @param [Symbol,String] name name of object in model
269
329
  # @param [Hash] options
270
330
  # @return [Elem]
271
331
  # @see Types::VisibleString#initialize
272
- # @method ia5_string(name, options)
332
+ # @!method ia5_string(name, options)
333
+ # @!scope class
273
334
  # @param [Symbol,String] name name of object in model
274
335
  # @param [Hash] options
275
336
  # @return [Elem]
@@ -285,7 +346,7 @@ module RASN1
285
346
  # @param [Hash] args
286
347
  def initialize(args={})
287
348
  root = generate_root
288
- set_elements(root)
349
+ generate_elements(root)
289
350
  initialize_elements(self, args)
290
351
  end
291
352
 
@@ -296,7 +357,7 @@ module RASN1
296
357
  @elements[name]
297
358
  end
298
359
 
299
- # Set value of element +name+. Element should be a {Base}.
360
+ # Set value of element +name+. Element should be a {Types::Base}.
300
361
  # @param [String,Symbol] name
301
362
  # @param [Object] value
302
363
  # @return [Object] value
@@ -460,14 +521,33 @@ module RASN1
460
521
  class_element
461
522
  end
462
523
 
463
- def set_elements(element) # rubocop:disable Naming/AccessorMethodName
524
+ def generate_elements(element)
525
+ if element.is_a?(WrapElem)
526
+ generate_wrapper(element)
527
+ return
528
+ end
464
529
  return unless element.content.is_a? Array
465
530
 
466
531
  @elements[name].value = element.content.map do |another_element|
467
- subel = get_type(another_element.proc_or_class)
468
- @elements[another_element.name] = subel
469
- set_elements(another_element) if composed?(subel) && another_element.content.is_a?(Array)
532
+ add_subelement(another_element)
533
+ end
534
+ end
535
+
536
+ def generate_wrapper(wrap_elem)
537
+ inner_elem = wrap_elem.element
538
+ subel = add_subelement(inner_elem)
539
+ Wrapper.new(subel, wrap_elem.options)
540
+ end
541
+
542
+ def add_subelement(subelement)
543
+ case subelement
544
+ when Elem
545
+ subel = get_type(subelement.proc_or_class)
546
+ @elements[subelement.name] = subel
547
+ generate_elements(subelement) if composed?(subel) && subelement.content.is_a?(Array)
470
548
  subel
549
+ when WrapElem
550
+ generate_wrapper(subelement)
471
551
  end
472
552
  end
473
553
 
@@ -477,7 +557,7 @@ module RASN1
477
557
 
478
558
  case value
479
559
  when Hash
480
- raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model
560
+ raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a?(Model)
481
561
 
482
562
  initialize_elements obj[name], value
483
563
  when Array
@@ -499,7 +579,7 @@ module RASN1
499
579
  end
500
580
  end
501
581
 
502
- def private_to_h(element=nil)
582
+ def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
503
583
  my_element = element || root
504
584
  my_element = my_element.root if my_element.is_a?(Model)
505
585
  value = case my_element
@@ -507,6 +587,13 @@ module RASN1
507
587
  sequence_of_to_h(my_element)
508
588
  when Types::Sequence
509
589
  sequence_to_h(my_element)
590
+ # @author adfoster-r7
591
+ when Types::Choice
592
+ raise ChoiceError.new(my_element) if my_element.chosen.nil?
593
+
594
+ private_to_h(my_element.value[my_element.chosen])
595
+ when Wrapper
596
+ wrapper_to_h(my_element)
510
597
  else
511
598
  my_element.value
512
599
  end
@@ -529,11 +616,29 @@ module RASN1
529
616
  ary = seq.value.map do |el|
530
617
  next if el.optional? && el.value.nil?
531
618
 
532
- name = el.is_a?(Model) ? @elements.key(el) : el.name
533
- [name, private_to_h(el)]
619
+ case el
620
+ when Model
621
+ hsh = el.to_h
622
+ hsh = hsh[hsh.keys.first]
623
+ [@elements.key(el), hsh]
624
+ when Wrapper
625
+ [@elements.key(el.element), wrapper_to_h(el)]
626
+ else
627
+ [el.name, private_to_h(el)]
628
+ end
534
629
  end
535
630
  ary.compact!
536
631
  ary.to_h
537
632
  end
633
+
634
+ def wrapper_to_h(wrap)
635
+ case wrap.element
636
+ when Model
637
+ hsh = wrap.element.to_h
638
+ hsh[hsh.keys.first]
639
+ else
640
+ private_to_h(wrap.element)
641
+ end
642
+ end
538
643
  end
539
644
  end
@@ -21,6 +21,10 @@ module RASN1
21
21
  end
22
22
  end
23
23
 
24
+ def can_build?
25
+ value? || !optional?
26
+ end
27
+
24
28
  # Parse a DER string. This method updates object: {#value} will be a DER
25
29
  # string.
26
30
  # @param [String] der DER string
@@ -69,6 +69,8 @@ module RASN1
69
69
  attr_reader :asn1_class
70
70
  # @return [Object,nil] default value, if defined
71
71
  attr_reader :default
72
+ # @return [Hash[Symbol, Object]]
73
+ attr_reader :options
72
74
 
73
75
  # Get ASN.1 type
74
76
  # @return [String]
@@ -116,7 +118,8 @@ module RASN1
116
118
  # @option options [::String] :name name for this node
117
119
  def initialize(options={})
118
120
  @constructed = nil
119
- set_options options
121
+ set_value(options.delete(:value))
122
+ self.options = options
120
123
  specific_initializer
121
124
  end
122
125
 
@@ -253,11 +256,38 @@ module RASN1
253
256
  (other.class == self.class) && (other.to_der == self.to_der)
254
257
  end
255
258
 
259
+ # Set options to this object
260
+ # @param [Hash] options
261
+ # @return [void]
262
+ # @since 0.12
263
+ def options=(options)
264
+ set_class options[:class]
265
+ set_optional options[:optional]
266
+ set_default options[:default]
267
+ set_tag options
268
+ @name = options[:name]
269
+ @options = options
270
+ end
271
+
272
+ # Say if a value is set
273
+ # @return [Boolean]
274
+ # @since 0.12.0
275
+ def value?
276
+ !@no_value
277
+ end
278
+
279
+ # Say if DER can be built (not default value, not optional without value, has a value)
280
+ # @return [Boolean]
281
+ # @since 0.12.0
282
+ def can_build?
283
+ value? && (@default.nil? || (@value != @default))
284
+ end
285
+
256
286
  private
257
287
 
258
288
  def pc_bit
259
289
  if @constructed.nil?
260
- self.class::ASN1_PC
290
+ self.class.const_get(:ASN1_PC)
261
291
  elsif @constructed # true
262
292
  Constructed::ASN1_PC
263
293
  else # false
@@ -296,16 +326,6 @@ module RASN1
296
326
  @value = der
297
327
  end
298
328
 
299
- def set_options(options) # rubocop:disable Naming/AccessorMethodName
300
- set_class options[:class]
301
- set_optional options[:optional]
302
- set_default options[:default]
303
- set_tag options
304
- set_value options[:value]
305
- @name = options[:name]
306
- @options = options
307
- end
308
-
309
329
  def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
310
330
  case asn1_class
311
331
  when nil
@@ -330,17 +350,15 @@ module RASN1
330
350
  # handle undocumented option +:tag_value+, used internally by
331
351
  # {RASN1.parse} to parse non-universal class tags.
332
352
  def set_tag(options) # rubocop:disable Naming/AccessorMethodName
353
+ @constructed = options[:constructed]
333
354
  if options[:explicit]
334
355
  @tag = :explicit
335
356
  @id_value = options[:explicit]
336
- @constructed = options[:constructed]
337
357
  elsif options[:implicit]
338
358
  @tag = :implicit
339
359
  @id_value = options[:implicit]
340
- @constructed = options[:constructed]
341
360
  elsif options[:tag_value]
342
361
  @id_value = options[:tag_value]
343
- @constructed = options[:constructed]
344
362
  end
345
363
 
346
364
  @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
@@ -357,15 +375,6 @@ module RASN1
357
375
  value
358
376
  end
359
377
 
360
- def value?
361
- !@no_value
362
- end
363
-
364
- def can_build?
365
- (@default.nil? || (value? && (@value != @default))) &&
366
- (!optional? || value?)
367
- end
368
-
369
378
  def build
370
379
  if can_build?
371
380
  if explicit?
@@ -382,9 +391,9 @@ module RASN1
382
391
  end
383
392
 
384
393
  def id_value
385
- return @id_value if defined? @id_value
394
+ return @id_value if defined?(@id_value) && !@id_value.nil?
386
395
 
387
- self.class::ID
396
+ self.class.const_get(:ID)
388
397
  end
389
398
 
390
399
  def encode_identifier_octets
@@ -42,14 +42,14 @@ module RASN1
42
42
  str << " #{value.inspect} (bit length: #{bit_length})"
43
43
  end
44
44
 
45
- private
46
-
47
45
  def can_build?
48
- !(!@default.nil? && (!value? || (@value == @default) &&
49
- (@bit_length == @default_bit_length))) &&
50
- !(optional? && !value?)
46
+ super || (!@default.nil? && (@bit_length != @default_bit_length))
51
47
  end
52
48
 
49
+ private
50
+
51
+ # @author Sylvain Daubert
52
+ # @author adfoster-r7
53
53
  def value_to_der
54
54
  raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
55
55
 
@@ -70,9 +70,8 @@ module RASN1
70
70
  end
71
71
 
72
72
  def generate_value_with_correct_length
73
- value = @value || ''
74
- value << "\x00" while value.length * 8 < @bit_length.to_i
75
- value.force_encoding('BINARY')
73
+ value = (@value || '').dup.force_encoding('BINARY')
74
+ value << "\x00".b while value.length * 8 < @bit_length.to_i
76
75
  return value unless value.length * 8 > @bit_length.to_i
77
76
 
78
77
  max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
@@ -85,6 +84,10 @@ module RASN1
85
84
  @bit_length = value.length * 8 - unused
86
85
  @value = value
87
86
  end
87
+
88
+ def explicit_type
89
+ self.class.new(value: @value, bit_length: @bit_length)
90
+ end
88
91
  end
89
92
  end
90
93
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 BmpString
6
+ # @since 0.12.0
7
+ # @author adfoster-r7
8
+ class BmpString < OctetString
9
+ # BmpString id value
10
+ ID = 30
11
+
12
+ # Get ASN.1 type
13
+ # @return [String]
14
+ def self.type
15
+ 'BmpString'
16
+ end
17
+
18
+ private
19
+
20
+ def value_to_der
21
+ @value.to_s.dup.encode('UTF-16BE').b
22
+ end
23
+
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ @value = der.to_s.dup.force_encoding('UTF-16BE')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -100,7 +100,7 @@ module RASN1
100
100
  private
101
101
 
102
102
  def check_chosen
103
- raise ChoiceError if !defined?(@chosen) || @chosen.nil?
103
+ raise ChoiceError.new(self) if !defined?(@chosen) || @chosen.nil?
104
104
  end
105
105
  end
106
106
  end
@@ -3,7 +3,7 @@
3
3
  module RASN1
4
4
  module Types
5
5
  # Mixin to had constraints on a RASN1 type.
6
- # Should not be used directly but through {Model.define_type}.
6
+ # Should not be used directly but through {Types.define_type}.
7
7
  # @version 0.11.0
8
8
  # @author Sylvain Daubert
9
9
  module Constrained
@@ -10,6 +10,22 @@ module RASN1
10
10
  # Constructed value
11
11
  ASN1_PC = 0x20
12
12
 
13
+ # @return [Boolean]
14
+ # @since 0.12.0
15
+ # @see Base#can_build?
16
+ def can_build? # rubocop:disable Metrics/CyclomaticComplexity
17
+ return super unless @value.is_a?(Array) && optional?
18
+ return false unless super
19
+
20
+ @value.any? do |el|
21
+ el.can_build? && (
22
+ el.primitive? ||
23
+ (el.value.respond_to?(:empty?) ? !el.value.empty? : !el.value.nil?))
24
+ end
25
+ end
26
+
27
+ # @param [::Integer] level (default: 0)
28
+ # @return [String]
13
29
  def inspect(level=0)
14
30
  case @value
15
31
  when Array
@@ -18,7 +34,7 @@ module RASN1
18
34
  level = level.abs + 1
19
35
  @value.each do |item|
20
36
  case item
21
- when Base, Model
37
+ when Base, Model, Wrapper
22
38
  str << "#{item.inspect(level)}\n"
23
39
  else
24
40
  str << ' ' * level
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'strptime'
4
+
3
5
  module RASN1
4
6
  module Types
5
7
  # ASN.1 GeneralizedTime
@@ -24,25 +26,33 @@ module RASN1
24
26
  # GeneralizedTime id value
25
27
  ID = 24
26
28
 
29
+ # @private
30
+ HOUR_TO_SEC = 3600
31
+ # @private
32
+ MINUTE_TO_SEC = 60
33
+ # @private
34
+ SECOND_TO_SEC = 1
35
+
27
36
  # Get ASN.1 type
28
37
  # @return [String]
29
38
  def self.type
30
39
  'GeneralizedTime'
31
40
  end
32
41
 
33
- # @return [DateTime]
42
+ # @return [Time]
34
43
  def void_value
35
- DateTime.now
44
+ Time.now
36
45
  end
37
46
 
38
47
  private
39
48
 
40
49
  def value_to_der
41
- if @value.nsec.positive?
42
- der = @value.getutc.strftime('%Y%m%d%H%M%S.%9NZ')
50
+ utc_value = @value.getutc
51
+ if utc_value.nsec.positive?
52
+ der = utc_value.strftime('%Y%m%d%H%M%S.%9NZ')
43
53
  der.sub(/0+Z/, 'Z')
44
54
  else
45
- @value.getutc.strftime('%Y%m%d%H%M%SZ')
55
+ utc_value.strftime('%Y%m%d%H%M%SZ')
46
56
  end
47
57
  end
48
58
 
@@ -61,18 +71,13 @@ module RASN1
61
71
  end
62
72
 
63
73
  def value_when_fraction_empty(date_hour)
64
- utc_offset_forced = false
65
-
66
74
  if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
67
75
  # If not UTC, have to add offset with UTC to force
68
- # DateTime#strptime to generate a local time. But this difference
69
- # may be errored because of DST.
76
+ # Strptime to generate a local time.
70
77
  date_hour << Time.now.strftime('%z')
71
- utc_offset_forced = true
72
78
  end
73
79
 
74
80
  value_from(date_hour)
75
- fix_dst if utc_offset_forced
76
81
  end
77
82
 
78
83
  def value_when_fraction_ends_with_z(date_hour, fraction)
@@ -90,62 +95,44 @@ module RASN1
90
95
  date_hour << match[2]
91
96
  else
92
97
  # fraction only contains fraction.
93
- # Have to add offset with UTC to force DateTime#strptime to
94
- # generate a local time. But this difference may be errored
95
- # because of DST.
98
+ # Have to add offset with UTC to force Strptime to
99
+ # generate a local time.
96
100
  date_hour << Time.now.strftime('%z')
97
- utc_offset_forced = true
98
101
  end
99
102
 
100
103
  frac_base = value_from(date_hour)
101
- fix_dst if utc_offset_forced
102
104
  fix_value(fraction, frac_base)
103
105
  end
104
106
 
105
107
  def value_from(date_hour)
106
108
  format, frac_base = strformat(date_hour)
107
- @value = DateTime.strptime(date_hour, format).to_time
109
+ @value = Strptime.new(format).exec(date_hour)
108
110
  frac_base
109
111
  end
110
112
 
111
- # Check DST. There may be a shift of one hour...
112
- def fix_dst
113
- compare_time = Time.new(*@value.to_a[0..5].reverse)
114
- @value = compare_time if compare_time.utc_offset != @value.utc_offset
115
- end
116
-
117
113
  def fix_value(fraction, frac_base)
118
- @value += ".#{fraction}".to_r * frac_base unless fraction.nil?
114
+ frac = ".#{fraction}".to_r * frac_base
115
+ @value = (@value + frac) unless fraction.nil?
119
116
  end
120
117
 
121
- def strformat(date_hour) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
118
+ def strformat(date_hour)
122
119
  case date_hour.size
123
120
  when 11
124
- frac_base = 60 * 60
125
- format = '%Y%m%d%HZ'
126
- when 13
127
- frac_base = 60
128
- format = '%Y%m%d%H%MZ'
121
+ ['%Y%m%d%H%z', HOUR_TO_SEC]
122
+ when 13, 17
123
+ ['%Y%m%d%H%M%z', MINUTE_TO_SEC]
129
124
  when 15
130
125
  if date_hour[-1] == 'Z'
131
- frac_base = 1
132
- format = '%Y%m%d%H%M%SZ'
126
+ ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
133
127
  else
134
- frac_base = 60 * 60
135
- format = '%Y%m%d%H%z'
128
+ ['%Y%m%d%H%z', HOUR_TO_SEC]
136
129
  end
137
- when 17
138
- frac_base = 60
139
- format = '%Y%m%d%H%M%z'
140
130
  when 19
141
- frac_base = 1
142
- format = '%Y%m%d%H%M%S%z'
131
+ ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
143
132
  else
144
133
  prefix = @name.nil? ? type : "tag #{@name}"
145
- raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
134
+ raise ASN1Error, "#{prefix}: unrecognized format: #{date_hour}"
146
135
  end
147
-
148
- [format, frac_base]
149
136
  end
150
137
  end
151
138
  end
@@ -17,12 +17,12 @@ module RASN1
17
17
  private
18
18
 
19
19
  def value_to_der
20
- @value.to_s.force_encoding('US-ASCII').force_encoding('BINARY')
20
+ @value.to_s.dup.force_encoding('US-ASCII').b
21
21
  end
22
22
 
23
23
  def der_to_value(der, ber: false)
24
24
  super
25
- @value.to_s.force_encoding('US-ASCII')
25
+ @value.to_s.dup.force_encoding('US-ASCII')
26
26
  end
27
27
  end
28
28
  end
@@ -133,8 +133,9 @@ module RASN1
133
133
  return if @enum.empty?
134
134
 
135
135
  int_value = @value
136
+ raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless @enum.value?(@value)
137
+
136
138
  @value = @enum.key(@value)
137
- raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless value?
138
139
  end
139
140
 
140
141
  def explicit_type
@@ -15,6 +15,10 @@ module RASN1
15
15
  str
16
16
  end
17
17
 
18
+ def can_build?
19
+ !optional?
20
+ end
21
+
18
22
  private
19
23
 
20
24
  def value_to_der
@@ -18,7 +18,7 @@ module RASN1
18
18
 
19
19
  def value_to_der
20
20
  check_characters
21
- @value.to_s.force_encoding('BINARY')
21
+ @value.to_s.b
22
22
  end
23
23
 
24
24
  def der_to_value(der, ber: false)
@@ -18,7 +18,7 @@ module RASN1
18
18
 
19
19
  def value_to_der
20
20
  check_characters
21
- @value.to_s.force_encoding('BINARY')
21
+ @value.to_s.b
22
22
  end
23
23
 
24
24
  def der_to_value(der, ber: false)
@@ -59,7 +59,6 @@ module RASN1
59
59
  Sequence.encoded_type
60
60
  end
61
61
 
62
- # @param [Symbol, String] name name for this tag in grammar
63
62
  # @param [Class, Base] of_type base type for sequence of
64
63
  # @see Base#initialize
65
64
  def initialize(of_type, options={})
@@ -105,6 +104,7 @@ module RASN1
105
104
  @value.length
106
105
  end
107
106
 
107
+ # @return [String]
108
108
  def inspect(level=0)
109
109
  str = common_inspect(level)
110
110
  str << "\n"
@@ -35,20 +35,16 @@ module RASN1
35
35
 
36
36
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
37
37
  format = case der.size
38
- when 11
39
- '%Y%m%d%H%MZ'
40
- when 13
41
- '%Y%m%d%H%M%SZ'
42
- when 15
38
+ when 11, 15
43
39
  '%Y%m%d%H%M%z'
44
- when 17
40
+ when 13, 17
45
41
  '%Y%m%d%H%M%S%z'
46
42
  else
47
43
  prefix = @name.nil? ? type : "tag #{@name}"
48
44
  raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
49
45
  end
50
46
  century = (Time.now.year / 100).to_s
51
- @value = DateTime.strptime(century + der, format).to_time
47
+ @value = Strptime.new(format).exec(century + der)
52
48
  end
53
49
  end
54
50
  end
@@ -17,12 +17,12 @@ module RASN1
17
17
  private
18
18
 
19
19
  def value_to_der
20
- @value.to_s.force_encoding('UTF-8').force_encoding('BINARY')
20
+ @value.to_s.dup.force_encoding('UTF-8').b
21
21
  end
22
22
 
23
23
  def der_to_value(der, ber: false)
24
24
  super
25
- @value = der.force_encoding('UTF-8')
25
+ @value = der.dup.force_encoding('UTF-8')
26
26
  end
27
27
  end
28
28
  end
data/lib/rasn1/types.rb CHANGED
@@ -4,18 +4,25 @@ module RASN1
4
4
  # This modules is a namesapce for all ASN.1 type classes.
5
5
  # @author Sylvain Daubert
6
6
  module Types
7
+ @primitives = []
8
+ @constructed = []
9
+
7
10
  # Give all primitive types
8
11
  # @return [Array<Types::Primitive>]
9
12
  def self.primitives
10
- @primitives ||= self.constants.map { |c| Types.const_get(c) }
11
- .select { |klass| klass < Primitive }
13
+ return @primitives unless @primitives.empty?
14
+
15
+ @primitives = self.constants.map { |c| Types.const_get(c) }
16
+ .select { |klass| klass < Primitive }
12
17
  end
13
18
 
14
19
  # Give all constructed types
15
20
  # @return [Array<Types::Constructed>]
16
21
  def self.constructed
17
- @constructed ||= self.constants.map { |c| Types.const_get(c) }
18
- .select { |klass| klass < Constructed }
22
+ return @constructed unless @constructed.empty?
23
+
24
+ @constructed = self.constants.map { |c| Types.const_get(c) }
25
+ .select { |klass| klass < Constructed }
19
26
  end
20
27
 
21
28
  # @private
@@ -67,8 +74,8 @@ module RASN1
67
74
  def self.generate_id2type_cache
68
75
  constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
69
76
  primitives = self.primitives - [Types::Enumerated]
70
- ary = (primitives + constructed).select { |type| type.const_defined? :ID }
71
- .map { |type| [type::ID, type] }
77
+ ary = (primitives + constructed).select { |type| type.const_defined?(:ID) }
78
+ .map { |type| [type.const_get(:ID), type] }
72
79
  @id2types = ary.to_h
73
80
  @id2types.default = Types::Base
74
81
  @id2types.freeze
@@ -77,9 +84,12 @@ module RASN1
77
84
  # Define a new ASN.1 type from a base one.
78
85
  # This new type may have a constraint defines on it.
79
86
  # @param [Symbol,String] name New type name. Must start with a capital letter.
80
- # @param [Types::Base] from
87
+ # @param [Types::Base] from class from which inherits
88
+ # @param [Module] in_module module in which creates new type (default to {RASN1::Types})
81
89
  # @return [Class] newly created class
82
- def self.define_type(name, from:, &block)
90
+ # @since 0.11.0
91
+ # @since 0.12.0 in_module parameter
92
+ def self.define_type(name, from:, in_module: self, &block)
83
93
  constraint = block.nil? ? nil : block.to_proc
84
94
 
85
95
  new_klass = Class.new(from) do
@@ -87,12 +97,13 @@ module RASN1
87
97
  end
88
98
  new_klass.constraint = constraint
89
99
 
90
- self.const_set(name, new_klass)
91
- Model.define_type_accel(name.downcase, new_klass)
100
+ in_module.const_set(name, new_klass)
101
+ accel_name = name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
102
+ Model.define_type_accel(accel_name, new_klass)
92
103
 
93
104
  # Empty type caches
94
- @primitives = nil
95
- @constructed = nil
105
+ @primitives = []
106
+ @constructed = []
96
107
 
97
108
  new_klass
98
109
  end
@@ -108,6 +119,7 @@ require_relative 'types/octet_string'
108
119
  require_relative 'types/null'
109
120
  require_relative 'types/object_id'
110
121
  require_relative 'types/enumerated'
122
+ require_relative 'types/bmp_string'
111
123
  require_relative 'types/utf8_string'
112
124
  require_relative 'types/numeric_string'
113
125
  require_relative 'types/printable_string'
data/lib/rasn1/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RASN1
4
- VERSION = '0.11.0'
4
+ VERSION = '0.12.0'
5
5
  end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module RASN1
6
+ # This class is used to wrap a {Types::Base} or {Model} instance to force its options.
7
+ #
8
+ # == Usage
9
+ # This class may be used to wrap another RASN1 object by 3 ways:
10
+ # * wrap an object to modify its options,
11
+ # * implicitly wrap an object (i.e. change its tag),
12
+ # * explicitly wrap an object (i.e wrap the object in another explicit ASN.1 tag)
13
+ #
14
+ # @example
15
+ # # object to wrap
16
+ # int = RASN1::Types::Integer.new(implicit: 1) # its tag is 0x81
17
+ # # simple wraper, change an option
18
+ # wrapper = RASN1::Wrapper.new(int, optional: true, default: 1)
19
+ # # implicit wrapper
20
+ # wrapper = RASN1::Wrapper.new(int, implicit: 3) # wrapped int tag is now 0x83
21
+ # # explicit wrapper
22
+ # wrapper = RASN1::Wrapper.new(int, explicit: 4) # int tag is always 0x81, but it is wrapped in a 0x84 tag
23
+ # @since 0.12.0
24
+ class Wrapper < SimpleDelegator
25
+ # @private Private class used to build/parse explicit wrappers
26
+ class ExplicitWrapper < Types::Base
27
+ ID = 0 # not used
28
+ ASN1_PC = 0 # not constructed
29
+
30
+ def self.type
31
+ ''
32
+ end
33
+
34
+ # @return [Boolean]
35
+ # @see Types::Base#can_build?
36
+ def can_build?
37
+ ok = super
38
+ return ok unless optional?
39
+
40
+ ok && @value.can_build?
41
+ end
42
+
43
+ private
44
+
45
+ def value_to_der
46
+ @value.is_a?(String) ? @value : @value.to_der
47
+ end
48
+
49
+ def inspect_value
50
+ ''
51
+ end
52
+ end
53
+
54
+ # @param [Types::Base,Model] element element to wrap
55
+ # @param [Hash] options
56
+ def initialize(element, options={})
57
+ opts = explicit_implicit(options)
58
+
59
+ if explicit?
60
+ generate_explicit_wrapper(opts)
61
+ element.options = element.options.merge(generate_explicit_wrapper_options(opts))
62
+ @options = opts
63
+ else
64
+ opts[:value] = element.value
65
+ element.options = element.options.merge(opts)
66
+ @options = {}
67
+ end
68
+ raise RASN1::Error, 'Cannot be implicit and explicit' if explicit? && implicit?
69
+
70
+ super(element)
71
+ end
72
+
73
+ def explicit_implicit(options)
74
+ opts = options.dup
75
+ @explicit = opts.delete(:explicit)
76
+ @implicit = opts.delete(:implicit)
77
+ opts
78
+ end
79
+
80
+ def generate_explicit_wrapper(options)
81
+ # ExplicitWrapper is a hand-made explicit tag, but we have to use its implicit option
82
+ # to force its tag value.
83
+ @explicit_wrapper = ExplicitWrapper.new(options.merge(implicit: @explicit))
84
+ end
85
+
86
+ def generate_explicit_wrapper_options(options)
87
+ new_opts = {}
88
+ new_opts[:default] = options[:default] if options.key?(:default)
89
+ new_opts[:optional] = options[:optional] if options.key?(:optional)
90
+ new_opts
91
+ end
92
+
93
+ # Say if wrapper is an explicit one (i.e. add tag and length to its element)
94
+ # @return [Boolean]
95
+ def explicit?
96
+ !!@explicit
97
+ end
98
+
99
+ # Say if wrapper is an implicit one (i.e. change tag of its element)
100
+ # @return [Boolean]
101
+ def implicit?
102
+ !!@implicit
103
+ end
104
+
105
+ # Convert wrapper and its element to a DER string
106
+ # @return [String]
107
+ def to_der
108
+ if implicit?
109
+ el = generate_implicit_element
110
+ el.to_der
111
+ elsif explicit?
112
+ @explicit_wrapper.value = element
113
+ @explicit_wrapper.to_der
114
+ else
115
+ element.to_der
116
+ end
117
+ end
118
+
119
+ # Parse a DER string. This method updates object.
120
+ # @param [String] der DER string
121
+ # @param [Boolean] ber if +true+, accept BER encoding
122
+ # @return [Integer] total number of parsed bytes
123
+ # @raise [ASN1Error] error on parsing
124
+ def parse!(der, ber: false)
125
+ if implicit?
126
+ el = generate_implicit_element
127
+ parsed = el.parse!(der, ber: ber)
128
+ element.value = el.value
129
+ parsed
130
+ elsif explicit?
131
+ parsed = @explicit_wrapper.parse!(der, ber: ber)
132
+ element.parse!(@explicit_wrapper.value, ber: ber) if parsed.positive?
133
+ parsed
134
+ else
135
+ element.parse!(der, ber: ber)
136
+ end
137
+ end
138
+
139
+ def value?
140
+ if explicit?
141
+ @explicit_wrapper.value?
142
+ else
143
+ __getobj__.value?
144
+ end
145
+ end
146
+
147
+ # Return Wrapped element
148
+ # @return [Types::Base,Model]
149
+ def element
150
+ __getobj__
151
+ end
152
+
153
+ # @return [::Integer]
154
+ def id
155
+ if implicit?
156
+ @implicit
157
+ elsif explicit?
158
+ @explicit
159
+ else
160
+ element.id
161
+ end
162
+ end
163
+
164
+ # @return [Symbol]
165
+ def asn1_class
166
+ return element.asn1_class unless @options.key?(:class)
167
+
168
+ @options[:class]
169
+ end
170
+
171
+ # @return [Boolean]
172
+ def constructed?
173
+ return element.constructed? unless @options.key?(:constructed)
174
+
175
+ @options[:constructed]
176
+ end
177
+
178
+ # @return [Boolean]
179
+ def primitive?
180
+ !constructed?
181
+ end
182
+
183
+ def inspect(level=0)
184
+ return super(level) unless explicit?
185
+
186
+ @explicit_wrapper.inspect(level) << ' ' << super(level)
187
+ end
188
+
189
+ private
190
+
191
+ def generate_implicit_element
192
+ el = element.dup
193
+ if el.explicit?
194
+ el.options = el.options.merge(explicit: @implicit)
195
+ elsif el.implicit?
196
+ el.options = el.options.merge(implicit: @implicit)
197
+ end
198
+ el
199
+ end
200
+ end
201
+ end
data/lib/rasn1.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rasn1/version'
4
- require 'rasn1/errors'
5
- require 'rasn1/types'
6
- require 'rasn1/model'
3
+ require_relative 'rasn1/version'
4
+ require_relative 'rasn1/errors'
5
+ require_relative 'rasn1/types'
6
+ require_relative 'rasn1/model'
7
+ require_relative 'rasn1/wrapper'
7
8
 
8
9
  # Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
9
10
  # @author Sylvain Daubert
metadata CHANGED
@@ -1,57 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasn1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvain Daubert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-13 00:00:00.000000000 Z
11
+ date: 2022-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: strptime
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '12.3'
20
- type: :development
19
+ version: 0.2.5
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '12.3'
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '3.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '3.0'
41
- - !ruby/object:Gem::Dependency
42
- name: yard
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.9'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.9'
26
+ version: 0.2.5
55
27
  description: |
56
28
  RASN1 is a pure ruby ASN.1 library. It may encode and decode DER and BER
57
29
  encodings.
@@ -72,6 +44,7 @@ files:
72
44
  - lib/rasn1/types/any.rb
73
45
  - lib/rasn1/types/base.rb
74
46
  - lib/rasn1/types/bit_string.rb
47
+ - lib/rasn1/types/bmp_string.rb
75
48
  - lib/rasn1/types/boolean.rb
76
49
  - lib/rasn1/types/choice.rb
77
50
  - lib/rasn1/types/constrained.rb
@@ -94,6 +67,7 @@ files:
94
67
  - lib/rasn1/types/utf8_string.rb
95
68
  - lib/rasn1/types/visible_string.rb
96
69
  - lib/rasn1/version.rb
70
+ - lib/rasn1/wrapper.rb
97
71
  homepage: https://github.com/sdaubert/rasn1
98
72
  licenses:
99
73
  - MIT