rasn1 0.8.0 → 0.9.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.
@@ -44,7 +44,7 @@ module RASN1
44
44
  # Implicit tagged values may also be defined:
45
45
  # ctype_implicit = RASN1::Types::Integer.new(implicit: 0)
46
46
  # @author Sylvain Daubert
47
- class Base
47
+ class Base # rubocop:disable Metrics/ClassLength
48
48
  # Allowed ASN.1 classes
49
49
  CLASSES = {
50
50
  universal: 0x00,
@@ -69,8 +69,6 @@ module RASN1
69
69
  attr_reader :asn1_class
70
70
  # @return [Object,nil] default value, if defined
71
71
  attr_reader :default
72
- # @return [Object]
73
- attr_writer :value
74
72
 
75
73
  # Get ASN.1 type
76
74
  # @return [String]
@@ -97,57 +95,50 @@ module RASN1
97
95
  obj
98
96
  end
99
97
 
100
- # @overload initialize(options={})
101
- # @param [Hash] options
102
- # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
103
- # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
104
- # @option options [::Boolean] :optional define this tag as optional. Default
105
- # is +false+
106
- # @option options [Object] :default default value (ASN.1 DEFAULT)
107
- # @option options [Object] :value value to set
108
- # @option options [::Integer] :implicit define an IMPLICIT tagged type
109
- # @option options [::Integer] :explicit define an EXPLICIT tagged type
110
- # @option options [::Boolean] :constructed if +true+, set type as constructed.
111
- # May only be used when +:explicit+ is defined, else it is discarded.
112
- # @option options [::String] :name name for this node
113
- # @overload initialize(value, options={})
114
- # @param [Object] value value to set for this ASN.1 object
115
- # @param [Hash] options
116
- # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
117
- # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
118
- # @option options [::Boolean] :optional define this value as optional. Default
119
- # is +false+
120
- # @option options [Object] :default default value (ASN.1 DEFAULT)
121
- # @option options [::Integer] :implicit define an IMPLICIT tagged type
122
- # @option options [::Integer] :explicit define an EXPLICIT tagged type
123
- # @option options [::Boolean] :constructed if +true+, set type as constructed.
124
- # May only be used when +:explicit+ is defined, else it is discarded.
125
- # @option options [::String] :name name for this node
126
- def initialize(value_or_options={}, options={})
98
+ # @param [Hash] options
99
+ # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
100
+ # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
101
+ # @option options [::Boolean] :optional define this tag as optional. Default
102
+ # is +false+
103
+ # @option options [Object] :default default value (ASN.1 DEFAULT)
104
+ # @option options [Object] :value value to set
105
+ # @option options [::Integer] :implicit define an IMPLICIT tagged type
106
+ # @option options [::Integer] :explicit define an EXPLICIT tagged type
107
+ # @option options [::Boolean] :constructed if +true+, set type as constructed.
108
+ # May only be used when +:explicit+ is defined, else it is discarded.
109
+ # @option options [::String] :name name for this node
110
+ def initialize(options={})
127
111
  @constructed = nil
128
- if value_or_options.is_a? Hash
129
- set_options value_or_options
130
- else
131
- set_options options
132
- @value = value_or_options
133
- end
112
+ set_options options
134
113
  end
135
114
 
136
115
  # Used by +#dup+ and +#clone+. Deep copy @value and @default.
137
116
  def initialize_copy(_other)
138
117
  @value = @value.dup
118
+ @no_value = @no_value.dup
139
119
  @default = @default.dup
140
120
  end
141
121
 
142
122
  # Get value or default value
143
123
  def value
144
- if @value.nil?
145
- @default
146
- else
124
+ if value?
147
125
  @value
126
+ else
127
+ @default
148
128
  end
149
129
  end
150
130
 
131
+ # Set value. If +val+ is +nil+, unset value
132
+ # @param [Object,nil] val
133
+ def value=(val)
134
+ set_value(val)
135
+ end
136
+
137
+ # @abstract Define 'void' value (i.e. 'value' when no value was set)
138
+ def void_value
139
+ ''
140
+ end
141
+
151
142
  # @return [::Boolean]
152
143
  def optional?
153
144
  @optional
@@ -163,14 +154,14 @@ module RASN1
163
154
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
164
155
  # if explicit, else +false+
165
156
  def explicit?
166
- !defined?(@tag) ? nil : @tag == :explicit
157
+ defined?(@tag) ? @tag == :explicit : nil
167
158
  end
168
159
 
169
160
  # Say if a tagged type is implicit
170
161
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
171
162
  # if implicit, else +false+
172
163
  def implicit?
173
- !defined?(@tag) ? nil : @tag == :implicit
164
+ defined?(@tag) ? @tag == :implicit : nil
174
165
  end
175
166
 
176
167
  # @abstract This method SHOULD be partly implemented by subclasses, which
@@ -215,10 +206,10 @@ module RASN1
215
206
  id_size = Types.decode_identifier_octets(der).last
216
207
  total_length, data = get_data(der[id_size..-1], ber)
217
208
  total_length += id_size
209
+ @no_value = false
218
210
  if explicit?
219
211
  # Delegate to #explicit type to generate sub-value
220
212
  type = explicit_type
221
- type.value = @value
222
213
  type.parse!(data)
223
214
  @value = type.value
224
215
  else
@@ -238,7 +229,7 @@ module RASN1
238
229
  # @return [String]
239
230
  def inspect(level=0)
240
231
  str = common_inspect(level)
241
- str << " #{value.inspect}"
232
+ str << ' ' << inspect_value
242
233
  str << ' OPTIONAL' if optional?
243
234
  str << " DEFAULT #{@default}" unless @default.nil?
244
235
  str
@@ -267,9 +258,20 @@ module RASN1
267
258
  lvl = level >= 0 ? level : 0
268
259
  str = ' ' * lvl
269
260
  str << "#{@name} " unless @name.nil?
261
+ str << asn1_class.to_s.upcase << ' ' unless asn1_class == :universal
262
+ str << "[#{id}] EXPLICIT " if explicit?
263
+ str << "[#{id}] IMPLICIT " if implicit?
270
264
  str << "#{type}:"
271
265
  end
272
266
 
267
+ def inspect_value
268
+ if value?
269
+ value.inspect
270
+ else
271
+ '(NO VALUE)'
272
+ end
273
+ end
274
+
273
275
  def value_to_der
274
276
  case @value
275
277
  when Base
@@ -279,21 +281,21 @@ module RASN1
279
281
  end
280
282
  end
281
283
 
282
- def der_to_value(der, ber: false)
284
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
283
285
  @value = der
284
286
  end
285
287
 
286
- def set_options(options)
288
+ def set_options(options) # rubocop:disable Naming/AccessorMethodName
287
289
  set_class options[:class]
288
290
  set_optional options[:optional]
289
291
  set_default options[:default]
290
292
  set_tag options
291
- @value = options[:value]
293
+ set_value options[:value]
292
294
  @name = options[:name]
293
295
  @options = options
294
296
  end
295
297
 
296
- def set_class(asn1_class)
298
+ def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
297
299
  case asn1_class
298
300
  when nil
299
301
  @asn1_class = :universal
@@ -306,17 +308,17 @@ module RASN1
306
308
  end
307
309
  end
308
310
 
309
- def set_optional(optional)
311
+ def set_optional(optional) # rubocop:disable Naming/AccessorMethodName
310
312
  @optional = !!optional
311
313
  end
312
314
 
313
- def set_default(default)
315
+ def set_default(default) # rubocop:disable Naming/AccessorMethodName
314
316
  @default = default
315
317
  end
316
318
 
317
319
  # handle undocumented option +:tag_value+, used internally by
318
320
  # {RASN1.parse} to parse non-universal class tags.
319
- def set_tag(options)
321
+ def set_tag(options) # rubocop:disable Naming/AccessorMethodName
320
322
  if options[:explicit]
321
323
  @tag = :explicit
322
324
  @id_value = options[:explicit]
@@ -333,9 +335,24 @@ module RASN1
333
335
  @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
334
336
  end
335
337
 
338
+ def set_value(value) # rubocop:disable Naming/AccessorMethodName
339
+ if value.nil?
340
+ @no_value = true
341
+ @value = void_value
342
+ else
343
+ @no_value = false
344
+ @value = value
345
+ end
346
+ value
347
+ end
348
+
349
+ def value?
350
+ !@no_value
351
+ end
352
+
336
353
  def can_build?
337
- !(!@default.nil? && (@value.nil? || (@value == @default))) &&
338
- !(optional? && @value.nil?)
354
+ (@default.nil? || (value? && (@value != @default))) &&
355
+ (!optional? || value?)
339
356
  end
340
357
 
341
358
  def build
@@ -403,41 +420,31 @@ module RASN1
403
420
  def check_id(der)
404
421
  expected_id = encode_identifier_octets
405
422
  real_id = der[0, expected_id.size]
406
- if real_id != expected_id
407
- if optional?
408
- @value = nil
409
- elsif !@default.nil?
410
- @value = @default
411
- else
412
- raise_id_error(der)
413
- end
414
- false
423
+ return true if real_id == expected_id
424
+
425
+ if optional?
426
+ @no_value = true
427
+ @value = void_value
428
+ elsif !@default.nil?
429
+ @value = @default
415
430
  else
416
- true
431
+ raise_id_error(der)
417
432
  end
433
+ false
418
434
  end
419
435
 
420
436
  def get_data(der, ber)
421
- length = der[0, 1].unpack1('C')
437
+ length = der.unpack1('C').to_i
422
438
  length_length = 0
423
439
 
424
440
  if length == INDEFINITE_LENGTH
425
- if primitive?
426
- raise ASN1Error, "malformed #{type}: indefinite length " \
427
- 'forbidden for primitive types'
428
- elsif ber
429
- raise NotImplementedError, 'indefinite length not supported'
430
- else
431
- raise ASN1Error, 'indefinite length forbidden in DER encoding'
432
- end
433
- elsif length < INDEFINITE_LENGTH
434
- data = der[1, length]
435
- else
441
+ raise_on_indefinite_length(ber)
442
+ elsif length > INDEFINITE_LENGTH
436
443
  length_length = length & 0x7f
437
444
  length = der[1, length_length].unpack('C*')
438
445
  .reduce(0) { |len, b| (len << 8) | b }
439
- data = der[1 + length_length, length]
440
446
  end
447
+ data = der[1 + length_length, length]
441
448
 
442
449
  total_length = 1 + length
443
450
  total_length += length_length if length_length.positive?
@@ -445,6 +452,17 @@ module RASN1
445
452
  [total_length, data]
446
453
  end
447
454
 
455
+ def raise_on_indefinite_length(ber)
456
+ if primitive?
457
+ raise ASN1Error, "malformed #{type}: indefinite length " \
458
+ 'forbidden for primitive types'
459
+ elsif ber
460
+ raise NotImplementedError, 'indefinite length not supported'
461
+ else
462
+ raise ASN1Error, 'indefinite length forbidden in DER encoding'
463
+ end
464
+ end
465
+
448
466
  def explicit_type
449
467
  self.class.new
450
468
  end
@@ -473,12 +491,16 @@ module RASN1
473
491
 
474
492
  asn1_class, pc, id, id_size = Types.decode_identifier_octets(der)
475
493
  name = +"#{asn1_class.to_s.upcase} #{pc.to_s.upcase}"
476
- type = Types.constants.map { |c| Types.const_get(c) }
477
- .select { |klass| klass < Primitive || klass < Constructed }
478
- .find { |klass| klass::ID == id }
494
+ type = find_type(id)
479
495
  name << " #{type.nil? ? '0x%X (0x%s)' % [id, bin2hex(der[0...id_size])] : type.encoded_type}"
480
496
  end
481
497
 
498
+ def find_type(id)
499
+ Types.constants.map { |c| Types.const_get(c) }
500
+ .select { |klass| klass < Primitive || klass < Constructed }
501
+ .find { |klass| klass::ID == id }
502
+ end
503
+
482
504
  def bin2hex(str)
483
505
  str.unpack1('H*')
484
506
  end
@@ -12,17 +12,11 @@ module RASN1
12
12
  # @return [Integer]
13
13
  attr_writer :bit_length
14
14
 
15
- # @overload initialize(options={})
16
- # @param [Hash] options
17
- # @option options [Object] :bit_length default bit_length value. Should be
18
- # present if +:default+ is set
19
- # @overload initialize(value, options={})
20
- # @param [Object] value value to set for this ASN.1 object
21
- # @param [Hash] options
22
- # @option options [Object] :bit_length default bit_length value. Should be
23
- # present if +:default+ is set
15
+ # @param [Hash] options
16
+ # @option options [Object] :bit_length default bit_length value. Should be
17
+ # present if +:default+ is set
24
18
  # @see Base#initialize common options to all ASN.1 types
25
- def initialize(value_or_options={}, options={})
19
+ def initialize(options={})
26
20
  super
27
21
  if @default
28
22
  raise ASN1Error, "#{@name}: default bit length is not defined" if @options[:bit_length].nil?
@@ -34,10 +28,10 @@ module RASN1
34
28
 
35
29
  # Get bit length
36
30
  def bit_length
37
- if @value.nil?
38
- @default_bit_length
39
- else
31
+ if value?
40
32
  @bit_length
33
+ else
34
+ @default_bit_length
41
35
  end
42
36
  end
43
37
 
@@ -51,37 +45,45 @@ module RASN1
51
45
  private
52
46
 
53
47
  def can_build?
54
- !(!@default.nil? && (@value.nil? || (@value == @default) &&
48
+ !(!@default.nil? && (!value? || (@value == @default) &&
55
49
  (@bit_length == @default_bit_length))) &&
56
- !(optional? && @value.nil?)
50
+ !(optional? && !value?)
57
51
  end
58
52
 
59
53
  def value_to_der
60
54
  raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
61
55
 
62
- @value << "\x00" while @value.length * 8 < @bit_length
63
- @value.force_encoding('BINARY')
64
-
65
- if @value.length * 8 > @bit_length
66
- max_len = @bit_length / 8 + ((@bit_length % 8).positive? ? 1 : 0)
67
- @value = @value[0, max_len]
68
- end
56
+ value = generate_value_with_correct_length
69
57
 
70
- unused = @value.length * 8 - @bit_length
71
- der = [unused, @value].pack('CA*')
58
+ unused = value.length * 8 - @bit_length.to_i
59
+ der = [unused, value].pack('CA*')
72
60
 
73
61
  if unused.positive?
74
- last_byte = @value[-1].unpack1('C')
62
+ last_byte = value[-1].to_s.unpack1('C').to_i
75
63
  last_byte &= (0xff >> unused) << unused
76
64
  der[-1] = [last_byte].pack('C')
77
65
  end
78
66
 
67
+ @value = value
68
+
79
69
  der
80
70
  end
81
71
 
82
- def der_to_value(der, ber: false)
83
- unused, @value = der.unpack('CA*')
84
- @bit_length = @value.length * 8 - unused
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')
76
+ return value unless value.length * 8 > @bit_length.to_i
77
+
78
+ max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
79
+ value[0, max_len].to_s
80
+ end
81
+
82
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
83
+ unused = der.unpack1('C').to_i
84
+ value = der[1..-1].to_s
85
+ @bit_length = value.length * 8 - unused
86
+ @value = value
85
87
  end
86
88
  end
87
89
  end
@@ -13,6 +13,11 @@ module RASN1
13
13
  # @private
14
14
  DER_FALSE = 0
15
15
 
16
+ # @return [false]
17
+ def void_value
18
+ false
19
+ end
20
+
16
21
  private
17
22
 
18
23
  def value_to_der
@@ -41,7 +41,7 @@ module RASN1
41
41
  # @param [Object] value
42
42
  # @return [Object] value
43
43
  # @raise [ChoiceError] {#chosen} not set
44
- def set_chosen_value(value)
44
+ def set_chosen_value(value) # rubocop:disable Naming/AccessorMethodName
45
45
  check_chosen
46
46
  @value[@chosen].value = value
47
47
  end
@@ -92,10 +92,10 @@ module RASN1
92
92
 
93
93
  def inspect(level=0)
94
94
  str = common_inspect(level)
95
- str << if !defined? @chosen
96
- ' not chosen!'
97
- else
95
+ str << if defined? @chosen
98
96
  "\n#{@value[@chosen].inspect(level + 1)}"
97
+ else
98
+ ' not chosen!'
99
99
  end
100
100
  end
101
101
 
@@ -19,8 +19,6 @@ module RASN1
19
19
  @value.each do |item|
20
20
  case item
21
21
  when Base, Model
22
- next if item.optional? && item.value.nil?
23
-
24
22
  str << "#{item.inspect(level)}\n"
25
23
  else
26
24
  str << ' ' * level
@@ -25,21 +25,14 @@ module RASN1
25
25
  # Enumerated id value
26
26
  ID = 0x0a
27
27
 
28
- # @overload initialize(options={})
29
- # @option options [Hash] :enum enumeration hash. Keys are names, and values
30
- # are integers. This key is mandatory.
31
- # @raise [EnumeratedError] +:enum+ key is not present
32
- # @raise [EnumeratedError] +:default+ value is unknown
33
- # @overload initialize(value, options={})
34
- # @param [Object] value value to set for this ASN.1 object
35
- # @option options [Hash] :enum enumeration hash. Keys are names, and values
36
- # are integers. This key is mandatory.
37
- # @raise [EnumeratedError] +:enum+ key is not present
38
- # @raise [EnumeratedError] +:default+ value is unknown
28
+ # @option options [Hash] :enum enumeration hash. Keys are names, and values
29
+ # are integers. This key is mandatory.
30
+ # @raise [EnumeratedError] +:enum+ key is not present
31
+ # @raise [EnumeratedError] +:default+ value is unknown
39
32
  # @see Base#initialize common options to all ASN.1 types
40
- def initialize(value_or_options={}, options={})
33
+ def initialize(options={})
41
34
  super
42
- raise EnumeratedError, 'no enumeration given' if @enum.nil?
35
+ raise EnumeratedError, 'no enumeration given' if @enum.empty?
43
36
  end
44
37
 
45
38
  # @return [Hash]
@@ -30,6 +30,11 @@ module RASN1
30
30
  'GeneralizedTime'
31
31
  end
32
32
 
33
+ # @return [DateTime]
34
+ def void_value
35
+ DateTime.now
36
+ end
37
+
33
38
  private
34
39
 
35
40
  def value_to_der
@@ -41,69 +46,107 @@ module RASN1
41
46
  end
42
47
  end
43
48
 
44
- def der_to_value(der, ber: false)
49
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
45
50
  date_hour, fraction = der.split('.')
46
- utc_offset_forced = false
47
- if fraction.nil?
48
- if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
49
- # If not UTC, have to add offset with UTC to force
50
- # DateTime#strptime to generate a local time. But this difference
51
- # may be errored because of DST.
52
- date_hour << Time.now.strftime('%z')
53
- utc_offset_forced = true
54
- end
51
+ date_hour = date_hour.to_s
52
+ fraction = fraction.to_s
53
+
54
+ if fraction.empty?
55
+ value_when_fraction_empty(date_hour)
55
56
  elsif fraction[-1] == 'Z'
56
- fraction = fraction[0...-1]
57
- date_hour << 'Z'
57
+ value_when_fraction_ends_with_z(date_hour, fraction)
58
58
  else
59
- match = fraction.match(/(\d+)([+-]\d+)/)
60
- if match
61
- # fraction contains fraction and timezone info. Split them
62
- fraction = match[1]
63
- date_hour << match[2]
64
- else
65
- # fraction only contains fraction.
66
- # Have to add offset with UTC to force DateTime#strptime to
67
- # generate a local time. But this difference may be errored
68
- # because of DST.
69
- date_hour << Time.now.strftime('%z')
70
- utc_offset_forced = true
71
- end
59
+ value_on_others_cases(date_hour, fraction)
72
60
  end
73
- format = case date_hour.size
74
- when 11
75
- frac_base = 60 * 60
76
- '%Y%m%d%HZ'
77
- when 13
78
- frac_base = 60
79
- '%Y%m%d%H%MZ'
80
- when 15
81
- if date_hour[-1] == 'Z'
82
- frac_base = 1
83
- '%Y%m%d%H%M%SZ'
84
- else
85
- frac_base = 60 * 60
86
- '%Y%m%d%H%z'
87
- end
88
- when 17
89
- frac_base = 60
90
- '%Y%m%d%H%M%z'
91
- when 19
92
- frac_base = 1
93
- '%Y%m%d%H%M%S%z'
94
- else
95
- prefix = @name.nil? ? type : "tag #{@name}"
96
- raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
97
- end
98
- @value = DateTime.strptime(date_hour, format).to_time
99
- # local time format.
100
- # Check DST. There may be a shift of one hour...
101
- if utc_offset_forced
102
- compare_time = Time.new(*@value.to_a[0..5].reverse)
103
- @value = compare_time if compare_time.utc_offset != @value.utc_offset
61
+ end
62
+
63
+ def value_when_fraction_empty(date_hour)
64
+ utc_offset_forced = false
65
+
66
+ if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
67
+ # 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.
70
+ date_hour << Time.now.strftime('%z')
71
+ utc_offset_forced = true
72
+ end
73
+
74
+ value_from(date_hour)
75
+ fix_dst if utc_offset_forced
76
+ end
77
+
78
+ def value_when_fraction_ends_with_z(date_hour, fraction)
79
+ fraction = fraction[0...-1]
80
+ date_hour << 'Z'
81
+ frac_base = value_from(date_hour)
82
+ fix_value(fraction, frac_base)
83
+ end
84
+
85
+ def value_on_others_cases(date_hour, fraction)
86
+ match = fraction.match(/(\d+)([+-]\d+)/)
87
+ if match
88
+ # fraction contains fraction and timezone info. Split them
89
+ fraction = match[1]
90
+ date_hour << match[2]
91
+ else
92
+ # 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.
96
+ date_hour << Time.now.strftime('%z')
97
+ utc_offset_forced = true
104
98
  end
99
+
100
+ frac_base = value_from(date_hour)
101
+ fix_dst if utc_offset_forced
102
+ fix_value(fraction, frac_base)
103
+ end
104
+
105
+ def value_from(date_hour)
106
+ format, frac_base = strformat(date_hour)
107
+ @value = DateTime.strptime(date_hour, format).to_time
108
+ frac_base
109
+ end
110
+
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
+ def fix_value(fraction, frac_base)
105
118
  @value += ".#{fraction}".to_r * frac_base unless fraction.nil?
106
119
  end
120
+
121
+ def strformat(date_hour) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
122
+ case date_hour.size
123
+ 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'
129
+ when 15
130
+ if date_hour[-1] == 'Z'
131
+ frac_base = 1
132
+ format = '%Y%m%d%H%M%SZ'
133
+ else
134
+ frac_base = 60 * 60
135
+ format = '%Y%m%d%H%z'
136
+ end
137
+ when 17
138
+ frac_base = 60
139
+ format = '%Y%m%d%H%M%z'
140
+ when 19
141
+ frac_base = 1
142
+ format = '%Y%m%d%H%M%S%z'
143
+ else
144
+ prefix = @name.nil? ? type : "tag #{@name}"
145
+ raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
146
+ end
147
+
148
+ [format, frac_base]
149
+ end
107
150
  end
108
151
  end
109
152
  end