rasn1 0.6.6 → 0.8.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.
@@ -1,19 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 ANY: accepts any types
5
6
  #
6
- # If `any#value` is `nil`, `any` will be encoded as a {Null} object.
7
+ # If `any#value` is `nil` and Any object is not {#optional?}, `any` will be encoded as a {Null} object.
7
8
  # @author Sylvain Daubert
8
9
  class Any < Base
9
-
10
10
  # @return [String] DER-formated string
11
11
  def to_der
12
12
  case @value
13
13
  when Base, Model
14
14
  @value.to_der
15
15
  when nil
16
- Null.new.to_der
16
+ optional? ? '' : Null.new.to_der
17
17
  else
18
18
  @value.to_s
19
19
  end
@@ -25,24 +25,34 @@ module RASN1
25
25
  # @param [Boolean] ber if +true+, accept BER encoding
26
26
  # @return [Integer] total number of parsed bytes
27
27
  def parse!(der, ber: false)
28
- total_length, = get_data(der, ber)
28
+ if der.nil? || der.empty?
29
+ return 0 if optional?
30
+
31
+ raise ASN1Error, 'Expected ANY but get nothing'
32
+ end
33
+
34
+ id_size = Types.decode_identifier_octets(der).last
35
+ total_length, = get_data(der[id_size..-1], ber)
36
+ total_length += id_size
37
+
29
38
  @value = der[0, total_length]
39
+
30
40
  total_length
31
41
  end
32
42
 
33
43
  def inspect(level=0)
34
- str = ''
35
- str << ' ' * level if level > 0
44
+ lvl = level >= 0 ? level : 0
45
+ str = ' ' * lvl
36
46
  str << "#{@name} " unless @name.nil?
37
- if @value.nil?
38
- str << "(ANY) NULL"
39
- elsif @value.is_a?(OctetString) or @value.is_a?(BitString)
40
- str << "(ANY) #{@value.type}: #{value.value.inspect}"
41
- elsif @value.class < Base
42
- str << "(ANY) #{@value.type}: #{value.value}"
43
- else
44
- str << "ANY: #{value.to_s.inspect}"
45
- end
47
+ str << if @value.nil?
48
+ '(ANY) NULL'
49
+ elsif @value.is_a?(OctetString) || @value.is_a?(BitString)
50
+ "(ANY) #{@value.type}: #{value.value.inspect}"
51
+ elsif @value.class < Base
52
+ "(ANY) #{@value.type}: #{value.value}"
53
+ else
54
+ "ANY: #{value.to_s.inspect}"
55
+ end
46
56
  end
47
57
  end
48
58
  end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # @abstract This is base class for all ASN.1 types.
5
6
  #
6
7
  # Subclasses SHOULD define:
7
- # * a TAG constant defining ASN.1 tag number,
8
+ # * an ID constant defining ASN.1 BER/DER identification number,
8
9
  # * a private method {#value_to_der} converting its {#value} to DER,
9
10
  # * a private method {#der_to_value} converting DER into {#value}.
10
11
  #
@@ -12,15 +13,15 @@ module RASN1
12
13
  # An optional value may be defined using +:optional+ key from {#initialize}:
13
14
  # Integer.new(:int, optional: true)
14
15
  # An optional value implies:
15
- # * while parsing, if decoded tag is not optional expected tag, no {ASN1Error}
16
- # is raised, and parser tries net tag,
16
+ # * while parsing, if decoded ID is not optional expected ID, no {ASN1Error}
17
+ # is raised, and parser tries next field,
17
18
  # * while encoding, if {#value} is +nil+, this value is not encoded.
18
19
  # ==Define a default value
19
20
  # A default value may be defined using +:default+ key from {#initialize}:
20
21
  # Integer.new(:int, default: 0)
21
22
  # A default value implies:
22
- # * while parsing, if decoded tag is not expected tag, no {ASN1Error} is raised
23
- # and parser sets default value to this tag. Then parser tries nex tag,
23
+ # * while parsing, if decoded ID is not expected one, no {ASN1Error} is raised
24
+ # and parser sets default value to this ID. Then parser tries next field,
24
25
  # * while encoding, if {#value} is equal to default value, this value is not
25
26
  # encoded.
26
27
  # ==Define a tagged value
@@ -44,16 +45,20 @@ module RASN1
44
45
  # ctype_implicit = RASN1::Types::Integer.new(implicit: 0)
45
46
  # @author Sylvain Daubert
46
47
  class Base
47
- # Allowed ASN.1 tag classes
48
+ # Allowed ASN.1 classes
48
49
  CLASSES = {
49
- universal: 0x00,
50
- application: 0x40,
51
- context: 0x80,
52
- private: 0xc0
53
- }
50
+ universal: 0x00,
51
+ application: 0x40,
52
+ context: 0x80,
53
+ private: 0xc0
54
+ }.freeze
54
55
 
55
- # Maximum ASN.1 tag number
56
- MAX_TAG = 0x1e
56
+ # Binary mask to get class
57
+ # @private
58
+ CLASS_MASK = 0xc0
59
+
60
+ # @private first octet identifier for multi-octets identifier
61
+ MULTI_OCTETS_ID = 0x1f
57
62
 
58
63
  # Length value for indefinite length
59
64
  INDEFINITE_LENGTH = 0x80
@@ -70,13 +75,14 @@ module RASN1
70
75
  # Get ASN.1 type
71
76
  # @return [String]
72
77
  def self.type
73
- return @type if @type
78
+ return @type if defined? @type
79
+
74
80
  @type = self.to_s.gsub(/.*::/, '').gsub(/([a-z0-9])([A-Z])/, '\1 \2').upcase
75
81
  end
76
82
 
77
83
  # Get ASN.1 type used to encode this one
78
84
  # @return [String]
79
- def self.encode_type
85
+ def self.encoded_type
80
86
  type
81
87
  end
82
88
 
@@ -91,14 +97,13 @@ module RASN1
91
97
  obj
92
98
  end
93
99
 
94
-
95
100
  # @overload initialize(options={})
96
101
  # @param [Hash] options
97
- # @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+.
102
+ # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
98
103
  # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
99
104
  # @option options [::Boolean] :optional define this tag as optional. Default
100
105
  # is +false+
101
- # @option options [Object] :default default value for DEFAULT tag
106
+ # @option options [Object] :default default value (ASN.1 DEFAULT)
102
107
  # @option options [Object] :value value to set
103
108
  # @option options [::Integer] :implicit define an IMPLICIT tagged type
104
109
  # @option options [::Integer] :explicit define an EXPLICIT tagged type
@@ -108,11 +113,11 @@ module RASN1
108
113
  # @overload initialize(value, options={})
109
114
  # @param [Object] value value to set for this ASN.1 object
110
115
  # @param [Hash] options
111
- # @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+.
116
+ # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
112
117
  # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
113
- # @option options [::Boolean] :optional define this tag as optional. Default
118
+ # @option options [::Boolean] :optional define this value as optional. Default
114
119
  # is +false+
115
- # @option options [Object] :default default value for DEFAULT tag
120
+ # @option options [Object] :default default value (ASN.1 DEFAULT)
116
121
  # @option options [::Integer] :implicit define an IMPLICIT tagged type
117
122
  # @option options [::Integer] :explicit define an EXPLICIT tagged type
118
123
  # @option options [::Boolean] :constructed if +true+, set type as constructed.
@@ -128,20 +133,10 @@ module RASN1
128
133
  end
129
134
  end
130
135
 
131
- # Used by +#dup+ and +#clone+. Deep copy @value.
132
- def initialize_copy(other)
133
- @value = case
134
- when NilClass, TrueClass, FalseClass, Integer
135
- @value
136
- else
137
- @value.dup
138
- end
139
- @default = case
140
- when NilClass, TrueClass, FalseClass, Integer
141
- @default
142
- else
143
- @default.dup
144
- end
136
+ # Used by +#dup+ and +#clone+. Deep copy @value and @default.
137
+ def initialize_copy(_other)
138
+ @value = @value.dup
139
+ @default = @default.dup
145
140
  end
146
141
 
147
142
  # Get value or default value
@@ -168,21 +163,21 @@ module RASN1
168
163
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
169
164
  # if explicit, else +false+
170
165
  def explicit?
171
- @tag.nil? ? @tag : @tag == :explicit
166
+ !defined?(@tag) ? nil : @tag == :explicit
172
167
  end
173
168
 
174
169
  # Say if a tagged type is implicit
175
170
  # @return [::Boolean,nil] return +nil+ if not tagged, return +true+
176
171
  # if implicit, else +false+
177
172
  def implicit?
178
- @tag.nil? ? @tag : @tag == :implicit
173
+ !defined?(@tag) ? nil : @tag == :implicit
179
174
  end
180
175
 
181
176
  # @abstract This method SHOULD be partly implemented by subclasses, which
182
177
  # SHOULD respond to +#value_to_der+.
183
178
  # @return [String] DER-formated string
184
179
  def to_der
185
- build_tag
180
+ build
186
181
  end
187
182
 
188
183
  # @return [::Boolean] +true+ if this is a primitive type
@@ -192,7 +187,7 @@ module RASN1
192
187
 
193
188
  # @return [::Boolean] +true+ if this is a constructed type
194
189
  def constructed?
195
- !!((self.class < Constructed) || @constructed)
190
+ (self.class < Constructed) || !!@constructed
196
191
  end
197
192
 
198
193
  # Get ASN.1 type
@@ -201,17 +196,10 @@ module RASN1
201
196
  self.class.type
202
197
  end
203
198
 
204
- # Get tag value
199
+ # Get identifier value
205
200
  # @return [Integer]
206
- def tag
207
- pc = if @constructed.nil?
208
- self.class::ASN1_PC
209
- elsif @constructed # true
210
- Constructed::ASN1_PC
211
- else # false
212
- 0
213
- end
214
- (@tag_value || self.class::TAG) | CLASSES[@asn1_class] | pc
201
+ def id
202
+ id_value
215
203
  end
216
204
 
217
205
  # @abstract This method SHOULD be partly implemented by subclasses to parse
@@ -222,11 +210,13 @@ module RASN1
222
210
  # @return [Integer] total number of parsed bytes
223
211
  # @raise [ASN1Error] error on parsing
224
212
  def parse!(der, ber: false)
225
- return 0 unless check_tag(der)
213
+ return 0 unless check_id(der)
226
214
 
227
- total_length, data = get_data(der, ber)
215
+ id_size = Types.decode_identifier_octets(der).last
216
+ total_length, data = get_data(der[id_size..-1], ber)
217
+ total_length += id_size
228
218
  if explicit?
229
- # Delegate to #explicit type to generate sub-tag
219
+ # Delegate to #explicit type to generate sub-value
230
220
  type = explicit_type
231
221
  type.value = @value
232
222
  type.parse!(data)
@@ -247,11 +237,9 @@ module RASN1
247
237
  # @param [Integer] level
248
238
  # @return [String]
249
239
  def inspect(level=0)
250
- str = ''
251
- str << ' ' * level if level > 0
252
- str << "#{@name} " unless @name.nil?
253
- str << "#{type}: #{value.inspect}"
254
- str << " OPTIONAL" if optional?
240
+ str = common_inspect(level)
241
+ str << " #{value.inspect}"
242
+ str << ' OPTIONAL' if optional?
255
243
  str << " DEFAULT #{@default}" unless @default.nil?
256
244
  str
257
245
  end
@@ -265,6 +253,23 @@ module RASN1
265
253
 
266
254
  private
267
255
 
256
+ def pc_bit
257
+ if @constructed.nil?
258
+ self.class::ASN1_PC
259
+ elsif @constructed # true
260
+ Constructed::ASN1_PC
261
+ else # false
262
+ Primitive::ASN1_PC
263
+ end
264
+ end
265
+
266
+ def common_inspect(level)
267
+ lvl = level >= 0 ? level : 0
268
+ str = ' ' * lvl
269
+ str << "#{@name} " unless @name.nil?
270
+ str << "#{type}:"
271
+ end
272
+
268
273
  def value_to_der
269
274
  case @value
270
275
  when Base
@@ -274,7 +279,7 @@ module RASN1
274
279
  end
275
280
  end
276
281
 
277
- def der_to_value(der, ber:false)
282
+ def der_to_value(der, ber: false)
278
283
  @value = der
279
284
  end
280
285
 
@@ -285,6 +290,7 @@ module RASN1
285
290
  set_tag options
286
291
  @value = options[:value]
287
292
  @name = options[:name]
293
+ @options = options
288
294
  end
289
295
 
290
296
  def set_class(asn1_class)
@@ -292,11 +298,9 @@ module RASN1
292
298
  when nil
293
299
  @asn1_class = :universal
294
300
  when Symbol
295
- if CLASSES.keys.include? asn1_class
296
- @asn1_class = asn1_class
297
- else
298
- raise ClassError
299
- end
301
+ raise ClassError unless CLASSES.key? asn1_class
302
+
303
+ @asn1_class = asn1_class
300
304
  else
301
305
  raise ClassError
302
306
  end
@@ -315,27 +319,27 @@ module RASN1
315
319
  def set_tag(options)
316
320
  if options[:explicit]
317
321
  @tag = :explicit
318
- @tag_value = options[:explicit]
322
+ @id_value = options[:explicit]
319
323
  @constructed = options[:constructed]
320
324
  elsif options[:implicit]
321
325
  @tag = :implicit
322
- @tag_value = options[:implicit]
326
+ @id_value = options[:implicit]
323
327
  @constructed = options[:constructed]
324
328
  elsif options[:tag_value]
325
- @tag_value = options[:tag_value]
329
+ @id_value = options[:tag_value]
326
330
  @constructed = options[:constructed]
327
331
  end
328
332
 
329
- @asn1_class = :context if @tag and @asn1_class == :universal
333
+ @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
330
334
  end
331
335
 
332
- def build_tag?
333
- !(!@default.nil? and (@value.nil? or @value == @default)) and
334
- !(optional? and @value.nil?)
336
+ def can_build?
337
+ !(!@default.nil? && (@value.nil? || (@value == @default))) &&
338
+ !(optional? && @value.nil?)
335
339
  end
336
340
 
337
- def build_tag
338
- if build_tag?
341
+ def build
342
+ if can_build?
339
343
  if explicit?
340
344
  v = explicit_type
341
345
  v.value = @value
@@ -343,29 +347,52 @@ module RASN1
343
347
  else
344
348
  encoded_value = value_to_der
345
349
  end
346
- encode_tag << encode_size(encoded_value.size) << encoded_value
350
+ encode_identifier_octets << encode_size(encoded_value.size) << encoded_value
347
351
  else
348
352
  ''
349
353
  end
350
354
  end
351
355
 
352
- def encode_tag
353
- if (@tag_value || self.class::TAG) <= MAX_TAG
354
- [tag].pack('C')
356
+ def id_value
357
+ return @id_value if defined? @id_value
358
+
359
+ self.class::ID
360
+ end
361
+
362
+ def encode_identifier_octets
363
+ id2octets.pack('C*')
364
+ end
365
+
366
+ def id2octets
367
+ first_octet = CLASSES[asn1_class] | pc_bit
368
+ if id < MULTI_OCTETS_ID
369
+ [first_octet | id]
355
370
  else
356
- raise ASN1Error, 'multi-byte tag value are not supported'
371
+ [first_octet | MULTI_OCTETS_ID] + unsigned_to_chained_octets(id)
357
372
  end
358
373
  end
359
374
 
375
+ # Encode an unsigned integer on multiple octets.
376
+ # Value is encoded on bit 6-0 of each octet, bit 7(MSB) indicates wether
377
+ # further octets follow.
378
+ def unsigned_to_chained_octets(value)
379
+ ary = []
380
+ while value.positive?
381
+ ary.unshift(value & 0x7f | 0x80)
382
+ value >>= 7
383
+ end
384
+ ary[-1] &= 0x7f
385
+ ary
386
+ end
387
+
360
388
  def encode_size(size)
361
389
  if size >= INDEFINITE_LENGTH
362
390
  bytes = []
363
391
  while size > 255
364
- bytes << (size & 0xff)
392
+ bytes.unshift(size & 0xff)
365
393
  size >>= 8
366
394
  end
367
- bytes << size
368
- bytes.reverse!
395
+ bytes.unshift(size)
369
396
  bytes.unshift(INDEFINITE_LENGTH | bytes.size)
370
397
  bytes.pack('C*')
371
398
  else
@@ -373,15 +400,16 @@ module RASN1
373
400
  end
374
401
  end
375
402
 
376
- def check_tag(der)
377
- tag = der[0, 1]
378
- if tag != encode_tag
403
+ def check_id(der)
404
+ expected_id = encode_identifier_octets
405
+ real_id = der[0, expected_id.size]
406
+ if real_id != expected_id
379
407
  if optional?
380
408
  @value = nil
381
409
  elsif !@default.nil?
382
410
  @value = @default
383
411
  else
384
- raise_tag_error(tag)
412
+ raise_id_error(der)
385
413
  end
386
414
  false
387
415
  else
@@ -390,33 +418,29 @@ module RASN1
390
418
  end
391
419
 
392
420
  def get_data(der, ber)
393
- length = der[1, 1].unpack('C').first
421
+ length = der[0, 1].unpack1('C')
394
422
  length_length = 0
395
423
 
396
424
  if length == INDEFINITE_LENGTH
397
425
  if primitive?
398
- raise ASN1Error, "malformed #{type} TAG: indefinite length " \
399
- "forbidden for primitive types"
426
+ raise ASN1Error, "malformed #{type}: indefinite length " \
427
+ 'forbidden for primitive types'
428
+ elsif ber
429
+ raise NotImplementedError, 'indefinite length not supported'
400
430
  else
401
- if ber
402
- raise NotImplementedError, "TAG: indefinite length not " \
403
- "supported yet"
404
- else
405
- raise ASN1Error, "TAG: indefinite length forbidden in DER " \
406
- "encoding"
407
- end
431
+ raise ASN1Error, 'indefinite length forbidden in DER encoding'
408
432
  end
409
433
  elsif length < INDEFINITE_LENGTH
410
- data = der[2, length]
434
+ data = der[1, length]
411
435
  else
412
436
  length_length = length & 0x7f
413
- length = der[2, length_length].unpack('C*').
414
- reduce(0) { |len, b| (len << 8) | b }
415
- data = der[2 + length_length, length]
437
+ length = der[1, length_length].unpack('C*')
438
+ .reduce(0) { |len, b| (len << 8) | b }
439
+ data = der[1 + length_length, length]
416
440
  end
417
441
 
418
- total_length = 2 + length
419
- total_length += length_length if length_length > 0
442
+ total_length = 1 + length
443
+ total_length += length_length if length_length.positive?
420
444
 
421
445
  [total_length, data]
422
446
  end
@@ -425,31 +449,38 @@ module RASN1
425
449
  self.class.new
426
450
  end
427
451
 
428
- def raise_tag_error(tag)
429
- msg = "Expected #{self2name} but get #{tag2name(tag)}"
452
+ def raise_id_error(der)
453
+ msg = name.nil? ? +'' : +"#{name}: "
454
+ msg << "Expected #{self2name} but get #{der2name(der)}"
430
455
  raise ASN1Error, msg
431
456
  end
432
457
 
458
+ def class_from_numeric_id(id)
459
+ CLASSES.key(id & CLASS_MASK)
460
+ end
461
+
433
462
  def self2name
434
- name = CLASSES.key(tag & 0xc0).to_s.upcase
435
- name << " #{tag & Constructed::ASN1_PC > 0 ? 'CONSTRUCTED' : 'PRIMITIVE'}"
463
+ name = +"#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}"
436
464
  if implicit? || explicit?
437
- name << ' 0x%02X' % (tag & 0x1f)
465
+ name << ' 0x%X (0x%s)' % [id, bin2hex(encode_identifier_octets)]
438
466
  else
439
467
  name << ' ' << self.class.type
440
468
  end
441
469
  end
442
470
 
443
- def tag2name(tag)
444
- return 'no tag' if tag.nil? or tag.empty?
471
+ def der2name(der)
472
+ return 'no ID' if der.nil? || der.empty?
473
+
474
+ asn1_class, pc, id, id_size = Types.decode_identifier_octets(der)
475
+ name = +"#{asn1_class.to_s.upcase} #{pc.to_s.upcase}"
476
+ type = Types.constants.map { |c| Types.const_get(c) }
477
+ .select { |klass| klass < Primitive || klass < Constructed }
478
+ .find { |klass| klass::ID == id }
479
+ name << " #{type.nil? ? '0x%X (0x%s)' % [id, bin2hex(der[0...id_size])] : type.encoded_type}"
480
+ end
445
481
 
446
- itag = tag.unpack('C').first
447
- name = CLASSES.key(itag & 0xc0).to_s.upcase
448
- name << " #{itag & Constructed::ASN1_PC > 0 ? 'CONSTRUCTED' : 'PRIMITIVE'}"
449
- type = Types.constants.map { |c| Types.const_get(c) }.
450
- select { |klass| klass < Primitive || klass < Constructed }.
451
- find { |klass| klass::TAG == itag & 0x1f }
452
- name << " #{type.nil? ? "0x%02X" % (itag & 0x1f) : type.encode_type }"
482
+ def bin2hex(str)
483
+ str.unpack1('H*')
453
484
  end
454
485
  end
455
486
  end