rasn1 0.12.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 552859c3f49caec5374c8db4459ad23e1f9c43e930b0d6511cab8f997c798d20
4
- data.tar.gz: 29eaf2fc529b470c9bc6283efbaf82e580d5b4b86d5c46920ee4a882a18b0d72
3
+ metadata.gz: 50b3f9ddc3a11279f36c21d14768a908b0fe4f22ff75d650791a7a58ac47eaa7
4
+ data.tar.gz: d229ea04a626e34d8411e3787101ace2c55e3cef7178b617073ed6b65fefb867
5
5
  SHA512:
6
- metadata.gz: 9e5df0e417db7e1f02accae9cc4f17b4a49975d1dc17b716dbf587bb3f5e8ca68ed842d22358d7de037041255c164df5a92c6ada52adba944f0ac635793a64b1
7
- data.tar.gz: e193284ea13ca5f80a460b8b390839a0283a8424ca8201f24cbd6113919399fcd5b83a2fc39dd5b26925187e19930a1aceccb1485255de8301435e20a2c82761
6
+ metadata.gz: 37ea433265c6229a89d896e47918c242b2edc48c24ffa585873e3706dfc3eaf73628b76c91bad3e15e1d969a5caf52237c92336aefdba2668f63f9468e7e2004
7
+ data.tar.gz: 0035453de20ea4873999ec18eb3065afa9f1fdddc2927f446ecd2b1851a3a0d1bef32b5bbc2d9029f804a57cd71fefbf95dae4cd4babae5f8491f1e9ea8057ad
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/rasn1.svg)](https://badge.fury.io/rb/rasn1)
2
+ [![Action status](https://github.com/sdaubert/rasn1/workflows/ci/badge.svg?branch=master)](https://github.com/sdaubert/rasn1/actions?query=workflow%3Aci)
2
3
 
3
4
  # Rasn1
4
5
 
data/lib/rasn1/errors.rb CHANGED
@@ -18,7 +18,7 @@ module RASN1
18
18
  # Enumerated error
19
19
  class EnumeratedError < Error; end
20
20
 
21
- # CHOICE error: #chosen not set
21
+ # CHOICE error: {Types::Choice#chosen} not set
22
22
  class ChoiceError < RASN1::Error
23
23
  # @param [Types::Base] object
24
24
  def initialize(object)
@@ -26,6 +26,7 @@ module RASN1
26
26
  super()
27
27
  end
28
28
 
29
+ # @return [String]
29
30
  def message
30
31
  "CHOICE #{@object.name}: #chosen not set"
31
32
  end
data/lib/rasn1/model.rb CHANGED
@@ -66,6 +66,9 @@ module RASN1
66
66
  class Model # rubocop:disable Metrics/ClassLength
67
67
  # @private
68
68
  Elem = Struct.new(:name, :proc_or_class, :content) do
69
+ # @param [String,Symbol] name
70
+ # @param [Proc,Class] proc_or_class
71
+ # @param [Hash,nil] content
69
72
  def initialize(name, proc_or_class, content)
70
73
  if content.is_a?(Array)
71
74
  duplicate_names = find_all_duplicate_names(content.map(&:name) + [name])
@@ -88,11 +91,13 @@ module RASN1
88
91
 
89
92
  # @private
90
93
  WrapElem = Struct.new(:element, :options) do
94
+ # @return [String]
91
95
  def name
92
96
  "#{element.name}_wrapper"
93
97
  end
94
98
  end
95
99
 
100
+ # Define helper methods to define models
96
101
  module Accel
97
102
  # @return [Hash]
98
103
  attr_reader :options
@@ -145,6 +150,9 @@ module RASN1
145
150
  klass.class_eval { @root = root }
146
151
  end
147
152
 
153
+ # @private
154
+ # @param [String,Symbol] accel_name
155
+ # @param [Class] klass
148
156
  # @since 0.11.0
149
157
  # @since 0.12.0 track source location on error (adfoster-r7)
150
158
  def define_type_accel_base(accel_name, klass)
@@ -159,6 +167,9 @@ module RASN1
159
167
  EVAL
160
168
  end
161
169
 
170
+ # @private
171
+ # @param [String,Symbol] accel_name
172
+ # @param [Class] klass
162
173
  # @since 0.11.0
163
174
  # @since 0.12.0 track source location on error (adfoster-r7)
164
175
  def define_type_accel_of(accel_name, klass)
@@ -175,8 +186,9 @@ module RASN1
175
186
 
176
187
  # Define an accelarator to access a type in a model definition
177
188
  # @param [String] accel_name
178
- # @param [Class] klass
189
+ # @param [Class] klass class to instanciate
179
190
  # @since 0.11.0
191
+ # @since 0.12.0 track source location on error (adfoster-r7)
180
192
  def define_type_accel(accel_name, klass)
181
193
  if klass < Types::SequenceOf
182
194
  define_type_accel_of(accel_name, klass)
@@ -287,6 +299,12 @@ module RASN1
287
299
  # @param [Hash] options
288
300
  # @return [Elem]
289
301
  # @see Types::BitString#initialize
302
+ # @!method bmp_string(name, options)
303
+ # @!scope class
304
+ # @param [Symbol,String] name name of object in model
305
+ # @param [Hash] options
306
+ # @return [Elem]
307
+ # @see Types::BmpString#initialize
290
308
  # @!method octet_string(name, options)
291
309
  # @!scope class
292
310
  # @param [Symbol,String] name name of object in model
@@ -305,6 +323,12 @@ module RASN1
305
323
  # @param [Hash] options
306
324
  # @return [Elem]
307
325
  # @see Types::Enumerated#initialize
326
+ # @!method universal_string(name, options)
327
+ # @!scope class
328
+ # @param [Symbol,String] name name of object in model
329
+ # @param [Hash] options
330
+ # @return [Elem]
331
+ # @see Types::UniversalString#initialize
308
332
  # @!method utf8_string(name, options)
309
333
  # @!scope class
310
334
  # @param [Symbol,String] name name of object in model
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ # @private
5
+ class Tracer
6
+ # @return [IO]
7
+ attr_reader :io
8
+ # @return [Integer]
9
+ attr_accessor :tracing_level
10
+
11
+ TRACED_CLASSES = [Types::Any, Types::Choice, Types::Sequence, Types::SequenceOf, Types::Base].freeze
12
+
13
+ # @param [IO] io
14
+ def initialize(io)
15
+ @io = io
16
+ @tracing_level = 0
17
+ end
18
+
19
+ # Puts +msg+ onto {#io}.
20
+ # @param [String] msg
21
+ # @return [void]
22
+ def trace(msg)
23
+ @io.puts(indent << msg)
24
+ end
25
+
26
+ # Return identation for given +level+. If +nil+, use {#tracing_level}.
27
+ # @param [Integer,nil] level
28
+ # @return [String]
29
+ def indent(level=nil)
30
+ level ||= @tracing_level
31
+ ' ' * level
32
+ end
33
+ end
34
+
35
+ # Trace RASN1 parsing to +io+.
36
+ # All parsing methods called in block are traced to +io+. Each ASN.1 element is
37
+ # traced in a line showing element's id, its length and its data.
38
+ # @param [IO] io
39
+ # @example
40
+ # RASN1.trace do
41
+ # RASN1.parse("\x02\x01\x01") # puts "INTEGER id: 2 (0x02), len: 1 (0x01), data: 0x01"
42
+ # end
43
+ # RASN1.parse("\x01\x01\xff") # puts nothing onto STDOUT
44
+ # @return [void]
45
+ def self.trace(io=$stdout)
46
+ @tracer = Tracer.new(io)
47
+ Tracer::TRACED_CLASSES.each(&:start_tracing)
48
+
49
+ begin
50
+ yield @tracer
51
+ ensure
52
+ Tracer::TRACED_CLASSES.reverse.each(&:stop_tracing)
53
+ @tracer.io.flush
54
+ @tracer = nil
55
+ end
56
+ end
57
+
58
+ # @private
59
+ def self.tracer
60
+ @tracer
61
+ end
62
+
63
+ module Types
64
+ class Base
65
+ class << self
66
+ # @private
67
+ # Patch {#do_parse} to add tracing ability
68
+ def start_tracing
69
+ alias_method :do_parse_without_tracing, :do_parse
70
+ alias_method :do_parse, :do_parse_with_tracing
71
+ alias_method :do_parse_explicit_without_tracing, :do_parse_explicit
72
+ alias_method :do_parse_explicit, :do_parse_explicit_with_tracing
73
+ end
74
+
75
+ # @private
76
+ # Unpatch {#do_parse} to remove tracing ability
77
+ def stop_tracing
78
+ alias_method :do_parse, :do_parse_without_tracing # rubocop:disable Lint/DuplicateMethods
79
+ alias_method :do_parse_explicit, :do_parse_explicit_without_tracing # rubocop:disable Lint/DuplicateMethods
80
+ end
81
+ end
82
+
83
+ # @private
84
+ # Parse +der+ with tracing abillity
85
+ # @see #parse!
86
+ def do_parse_with_tracing(der, ber)
87
+ ret = do_parse_without_tracing(der, ber)
88
+ RASN1.tracer.trace(self.trace)
89
+ ret
90
+ end
91
+
92
+ def do_parse_explicit_with_tracing(data)
93
+ RASN1.tracer.tracing_level += 1
94
+ do_parse_explicit_without_tracing(data)
95
+ RASN1.tracer.tracing_level -= 1
96
+ end
97
+ end
98
+
99
+ class Choice
100
+ class << self
101
+ # @private
102
+ # Patch {#parse!} to add tracing ability
103
+ def start_tracing
104
+ alias_method :parse_without_tracing, :parse!
105
+ alias_method :parse!, :parse_with_tracing
106
+ end
107
+
108
+ # @private
109
+ # Unpatch {#parse!} to remove tracing ability
110
+ def stop_tracing
111
+ alias_method :parse!, :parse_without_tracing # rubocop:disable Lint/DuplicateMethods
112
+ end
113
+ end
114
+
115
+ # @private
116
+ # Parse +der+ with tracing abillity
117
+ # @see #parse!
118
+ def parse_with_tracing(der, ber: false)
119
+ RASN1.tracer.trace(self.trace)
120
+ parse_without_tracing(der, ber: ber)
121
+ end
122
+ end
123
+
124
+ class Sequence
125
+ class << self
126
+ # @private
127
+ # Patch {#der_to_value} to add tracing ability
128
+ def start_tracing
129
+ alias_method :der_to_value_without_tracing, :der_to_value
130
+ alias_method :der_to_value, :der_to_value_with_tracing
131
+ end
132
+
133
+ # @private
134
+ # Unpatch {#der_to_value!} to remove tracing ability
135
+ def stop_tracing
136
+ alias_method :der_to_value, :der_to_value_without_tracing # rubocop:disable Lint/DuplicateMethods
137
+ end
138
+ end
139
+
140
+ # @private
141
+ # der_to_value +der+ with tracing abillity
142
+ def der_to_value_with_tracing(der, ber: false)
143
+ RASN1.tracer.tracing_level += 1
144
+ der_to_value_without_tracing(der, ber: ber)
145
+ RASN1.tracer.tracing_level -= 1
146
+ end
147
+ end
148
+
149
+ class SequenceOf
150
+ class << self
151
+ # @private
152
+ # Patch {#der_to_value} to add tracing ability
153
+ def start_tracing
154
+ alias_method :der_to_value_without_tracing, :der_to_value
155
+ alias_method :der_to_value, :der_to_value_with_tracing
156
+ end
157
+
158
+ # @private
159
+ # Unpatch {#der_to_value!} to remove tracing ability
160
+ def stop_tracing
161
+ alias_method :der_to_value, :der_to_value_without_tracing # rubocop:disable Lint/DuplicateMethods
162
+ end
163
+ end
164
+
165
+ # @private
166
+ # der_to_value +der+ with tracing abillity
167
+ def der_to_value_with_tracing(der, ber: false)
168
+ RASN1.tracer.tracing_level += 1
169
+ der_to_value_without_tracing(der, ber: ber)
170
+ RASN1.tracer.tracing_level -= 1
171
+ end
172
+ end
173
+ end
174
+ end
@@ -31,22 +31,12 @@ module RASN1
31
31
  # @param [Boolean] ber if +true+, accept BER encoding
32
32
  # @return [Integer] total number of parsed bytes
33
33
  def parse!(der, ber: false)
34
- if der.empty?
35
- return 0 if optional?
36
-
37
- raise ASN1Error, 'Expected ANY but get nothing'
38
- end
39
-
40
- id_size = Types.decode_identifier_octets(der).last
41
- total_length, = get_data(der[id_size..-1], ber)
42
- total_length += id_size
43
-
44
- @no_value = false
45
- @value = der[0, total_length]
46
-
34
+ total_length, _data = do_parse(der, ber)
47
35
  total_length
48
36
  end
49
37
 
38
+ # @param [::Integer] level
39
+ # @return [String]
50
40
  def inspect(level=0)
51
41
  str = common_inspect(level)
52
42
  str << if !value?
@@ -60,6 +50,16 @@ module RASN1
60
50
  end
61
51
  end
62
52
 
53
+ # @private Tracer private API
54
+ # @return [String]
55
+ def trace
56
+ return trace_any if value?
57
+
58
+ msg_type(no_id: true) << ' NONE'
59
+ end
60
+
61
+ private
62
+
63
63
  def common_inspect(level)
64
64
  lvl = level >= 0 ? level : 0
65
65
  str = ' ' * lvl
@@ -69,6 +69,27 @@ module RASN1
69
69
  str << "[#{id}] IMPLICIT " if implicit?
70
70
  str << '(ANY) '
71
71
  end
72
+
73
+ def do_parse(der, ber)
74
+ if der.empty?
75
+ return [0, ''] if optional?
76
+
77
+ raise ASN1Error, 'Expected ANY but get nothing'
78
+ end
79
+
80
+ id_size = Types.decode_identifier_octets(der).last
81
+ total_length, = get_data(der[id_size..-1], ber)
82
+ total_length += id_size
83
+
84
+ @no_value = false
85
+ @value = der[0, total_length]
86
+
87
+ [total_length, @value]
88
+ end
89
+
90
+ def trace_any
91
+ msg_type(no_id: true) << trace_data(value)
92
+ end
72
93
  end
73
94
  end
74
95
  end
@@ -71,6 +71,12 @@ module RASN1
71
71
  attr_reader :default
72
72
  # @return [Hash[Symbol, Object]]
73
73
  attr_reader :options
74
+ # @return [String] raw parsed data
75
+ attr_reader :raw_data
76
+ # @return [String] raw parsed length
77
+ attr_reader :raw_length
78
+
79
+ private :raw_data, :raw_length
74
80
 
75
81
  # Get ASN.1 type
76
82
  # @return [String]
@@ -121,13 +127,16 @@ module RASN1
121
127
  set_value(options.delete(:value))
122
128
  self.options = options
123
129
  specific_initializer
130
+ @raw_data = ''.b
131
+ @raw_length = ''.b
124
132
  end
125
133
 
126
134
  # @abstract To help subclass initialize itself. Default implementation do nothing.
127
135
  def specific_initializer; end
128
136
 
129
- # Used by +#dup+ and +#clone+. Deep copy @value and @default.
130
- def initialize_copy(_other)
137
+ # Deep copy @value and @default.
138
+ def initialize_copy(*)
139
+ super
131
140
  @value = @value.dup
132
141
  @no_value = @no_value.dup
133
142
  @default = @default.dup
@@ -153,6 +162,7 @@ module RASN1
153
162
  ''
154
163
  end
155
164
 
165
+ # Say if this type is optional
156
166
  # @return [::Boolean]
157
167
  def optional?
158
168
  @optional
@@ -215,17 +225,11 @@ module RASN1
215
225
  # @return [Integer] total number of parsed bytes
216
226
  # @raise [ASN1Error] error on parsing
217
227
  def parse!(der, ber: false)
218
- return 0 unless check_id(der)
228
+ total_length, data = do_parse(der, ber)
229
+ return 0 if total_length.zero?
219
230
 
220
- id_size = Types.decode_identifier_octets(der).last
221
- total_length, data = get_data(der[id_size..-1], ber)
222
- total_length += id_size
223
- @no_value = false
224
231
  if explicit?
225
- # Delegate to #explicit type to generate sub-value
226
- type = explicit_type
227
- type.parse!(data)
228
- @value = type.value
232
+ do_parse_explicit(data)
229
233
  else
230
234
  der_to_value(data, ber: ber)
231
235
  end
@@ -283,8 +287,82 @@ module RASN1
283
287
  value? && (@default.nil? || (@value != @default))
284
288
  end
285
289
 
290
+ # @private Tracer private API
291
+ # @return [String]
292
+ def trace
293
+ return trace_real if value?
294
+
295
+ msg = msg_type
296
+ if default.nil? # rubocop:disable Style/ConditionalAssignment
297
+ msg << ' NONE'
298
+ else
299
+ msg << " DEFAULT VALUE #{default}"
300
+ end
301
+ end
302
+
286
303
  private
287
304
 
305
+ def trace_real
306
+ encoded_id = unpack(encode_identifier_octets)
307
+ data_length = raw_data.length
308
+ encoded_length = unpack(raw_length)
309
+ msg = msg_type
310
+ msg << " (0x#{encoded_id}),"
311
+ msg << " len: #{data_length} (0x#{encoded_length})"
312
+ msg << trace_data
313
+ end
314
+
315
+ def trace_data(data=nil)
316
+ byte_count = 0
317
+ data ||= raw_data
318
+
319
+ lines = []
320
+ str = ''
321
+ data.each_byte do |byte|
322
+ if (byte_count % 16).zero?
323
+ str = trace_format_new_data_line(byte_count)
324
+ lines << str
325
+ end
326
+ str[compute_trace_index(byte_count, 3), 2] = '%02x' % byte
327
+ str[compute_trace_index(byte_count, 1, 49)] = byte >= 32 && byte <= 126 ? byte.chr : '.'
328
+ byte_count += 1
329
+ end
330
+ lines.map(&:rstrip).join << "\n"
331
+ end
332
+
333
+ def trace_format_new_data_line(count)
334
+ head_line = RASN1.tracer.indent(RASN1.tracer.tracing_level + 1)
335
+ ("\n#{head_line}%04x " % count) << ' ' * 68
336
+ end
337
+
338
+ def compute_trace_index(byte_count, byte_count_mul=1, offset=0)
339
+ base_idx = 7 + RASN1.tracer.indent(RASN1.tracer.tracing_level + 1).length
340
+ base_idx + offset + (byte_count % 16) * byte_count_mul
341
+ end
342
+
343
+ def unpack(binstr)
344
+ binstr.unpack1('H*')
345
+ end
346
+
347
+ def asn1_class_to_s
348
+ asn1_class == :universal ? '' : asn1_class.to_s.upcase << ' '
349
+ end
350
+
351
+ def msg_type(no_id: false)
352
+ msg = name.nil? ? +'' : +"#{name} "
353
+ msg << "[ #{asn1_class_to_s}#{id} ] " unless no_id
354
+ msg << if explicit?
355
+ +'EXPLICIT '
356
+ elsif implicit?
357
+ +'IMPLICIT '
358
+ else
359
+ +''
360
+ end
361
+ msg << type
362
+ msg << ' OPTIONAL' if optional?
363
+ msg
364
+ end
365
+
288
366
  def pc_bit
289
367
  if @constructed.nil?
290
368
  self.class.const_get(:ASN1_PC)
@@ -299,7 +377,7 @@ module RASN1
299
377
  lvl = level >= 0 ? level : 0
300
378
  str = ' ' * lvl
301
379
  str << "#{@name} " unless @name.nil?
302
- str << asn1_class.to_s.upcase << ' ' unless asn1_class == :universal
380
+ str << asn1_class_to_s
303
381
  str << "[#{id}] EXPLICIT " if explicit?
304
382
  str << "[#{id}] IMPLICIT " if implicit?
305
383
  str << "#{type}:"
@@ -313,6 +391,24 @@ module RASN1
313
391
  end
314
392
  end
315
393
 
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
+
316
412
  def value_to_der
317
413
  case @value
318
414
  when Base
@@ -454,6 +550,21 @@ module RASN1
454
550
  end
455
551
 
456
552
  def get_data(der, ber)
553
+ return [0, ''] if der.nil? || der.empty?
554
+
555
+ length, length_length = get_length(der, ber)
556
+
557
+ data = der[1 + length_length, length]
558
+ @raw_length = der[0, length_length + 1]
559
+ @raw_data = data
560
+
561
+ total_length = 1 + length
562
+ total_length += length_length if length_length.positive?
563
+
564
+ [total_length, data]
565
+ end
566
+
567
+ def get_length(der, ber)
457
568
  length = der.unpack1('C').to_i
458
569
  length_length = 0
459
570
 
@@ -464,12 +575,8 @@ module RASN1
464
575
  length = der[1, length_length].unpack('C*')
465
576
  .reduce(0) { |len, b| (len << 8) | b }
466
577
  end
467
- data = der[1 + length_length, length]
468
578
 
469
- total_length = 1 + length
470
- total_length += length_length if length_length.positive?
471
-
472
- [total_length, data]
579
+ [length, length_length]
473
580
  end
474
581
 
475
582
  def raise_on_indefinite_length(ber)
@@ -484,7 +591,7 @@ module RASN1
484
591
  end
485
592
 
486
593
  def explicit_type
487
- self.class.new
594
+ self.class.new(name: name)
488
595
  end
489
596
 
490
597
  def raise_id_error(der)
@@ -493,10 +600,6 @@ module RASN1
493
600
  raise ASN1Error, msg
494
601
  end
495
602
 
496
- def class_from_numeric_id(id)
497
- CLASSES.key(id & CLASS_MASK)
498
- end
499
-
500
603
  def self2name
501
604
  name = +"#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}"
502
605
  if implicit? || explicit?
@@ -8,7 +8,6 @@ module RASN1
8
8
  # BitString id value
9
9
  ID = 3
10
10
 
11
- # @param [Integer] bit_length
12
11
  # @return [Integer]
13
12
  attr_writer :bit_length
14
13
 
@@ -42,6 +41,9 @@ module RASN1
42
41
  str << " #{value.inspect} (bit length: #{bit_length})"
43
42
  end
44
43
 
44
+ # Same as {Base#can_build?} but also check bit_length
45
+ # @see Base#can_build?
46
+ # @return [Boolean]
45
47
  def can_build?
46
48
  super || (!@default.nil? && (@bit_length != @default_bit_length))
47
49
  end
@@ -86,7 +88,7 @@ module RASN1
86
88
  end
87
89
 
88
90
  def explicit_type
89
- self.class.new(value: @value, bit_length: @bit_length)
91
+ self.class.new(name: name, value: @value, bit_length: @bit_length)
90
92
  end
91
93
  end
92
94
  end
@@ -39,6 +39,12 @@ module RASN1
39
39
  @value = true
40
40
  end
41
41
  end
42
+
43
+ def trace_data
44
+ return super if explicit?
45
+
46
+ " #{raw_data == "\x00".b ? 'FALSE' : 'TRUE'} (0x#{raw_data.unpack1('H*')})"
47
+ end
42
48
  end
43
49
  end
44
50
  end
@@ -75,28 +75,39 @@ module RASN1
75
75
  # @return [Integer] total number of parsed bytes
76
76
  # @raise [ASN1Error] error on parsing
77
77
  def parse!(der, ber: false)
78
- parsed = false
79
78
  @value.each_with_index do |element, i|
80
79
  @chosen = i
81
80
  nb_bytes = element.parse!(der, ber: ber)
82
- parsed = true
83
81
  return nb_bytes
84
82
  rescue ASN1Error
85
83
  @chosen = nil
86
84
  next
87
85
  end
88
- raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless parsed
86
+
87
+ @no_value = true
88
+ @value = void_value
89
+ raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless optional?
90
+
91
+ 0
89
92
  end
90
93
 
94
+ # @param [::Integer] level
95
+ # @return [String]
91
96
  def inspect(level=0)
92
97
  str = common_inspect(level)
93
- str << if defined? @chosen
98
+ str << if defined?(@chosen) && value?
94
99
  "\n#{@value[@chosen].inspect(level + 1)}"
95
100
  else
96
101
  ' not chosen!'
97
102
  end
98
103
  end
99
104
 
105
+ # @private Tracer private API
106
+ # @return [String]
107
+ def trace
108
+ msg_type(no_id: true)
109
+ end
110
+
100
111
  private
101
112
 
102
113
  def check_chosen
@@ -2,11 +2,12 @@
2
2
 
3
3
  module RASN1
4
4
  module Types
5
- # Mixin to had constraints on a RASN1 type.
5
+ # Mixin to add constraints on a RASN1 type.
6
6
  # Should not be used directly but through {Types.define_type}.
7
7
  # @version 0.11.0
8
8
  # @author Sylvain Daubert
9
9
  module Constrained
10
+ # Define class/module methods for {Constrained} module
10
11
  module ClassMethods
11
12
  # Setter for constraint
12
13
  # @param [Proc,nil] constraint
@@ -31,8 +32,10 @@ module RASN1
31
32
  end
32
33
 
33
34
  class << self
35
+ # @return [Proc] proc to check constraints
34
36
  attr_reader :constraint
35
37
 
38
+ # Extend +base+ with {ClassMethods}
36
39
  def included(base)
37
40
  base.extend ClassMethods
38
41
  end
@@ -46,6 +49,8 @@ module RASN1
46
49
  super
47
50
  end
48
51
 
52
+ private
53
+
49
54
  def der_to_value(der, ber: false)
50
55
  super
51
56
  self.class.check_constraint(@value)
@@ -71,19 +71,33 @@ module RASN1
71
71
  end
72
72
 
73
73
  def value_when_fraction_empty(date_hour)
74
- if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
75
- # If not UTC, have to add offset with UTC to force
76
- # Strptime to generate a local time.
77
- date_hour << Time.now.strftime('%z')
74
+ tz = if date_hour[-1] == 'Z'
75
+ date_hour.slice!(-1, 1)
76
+ '+00:00' # Ruby 3.0: to remove after end-of support of ruby 3.0
77
+ elsif date_hour.match?(/[+-]\d+$/)
78
+ # Ruby 3.0
79
+ # date_hour.slice!(-5, 5)
80
+ zone = date_hour.slice!(-5, 5)
81
+ zone[0, 3] << ':' << zone[3, 2]
82
+ end
83
+ year = date_hour.slice!(0, 4).to_i
84
+ others = date_hour.scan(/../).map(&:to_i)
85
+ # Ruby 3.0
86
+ # From 3.1: "Z" and "-0100" are supported
87
+ # Below 3.1: should be "-01:00" or "+00:00"
88
+ unless tz.nil?
89
+ others += [0] * (5 - others.size)
90
+ others << tz
78
91
  end
79
-
80
- value_from(date_hour)
92
+ @value = Time.new(year, *others)
93
+ # From 3.1: replace all this code by: Time.new(year, *others, in: tz)
81
94
  end
82
95
 
83
96
  def value_when_fraction_ends_with_z(date_hour, fraction)
84
97
  fraction = fraction[0...-1]
85
98
  date_hour << 'Z'
86
- frac_base = value_from(date_hour)
99
+ frac_base = compute_frac_base(date_hour)
100
+ value_when_fraction_empty(date_hour)
87
101
  fix_value(fraction, frac_base)
88
102
  end
89
103
 
@@ -93,47 +107,43 @@ module RASN1
93
107
  # fraction contains fraction and timezone info. Split them
94
108
  fraction = match[1]
95
109
  date_hour << match[2]
96
- else
97
- # fraction only contains fraction.
98
- # Have to add offset with UTC to force Strptime to
99
- # generate a local time.
100
- date_hour << Time.now.strftime('%z')
101
110
  end
102
111
 
103
- frac_base = value_from(date_hour)
112
+ frac_base = compute_frac_base(date_hour)
113
+ value_when_fraction_empty(date_hour)
104
114
  fix_value(fraction, frac_base)
105
115
  end
106
116
 
107
- def value_from(date_hour)
108
- format, frac_base = strformat(date_hour)
109
- @value = Strptime.new(format).exec(date_hour)
110
- frac_base
111
- end
112
-
113
117
  def fix_value(fraction, frac_base)
114
118
  frac = ".#{fraction}".to_r * frac_base
115
119
  @value = (@value + frac) unless fraction.nil?
116
120
  end
117
121
 
118
- def strformat(date_hour)
122
+ def compute_frac_base(date_hour)
119
123
  case date_hour.size
120
- when 11
121
- ['%Y%m%d%H%z', HOUR_TO_SEC]
122
- when 13, 17
123
- ['%Y%m%d%H%M%z', MINUTE_TO_SEC]
124
+ when 10, 11
125
+ HOUR_TO_SEC
126
+ when 12, 13, 17
127
+ MINUTE_TO_SEC
124
128
  when 15
125
129
  if date_hour[-1] == 'Z'
126
- ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
130
+ SECOND_TO_SEC
127
131
  else
128
- ['%Y%m%d%H%z', HOUR_TO_SEC]
132
+ HOUR_TO_SEC
129
133
  end
130
- when 19
131
- ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
134
+ when 14, 19
135
+ SECOND_TO_SEC
132
136
  else
133
137
  prefix = @name.nil? ? type : "tag #{@name}"
134
138
  raise ASN1Error, "#{prefix}: unrecognized format: #{date_hour}"
135
139
  end
136
140
  end
141
+
142
+ def trace_data
143
+ return super if explicit?
144
+
145
+ +' ' << raw_data
146
+ end
137
147
  end
138
148
  end
139
149
  end
@@ -20,7 +20,7 @@ module RASN1
20
20
  initialize_enum(@options[:enum])
21
21
  end
22
22
 
23
- # @param [Integer,String,Symbol,nil] v
23
+ # @param [Integer,String,Symbol,nil] val
24
24
  # @return [void]
25
25
  def value=(val)
26
26
  @no_value = false
@@ -128,18 +128,29 @@ module RASN1
128
128
  v
129
129
  end
130
130
 
131
+ def int_to_enum(int, check_enum: true)
132
+ raise EnumeratedError, "#{@name}: value #{int} not in enumeration" if check_enum && !@enum.value?(int)
133
+
134
+ @enum.key(int) || int
135
+ end
136
+
131
137
  def der_to_value(der, ber: false)
132
138
  @value = der_to_int_value(der, ber: ber)
133
139
  return if @enum.empty?
134
140
 
135
- int_value = @value
136
- raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless @enum.value?(@value)
137
-
138
- @value = @enum.key(@value)
141
+ @value = int_to_enum(@value)
139
142
  end
140
143
 
141
144
  def explicit_type
142
- self.class.new(name: @name, enum: @enum)
145
+ self.class.new(name: name, enum: enum)
146
+ end
147
+
148
+ def trace_data
149
+ return super if explicit?
150
+ return " #{der_to_int_value(raw_data)} (0x#{raw_data.unpack1('H*')})" if @enum.empty?
151
+
152
+ v = int_to_enum(der_to_int_value(raw_data), check_enum: false)
153
+ " #{v} (0x#{raw_data.unpack1('H*')})"
143
154
  end
144
155
  end
145
156
  end
@@ -15,6 +15,8 @@ module RASN1
15
15
  str
16
16
  end
17
17
 
18
+ # @return [Boolean]
19
+ # Build only if not optional
18
20
  def can_build?
19
21
  !optional?
20
22
  end
@@ -15,6 +15,8 @@ module RASN1
15
15
  # OctetString id value
16
16
  ID = 4
17
17
 
18
+ # @param [::Integer] level
19
+ # @return [String]
18
20
  def inspect(level=0)
19
21
  str = common_inspect(level)
20
22
  str << " #{value.inspect}"
@@ -36,7 +36,8 @@ module RASN1
36
36
  @value ||= []
37
37
  end
38
38
 
39
- def initialize_copy(other)
39
+ # Deep copy @value
40
+ def initialize_copy(*)
40
41
  super
41
42
  @value = case @value
42
43
  when Array
@@ -88,8 +89,12 @@ module RASN1
88
89
  end
89
90
  end
90
91
 
92
+ def trace_data
93
+ ''
94
+ end
95
+
91
96
  def explicit_type
92
- self.class.new(value: @value)
97
+ self.class.new(name: name, value: @value)
93
98
  end
94
99
  end
95
100
  end
@@ -67,7 +67,8 @@ module RASN1
67
67
  @no_value = false
68
68
  end
69
69
 
70
- def initialize_copy(other)
70
+ # Clone @#of_type and values
71
+ def initialize_copy(*)
71
72
  super
72
73
  @of_type = @of_type.dup
73
74
  @value = @value.map(&:dup)
@@ -104,6 +105,7 @@ module RASN1
104
105
  @value.length
105
106
  end
106
107
 
108
+ # @param [::Integer] level
107
109
  # @return [String]
108
110
  def inspect(level=0)
109
111
  str = common_inspect(level)
@@ -153,7 +155,7 @@ module RASN1
153
155
  end
154
156
 
155
157
  def explicit_type
156
- self.class.new(self.of_type)
158
+ self.class.new(self.of_type, name: name)
157
159
  end
158
160
 
159
161
  def push_primitive(obj)
@@ -188,6 +190,10 @@ module RASN1
188
190
  raise ASN1Error, "object to add should be a #{of_type_class} or a Hash"
189
191
  end
190
192
  end
193
+
194
+ def trace_data
195
+ ''
196
+ end
191
197
  end
192
198
  end
193
199
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 UniversalString
6
+ # @since 0.13.0
7
+ # @author zeroSteiner
8
+ class UniversalString < OctetString
9
+ # UniversalString id value
10
+ ID = 28
11
+
12
+ # Get ASN.1 type
13
+ # @return [String]
14
+ def self.type
15
+ 'UniversalString'
16
+ end
17
+
18
+ private
19
+
20
+ def value_to_der
21
+ @value.to_s.dup.encode('UTF-32BE').b
22
+ end
23
+
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ @value = der.to_s.dup.force_encoding('UTF-32BE')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -46,6 +46,12 @@ module RASN1
46
46
  century = (Time.now.year / 100).to_s
47
47
  @value = Strptime.new(format).exec(century + der)
48
48
  end
49
+
50
+ def trace_data
51
+ return super if explicit?
52
+
53
+ +' ' << raw_data
54
+ end
49
55
  end
50
56
  end
51
57
  end
data/lib/rasn1/types.rb CHANGED
@@ -87,8 +87,16 @@ module RASN1
87
87
  # @param [Types::Base] from class from which inherits
88
88
  # @param [Module] in_module module in which creates new type (default to {RASN1::Types})
89
89
  # @return [Class] newly created class
90
+ # @yieldparam [Object] value value to set to type, or infered at parsing
91
+ # @yieldreturn [Boolean]
90
92
  # @since 0.11.0
91
93
  # @since 0.12.0 in_module parameter
94
+ # @example
95
+ # # Define a new UInt32 type
96
+ # # UInt32 ::= INTEGER (0 .. 4294967295)
97
+ # RASN1::Types.define_type('UInt32', from: RASN1::Types::Integer) do |value|
98
+ # (value >= 0) && (value < 2**32)
99
+ # end
92
100
  def self.define_type(name, from:, in_module: self, &block)
93
101
  constraint = block.nil? ? nil : block.to_proc
94
102
 
@@ -120,6 +128,7 @@ require_relative 'types/null'
120
128
  require_relative 'types/object_id'
121
129
  require_relative 'types/enumerated'
122
130
  require_relative 'types/bmp_string'
131
+ require_relative 'types/universal_string'
123
132
  require_relative 'types/utf8_string'
124
133
  require_relative 'types/numeric_string'
125
134
  require_relative 'types/printable_string'
data/lib/rasn1/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RASN1
4
- VERSION = '0.12.0'
4
+ VERSION = '0.13.0'
5
5
  end
data/lib/rasn1/wrapper.rb CHANGED
@@ -14,8 +14,8 @@ module RASN1
14
14
  # @example
15
15
  # # object to wrap
16
16
  # int = RASN1::Types::Integer.new(implicit: 1) # its tag is 0x81
17
- # # simple wraper, change an option
18
- # wrapper = RASN1::Wrapper.new(int, optional: true, default: 1)
17
+ # # simple wrapper, change an option
18
+ # wrapper = RASN1::Wrapper.new(int, default: 1)
19
19
  # # implicit wrapper
20
20
  # wrapper = RASN1::Wrapper.new(int, implicit: 3) # wrapped int tag is now 0x83
21
21
  # # explicit wrapper
@@ -70,26 +70,6 @@ module RASN1
70
70
  super(element)
71
71
  end
72
72
 
73
- def explicit_implicit(options)
74
- opts = options.dup
75
- @explicit = opts.delete(:explicit)
76
- @implicit = opts.delete(:implicit)
77
- opts
78
- end
79
-
80
- def generate_explicit_wrapper(options)
81
- # ExplicitWrapper is a hand-made explicit tag, but we have to use its implicit option
82
- # to force its tag value.
83
- @explicit_wrapper = ExplicitWrapper.new(options.merge(implicit: @explicit))
84
- end
85
-
86
- def generate_explicit_wrapper_options(options)
87
- new_opts = {}
88
- new_opts[:default] = options[:default] if options.key?(:default)
89
- new_opts[:optional] = options[:optional] if options.key?(:optional)
90
- new_opts
91
- end
92
-
93
73
  # Say if wrapper is an explicit one (i.e. add tag and length to its element)
94
74
  # @return [Boolean]
95
75
  def explicit?
@@ -136,6 +116,8 @@ module RASN1
136
116
  end
137
117
  end
138
118
 
119
+ # @return [Boolean]
120
+ # @see Types::Base#value?
139
121
  def value?
140
122
  if explicit?
141
123
  @explicit_wrapper.value?
@@ -151,6 +133,7 @@ module RASN1
151
133
  end
152
134
 
153
135
  # @return [::Integer]
136
+ # @see Types::Base#id
154
137
  def id
155
138
  if implicit?
156
139
  @implicit
@@ -162,6 +145,7 @@ module RASN1
162
145
  end
163
146
 
164
147
  # @return [Symbol]
148
+ # @see Types::Base#asn1_class
165
149
  def asn1_class
166
150
  return element.asn1_class unless @options.key?(:class)
167
151
 
@@ -169,6 +153,7 @@ module RASN1
169
153
  end
170
154
 
171
155
  # @return [Boolean]
156
+ # @see Types::Base#constructed
172
157
  def constructed?
173
158
  return element.constructed? unless @options.key?(:constructed)
174
159
 
@@ -176,10 +161,13 @@ module RASN1
176
161
  end
177
162
 
178
163
  # @return [Boolean]
164
+ # @see Types::Base#primitive
179
165
  def primitive?
180
166
  !constructed?
181
167
  end
182
168
 
169
+ # @param [::Integer] level
170
+ # @return [String]
183
171
  def inspect(level=0)
184
172
  return super(level) unless explicit?
185
173
 
@@ -188,6 +176,26 @@ module RASN1
188
176
 
189
177
  private
190
178
 
179
+ def explicit_implicit(options)
180
+ opts = options.dup
181
+ @explicit = opts.delete(:explicit)
182
+ @implicit = opts.delete(:implicit)
183
+ opts
184
+ end
185
+
186
+ def generate_explicit_wrapper(options)
187
+ # ExplicitWrapper is a hand-made explicit tag, but we have to use its implicit option
188
+ # to force its tag value.
189
+ @explicit_wrapper = ExplicitWrapper.new(options.merge(implicit: @explicit))
190
+ end
191
+
192
+ def generate_explicit_wrapper_options(options)
193
+ new_opts = {}
194
+ new_opts[:default] = options[:default] if options.key?(:default)
195
+ new_opts[:optional] = options[:optional] if options.key?(:optional)
196
+ new_opts
197
+ end
198
+
191
199
  def generate_implicit_element
192
200
  el = element.dup
193
201
  if el.explicit?
data/lib/rasn1.rb CHANGED
@@ -5,10 +5,14 @@ require_relative 'rasn1/errors'
5
5
  require_relative 'rasn1/types'
6
6
  require_relative 'rasn1/model'
7
7
  require_relative 'rasn1/wrapper'
8
+ require_relative 'rasn1/tracer'
8
9
 
9
10
  # Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
10
11
  # @author Sylvain Daubert
11
12
  module RASN1
13
+ # @private
14
+ CONTAINER_CLASSES = [Types::Sequence, Types::Set].freeze
15
+
12
16
  # Parse a DER/BER string without checking a model
13
17
  # @note If you want to check ASN.1 grammary, you should define a {Model}
14
18
  # and use {Model#parse}.
@@ -19,24 +23,21 @@ module RASN1
19
23
  # @param [String] der binary string to parse
20
24
  # @param [Boolean] ber if +true+, decode a BER string, else a DER one
21
25
  # @return [Types::Base]
22
- def self.parse(der, ber: false)
23
- root = nil
24
- until der.empty?
25
- type = Types.id2type(der)
26
- type.parse!(der, ber: ber)
27
- root ||= type
26
+ def self.parse(der, ber: false) # rubocop:disable Metrics:AbcSize
27
+ type = Types.id2type(der)
28
+ type.parse!(der, ber: ber)
28
29
 
29
- if [Types::Sequence, Types::Set].include? type.class
30
- subder = type.value
31
- ary = []
32
- until subder.empty?
33
- ary << self.parse(subder)
34
- subder = subder[ary.last.to_der.size..-1]
35
- end
36
- type.value = ary
30
+ if CONTAINER_CLASSES.include?(type.class)
31
+ subder = type.value
32
+ ary = []
33
+ RASN1.tracer.tracing_level += 1 unless RASN1.tracer.nil?
34
+ until subder.empty?
35
+ ary << self.parse(subder)
36
+ subder = subder[ary.last.to_der.size..-1]
37
37
  end
38
- der = der[type.to_der.size..-1]
38
+ RASN1.tracer.tracing_level -= 1 unless RASN1.tracer.nil?
39
+ type.value = ary
39
40
  end
40
- root
41
+ type
41
42
  end
42
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasn1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvain Daubert
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-12 00:00:00.000000000 Z
11
+ date: 2024-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strptime
@@ -40,6 +40,7 @@ files:
40
40
  - lib/rasn1.rb
41
41
  - lib/rasn1/errors.rb
42
42
  - lib/rasn1/model.rb
43
+ - lib/rasn1/tracer.rb
43
44
  - lib/rasn1/types.rb
44
45
  - lib/rasn1/types/any.rb
45
46
  - lib/rasn1/types/base.rb
@@ -63,6 +64,7 @@ files:
63
64
  - lib/rasn1/types/sequence_of.rb
64
65
  - lib/rasn1/types/set.rb
65
66
  - lib/rasn1/types/set_of.rb
67
+ - lib/rasn1/types/universal_string.rb
66
68
  - lib/rasn1/types/utc_time.rb
67
69
  - lib/rasn1/types/utf8_string.rb
68
70
  - lib/rasn1/types/visible_string.rb
@@ -76,7 +78,7 @@ metadata:
76
78
  source_code_uri: https://github.com/sdaubert/rasn1
77
79
  bug_tracker_uri: https://github.com/sdaubert/rasn1/issues
78
80
  documentation_uri: https://www.rubydoc.info/gems/rasn1
79
- post_install_message:
81
+ post_install_message:
80
82
  rdoc_options:
81
83
  - "--title"
82
84
  - RASN1 - A pure ruby ASN.1 library
@@ -90,15 +92,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
92
  requirements:
91
93
  - - ">="
92
94
  - !ruby/object:Gem::Version
93
- version: 2.5.0
95
+ version: 2.7.0
94
96
  required_rubygems_version: !ruby/object:Gem::Requirement
95
97
  requirements:
96
98
  - - ">="
97
99
  - !ruby/object:Gem::Version
98
100
  version: '0'
99
101
  requirements: []
100
- rubygems_version: 3.2.5
101
- signing_key:
102
+ rubygems_version: 3.3.15
103
+ signing_key:
102
104
  specification_version: 4
103
105
  summary: Ruby ASN.1 library
104
106
  test_files: []