rasn1 0.6.8 → 0.9.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,91 +1,89 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 Bit String
5
6
  # @author Sylvain Daubert
6
7
  class BitString < Primitive
7
- TAG = 0x03
8
+ # BitString id value
9
+ ID = 3
8
10
 
9
11
  # @param [Integer] bit_length
10
12
  # @return [Integer]
11
13
  attr_writer :bit_length
12
14
 
13
- # @overload initialize(options={})
14
- # @param [Hash] options
15
- # @option options [Object] :bit_length default bit_length value. Should be
16
- # present if +:default+ is set
17
- # @overload initialize(value, options={})
18
- # @param [Object] value value to set for this ASN.1 object
19
- # @param [Hash] options
20
- # @option options [Object] :bit_length default bit_length value. Should be
21
- # present if +:default+ is set
15
+ # @param [Hash] options
16
+ # @option options [Object] :bit_length default bit_length value. Should be
17
+ # present if +:default+ is set
22
18
  # @see Base#initialize common options to all ASN.1 types
23
- def initialize(value_or_options={}, options={})
19
+ def initialize(options={})
24
20
  super
25
- opts = value_or_options.is_a?(Hash) ? value_or_options : options
26
21
  if @default
27
- if opts[:bit_length].nil?
28
- raise ASN1Error, "TAG #@name: default bit length is not defined"
29
- end
30
- @default_bit_length = opts[:bit_length]
22
+ raise ASN1Error, "#{@name}: default bit length is not defined" if @options[:bit_length].nil?
23
+
24
+ @default_bit_length = @options[:bit_length]
31
25
  end
32
- @bit_length = opts[:bit_length]
26
+ @bit_length = @options[:bit_length]
33
27
  end
34
28
 
35
29
  # Get bit length
36
30
  def bit_length
37
- if @value.nil?
38
- @default_bit_length
39
- else
31
+ if value?
40
32
  @bit_length
33
+ else
34
+ @default_bit_length
41
35
  end
42
36
  end
43
37
 
44
38
  # @param [Integer] level
45
39
  # @return [String]
46
40
  def inspect(level=0)
47
- str = ''
48
- str << ' ' * level if level > 0
49
- str << "#{name} " unless @name.nil?
50
- str << "#{type}: #{value.inspect} (bit length: #{bit_length})"
41
+ str = common_inspect(level)
42
+ str << " #{value.inspect} (bit length: #{bit_length})"
51
43
  end
52
44
 
53
45
  private
54
46
 
55
- def build_tag?
56
- !(!@default.nil? and (@value.nil? or @value == @default and
57
- @bit_length == @default_bit_length)) and
58
- !(optional? and @value.nil?)
47
+ def can_build?
48
+ !(!@default.nil? && (!value? || (@value == @default) &&
49
+ (@bit_length == @default_bit_length))) &&
50
+ !(optional? && !value?)
59
51
  end
60
52
 
61
53
  def value_to_der
62
- raise ASN1Error, "TAG #@name: bit length is not set" if bit_length.nil?
54
+ raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
63
55
 
64
- while @value.length * 8 < @bit_length
65
- @value << "\x00"
66
- end
67
- @value.force_encoding('BINARY')
68
-
69
- if @value.length * 8 > @bit_length
70
- max_len = @bit_length / 8 + (@bit_length % 8 > 0 ? 1 : 0)
71
- @value = @value[0, max_len]
72
- end
56
+ value = generate_value_with_correct_length
73
57
 
74
- unused = @value.length * 8 - @bit_length
75
- der = [unused, @value].pack('CA*')
58
+ unused = value.length * 8 - @bit_length.to_i
59
+ der = [unused, value].pack('CA*')
76
60
 
77
- if unused > 0
78
- last_byte = @value[-1].unpack('C').first
61
+ if unused.positive?
62
+ last_byte = value[-1].to_s.unpack1('C').to_i
79
63
  last_byte &= (0xff >> unused) << unused
80
64
  der[-1] = [last_byte].pack('C')
81
65
  end
82
66
 
67
+ @value = value
68
+
83
69
  der
84
70
  end
85
71
 
86
- def der_to_value(der, ber:false)
87
- unused, @value = der.unpack('CA*')
88
- @bit_length = @value.length * 8 - unused
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')
76
+ return value unless value.length * 8 > @bit_length.to_i
77
+
78
+ max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
79
+ value[0, max_len].to_s
80
+ end
81
+
82
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
83
+ unused = der.unpack1('C').to_i
84
+ value = der[1..-1].to_s
85
+ @bit_length = value.length * 8 - unused
86
+ @value = value
89
87
  end
90
88
  end
91
89
  end
@@ -1,34 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 Boolean
5
6
  # @author Sylvain Daubert
6
7
  class Boolean < Primitive
7
- TAG = 0x01
8
+ # Boolean id value
9
+ ID = 0x01
10
+
11
+ # @private
12
+ DER_TRUE = 0xff
13
+ # @private
14
+ DER_FALSE = 0
15
+
16
+ # @return [false]
17
+ def void_value
18
+ false
19
+ end
8
20
 
9
21
  private
10
22
 
11
23
  def value_to_der
12
- [@value ? 0xff : 0x00].pack('C')
24
+ [@value ? DER_TRUE : DER_FALSE].pack('C')
13
25
  end
14
26
 
15
27
  def der_to_value(der, ber: false)
16
- unless der.size == 1
17
- raise ASN1Error, "tag #@name: BOOLEAN should have a length of 1"
18
- end
28
+ raise ASN1Error, "tag #{@name}: BOOLEAN should have a length of 1" unless der.size == 1
19
29
 
20
- bool = der.unpack('C').first
30
+ bool = der.unpack1('C')
21
31
  case bool
22
- when 0
32
+ when DER_FALSE
23
33
  @value = false
24
- when 0xff
34
+ when DER_TRUE
25
35
  @value = true
26
36
  else
27
- if ber
28
- @value = true
29
- else
30
- raise ASN1Error, "tag #@name: bad value 0x%02x for BOOLEAN" % bool
31
- end
37
+ raise ASN1Error, "tag #{@name}: bad value 0x%02x for BOOLEAN" % bool unless ber
38
+
39
+ @value = true
32
40
  end
33
41
  end
34
42
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # A ASN.1 CHOICE is a choice between different types.
5
6
  #
6
7
  # == Create a CHOICE
@@ -31,7 +32,6 @@ module RASN1
31
32
  # choice.chosen_value # => "abc"
32
33
  # @author Sylvain Daubert
33
34
  class Choice < Base
34
-
35
35
  # Chosen type
36
36
  # @return [Integer] index of type in choice value
37
37
  attr_accessor :chosen
@@ -41,7 +41,7 @@ module RASN1
41
41
  # @param [Object] value
42
42
  # @return [Object] value
43
43
  # @raise [ChoiceError] {#chosen} not set
44
- def set_chosen_value(value)
44
+ def set_chosen_value(value) # rubocop:disable Naming/AccessorMethodName
45
45
  check_chosen
46
46
  @value[@chosen].value = value
47
47
  end
@@ -79,7 +79,7 @@ module RASN1
79
79
  @value.each_with_index do |element, i|
80
80
  begin
81
81
  @chosen = i
82
- nb_bytes = element.parse!(der)
82
+ nb_bytes = element.parse!(der, ber: ber)
83
83
  parsed = true
84
84
  return nb_bytes
85
85
  rescue ASN1Error
@@ -87,19 +87,16 @@ module RASN1
87
87
  next
88
88
  end
89
89
  end
90
- raise ASN1Error, "CHOICE #@name: no type matching #{der.inspect}" unless parsed
90
+ raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless parsed
91
91
  end
92
92
 
93
93
  def inspect(level=0)
94
- str = ''
95
- str << ' ' * level if level > 0
96
- str << "#{name} " if name
97
- str << "#{type}:"
98
- if !defined? @chosen
99
- str << ' not chosen!'
100
- else
101
- str << "\n#{@value[@chosen].inspect(level+1)}"
102
- end
94
+ str = common_inspect(level)
95
+ str << if defined? @chosen
96
+ "\n#{@value[@chosen].inspect(level + 1)}"
97
+ else
98
+ ' not chosen!'
99
+ end
103
100
  end
104
101
 
105
102
  private
@@ -110,5 +107,3 @@ module RASN1
110
107
  end
111
108
  end
112
109
  end
113
-
114
-
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # @abstract This class SHOULD be used as base class for all ASN.1 primitive
5
6
  # types.
6
7
  # Base class for all ASN.1 constructed types
@@ -12,19 +13,16 @@ module RASN1
12
13
  def inspect(level=0)
13
14
  case @value
14
15
  when Array
15
- str = ''
16
- str << ' ' * level if level > 0
17
- str << "#{@name} " unless @name.nil?
18
- level = level.abs
19
- str << "#{type}:\n"
20
- level += 1
16
+ str = common_inspect(level)
17
+ str << "\n"
18
+ level = level.abs + 1
21
19
  @value.each do |item|
22
20
  case item
23
21
  when Base, Model
24
- next if item.optional? and item.value.nil?
25
22
  str << "#{item.inspect(level)}\n"
26
23
  else
27
- str << ' ' * level + item.inspect + "\n"
24
+ str << ' ' * level
25
+ str << "#{item.inspect}\n"
28
26
  end
29
27
  end
30
28
  str
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 Enumerated
5
6
  #
6
7
  # An enumerated type permits to assign names to integer values. It may be defined
@@ -21,23 +22,17 @@ module RASN1
21
22
  # @!attribute enum
22
23
  # @return [Hash]
23
24
 
24
- TAG = 0x0a
25
+ # Enumerated id value
26
+ ID = 0x0a
25
27
 
26
- # @overload initialize(options={})
27
- # @option options [Hash] :enum enumeration hash. Keys are names, and values
28
- # are integers. This key is mandatory.
29
- # @raise [EnumeratedError] +:enum+ key is not present
30
- # @raise [EnumeratedError] +:default+ value is unknown
31
- # @overload initialize(value, options={})
32
- # @param [Object] value value to set for this ASN.1 object
33
- # @option options [Hash] :enum enumeration hash. Keys are names, and values
34
- # are integers. This key is mandatory.
35
- # @raise [EnumeratedError] +:enum+ key is not present
36
- # @raise [EnumeratedError] +:default+ value is unknown
28
+ # @option options [Hash] :enum enumeration hash. Keys are names, and values
29
+ # are integers. This key is mandatory.
30
+ # @raise [EnumeratedError] +:enum+ key is not present
31
+ # @raise [EnumeratedError] +:default+ value is unknown
37
32
  # @see Base#initialize common options to all ASN.1 types
38
- def initialize(value_or_options={}, options={})
33
+ def initialize(options={})
39
34
  super
40
- raise EnumeratedError, 'no enumeration given' if @enum.nil?
35
+ raise EnumeratedError, 'no enumeration given' if @enum.empty?
41
36
  end
42
37
 
43
38
  # @return [Hash]
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 GeneralizedTime
5
6
  #
6
7
  # +{#value} of a {GeneralizedTime} should be a ruby Time.
@@ -20,7 +21,8 @@ module RASN1
20
21
  # second are supported.
21
22
  # @author Sylvain Daubert
22
23
  class GeneralizedTime < Primitive
23
- TAG = 24
24
+ # GeneralizedTime id value
25
+ ID = 24
24
26
 
25
27
  # Get ASN.1 type
26
28
  # @return [String]
@@ -28,83 +30,122 @@ module RASN1
28
30
  'GeneralizedTime'
29
31
  end
30
32
 
33
+ # @return [DateTime]
34
+ def void_value
35
+ DateTime.now
36
+ end
37
+
31
38
  private
32
-
39
+
33
40
  def value_to_der
34
- if @value.nsec > 0
41
+ if @value.nsec.positive?
35
42
  der = @value.getutc.strftime('%Y%m%d%H%M%S.%9NZ')
36
43
  der.sub(/0+Z/, 'Z')
37
44
  else
38
45
  @value.getutc.strftime('%Y%m%d%H%M%SZ')
39
46
  end
40
47
  end
41
-
42
- def der_to_value(der, ber: false)
48
+
49
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
43
50
  date_hour, fraction = der.split('.')
51
+ date_hour = date_hour.to_s
52
+ fraction = fraction.to_s
53
+
54
+ if fraction.empty?
55
+ value_when_fraction_empty(date_hour)
56
+ elsif fraction[-1] == 'Z'
57
+ value_when_fraction_ends_with_z(date_hour, fraction)
58
+ else
59
+ value_on_others_cases(date_hour, fraction)
60
+ end
61
+ end
62
+
63
+ def value_when_fraction_empty(date_hour)
44
64
  utc_offset_forced = false
45
- if fraction.nil?
46
- if date_hour[-1] != 'Z' and date_hour !~ /[+-]\d+$/
47
- # If not UTC, have to add offset with UTC to force
48
- # DateTime#strptime to generate a local time. But this difference
49
- # may be errored because of DST.
50
- date_hour << Time.now.strftime('%z')
51
- utc_offset_forced = true
52
- end
65
+
66
+ if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
67
+ # 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.
70
+ date_hour << Time.now.strftime('%z')
71
+ utc_offset_forced = true
72
+ end
73
+
74
+ value_from(date_hour)
75
+ fix_dst if utc_offset_forced
76
+ end
77
+
78
+ def value_when_fraction_ends_with_z(date_hour, fraction)
79
+ fraction = fraction[0...-1]
80
+ date_hour << 'Z'
81
+ frac_base = value_from(date_hour)
82
+ fix_value(fraction, frac_base)
83
+ end
84
+
85
+ def value_on_others_cases(date_hour, fraction)
86
+ match = fraction.match(/(\d+)([+-]\d+)/)
87
+ if match
88
+ # fraction contains fraction and timezone info. Split them
89
+ fraction = match[1]
90
+ date_hour << match[2]
53
91
  else
54
- if fraction[-1] == 'Z'
55
- fraction = fraction[0...-1]
56
- date_hour << 'Z'
57
- else
58
- match = fraction.match(/(\d+)([+-]\d+)/)
59
- if match
60
- # fraction contains fraction and timezone info. Split them
61
- fraction = match[1]
62
- date_hour << match[2]
63
- else
64
- # fraction only contains fraction.
65
- # Have to add offset with UTC to force DateTime#strptime to
66
- # generate a local time. But this difference may be errored
67
- # because of DST.
68
- date_hour << Time.now.strftime('%z')
69
- utc_offset_forced = true
70
- end
71
- end
92
+ # 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.
96
+ date_hour << Time.now.strftime('%z')
97
+ utc_offset_forced = true
72
98
  end
73
- format = case date_hour.size
74
- when 11
75
- frac_base = 60*60
76
- '%Y%m%d%HZ'
77
- when 13
78
- frac_base = 60
79
- '%Y%m%d%H%MZ'
80
- when 15
81
- if date_hour[-1] == 'Z'
82
- frac_base = 1
83
- '%Y%m%d%H%M%SZ'
84
- else
85
- frac_base = 60 * 60
86
- '%Y%m%d%H%z'
87
- end
88
- when 17
89
- frac_base = 60
90
- '%Y%m%d%H%M%z'
91
- when 19
92
- frac_base = 1
93
- '%Y%m%d%H%M%S%z'
94
- else
95
- prefix = @name.nil? ? type : "tag #{@name}"
96
- raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
97
- end
99
+
100
+ frac_base = value_from(date_hour)
101
+ fix_dst if utc_offset_forced
102
+ fix_value(fraction, frac_base)
103
+ end
104
+
105
+ def value_from(date_hour)
106
+ format, frac_base = strformat(date_hour)
98
107
  @value = DateTime.strptime(date_hour, format).to_time
99
- # local time format.
100
- # Check DST. There may be a shift of one hour...
101
- if utc_offset_forced
102
- compare_time = Time.new(*@value.to_a[0..5].reverse)
103
- if compare_time.utc_offset != @value.utc_offset
104
- @value = compare_time
108
+ frac_base
109
+ end
110
+
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
+ def fix_value(fraction, frac_base)
118
+ @value += ".#{fraction}".to_r * frac_base unless fraction.nil?
119
+ end
120
+
121
+ def strformat(date_hour) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
122
+ case date_hour.size
123
+ 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'
129
+ when 15
130
+ if date_hour[-1] == 'Z'
131
+ frac_base = 1
132
+ format = '%Y%m%d%H%M%SZ'
133
+ else
134
+ frac_base = 60 * 60
135
+ format = '%Y%m%d%H%z'
105
136
  end
137
+ when 17
138
+ frac_base = 60
139
+ format = '%Y%m%d%H%M%z'
140
+ when 19
141
+ frac_base = 1
142
+ format = '%Y%m%d%H%M%S%z'
143
+ else
144
+ prefix = @name.nil? ? type : "tag #{@name}"
145
+ raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
106
146
  end
107
- @value += ".#{fraction}".to_r * frac_base unless fraction.nil?
147
+
148
+ [format, frac_base]
108
149
  end
109
150
  end
110
151
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 IA5 String
5
6
  # @author Sylvain Daubert
6
7
  class IA5String < OctetString
7
- TAG = 22
8
+ # IA5String id value
9
+ ID = 22
8
10
 
9
11
  # Get ASN.1 type
10
12
  # @return [String]
@@ -13,15 +15,15 @@ module RASN1
13
15
  end
14
16
 
15
17
  private
16
-
18
+
17
19
  def value_to_der
18
20
  @value.to_s.force_encoding('US-ASCII').force_encoding('BINARY')
19
21
  end
20
22
 
21
- def der_to_value(der, ber:false)
23
+ def der_to_value(der, ber: false)
22
24
  super
23
- @value.force_encoding('US-ASCII')
25
+ @value.to_s.force_encoding('US-ASCII')
24
26
  end
25
- end
27
+ end
26
28
  end
27
29
  end