rasn1 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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