rasn1 0.11.0 → 0.12.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: 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