rasn1 0.10.0 → 0.12.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.
@@ -69,6 +69,8 @@ module RASN1
69
69
  attr_reader :asn1_class
70
70
  # @return [Object,nil] default value, if defined
71
71
  attr_reader :default
72
+ # @return [Hash[Symbol, Object]]
73
+ attr_reader :options
72
74
 
73
75
  # Get ASN.1 type
74
76
  # @return [String]
@@ -95,6 +97,13 @@ module RASN1
95
97
  obj
96
98
  end
97
99
 
100
+ # Say if a type is constrained.
101
+ # Always return +false+ for predefined types
102
+ # @return [Booleran]
103
+ def self.constrained?
104
+ false
105
+ end
106
+
98
107
  # @param [Hash] options
99
108
  # @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
100
109
  # If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
@@ -109,9 +118,14 @@ module RASN1
109
118
  # @option options [::String] :name name for this node
110
119
  def initialize(options={})
111
120
  @constructed = nil
112
- set_options options
121
+ set_value(options.delete(:value))
122
+ self.options = options
123
+ specific_initializer
113
124
  end
114
125
 
126
+ # @abstract To help subclass initialize itself. Default implementation do nothing.
127
+ def specific_initializer; end
128
+
115
129
  # Used by +#dup+ and +#clone+. Deep copy @value and @default.
116
130
  def initialize_copy(_other)
117
131
  @value = @value.dup
@@ -242,11 +256,38 @@ module RASN1
242
256
  (other.class == self.class) && (other.to_der == self.to_der)
243
257
  end
244
258
 
259
+ # Set options to this object
260
+ # @param [Hash] options
261
+ # @return [void]
262
+ # @since 0.12
263
+ def options=(options)
264
+ set_class options[:class]
265
+ set_optional options[:optional]
266
+ set_default options[:default]
267
+ set_tag options
268
+ @name = options[:name]
269
+ @options = options
270
+ end
271
+
272
+ # Say if a value is set
273
+ # @return [Boolean]
274
+ # @since 0.12.0
275
+ def value?
276
+ !@no_value
277
+ end
278
+
279
+ # Say if DER can be built (not default value, not optional without value, has a value)
280
+ # @return [Boolean]
281
+ # @since 0.12.0
282
+ def can_build?
283
+ value? && (@default.nil? || (@value != @default))
284
+ end
285
+
245
286
  private
246
287
 
247
288
  def pc_bit
248
289
  if @constructed.nil?
249
- self.class::ASN1_PC
290
+ self.class.const_get(:ASN1_PC)
250
291
  elsif @constructed # true
251
292
  Constructed::ASN1_PC
252
293
  else # false
@@ -285,16 +326,6 @@ module RASN1
285
326
  @value = der
286
327
  end
287
328
 
288
- def set_options(options) # rubocop:disable Naming/AccessorMethodName
289
- set_class options[:class]
290
- set_optional options[:optional]
291
- set_default options[:default]
292
- set_tag options
293
- set_value options[:value]
294
- @name = options[:name]
295
- @options = options
296
- end
297
-
298
329
  def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
299
330
  case asn1_class
300
331
  when nil
@@ -319,17 +350,15 @@ module RASN1
319
350
  # handle undocumented option +:tag_value+, used internally by
320
351
  # {RASN1.parse} to parse non-universal class tags.
321
352
  def set_tag(options) # rubocop:disable Naming/AccessorMethodName
353
+ @constructed = options[:constructed]
322
354
  if options[:explicit]
323
355
  @tag = :explicit
324
356
  @id_value = options[:explicit]
325
- @constructed = options[:constructed]
326
357
  elsif options[:implicit]
327
358
  @tag = :implicit
328
359
  @id_value = options[:implicit]
329
- @constructed = options[:constructed]
330
360
  elsif options[:tag_value]
331
361
  @id_value = options[:tag_value]
332
- @constructed = options[:constructed]
333
362
  end
334
363
 
335
364
  @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
@@ -346,15 +375,6 @@ module RASN1
346
375
  value
347
376
  end
348
377
 
349
- def value?
350
- !@no_value
351
- end
352
-
353
- def can_build?
354
- (@default.nil? || (value? && (@value != @default))) &&
355
- (!optional? || value?)
356
- end
357
-
358
378
  def build
359
379
  if can_build?
360
380
  if explicit?
@@ -371,9 +391,9 @@ module RASN1
371
391
  end
372
392
 
373
393
  def id_value
374
- return @id_value if defined? @id_value
394
+ return @id_value if defined?(@id_value) && !@id_value.nil?
375
395
 
376
- self.class::ID
396
+ self.class.const_get(:ID)
377
397
  end
378
398
 
379
399
  def encode_identifier_octets
@@ -455,7 +475,7 @@ module RASN1
455
475
  def raise_on_indefinite_length(ber)
456
476
  if primitive?
457
477
  raise ASN1Error, "malformed #{type}: indefinite length " \
458
- 'forbidden for primitive types'
478
+ 'forbidden for primitive types'
459
479
  elsif ber
460
480
  raise NotImplementedError, 'indefinite length not supported'
461
481
  else
@@ -42,14 +42,14 @@ module RASN1
42
42
  str << " #{value.inspect} (bit length: #{bit_length})"
43
43
  end
44
44
 
45
- private
46
-
47
45
  def can_build?
48
- !(!@default.nil? && (!value? || (@value == @default) &&
49
- (@bit_length == @default_bit_length))) &&
50
- !(optional? && !value?)
46
+ super || (!@default.nil? && (@bit_length != @default_bit_length))
51
47
  end
52
48
 
49
+ private
50
+
51
+ # @author Sylvain Daubert
52
+ # @author adfoster-r7
53
53
  def value_to_der
54
54
  raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
55
55
 
@@ -70,9 +70,8 @@ module RASN1
70
70
  end
71
71
 
72
72
  def generate_value_with_correct_length
73
- value = @value || ''
74
- value << "\x00" while value.length * 8 < @bit_length.to_i
75
- value.force_encoding('BINARY')
73
+ value = (@value || '').dup.force_encoding('BINARY')
74
+ value << "\x00".b while value.length * 8 < @bit_length.to_i
76
75
  return value unless value.length * 8 > @bit_length.to_i
77
76
 
78
77
  max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
@@ -85,6 +84,10 @@ module RASN1
85
84
  @bit_length = value.length * 8 - unused
86
85
  @value = value
87
86
  end
87
+
88
+ def explicit_type
89
+ self.class.new(value: @value, bit_length: @bit_length)
90
+ end
88
91
  end
89
92
  end
90
93
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 BmpString
6
+ # @since 0.12.0
7
+ # @author adfoster-r7
8
+ class BmpString < OctetString
9
+ # BmpString id value
10
+ ID = 30
11
+
12
+ # Get ASN.1 type
13
+ # @return [String]
14
+ def self.type
15
+ 'BmpString'
16
+ end
17
+
18
+ private
19
+
20
+ def value_to_der
21
+ @value.to_s.dup.encode('UTF-16BE').b
22
+ end
23
+
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ @value = der.to_s.dup.force_encoding('UTF-16BE')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -77,15 +77,13 @@ module RASN1
77
77
  def parse!(der, ber: false)
78
78
  parsed = false
79
79
  @value.each_with_index do |element, i|
80
- begin
81
- @chosen = i
82
- nb_bytes = element.parse!(der, ber: ber)
83
- parsed = true
84
- return nb_bytes
85
- rescue ASN1Error
86
- @chosen = nil
87
- next
88
- end
80
+ @chosen = i
81
+ nb_bytes = element.parse!(der, ber: ber)
82
+ parsed = true
83
+ return nb_bytes
84
+ rescue ASN1Error
85
+ @chosen = nil
86
+ next
89
87
  end
90
88
  raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless parsed
91
89
  end
@@ -102,7 +100,7 @@ module RASN1
102
100
  private
103
101
 
104
102
  def check_chosen
105
- raise ChoiceError if !defined?(@chosen) || @chosen.nil?
103
+ raise ChoiceError.new(self) if !defined?(@chosen) || @chosen.nil?
106
104
  end
107
105
  end
108
106
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # Mixin to had constraints on a RASN1 type.
6
+ # Should not be used directly but through {Types.define_type}.
7
+ # @version 0.11.0
8
+ # @author Sylvain Daubert
9
+ module Constrained
10
+ module ClassMethods
11
+ # Setter for constraint
12
+ # @param [Proc,nil] constraint
13
+ # @return [Proc,nil]
14
+ def constraint=(constraint)
15
+ @constraint = constraint
16
+ end
17
+
18
+ # Check if a constraint is really defined
19
+ # @return [Boolean]
20
+ def constrained?
21
+ @constraint.is_a?(Proc)
22
+ end
23
+
24
+ # Check constraint, if defined
25
+ # @param [Object] value the value of the type to check
26
+ # @raise [ConstraintError] constraint is not verified
27
+ def check_constraint(value)
28
+ return unless constrained?
29
+ raise ConstraintError.new(self) unless @constraint.call(value)
30
+ end
31
+ end
32
+
33
+ class << self
34
+ attr_reader :constraint
35
+
36
+ def included(base)
37
+ base.extend ClassMethods
38
+ end
39
+ end
40
+
41
+ # Redefined +#value=+ to check constraint before assigning +val+
42
+ # @see Types::Base#value=
43
+ # @raise [ConstraintError] constraint is not verified
44
+ def value=(val)
45
+ self.class.check_constraint(val)
46
+ super
47
+ end
48
+
49
+ def der_to_value(der, ber: false)
50
+ super
51
+ self.class.check_constraint(@value)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -10,6 +10,22 @@ module RASN1
10
10
  # Constructed value
11
11
  ASN1_PC = 0x20
12
12
 
13
+ # @return [Boolean]
14
+ # @since 0.12.0
15
+ # @see Base#can_build?
16
+ def can_build? # rubocop:disable Metrics/CyclomaticComplexity
17
+ return super unless @value.is_a?(Array) && optional?
18
+ return false unless super
19
+
20
+ @value.any? do |el|
21
+ el.can_build? && (
22
+ el.primitive? ||
23
+ (el.value.respond_to?(:empty?) ? !el.value.empty? : !el.value.nil?))
24
+ end
25
+ end
26
+
27
+ # @param [::Integer] level (default: 0)
28
+ # @return [String]
13
29
  def inspect(level=0)
14
30
  case @value
15
31
  when Array
@@ -18,7 +34,7 @@ module RASN1
18
34
  level = level.abs + 1
19
35
  @value.each do |item|
20
36
  case item
21
- when Base, Model
37
+ when Base, Model, Wrapper
22
38
  str << "#{item.inspect(level)}\n"
23
39
  else
24
40
  str << ' ' * level
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'strptime'
4
+
3
5
  module RASN1
4
6
  module Types
5
7
  # ASN.1 GeneralizedTime
@@ -24,25 +26,33 @@ module RASN1
24
26
  # GeneralizedTime id value
25
27
  ID = 24
26
28
 
29
+ # @private
30
+ HOUR_TO_SEC = 3600
31
+ # @private
32
+ MINUTE_TO_SEC = 60
33
+ # @private
34
+ SECOND_TO_SEC = 1
35
+
27
36
  # Get ASN.1 type
28
37
  # @return [String]
29
38
  def self.type
30
39
  'GeneralizedTime'
31
40
  end
32
41
 
33
- # @return [DateTime]
42
+ # @return [Time]
34
43
  def void_value
35
- DateTime.now
44
+ Time.now
36
45
  end
37
46
 
38
47
  private
39
48
 
40
49
  def value_to_der
41
- if @value.nsec.positive?
42
- der = @value.getutc.strftime('%Y%m%d%H%M%S.%9NZ')
50
+ utc_value = @value.getutc
51
+ if utc_value.nsec.positive?
52
+ der = utc_value.strftime('%Y%m%d%H%M%S.%9NZ')
43
53
  der.sub(/0+Z/, 'Z')
44
54
  else
45
- @value.getutc.strftime('%Y%m%d%H%M%SZ')
55
+ utc_value.strftime('%Y%m%d%H%M%SZ')
46
56
  end
47
57
  end
48
58
 
@@ -61,18 +71,13 @@ module RASN1
61
71
  end
62
72
 
63
73
  def value_when_fraction_empty(date_hour)
64
- utc_offset_forced = false
65
-
66
74
  if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
67
75
  # If not UTC, have to add offset with UTC to force
68
- # DateTime#strptime to generate a local time. But this difference
69
- # may be errored because of DST.
76
+ # Strptime to generate a local time.
70
77
  date_hour << Time.now.strftime('%z')
71
- utc_offset_forced = true
72
78
  end
73
79
 
74
80
  value_from(date_hour)
75
- fix_dst if utc_offset_forced
76
81
  end
77
82
 
78
83
  def value_when_fraction_ends_with_z(date_hour, fraction)
@@ -90,62 +95,44 @@ module RASN1
90
95
  date_hour << match[2]
91
96
  else
92
97
  # fraction only contains fraction.
93
- # Have to add offset with UTC to force DateTime#strptime to
94
- # generate a local time. But this difference may be errored
95
- # because of DST.
98
+ # Have to add offset with UTC to force Strptime to
99
+ # generate a local time.
96
100
  date_hour << Time.now.strftime('%z')
97
- utc_offset_forced = true
98
101
  end
99
102
 
100
103
  frac_base = value_from(date_hour)
101
- fix_dst if utc_offset_forced
102
104
  fix_value(fraction, frac_base)
103
105
  end
104
106
 
105
107
  def value_from(date_hour)
106
108
  format, frac_base = strformat(date_hour)
107
- @value = DateTime.strptime(date_hour, format).to_time
109
+ @value = Strptime.new(format).exec(date_hour)
108
110
  frac_base
109
111
  end
110
112
 
111
- # Check DST. There may be a shift of one hour...
112
- def fix_dst
113
- compare_time = Time.new(*@value.to_a[0..5].reverse)
114
- @value = compare_time if compare_time.utc_offset != @value.utc_offset
115
- end
116
-
117
113
  def fix_value(fraction, frac_base)
118
- @value += ".#{fraction}".to_r * frac_base unless fraction.nil?
114
+ frac = ".#{fraction}".to_r * frac_base
115
+ @value = (@value + frac) unless fraction.nil?
119
116
  end
120
117
 
121
- def strformat(date_hour) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
118
+ def strformat(date_hour)
122
119
  case date_hour.size
123
120
  when 11
124
- frac_base = 60 * 60
125
- format = '%Y%m%d%HZ'
126
- when 13
127
- frac_base = 60
128
- format = '%Y%m%d%H%MZ'
121
+ ['%Y%m%d%H%z', HOUR_TO_SEC]
122
+ when 13, 17
123
+ ['%Y%m%d%H%M%z', MINUTE_TO_SEC]
129
124
  when 15
130
125
  if date_hour[-1] == 'Z'
131
- frac_base = 1
132
- format = '%Y%m%d%H%M%SZ'
126
+ ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
133
127
  else
134
- frac_base = 60 * 60
135
- format = '%Y%m%d%H%z'
128
+ ['%Y%m%d%H%z', HOUR_TO_SEC]
136
129
  end
137
- when 17
138
- frac_base = 60
139
- format = '%Y%m%d%H%M%z'
140
130
  when 19
141
- frac_base = 1
142
- format = '%Y%m%d%H%M%S%z'
131
+ ['%Y%m%d%H%M%S%z', SECOND_TO_SEC]
143
132
  else
144
133
  prefix = @name.nil? ? type : "tag #{@name}"
145
- raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
134
+ raise ASN1Error, "#{prefix}: unrecognized format: #{date_hour}"
146
135
  end
147
-
148
- [format, frac_base]
149
136
  end
150
137
  end
151
138
  end
@@ -17,12 +17,12 @@ module RASN1
17
17
  private
18
18
 
19
19
  def value_to_der
20
- @value.to_s.force_encoding('US-ASCII').force_encoding('BINARY')
20
+ @value.to_s.dup.force_encoding('US-ASCII').b
21
21
  end
22
22
 
23
23
  def der_to_value(der, ber: false)
24
24
  super
25
- @value.to_s.force_encoding('US-ASCII')
25
+ @value.to_s.dup.force_encoding('US-ASCII')
26
26
  end
27
27
  end
28
28
  end
@@ -133,8 +133,9 @@ module RASN1
133
133
  return if @enum.empty?
134
134
 
135
135
  int_value = @value
136
+ raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless @enum.value?(@value)
137
+
136
138
  @value = @enum.key(@value)
137
- raise EnumeratedError, "#{@name}: value #{int_value} not in enumeration" unless value?
138
139
  end
139
140
 
140
141
  def explicit_type
@@ -15,6 +15,10 @@ module RASN1
15
15
  str
16
16
  end
17
17
 
18
+ def can_build?
19
+ !optional?
20
+ end
21
+
18
22
  private
19
23
 
20
24
  def value_to_der
@@ -18,7 +18,7 @@ module RASN1
18
18
 
19
19
  def value_to_der
20
20
  check_characters
21
- @value.to_s.force_encoding('BINARY')
21
+ @value.to_s.b
22
22
  end
23
23
 
24
24
  def der_to_value(der, ber: false)
@@ -18,7 +18,7 @@ module RASN1
18
18
 
19
19
  def value_to_der
20
20
  check_characters
21
- @value.to_s.force_encoding('BINARY')
21
+ @value.to_s.b
22
22
  end
23
23
 
24
24
  def der_to_value(der, ber: false)
@@ -59,7 +59,6 @@ module RASN1
59
59
  Sequence.encoded_type
60
60
  end
61
61
 
62
- # @param [Symbol, String] name name for this tag in grammar
63
62
  # @param [Class, Base] of_type base type for sequence of
64
63
  # @see Base#initialize
65
64
  def initialize(of_type, options={})
@@ -105,6 +104,7 @@ module RASN1
105
104
  @value.length
106
105
  end
107
106
 
107
+ # @return [String]
108
108
  def inspect(level=0)
109
109
  str = common_inspect(level)
110
110
  str << "\n"
@@ -35,20 +35,16 @@ module RASN1
35
35
 
36
36
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
37
37
  format = case der.size
38
- when 11
39
- '%Y%m%d%H%MZ'
40
- when 13
41
- '%Y%m%d%H%M%SZ'
42
- when 15
38
+ when 11, 15
43
39
  '%Y%m%d%H%M%z'
44
- when 17
40
+ when 13, 17
45
41
  '%Y%m%d%H%M%S%z'
46
42
  else
47
43
  prefix = @name.nil? ? type : "tag #{@name}"
48
44
  raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
49
45
  end
50
46
  century = (Time.now.year / 100).to_s
51
- @value = DateTime.strptime(century + der, format).to_time
47
+ @value = Strptime.new(format).exec(century + der)
52
48
  end
53
49
  end
54
50
  end
@@ -17,12 +17,12 @@ module RASN1
17
17
  private
18
18
 
19
19
  def value_to_der
20
- @value.to_s.force_encoding('UTF-8').force_encoding('BINARY')
20
+ @value.to_s.dup.force_encoding('UTF-8').b
21
21
  end
22
22
 
23
23
  def der_to_value(der, ber: false)
24
24
  super
25
- @value = der.force_encoding('UTF-8')
25
+ @value = der.dup.force_encoding('UTF-8')
26
26
  end
27
27
  end
28
28
  end
data/lib/rasn1/types.rb CHANGED
@@ -4,18 +4,25 @@ module RASN1
4
4
  # This modules is a namesapce for all ASN.1 type classes.
5
5
  # @author Sylvain Daubert
6
6
  module Types
7
+ @primitives = []
8
+ @constructed = []
9
+
7
10
  # Give all primitive types
8
11
  # @return [Array<Types::Primitive>]
9
12
  def self.primitives
10
- @primitives ||= self.constants.map { |c| Types.const_get(c) }
11
- .select { |klass| klass < Primitive }
13
+ return @primitives unless @primitives.empty?
14
+
15
+ @primitives = self.constants.map { |c| Types.const_get(c) }
16
+ .select { |klass| klass < Primitive }
12
17
  end
13
18
 
14
19
  # Give all constructed types
15
20
  # @return [Array<Types::Constructed>]
16
21
  def self.constructed
17
- @constructed ||= self.constants.map { |c| Types.const_get(c) }
18
- .select { |klass| klass < Constructed }
22
+ return @constructed unless @constructed.empty?
23
+
24
+ @constructed = self.constants.map { |c| Types.const_get(c) }
25
+ .select { |klass| klass < Constructed }
19
26
  end
20
27
 
21
28
  # @private
@@ -67,12 +74,39 @@ module RASN1
67
74
  def self.generate_id2type_cache
68
75
  constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
69
76
  primitives = self.primitives - [Types::Enumerated]
70
- ary = (primitives + constructed).select { |type| type.const_defined? :ID }
71
- .map { |type| [type::ID, type] }
77
+ ary = (primitives + constructed).select { |type| type.const_defined?(:ID) }
78
+ .map { |type| [type.const_get(:ID), type] }
72
79
  @id2types = ary.to_h
73
80
  @id2types.default = Types::Base
74
81
  @id2types.freeze
75
82
  end
83
+
84
+ # Define a new ASN.1 type from a base one.
85
+ # This new type may have a constraint defines on it.
86
+ # @param [Symbol,String] name New type name. Must start with a capital letter.
87
+ # @param [Types::Base] from class from which inherits
88
+ # @param [Module] in_module module in which creates new type (default to {RASN1::Types})
89
+ # @return [Class] newly created class
90
+ # @since 0.11.0
91
+ # @since 0.12.0 in_module parameter
92
+ def self.define_type(name, from:, in_module: self, &block)
93
+ constraint = block.nil? ? nil : block.to_proc
94
+
95
+ new_klass = Class.new(from) do
96
+ include Constrained
97
+ end
98
+ new_klass.constraint = constraint
99
+
100
+ in_module.const_set(name, new_klass)
101
+ accel_name = name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
102
+ Model.define_type_accel(accel_name, new_klass)
103
+
104
+ # Empty type caches
105
+ @primitives = []
106
+ @constructed = []
107
+
108
+ new_klass
109
+ end
76
110
  end
77
111
  end
78
112
 
@@ -85,6 +119,7 @@ require_relative 'types/octet_string'
85
119
  require_relative 'types/null'
86
120
  require_relative 'types/object_id'
87
121
  require_relative 'types/enumerated'
122
+ require_relative 'types/bmp_string'
88
123
  require_relative 'types/utf8_string'
89
124
  require_relative 'types/numeric_string'
90
125
  require_relative 'types/printable_string'