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