rasn2 0.16.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +4 -0
  4. data/lib/rasn1/errors.rb +55 -0
  5. data/lib/rasn1/helpers/colorize.rb +76 -0
  6. data/lib/rasn1/model.rb +831 -0
  7. data/lib/rasn1/schema_parser.rb +470 -0
  8. data/lib/rasn1/tracer.rb +200 -0
  9. data/lib/rasn1/types/any.rb +98 -0
  10. data/lib/rasn1/types/base.rb +675 -0
  11. data/lib/rasn1/types/bit_string.rb +100 -0
  12. data/lib/rasn1/types/bmp_string.rb +22 -0
  13. data/lib/rasn1/types/boolean.rb +57 -0
  14. data/lib/rasn1/types/choice.rb +158 -0
  15. data/lib/rasn1/types/constrained.rb +51 -0
  16. data/lib/rasn1/types/constructed.rb +51 -0
  17. data/lib/rasn1/types/enumerated.rb +44 -0
  18. data/lib/rasn1/types/generalized_time.rb +156 -0
  19. data/lib/rasn1/types/ia5_string.rb +21 -0
  20. data/lib/rasn1/types/integer.rb +166 -0
  21. data/lib/rasn1/types/null.rb +43 -0
  22. data/lib/rasn1/types/numeric_string.rb +41 -0
  23. data/lib/rasn1/types/object_id.rb +64 -0
  24. data/lib/rasn1/types/octet_string.rb +52 -0
  25. data/lib/rasn1/types/primitive.rb +13 -0
  26. data/lib/rasn1/types/printable_string.rb +42 -0
  27. data/lib/rasn1/types/sequence.rb +105 -0
  28. data/lib/rasn1/types/sequence_of.rb +199 -0
  29. data/lib/rasn1/types/set.rb +28 -0
  30. data/lib/rasn1/types/set_of.rb +18 -0
  31. data/lib/rasn1/types/tag.rb +189 -0
  32. data/lib/rasn1/types/universal_string.rb +22 -0
  33. data/lib/rasn1/types/utc_time.rb +67 -0
  34. data/lib/rasn1/types/utf8_string.rb +21 -0
  35. data/lib/rasn1/types/visible_string.rb +30 -0
  36. data/lib/rasn1/types.rb +149 -0
  37. data/lib/rasn1/value_notation.rb +256 -0
  38. data/lib/rasn1/version.rb +6 -0
  39. data/lib/rasn1/wrapper.rb +279 -0
  40. data/lib/rasn1.rb +51 -0
  41. metadata +123 -0
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Integer
6
+ # @author Sylvain Daubert
7
+ class Integer < Primitive
8
+ # @return [Hash,nil]
9
+ attr_reader :enum
10
+
11
+ # Integer id value
12
+ ID = 2
13
+
14
+ # @option options [Hash] :enum enumeration hash. Keys are names, and values
15
+ # are integers.
16
+ # @raise [EnumeratedError] +:default+ value is unknown when +:enum+ key is present
17
+ # @see Base#initialize common options to all ASN.1 types
18
+ def initialize(options={})
19
+ super
20
+ initialize_enum(@options[:enum])
21
+ end
22
+
23
+ # @param [Integer,String,Symbol,nil] val
24
+ # @return [void]
25
+ def value=(val)
26
+ @no_value = false
27
+ case val
28
+ when String, Symbol
29
+ value_from_string_or_symbol(val)
30
+ when ::Integer
31
+ value_from_integer(val)
32
+ when nil
33
+ @no_value = true
34
+ @value = void_value
35
+ else
36
+ raise EnumeratedError, "#{@name}: not in enumeration"
37
+ end
38
+ end
39
+
40
+ # @return [Integer]
41
+ def void_value
42
+ 0
43
+ end
44
+
45
+ # Integer value
46
+ # @return [Integer]
47
+ def to_i
48
+ val = value? ? @value : @default
49
+ case val
50
+ when String, Symbol
51
+ @enum[val]
52
+ else
53
+ val || 0
54
+ end
55
+ end
56
+
57
+ # Make integer value from +der+ string.
58
+ # @param [String] der
59
+ # @param [::Boolean] ber
60
+ # @return [void]
61
+ def der_to_value(der, ber: false)
62
+ int_value = der_to_int_value(der, ber: ber)
63
+ @value = if @enum.empty?
64
+ int_value
65
+ else
66
+ int_to_enum(int_value)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def initialize_enum(enum)
73
+ @enum = enum || {}
74
+ return if @enum.empty?
75
+
76
+ # To ensure @value has the correct type
77
+ self.value = @value if value?
78
+
79
+ check_enum_default
80
+ end
81
+
82
+ def check_enum_default
83
+ case @default
84
+ when String, Symbol
85
+ raise EnumeratedError, "#{@name}: unknwon enumerated default value #@{default}" unless @enum.key?(@default)
86
+ when ::Integer
87
+ raise EnumeratedError, "#{@name}: default value #@{default} not in enumeration" unless @enum.value?(@default)
88
+
89
+ @default = @enum.key(@default)
90
+ when nil
91
+ else
92
+ raise TypeError, "#{@name}: #{@default.class} not handled as default value"
93
+ end
94
+ end
95
+
96
+ def value_from_string_or_symbol(val)
97
+ raise EnumeratedError, "#{@name} has no :enum" if @enum.empty?
98
+ raise EnumeratedError, "#{@name}: unknwon enumerated value #{val}" unless @enum.key? val
99
+
100
+ @value = val
101
+ end
102
+
103
+ def value_from_integer(val)
104
+ if @enum.empty?
105
+ @value = val
106
+ elsif @enum.value? val
107
+ @value = @enum.key(val)
108
+ else
109
+ raise EnumeratedError, "#{@name}: #{val} not in enumeration"
110
+ end
111
+ end
112
+
113
+ def int_value_to_der(value)
114
+ size = compute_size(value)
115
+ comp_value = value >= 0 ? value : (~(-value) + 1) & ((1 << (size * 8)) - 1)
116
+ ary = comp_value.digits(256)
117
+ # v is > 0 and its MSBit is 1. Add a 0 byte to mark it as positive
118
+ ary << 0 if value.positive? && (value >> (size * 8 - 1) == 1)
119
+ ary.reverse.pack('C*')
120
+ end
121
+
122
+ def compute_size(value)
123
+ size = value.bit_length / 8 + ((value.bit_length % 8).positive? ? 1 : 0)
124
+ size = 1 if size.zero?
125
+ size
126
+ end
127
+
128
+ def value_to_der
129
+ case @value
130
+ when String, Symbol
131
+ int_value_to_der(@enum[@value])
132
+ when ::Integer
133
+ int_value_to_der(@value)
134
+ else
135
+ raise TypeError, "#{@name}: #{@value.class} not handled"
136
+ end
137
+ end
138
+
139
+ def der_to_int_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
140
+ ary = der.unpack('C*')
141
+ v = ary.reduce(0) { |len, b| (len << 8) | b }
142
+ v = -((~v & ((1 << v.bit_length) - 1)) + 1) if ary[0] & 0x80 == 0x80
143
+ v
144
+ end
145
+
146
+ def int_to_enum(int, check_enum: true)
147
+ raise EnumeratedError, "#{@name}: value #{int} not in enumeration" if check_enum && !@enum.value?(int)
148
+
149
+ @enum.key(int) || int
150
+ end
151
+
152
+ def explicit_type
153
+ self.class.new(name: name, enum: enum)
154
+ end
155
+
156
+ def trace_data
157
+ return super if explicit?
158
+ i = der_to_int_value(raw_data)
159
+ return " #{int_with_hex(i, raw_data.unpack1('H*'))}" if @enum.empty?
160
+
161
+ v = int_to_enum(i, check_enum: false)
162
+ " #{colorize_enum(v, raw_data)}"
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Null
6
+ # @author Sylvain Daubert
7
+ class Null < Primitive
8
+ # Null id value
9
+ ID = 0x05
10
+
11
+ # @return [String]
12
+ def inspect(level=0)
13
+ str = common_inspect(level)[0..-2] # remove terminal ':'
14
+ str << ' OPTIONAL' if optional?
15
+ str
16
+ end
17
+
18
+ # @return [Boolean]
19
+ # Build only if not optional
20
+ def can_build?
21
+ !optional?
22
+ end
23
+
24
+ # Check +der+ string
25
+ # @param [String] der
26
+ # @param [::Boolean] ber
27
+ # @return [void]
28
+ # @raise [ASN1Error] +der+ is not empty
29
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
30
+ raise ASN1Error, 'NULL should not have content!' if der.length.positive?
31
+
32
+ @no_value = true
33
+ @value = void_value
34
+ end
35
+
36
+ private
37
+
38
+ def value_to_der
39
+ ''
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Numeric String
6
+ # @author Sylvain Daubert
7
+ class NumericString < OctetString
8
+ # NumericString id value
9
+ ID = 18
10
+ # Invalid characters in NumericString
11
+ INVALID_CHARS = /([^0-9 ])/
12
+
13
+ # Get ASN.1 type
14
+ # @return [String]
15
+ def self.type
16
+ 'NumericString'
17
+ end
18
+
19
+ # Make string value from +der+ string
20
+ # @param [String] der
21
+ # @param [::Boolean] ber
22
+ # @return [void]
23
+ # @raise [ASN1Error] invalid characters detected
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ check_characters
27
+ end
28
+
29
+ private
30
+
31
+ def value_to_der
32
+ check_characters
33
+ @value.to_s.b
34
+ end
35
+
36
+ def check_characters
37
+ raise ASN1Error, "NUMERIC STRING #{@name}: invalid character: '#{$1}'" if @value.to_s =~ INVALID_CHARS
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Object ID
6
+ # @author Sylvain Daubert
7
+ # @author LemonTree55
8
+ class ObjectId < Primitive
9
+ # ObjectId id value
10
+ ID = 6
11
+
12
+ # Make object id value from +der+ string
13
+ # @param [String] der
14
+ # @param [::Boolean] ber
15
+ # @return [void]
16
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
17
+ bytes = der.unpack('C*')
18
+
19
+ ids = []
20
+ current_id = 0
21
+ bytes.each do |byte|
22
+ current_id = (current_id << 7) | (byte & 0x7f)
23
+ if byte.nobits?(0x80)
24
+ ids << current_id
25
+ current_id = 0
26
+ end
27
+ end
28
+
29
+ first_id = [2, ids.first / 40].min
30
+ second_id = ids.first - first_id * 40
31
+ ids[0..0] = [first_id, second_id]
32
+
33
+ @value = ids.join('.')
34
+ end
35
+
36
+ private
37
+
38
+ # @raise [ASN1Error]
39
+ def value_to_der
40
+ ids = @value.to_s.split('.').map(&:to_i)
41
+
42
+ check_first_ids(ids)
43
+
44
+ ids[0, 2] = ids[0] * 40 + ids[1]
45
+ octets = []
46
+ ids.each do |v|
47
+ if v < 128
48
+ octets << v
49
+ else
50
+ octets.concat(unsigned_to_chained_octets(v))
51
+ end
52
+ end
53
+ octets.pack('C*')
54
+ end
55
+
56
+ # @param [Array[::Integer]] ids
57
+ # @raise [ASN1Error]
58
+ def check_first_ids(ids)
59
+ raise ASN1Error, "OBJECT ID #{@name}: first subidentifier should be less than 3" if ids[0] > 2
60
+ raise ASN1Error, "OBJECT ID #{@name}: second subidentifier should be less than 40" if (ids[0] < 2) && (ids[1] > 39)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Octet String
6
+ #
7
+ # An OCTET STRINT may contain another primtive object:
8
+ # os = OctetString.new
9
+ # int = Integer.new
10
+ # int.value = 12
11
+ # os.value = int
12
+ # os.to_der # => DER string with INTEGER in OCTET STRING
13
+ # @author Sylvain Daubert
14
+ class OctetString < Primitive
15
+ # OctetString id value
16
+ ID = 4
17
+
18
+ # @param [::Integer] level
19
+ # @return [String]
20
+ def inspect(level=0)
21
+ str = common_inspect(level)
22
+ str << " #{value.inspect}"
23
+ end
24
+
25
+ # Make string value from +der+ string. Force encoding to UTF-8
26
+ # @param [String] der
27
+ # @param [::Boolean] ber
28
+ # @return [void]
29
+ # @since 0.15.0 Handle ENCODING constant, if defined by subclass
30
+ def der_to_value(der, ber: false)
31
+ klass = self.class
32
+ if klass.const_defined?(:ENCODING)
33
+ @value = der.to_s.dup.force_encoding(klass.const_get(:ENCODING))
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # @since 0.15.0 Handle ENCODING constant, if defined by subclass
42
+ def value_to_der
43
+ klass = self.class
44
+ if klass.const_defined?(:ENCODING)
45
+ @value.to_s.encode(klass.const_get(:ENCODING)).b
46
+ else
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # @abstract This class SHOULD be used as base class for all ASN.1 primitive
6
+ # types.
7
+ # @author Sylvain Daubert
8
+ class Primitive < Base
9
+ # Primitive value
10
+ ASN1_PC = 0x00
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 Printable String
6
+ # @author Sylvain Daubert
7
+ class PrintableString < OctetString
8
+ # PrintableString id value
9
+ ID = 19
10
+ # Invalid characters in PrintableString
11
+ INVALID_CHARS = %r{([^a-zA-Z0-9 '=()+,\-./:?])}
12
+
13
+ # Get ASN.1 type
14
+ # @return [String]
15
+ def self.type
16
+ 'PrintableString'
17
+ end
18
+
19
+ # Make string value from +der+ string
20
+ # @param [String] der
21
+ # @param [::Boolean] ber
22
+ # @return [void]
23
+ # @raise [ASN1Error] invalid characters detected
24
+ def der_to_value(der, ber: false)
25
+ super
26
+ check_characters
27
+ end
28
+
29
+ private
30
+
31
+ def value_to_der
32
+ check_characters
33
+ @value.to_s.b
34
+ end
35
+
36
+ def check_characters
37
+ m = @value.to_s.match(INVALID_CHARS)
38
+ raise ASN1Error, "PRINTABLE STRING #{@name}: invalid character: '#{m[1]}'" if m
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RASN1
4
+ module Types
5
+ # ASN.1 sequence
6
+ #
7
+ # A sequence is a collection of another ASN.1 types.
8
+ #
9
+ # To encode this ASN.1 example:
10
+ # Record ::= SEQUENCE {
11
+ # id INTEGER,
12
+ # room [0] INTEGER OPTIONAL,
13
+ # house [1] IMPLICIT INTEGER DEFAULT 0
14
+ # }
15
+ # do:
16
+ # seq = RASN1::Types::Sequence.new
17
+ # seq.value = [
18
+ # RASN1::Types::Integer.new
19
+ # RASN1::Types::Integer.new(explicit: 0, optional: true),
20
+ # RASN1::Types::Integer.new(implicit: 1, default: 0)
21
+ # ]
22
+ #
23
+ # A sequence may also be used without value to not parse sequence content:
24
+ # seq = RASN1::Types::Sequence.new(:seq)
25
+ # seq.parse!(der_string)
26
+ # seq.value # => String
27
+ # @author Sylvain Daubert
28
+ class Sequence < Constructed
29
+ # Sequence id value
30
+ ID = 0x10
31
+
32
+ # @see Base#initialize
33
+ def initialize(options={})
34
+ super
35
+ @no_value = false
36
+ @value ||= []
37
+ end
38
+
39
+ # Deep copy @value
40
+ def initialize_copy(*)
41
+ super
42
+ @value = case @value
43
+ when Array
44
+ @value.map(&:dup)
45
+ else
46
+ @value.dup
47
+ end
48
+ end
49
+
50
+ # @return [Array]
51
+ def void_value
52
+ []
53
+ end
54
+
55
+ # Get element at index +idx+, or element of name +name+
56
+ # @param [Integer, String, Symbol] idx_or_name
57
+ # @return [Object,nil]
58
+ def [](idx_or_name)
59
+ return unless @value.is_a?(Array)
60
+
61
+ case idx_or_name
62
+ when ::Integer
63
+ @value[idx_or_name.to_i]
64
+ when String, Symbol
65
+ @value.find { |elt| elt.name == idx_or_name }
66
+ end
67
+ end
68
+
69
+ # Make sequence value from +der+ string
70
+ # @param [String] der
71
+ # @param [::Boolean] ber
72
+ # @return [void]
73
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
74
+ if @value.is_a?(Array) && !@value.empty?
75
+ nb_bytes = 0
76
+ @value.each do |element|
77
+ nb_bytes += element.parse!(der[nb_bytes..])
78
+ end
79
+ else
80
+ @value = der
81
+ der.length
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def value_to_der
88
+ case @value
89
+ when Array
90
+ @value.map(&:to_der).join
91
+ else
92
+ @value.to_s
93
+ end
94
+ end
95
+
96
+ def trace_data
97
+ ''
98
+ end
99
+
100
+ def explicit_type
101
+ self.class.new(name: name, value: @value)
102
+ end
103
+ end
104
+ end
105
+ end