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.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/lib/rasn1/model.rb +107 -67
- data/lib/rasn1/types/any.rb +26 -15
- data/lib/rasn1/types/base.rb +100 -78
- data/lib/rasn1/types/bit_string.rb +30 -28
- data/lib/rasn1/types/boolean.rb +5 -0
- data/lib/rasn1/types/choice.rb +4 -4
- data/lib/rasn1/types/constructed.rb +0 -2
- data/lib/rasn1/types/enumerated.rb +6 -13
- data/lib/rasn1/types/generalized_time.rb +99 -56
- data/lib/rasn1/types/ia5string.rb +1 -1
- data/lib/rasn1/types/integer.rb +76 -55
- data/lib/rasn1/types/null.rb +3 -2
- data/lib/rasn1/types/object_id.rb +2 -2
- data/lib/rasn1/types/sequence.rb +22 -5
- data/lib/rasn1/types/sequence_of.rb +43 -31
- data/lib/rasn1/types/utc_time.rb +1 -1
- data/lib/rasn1/types/utf8_string.rb +1 -1
- data/lib/rasn1/types.rb +16 -15
- data/lib/rasn1/version.rb +1 -1
- data/lib/rasn1.rb +1 -1
- metadata +4 -5
data/lib/rasn1/types/base.rb
CHANGED
@@ -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
|
-
# @
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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 <<
|
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
|
-
|
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
|
-
|
338
|
-
!
|
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
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
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
|
-
|
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
|
437
|
+
length = der.unpack1('C').to_i
|
422
438
|
length_length = 0
|
423
439
|
|
424
440
|
if length == INDEFINITE_LENGTH
|
425
|
-
|
426
|
-
|
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 =
|
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
|
-
# @
|
16
|
-
#
|
17
|
-
#
|
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(
|
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
|
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? && (
|
48
|
+
!(!@default.nil? && (!value? || (@value == @default) &&
|
55
49
|
(@bit_length == @default_bit_length))) &&
|
56
|
-
!(optional? &&
|
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
|
-
|
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 =
|
71
|
-
der = [unused,
|
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 =
|
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
|
83
|
-
|
84
|
-
|
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
|
data/lib/rasn1/types/boolean.rb
CHANGED
data/lib/rasn1/types/choice.rb
CHANGED
@@ -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
|
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
|
|
@@ -25,21 +25,14 @@ module RASN1
|
|
25
25
|
# Enumerated id value
|
26
26
|
ID = 0x0a
|
27
27
|
|
28
|
-
# @
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
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(
|
33
|
+
def initialize(options={})
|
41
34
|
super
|
42
|
-
raise EnumeratedError, 'no enumeration given' if @enum.
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
57
|
-
date_hour << 'Z'
|
57
|
+
value_when_fraction_ends_with_z(date_hour, fraction)
|
58
58
|
else
|
59
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|