rasn1 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,8 +6,8 @@ module RASN1
6
6
  #
7
7
  # Subclasses SHOULD define:
8
8
  # * an ID constant defining ASN.1 BER/DER identification number,
9
+ # * a method {#der_to_value} converting DER into {#value}.
9
10
  # * a private method {#value_to_der} converting its {#value} to DER,
10
- # * a private method {#der_to_value} converting DER into {#value}.
11
11
  #
12
12
  # ==Define an optional value
13
13
  # An optional value may be defined using +:optional+ key from {#initialize}:
@@ -178,14 +178,14 @@ module RASN1
178
178
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
179
179
  # if explicit, else +false+
180
180
  def explicit?
181
- defined?(@tag) ? @tag == :explicit : nil
181
+ defined?(@tag) ? @tag == :explicit : nil # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
182
182
  end
183
183
 
184
184
  # Say if a tagged type is implicit
185
185
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
186
186
  # if implicit, else +false+
187
187
  def implicit?
188
- defined?(@tag) ? @tag == :implicit : nil
188
+ defined?(@tag) ? @tag == :implicit : nil # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
189
189
  end
190
190
 
191
191
  # @abstract This method SHOULD be partly implemented by subclasses, which
@@ -225,7 +225,7 @@ module RASN1
225
225
  # @return [Integer] total number of parsed bytes
226
226
  # @raise [ASN1Error] error on parsing
227
227
  def parse!(der, ber: false)
228
- total_length, data = do_parse(der, ber)
228
+ total_length, data = do_parse(der, ber: ber)
229
229
  return 0 if total_length.zero?
230
230
 
231
231
  if explicit?
@@ -300,6 +300,41 @@ module RASN1
300
300
  end
301
301
  end
302
302
 
303
+ # @private Parse tage and length from binary string. Return data length and binary data.
304
+ # @param [String] der
305
+ # @param [Boolean] ber
306
+ # @return [Array(::Integer, String)]
307
+ # @since 0.15.0 was private before
308
+ def do_parse(der, ber: false)
309
+ return [0, ''] unless check_id(der)
310
+
311
+ id_size = Types.decode_identifier_octets(der).last
312
+ total_length, data = get_data(der[id_size..], ber)
313
+ total_length += id_size
314
+ @no_value = false
315
+
316
+ [total_length, data]
317
+ end
318
+
319
+ # @private Delegate to #explicit type to generate sub-value
320
+ # @param [String] data
321
+ # @return [void]
322
+ # @since 0.15.0 was private before
323
+ def do_parse_explicit(data)
324
+ type = explicit_type
325
+ type.parse!(data)
326
+ @value = type.value
327
+ end
328
+
329
+ # Make value from DER/BER string
330
+ # @param [String] der
331
+ # @param [::Boolean] ber
332
+ # @return [void]
333
+ # @since 0.15.0 was private before
334
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
335
+ @value = der
336
+ end
337
+
303
338
  private
304
339
 
305
340
  def trace_real
@@ -349,7 +384,7 @@ module RASN1
349
384
  end
350
385
 
351
386
  def msg_type(no_id: false)
352
- msg = name.nil? ? +'' : +"#{name} "
387
+ msg = name.nil? ? +'' : "#{name} "
353
388
  msg << "[ #{asn1_class_to_s}#{id} ] " unless no_id
354
389
  msg << if explicit?
355
390
  +'EXPLICIT '
@@ -391,24 +426,6 @@ module RASN1
391
426
  end
392
427
  end
393
428
 
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
-
412
429
  def value_to_der
413
430
  case @value
414
431
  when Base
@@ -418,10 +435,6 @@ module RASN1
418
435
  end
419
436
  end
420
437
 
421
- def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
422
- @value = der
423
- end
424
-
425
438
  def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
426
439
  case asn1_class
427
440
  when nil
@@ -595,13 +608,13 @@ module RASN1
595
608
  end
596
609
 
597
610
  def raise_id_error(der)
598
- msg = name.nil? ? +'' : +"#{name}: "
611
+ msg = name.nil? ? +'' : "#{name}: "
599
612
  msg << "Expected #{self2name} but get #{der2name(der)}"
600
613
  raise ASN1Error, msg
601
614
  end
602
615
 
603
616
  def self2name
604
- name = +"#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}"
617
+ name = "#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}"
605
618
  if implicit? || explicit?
606
619
  name << ' 0x%X (0x%s)' % [id, bin2hex(encode_identifier_octets)]
607
620
  else
@@ -613,7 +626,7 @@ module RASN1
613
626
  return 'no ID' if der.nil? || der.empty?
614
627
 
615
628
  asn1_class, pc, id, id_size = Types.decode_identifier_octets(der)
616
- name = +"#{asn1_class.to_s.upcase} #{pc.to_s.upcase}"
629
+ name = "#{asn1_class.to_s.upcase} #{pc.to_s.upcase}"
617
630
  type = find_type(id)
618
631
  name << " #{type.nil? ? '0x%X (0x%s)' % [id, bin2hex(der[0...id_size])] : type.encoded_type}"
619
632
  end
@@ -48,6 +48,18 @@ module RASN1
48
48
  super || (!@default.nil? && (@bit_length != @default_bit_length))
49
49
  end
50
50
 
51
+ # Make value from DER/BER string. Also set {#bit_length}.
52
+ # @param [String] der
53
+ # @param [::Boolean] ber
54
+ # @return [void]
55
+ # @see Types::Base#der_to_value
56
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
57
+ unused = der.unpack1('C').to_i
58
+ value = der[1..].to_s
59
+ @bit_length = value.length * 8 - unused
60
+ @value = value
61
+ end
62
+
51
63
  private
52
64
 
53
65
  # @author Sylvain Daubert
@@ -80,13 +92,6 @@ module RASN1
80
92
  value[0, max_len].to_s
81
93
  end
82
94
 
83
- def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
84
- unused = der.unpack1('C').to_i
85
- value = der[1..-1].to_s
86
- @bit_length = value.length * 8 - unused
87
- @value = value
88
- end
89
-
90
95
  def explicit_type
91
96
  self.class.new(name: name, value: @value, bit_length: @bit_length)
92
97
  end
@@ -8,23 +8,15 @@ module RASN1
8
8
  class BmpString < OctetString
9
9
  # BmpString id value
10
10
  ID = 30
11
+ # UniversalString encoding
12
+ # @since 0.15.0
13
+ ENCODING = Encoding::UTF_16BE
11
14
 
12
15
  # Get ASN.1 type
13
16
  # @return [String]
14
17
  def self.type
15
18
  'BmpString'
16
19
  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
20
  end
29
21
  end
30
22
  end
@@ -8,9 +8,9 @@ module RASN1
8
8
  # Boolean id value
9
9
  ID = 0x01
10
10
 
11
- # @private
11
+ # DER true value
12
12
  DER_TRUE = 0xff
13
- # @private
13
+ # DER false value
14
14
  DER_FALSE = 0
15
15
 
16
16
  # @return [false]
@@ -18,12 +18,13 @@ module RASN1
18
18
  false
19
19
  end
20
20
 
21
- private
22
-
23
- def value_to_der
24
- [@value ? DER_TRUE : DER_FALSE].pack('C')
25
- end
26
-
21
+ # Make boolean value from DER/BER string.
22
+ # @param [String] der
23
+ # @param [::Boolean] ber
24
+ # @return [void]
25
+ # @see Types::Base#der_to_value
26
+ # @raise [ASN1Error] +der+ is not 1-byte long
27
+ # @raise [ASN1Error] +der+ is not {DER_TRUE} nor {DER_FALSE} and +ber+ is +false+
27
28
  def der_to_value(der, ber: false)
28
29
  raise ASN1Error, "tag #{@name}: BOOLEAN should have a length of 1" unless der.size == 1
29
30
 
@@ -40,6 +41,12 @@ module RASN1
40
41
  end
41
42
  end
42
43
 
44
+ private
45
+
46
+ def value_to_der
47
+ [@value ? DER_TRUE : DER_FALSE].pack('C')
48
+ end
49
+
43
50
  def trace_data
44
51
  return super if explicit?
45
52
 
@@ -57,6 +57,8 @@ module RASN1
57
57
  @value[@chosen].value
58
58
  when Model
59
59
  @value[@chosen]
60
+ when Wrapper
61
+ @value[@chosen].element
60
62
  end
61
63
  end
62
64
 
@@ -75,10 +77,25 @@ module RASN1
75
77
  # @return [Integer] total number of parsed bytes
76
78
  # @raise [ASN1Error] error on parsing
77
79
  def parse!(der, ber: false)
80
+ len, data = do_parse(der, ber: ber)
81
+ return 0 if len.zero?
82
+
83
+ element = @value[@chosen]
84
+ if element.explicit?
85
+ element.do_parse_explicit(data)
86
+ else
87
+ element.der_to_value(data, ber: ber)
88
+ end
89
+ len
90
+ end
91
+
92
+ # @private
93
+ # @see Types::Base#do_parse
94
+ # @since 0.15.0 Specific +#do_parse+ to handle recursivity
95
+ def do_parse(der, ber: false)
78
96
  @value.each_with_index do |element, i|
79
97
  @chosen = i
80
- nb_bytes = element.parse!(der, ber: ber)
81
- return nb_bytes
98
+ return element.do_parse(der, ber: ber)
82
99
  rescue ASN1Error
83
100
  @chosen = nil
84
101
  next
@@ -88,7 +105,23 @@ module RASN1
88
105
  @value = void_value
89
106
  raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless optional?
90
107
 
91
- 0
108
+ [0, ''.b]
109
+ end
110
+
111
+ # Make choice value from DER/BER string.
112
+ # @param [String] der
113
+ # @param [::Boolean] ber
114
+ # @return [void]
115
+ # @since 0.15.0 Specific +#der_to_value+ to handle recursivity
116
+ def der_to_value(der, ber: false)
117
+ @value.each_with_index do |element, i|
118
+ @chosen = i
119
+ element.parse!(der, ber: ber)
120
+ break
121
+ rescue ASN1Error
122
+ @chosen = nil
123
+ next
124
+ end
92
125
  end
93
126
 
94
127
  # @param [::Integer] level
@@ -108,6 +141,13 @@ module RASN1
108
141
  msg_type(no_id: true)
109
142
  end
110
143
 
144
+ # Return empty array
145
+ # @return [Array()]
146
+ # @since 0.15.0
147
+ def void_value
148
+ []
149
+ end
150
+
111
151
  private
112
152
 
113
153
  def check_chosen
@@ -37,8 +37,11 @@ module RASN1
37
37
  super
38
38
  end
39
39
 
40
- private
41
-
40
+ # Make value from +der+ string and check constraints
41
+ # @param [String] der
42
+ # @param [::Boolean] ber
43
+ # @return [void]
44
+ # @raise [ConstraintError] constraint is not verified
42
45
  def der_to_value(der, ber: false)
43
46
  super
44
47
  self.class.check_constraint(@value)
@@ -44,18 +44,10 @@ module RASN1
44
44
  Time.now
45
45
  end
46
46
 
47
- private
48
-
49
- def value_to_der
50
- utc_value = @value.getutc
51
- if utc_value.nsec.positive?
52
- der = utc_value.strftime('%Y%m%d%H%M%S.%9NZ')
53
- der.sub(/0+Z/, 'Z')
54
- else
55
- utc_value.strftime('%Y%m%d%H%M%SZ')
56
- end
57
- end
58
-
47
+ # Make time value from +der+ string
48
+ # @param [String] der
49
+ # @param [::Boolean] ber
50
+ # @return [void]
59
51
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
60
52
  date_hour, fraction = der.split('.')
61
53
  date_hour = date_hour.to_s
@@ -70,19 +62,20 @@ module RASN1
70
62
  end
71
63
  end
72
64
 
65
+ private
66
+
67
+ def value_to_der
68
+ utc_value = @value.getutc
69
+ if utc_value.nsec.positive?
70
+ der = utc_value.strftime('%Y%m%d%H%M%S.%9NZ')
71
+ der.sub(/0+Z/, 'Z')
72
+ else
73
+ utc_value.strftime('%Y%m%d%H%M%SZ')
74
+ end
75
+ end
76
+
73
77
  def value_when_fraction_empty(date_hour)
74
- # Ruby 3.0: special handle for timezone
75
- # From 3.1: "Z" and "-0100" are supported
76
- # Below 3.1: should be "-01:00" or "+00:00"
77
- tz = if date_hour[-1] == 'Z'
78
- date_hour.slice!(-1, 1)
79
- '+00:00' # Ruby 3.0: to remove after end-of support of ruby 3.0
80
- elsif date_hour.match?(/[+-]\d+$/)
81
- # Ruby 3.0
82
- # date_hour.slice!(-5, 5)
83
- zone = date_hour.slice!(-5, 5).to_s
84
- "#{zone[0, 3]}:#{zone[3, 2]}"
85
- end
78
+ tz = compute_tz(date_hour)
86
79
  year = date_hour.slice!(0, 4).to_i
87
80
  month = date_hour.slice!(0, 2).to_i
88
81
  day = date_hour.slice!(0, 2).to_i
@@ -92,6 +85,21 @@ module RASN1
92
85
  @value = Time.new(year, month, day, hour, minute, second, tz)
93
86
  end
94
87
 
88
+ # Ruby 3.0: special handle for timezone
89
+ # From 3.1: "Z" and "-0100" are supported
90
+ # Below 3.1: should be "-01:00" or "+00:00"
91
+ def compute_tz(date_hour)
92
+ if date_hour.end_with?('Z')
93
+ date_hour.slice!(-1, 1)
94
+ '+00:00' # Ruby 3.0: to remove after end-of support of ruby 3.0
95
+ elsif date_hour.match?(/[+-]\d+$/)
96
+ # Ruby 3.0
97
+ # date_hour.slice!(-5, 5)
98
+ zone = date_hour.slice!(-5, 5).to_s
99
+ "#{zone[0, 3]}:#{zone[3, 2]}"
100
+ end
101
+ end
102
+
95
103
  def value_when_fraction_ends_with_z(date_hour, fraction)
96
104
  fraction = fraction[0...-1]
97
105
  date_hour << 'Z'
@@ -7,23 +7,15 @@ module RASN1
7
7
  class IA5String < OctetString
8
8
  # IA5String id value
9
9
  ID = 22
10
+ # UniversalString encoding
11
+ # @since 0.15.0
12
+ ENCODING = Encoding::US_ASCII
10
13
 
11
14
  # Get ASN.1 type
12
15
  # @return [String]
13
16
  def self.type
14
17
  'IA5String'
15
18
  end
16
-
17
- private
18
-
19
- def value_to_der
20
- @value.to_s.dup.force_encoding('US-ASCII').b
21
- end
22
-
23
- def der_to_value(der, ber: false)
24
- super
25
- @value.to_s.dup.force_encoding('US-ASCII')
26
- end
27
19
  end
28
20
  end
29
21
  end
@@ -54,6 +54,19 @@ module RASN1
54
54
  end
55
55
  end
56
56
 
57
+ # Make integer value from +der+ string.
58
+ # @param [String] der
59
+ # @param [::Boolean] ber
60
+ # @return [void]
61
+ def der_to_value(der, ber: false)
62
+ int_value = der_to_int_value(der, ber: ber)
63
+ @value = if @enum.empty?
64
+ int_value
65
+ else
66
+ int_to_enum(int_value)
67
+ end
68
+ end
69
+
57
70
  private
58
71
 
59
72
  def initialize_enum(enum)
@@ -136,15 +149,6 @@ module RASN1
136
149
  @enum.key(int) || int
137
150
  end
138
151
 
139
- def der_to_value(der, ber: false)
140
- int_value = der_to_int_value(der, ber: ber)
141
- @value = if @enum.empty?
142
- int_value
143
- else
144
- int_to_enum(int_value)
145
- end
146
- end
147
-
148
152
  def explicit_type
149
153
  self.class.new(name: name, enum: enum)
150
154
  end
@@ -21,18 +21,23 @@ module RASN1
21
21
  !optional?
22
22
  end
23
23
 
24
- private
25
-
26
- def value_to_der
27
- ''
28
- end
29
-
24
+ # Check +der+ string
25
+ # @param [String] der
26
+ # @param [::Boolean] ber
27
+ # @return [void]
28
+ # @raise [ASN1Error] +der+ is not empty
30
29
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
31
30
  raise ASN1Error, 'NULL should not have content!' if der.length.positive?
32
31
 
33
32
  @no_value = true
34
33
  @value = void_value
35
34
  end
35
+
36
+ private
37
+
38
+ def value_to_der
39
+ ''
40
+ end
36
41
  end
37
42
  end
38
43
  end
@@ -7,6 +7,8 @@ module RASN1
7
7
  class NumericString < OctetString
8
8
  # NumericString id value
9
9
  ID = 18
10
+ # Invalid characters in NumericString
11
+ INVALID_CHARS = /([^0-9 ])/
10
12
 
11
13
  # Get ASN.1 type
12
14
  # @return [String]
@@ -14,6 +16,16 @@ module RASN1
14
16
  'NumericString'
15
17
  end
16
18
 
19
+ # Make string value from +der+ string
20
+ # @param [String] der
21
+ # @param [::Boolean] ber
22
+ # @return [void]
23
+ # @raise [ASN1Error] invalid characters detected
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ check_characters
27
+ end
28
+
17
29
  private
18
30
 
19
31
  def value_to_der
@@ -21,13 +33,8 @@ module RASN1
21
33
  @value.to_s.b
22
34
  end
23
35
 
24
- def der_to_value(der, ber: false)
25
- super
26
- check_characters
27
- end
28
-
29
36
  def check_characters
30
- raise ASN1Error, "NUMERIC STRING #{@name}: invalid character: '#{$1}'" if @value.to_s =~ /([^0-9 ])/
37
+ raise ASN1Error, "NUMERIC STRING #{@name}: invalid character: '#{$1}'" if @value.to_s =~ INVALID_CHARS
31
38
  end
32
39
  end
33
40
  end
@@ -4,30 +4,15 @@ module RASN1
4
4
  module Types
5
5
  # ASN.1 Object ID
6
6
  # @author Sylvain Daubert
7
+ # @author LemonTree55
7
8
  class ObjectId < Primitive
8
9
  # ObjectId id value
9
10
  ID = 6
10
11
 
11
- private
12
-
13
- def value_to_der
14
- ids = @value.to_s.split('.').map(&:to_i)
15
-
16
- raise ASN1Error, "OBJECT ID #{@name}: first subidentifier should be less than 3" if ids[0] > 2
17
- raise ASN1Error, "OBJECT ID #{@name}: second subidentifier should be less than 40" if (ids[0] < 2) && (ids[1] > 39)
18
-
19
- ids[0, 2] = ids[0] * 40 + ids[1]
20
- octets = []
21
- ids.each do |v|
22
- if v < 128
23
- octets << v
24
- else
25
- octets.concat(unsigned_to_chained_octets(v))
26
- end
27
- end
28
- octets.pack('C*')
29
- end
30
-
12
+ # Make object id value from +der+ string
13
+ # @param [String] der
14
+ # @param [::Boolean] ber
15
+ # @return [void]
31
16
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
32
17
  bytes = der.unpack('C*')
33
18
 
@@ -35,7 +20,7 @@ module RASN1
35
20
  current_id = 0
36
21
  bytes.each do |byte|
37
22
  current_id = (current_id << 7) | (byte & 0x7f)
38
- if (byte & 0x80).zero?
23
+ if byte.nobits?(0x80)
39
24
  ids << current_id
40
25
  current_id = 0
41
26
  end
@@ -47,6 +32,33 @@ module RASN1
47
32
 
48
33
  @value = ids.join('.')
49
34
  end
35
+
36
+ private
37
+
38
+ # @raise [ASN1Error]
39
+ def value_to_der
40
+ ids = @value.to_s.split('.').map(&:to_i)
41
+
42
+ check_first_ids(ids)
43
+
44
+ ids[0, 2] = ids[0] * 40 + ids[1]
45
+ octets = []
46
+ ids.each do |v|
47
+ if v < 128
48
+ octets << v
49
+ else
50
+ octets.concat(unsigned_to_chained_octets(v))
51
+ end
52
+ end
53
+ octets.pack('C*')
54
+ end
55
+
56
+ # @param [Array[::Integer]] ids
57
+ # @raise [ASN1Error]
58
+ def check_first_ids(ids)
59
+ raise ASN1Error, "OBJECT ID #{@name}: first subidentifier should be less than 3" if ids[0] > 2
60
+ raise ASN1Error, "OBJECT ID #{@name}: second subidentifier should be less than 40" if (ids[0] < 2) && (ids[1] > 39)
61
+ end
50
62
  end
51
63
  end
52
64
  end
@@ -21,6 +21,32 @@ module RASN1
21
21
  str = common_inspect(level)
22
22
  str << " #{value.inspect}"
23
23
  end
24
+
25
+ # Make string value from +der+ string. Force encoding to UTF-8
26
+ # @param [String] der
27
+ # @param [::Boolean] ber
28
+ # @return [void]
29
+ # @since 0.15.0 Handle ENCODING constant, if defined by subclass
30
+ def der_to_value(der, ber: false)
31
+ klass = self.class
32
+ if klass.const_defined?(:ENCODING)
33
+ @value = der.to_s.dup.force_encoding(klass.const_get(:ENCODING))
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # @since 0.15.0 Handle ENCODING constant, if defined by subclass
42
+ def value_to_der
43
+ klass = self.class
44
+ if klass.const_defined?(:ENCODING)
45
+ @value.to_s.encode(klass.const_get(:ENCODING)).b
46
+ else
47
+ super
48
+ end
49
+ end
24
50
  end
25
51
  end
26
52
  end