rasn1 0.12.0 → 0.13.0

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