rasn1 0.11.0 → 0.12.1

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.
@@ -69,6 +69,14 @@ 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
74
+ # @return [String] raw parsed data
75
+ attr_reader :raw_data
76
+ # @return [String] raw parsed length
77
+ attr_reader :raw_length
78
+
79
+ private :raw_data, :raw_length
72
80
 
73
81
  # Get ASN.1 type
74
82
  # @return [String]
@@ -116,15 +124,19 @@ module RASN1
116
124
  # @option options [::String] :name name for this node
117
125
  def initialize(options={})
118
126
  @constructed = nil
119
- set_options options
127
+ set_value(options.delete(:value))
128
+ self.options = options
120
129
  specific_initializer
130
+ @raw_data = ''.b
131
+ @raw_length = ''.b
121
132
  end
122
133
 
123
134
  # @abstract To help subclass initialize itself. Default implementation do nothing.
124
135
  def specific_initializer; end
125
136
 
126
- # Used by +#dup+ and +#clone+. Deep copy @value and @default.
127
- def initialize_copy(_other)
137
+ # Deep copy @value and @default.
138
+ def initialize_copy(*)
139
+ super
128
140
  @value = @value.dup
129
141
  @no_value = @no_value.dup
130
142
  @default = @default.dup
@@ -150,6 +162,7 @@ module RASN1
150
162
  ''
151
163
  end
152
164
 
165
+ # Say if this type is optional
153
166
  # @return [::Boolean]
154
167
  def optional?
155
168
  @optional
@@ -212,17 +225,11 @@ module RASN1
212
225
  # @return [Integer] total number of parsed bytes
213
226
  # @raise [ASN1Error] error on parsing
214
227
  def parse!(der, ber: false)
215
- return 0 unless check_id(der)
228
+ total_length, data = do_parse(der, ber)
229
+ return 0 if total_length.zero?
216
230
 
217
- id_size = Types.decode_identifier_octets(der).last
218
- total_length, data = get_data(der[id_size..-1], ber)
219
- total_length += id_size
220
- @no_value = false
221
231
  if explicit?
222
- # Delegate to #explicit type to generate sub-value
223
- type = explicit_type
224
- type.parse!(data)
225
- @value = type.value
232
+ do_parse_explicit(data)
226
233
  else
227
234
  der_to_value(data, ber: ber)
228
235
  end
@@ -253,11 +260,112 @@ module RASN1
253
260
  (other.class == self.class) && (other.to_der == self.to_der)
254
261
  end
255
262
 
263
+ # Set options to this object
264
+ # @param [Hash] options
265
+ # @return [void]
266
+ # @since 0.12
267
+ def options=(options)
268
+ set_class options[:class]
269
+ set_optional options[:optional]
270
+ set_default options[:default]
271
+ set_tag options
272
+ @name = options[:name]
273
+ @options = options
274
+ end
275
+
276
+ # Say if a value is set
277
+ # @return [Boolean]
278
+ # @since 0.12.0
279
+ def value?
280
+ !@no_value
281
+ end
282
+
283
+ # Say if DER can be built (not default value, not optional without value, has a value)
284
+ # @return [Boolean]
285
+ # @since 0.12.0
286
+ def can_build?
287
+ value? && (@default.nil? || (@value != @default))
288
+ end
289
+
290
+ # @private Tracer private API
291
+ # @return [String]
292
+ def trace
293
+ return trace_real if value?
294
+
295
+ msg = msg_type
296
+ if default.nil? # rubocop:disable Style/ConditionalAssignment
297
+ msg << ' NONE'
298
+ else
299
+ msg << " DEFAULT VALUE #{default}"
300
+ end
301
+ end
302
+
256
303
  private
257
304
 
305
+ def trace_real
306
+ encoded_id = unpack(encode_identifier_octets)
307
+ data_length = raw_data.length
308
+ encoded_length = unpack(raw_length)
309
+ msg = msg_type
310
+ msg << " (0x#{encoded_id}),"
311
+ msg << " len: #{data_length} (0x#{encoded_length})"
312
+ msg << trace_data
313
+ end
314
+
315
+ def trace_data(data=nil)
316
+ byte_count = 0
317
+ data ||= raw_data
318
+
319
+ lines = []
320
+ str = ''
321
+ data.each_byte do |byte|
322
+ if (byte_count % 16).zero?
323
+ str = trace_format_new_data_line(byte_count)
324
+ lines << str
325
+ end
326
+ str[compute_trace_index(byte_count, 3), 2] = '%02x' % byte
327
+ str[compute_trace_index(byte_count, 1, 49)] = byte >= 32 && byte <= 126 ? byte.chr : '.'
328
+ byte_count += 1
329
+ end
330
+ lines.map(&:rstrip).join << "\n"
331
+ end
332
+
333
+ def trace_format_new_data_line(count)
334
+ head_line = RASN1.tracer.indent(RASN1.tracer.tracing_level + 1)
335
+ ("\n#{head_line}%04x " % count) << ' ' * 68
336
+ end
337
+
338
+ def compute_trace_index(byte_count, byte_count_mul=1, offset=0)
339
+ base_idx = 7 + RASN1.tracer.indent(RASN1.tracer.tracing_level + 1).length
340
+ base_idx + offset + (byte_count % 16) * byte_count_mul
341
+ end
342
+
343
+ def unpack(binstr)
344
+ binstr.unpack1('H*')
345
+ end
346
+
347
+ def asn1_class_to_s
348
+ asn1_class == :universal ? '' : asn1_class.to_s.upcase << ' '
349
+ end
350
+
351
+ def msg_type(no_id: false)
352
+ msg = name.nil? ? +'' : +"#{name} "
353
+ msg << "[ #{asn1_class_to_s}#{id} ] " unless no_id
354
+ msg << if explicit?
355
+ +'EXPLICIT '
356
+ elsif implicit?
357
+ +'IMPLICIT '
358
+ else
359
+ +''
360
+ end
361
+ msg << type
362
+ msg << ' OPTIONAL' if optional?
363
+ msg
364
+ end
365
+
258
366
  def pc_bit
259
367
  if @constructed.nil?
260
- self.class::ASN1_PC
368
+ self.class.const_get(:ASN1_PC)
261
369
  elsif @constructed # true
262
370
  Constructed::ASN1_PC
263
371
  else # false
@@ -269,7 +377,7 @@ module RASN1
269
377
  lvl = level >= 0 ? level : 0
270
378
  str = ' ' * lvl
271
379
  str << "#{@name} " unless @name.nil?
272
- str << asn1_class.to_s.upcase << ' ' unless asn1_class == :universal
380
+ str << asn1_class_to_s
273
381
  str << "[#{id}] EXPLICIT " if explicit?
274
382
  str << "[#{id}] IMPLICIT " if implicit?
275
383
  str << "#{type}:"
@@ -283,6 +391,24 @@ module RASN1
283
391
  end
284
392
  end
285
393
 
394
+ def do_parse(der, ber)
395
+ return [0, ''] unless check_id(der)
396
+
397
+ id_size = Types.decode_identifier_octets(der).last
398
+ total_length, data = get_data(der[id_size..-1], ber)
399
+ total_length += id_size
400
+ @no_value = false
401
+
402
+ [total_length, data]
403
+ end
404
+
405
+ def do_parse_explicit(data)
406
+ # Delegate to #explicit type to generate sub-value
407
+ type = explicit_type
408
+ type.parse!(data)
409
+ @value = type.value
410
+ end
411
+
286
412
  def value_to_der
287
413
  case @value
288
414
  when Base
@@ -296,16 +422,6 @@ module RASN1
296
422
  @value = der
297
423
  end
298
424
 
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
425
  def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
310
426
  case asn1_class
311
427
  when nil
@@ -330,17 +446,15 @@ module RASN1
330
446
  # handle undocumented option +:tag_value+, used internally by
331
447
  # {RASN1.parse} to parse non-universal class tags.
332
448
  def set_tag(options) # rubocop:disable Naming/AccessorMethodName
449
+ @constructed = options[:constructed]
333
450
  if options[:explicit]
334
451
  @tag = :explicit
335
452
  @id_value = options[:explicit]
336
- @constructed = options[:constructed]
337
453
  elsif options[:implicit]
338
454
  @tag = :implicit
339
455
  @id_value = options[:implicit]
340
- @constructed = options[:constructed]
341
456
  elsif options[:tag_value]
342
457
  @id_value = options[:tag_value]
343
- @constructed = options[:constructed]
344
458
  end
345
459
 
346
460
  @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
@@ -357,15 +471,6 @@ module RASN1
357
471
  value
358
472
  end
359
473
 
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
474
  def build
370
475
  if can_build?
371
476
  if explicit?
@@ -382,9 +487,9 @@ module RASN1
382
487
  end
383
488
 
384
489
  def id_value
385
- return @id_value if defined? @id_value
490
+ return @id_value if defined?(@id_value) && !@id_value.nil?
386
491
 
387
- self.class::ID
492
+ self.class.const_get(:ID)
388
493
  end
389
494
 
390
495
  def encode_identifier_octets
@@ -445,6 +550,21 @@ module RASN1
445
550
  end
446
551
 
447
552
  def get_data(der, ber)
553
+ return [0, ''] if der.nil? || der.empty?
554
+
555
+ length, length_length = get_length(der, ber)
556
+
557
+ data = der[1 + length_length, length]
558
+ @raw_length = der[0, length_length + 1]
559
+ @raw_data = data
560
+
561
+ total_length = 1 + length
562
+ total_length += length_length if length_length.positive?
563
+
564
+ [total_length, data]
565
+ end
566
+
567
+ def get_length(der, ber)
448
568
  length = der.unpack1('C').to_i
449
569
  length_length = 0
450
570
 
@@ -455,12 +575,8 @@ module RASN1
455
575
  length = der[1, length_length].unpack('C*')
456
576
  .reduce(0) { |len, b| (len << 8) | b }
457
577
  end
458
- data = der[1 + length_length, length]
459
-
460
- total_length = 1 + length
461
- total_length += length_length if length_length.positive?
462
578
 
463
- [total_length, data]
579
+ [length, length_length]
464
580
  end
465
581
 
466
582
  def raise_on_indefinite_length(ber)
@@ -475,7 +591,7 @@ module RASN1
475
591
  end
476
592
 
477
593
  def explicit_type
478
- self.class.new
594
+ self.class.new(name: name)
479
595
  end
480
596
 
481
597
  def raise_id_error(der)
@@ -484,10 +600,6 @@ module RASN1
484
600
  raise ASN1Error, msg
485
601
  end
486
602
 
487
- def class_from_numeric_id(id)
488
- CLASSES.key(id & CLASS_MASK)
489
- end
490
-
491
603
  def self2name
492
604
  name = +"#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}"
493
605
  if implicit? || explicit?
@@ -8,7 +8,6 @@ module RASN1
8
8
  # BitString id value
9
9
  ID = 3
10
10
 
11
- # @param [Integer] bit_length
12
11
  # @return [Integer]
13
12
  attr_writer :bit_length
14
13
 
@@ -42,14 +41,17 @@ module RASN1
42
41
  str << " #{value.inspect} (bit length: #{bit_length})"
43
42
  end
44
43
 
45
- private
46
-
44
+ # Same as {Base#can_build?} but also check bit_length
45
+ # @see Base#can_build?
46
+ # @return [Boolean]
47
47
  def can_build?
48
- !(!@default.nil? && (!value? || (@value == @default) &&
49
- (@bit_length == @default_bit_length))) &&
50
- !(optional? && !value?)
48
+ super || (!@default.nil? && (@bit_length != @default_bit_length))
51
49
  end
52
50
 
51
+ private
52
+
53
+ # @author Sylvain Daubert
54
+ # @author adfoster-r7
53
55
  def value_to_der
54
56
  raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
55
57
 
@@ -70,9 +72,8 @@ module RASN1
70
72
  end
71
73
 
72
74
  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')
75
+ value = (@value || '').dup.force_encoding('BINARY')
76
+ value << "\x00".b while value.length * 8 < @bit_length.to_i
76
77
  return value unless value.length * 8 > @bit_length.to_i
77
78
 
78
79
  max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
@@ -85,6 +86,10 @@ module RASN1
85
86
  @bit_length = value.length * 8 - unused
86
87
  @value = value
87
88
  end
89
+
90
+ def explicit_type
91
+ self.class.new(name: name, value: @value, bit_length: @bit_length)
92
+ end
88
93
  end
89
94
  end
90
95
  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
@@ -39,6 +39,12 @@ module RASN1
39
39
  @value = true
40
40
  end
41
41
  end
42
+
43
+ def trace_data
44
+ return super if explicit?
45
+
46
+ " #{raw_data == "\x00".b ? 'FALSE' : 'TRUE'} (0x#{raw_data.unpack1('H*')})"
47
+ end
42
48
  end
43
49
  end
44
50
  end
@@ -88,6 +88,8 @@ module RASN1
88
88
  raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless parsed
89
89
  end
90
90
 
91
+ # @param [::Integer] level
92
+ # @return [String]
91
93
  def inspect(level=0)
92
94
  str = common_inspect(level)
93
95
  str << if defined? @chosen
@@ -97,10 +99,16 @@ module RASN1
97
99
  end
98
100
  end
99
101
 
102
+ # @private Tracer private API
103
+ # @return [String]
104
+ def trace
105
+ msg_type(no_id: true)
106
+ end
107
+
100
108
  private
101
109
 
102
110
  def check_chosen
103
- raise ChoiceError if !defined?(@chosen) || @chosen.nil?
111
+ raise ChoiceError.new(self) if !defined?(@chosen) || @chosen.nil?
104
112
  end
105
113
  end
106
114
  end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module RASN1
4
4
  module Types
5
- # Mixin to had constraints on a RASN1 type.
6
- # Should not be used directly but through {Model.define_type}.
5
+ # Mixin to add constraints on a RASN1 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
+ # Define class/module methods for {Constrained} module
10
11
  module ClassMethods
11
12
  # Setter for constraint
12
13
  # @param [Proc,nil] constraint
@@ -31,8 +32,10 @@ module RASN1
31
32
  end
32
33
 
33
34
  class << self
35
+ # @return [Proc] proc to check constraints
34
36
  attr_reader :constraint
35
37
 
38
+ # Extend +base+ with {ClassMethods}
36
39
  def included(base)
37
40
  base.extend ClassMethods
38
41
  end
@@ -46,6 +49,8 @@ module RASN1
46
49
  super
47
50
  end
48
51
 
52
+ private
53
+
49
54
  def der_to_value(der, ber: false)
50
55
  super
51
56
  self.class.check_constraint(@value)
@@ -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,50 @@ 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
136
+ end
137
+
138
+ def trace_data
139
+ return super if explicit?
147
140
 
148
- [format, frac_base]
141
+ +' ' << raw_data
149
142
  end
150
143
  end
151
144
  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