rasn1 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,8 @@ module RASN1
7
7
  class PrintableString < OctetString
8
8
  # PrintableString id value
9
9
  ID = 19
10
+ # Invalid characters in PrintableString
11
+ INVALID_CHARS = %r{([^a-zA-Z0-9 '=()+,\-./:?])}
10
12
 
11
13
  # Get ASN.1 type
12
14
  # @return [String]
@@ -14,6 +16,16 @@ module RASN1
14
16
  'PrintableString'
15
17
  end
16
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
+
17
29
  private
18
30
 
19
31
  def value_to_der
@@ -21,13 +33,8 @@ module RASN1
21
33
  @value.to_s.b
22
34
  end
23
35
 
24
- def der_to_value(der, ber: false)
25
- super
26
- check_characters
27
- end
28
-
29
36
  def check_characters
30
- m = @value.to_s.match(%r{([^a-zA-Z0-9 '=()+,\-./:?])})
37
+ m = @value.to_s.match(INVALID_CHARS)
31
38
  raise ASN1Error, "PRINTABLE STRING #{@name}: invalid character: '#{m[1]}'" if m
32
39
  end
33
40
  end
@@ -66,22 +66,15 @@ module RASN1
66
66
  end
67
67
  end
68
68
 
69
- private
70
-
71
- def value_to_der
72
- case @value
73
- when Array
74
- @value.map(&:to_der).join
75
- else
76
- @value.to_s
77
- end
78
- end
79
-
69
+ # Make sequence value from +der+ string
70
+ # @param [String] der
71
+ # @param [::Boolean] ber
72
+ # @return [void]
80
73
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
81
74
  if @value.is_a?(Array) && !@value.empty?
82
75
  nb_bytes = 0
83
76
  @value.each do |element|
84
- nb_bytes += element.parse!(der[nb_bytes..-1])
77
+ nb_bytes += element.parse!(der[nb_bytes..])
85
78
  end
86
79
  else
87
80
  @value = der
@@ -89,6 +82,17 @@ module RASN1
89
82
  end
90
83
  end
91
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
+
92
96
  def trace_data
93
97
  ''
94
98
  end
@@ -125,6 +125,21 @@ module RASN1
125
125
  str
126
126
  end
127
127
 
128
+ # Make sequence of value from +der+ string
129
+ # @param [String] der
130
+ # @param [::Boolean] ber
131
+ # @return [void]
132
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
133
+ @value = []
134
+ nb_bytes = 0
135
+
136
+ while nb_bytes < der.length
137
+ type = of_type_class.new
138
+ nb_bytes += type.parse!(der[nb_bytes, der.length])
139
+ @value << type
140
+ end
141
+ end
142
+
128
143
  private
129
144
 
130
145
  def of_type_class
@@ -139,17 +154,6 @@ module RASN1
139
154
  @value.map(&:to_der).join
140
155
  end
141
156
 
142
- def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
143
- @value = []
144
- nb_bytes = 0
145
-
146
- while nb_bytes < der.length
147
- type = of_type_class.new
148
- nb_bytes += type.parse!(der[nb_bytes, der.length])
149
- @value << type
150
- end
151
- end
152
-
153
157
  def explicit_type
154
158
  self.class.new(self.of_type, name: name)
155
159
  end
@@ -8,23 +8,15 @@ module RASN1
8
8
  class UniversalString < OctetString
9
9
  # UniversalString id value
10
10
  ID = 28
11
+ # UniversalString encoding
12
+ # @since 0.15.0
13
+ ENCODING = Encoding::UTF_32BE
11
14
 
12
15
  # Get ASN.1 type
13
16
  # @return [String]
14
17
  def self.type
15
18
  'UniversalString'
16
19
  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
20
  end
29
21
  end
30
22
  end
@@ -27,12 +27,11 @@ module RASN1
27
27
  'UTCTime'
28
28
  end
29
29
 
30
- private
31
-
32
- def value_to_der
33
- @value.getutc.strftime('%y%m%d%H%M%SZ')
34
- end
35
-
30
+ # Make time value from +der+ string
31
+ # @param [String] der
32
+ # @param [::Boolean] ber
33
+ # @return [void]
34
+ # @raise [ASN1Error] unrecognized time format
36
35
  def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
37
36
  format = case der.size
38
37
  when 11, 15
@@ -44,7 +43,18 @@ module RASN1
44
43
  raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
45
44
  end
46
45
  century = (Time.now.year / 100).to_s
47
- @value = Strptime.new(format).exec(century + der)
46
+ begin
47
+ @value = Strptime.new(format).exec(century + der)
48
+ rescue ArgumentError
49
+ prefix = @name.nil? ? type : "tag #{@name}"
50
+ raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def value_to_der
57
+ @value.getutc.strftime('%y%m%d%H%M%SZ')
48
58
  end
49
59
 
50
60
  def trace_data
@@ -7,23 +7,15 @@ module RASN1
7
7
  class Utf8String < OctetString
8
8
  # Utf8String id value
9
9
  ID = 12
10
+ # Utf8String encoding
11
+ # @since 0.15.0
12
+ ENCODING = Encoding::UTF_8
10
13
 
11
14
  # Get ASN.1 type
12
15
  # @return [String]
13
16
  def self.type
14
17
  'UTF8String'
15
18
  end
16
-
17
- private
18
-
19
- def value_to_der
20
- @value.to_s.dup.force_encoding('UTF-8').b
21
- end
22
-
23
- def der_to_value(der, ber: false)
24
- super
25
- @value = der.dup.force_encoding('UTF-8')
26
- end
27
19
  end
28
20
  end
29
21
  end
@@ -2,14 +2,26 @@
2
2
 
3
3
  module RASN1
4
4
  module Types
5
+ # Create VisibleString as a Constrained subclass of IA5String
6
+ Types.define_type(:VisibleString, from: IA5String) do |value|
7
+ value.nil? || value.chars.all? { |c| c.ord >= 32 && c.ord <= 126 }
8
+ end
9
+
5
10
  # ASN.1 Visible String
6
- # @author Sylvain Daubert
7
- class VisibleString < IA5String
11
+ #
12
+ # VisibleString may only contain ASCII characters from range 32 to 126.
13
+ # @author LemonTree55
14
+ # @since 0.3.0
15
+ # @since 0.15.0 VisibleString is defined as a constrained {IA5String}. It will now raise if value contains a non-visible character.
16
+ class VisibleString
8
17
  # VisibleString id value
18
+ # @since 0.3.0
9
19
  ID = 26
10
20
 
11
21
  # Get ASN.1 type
12
22
  # @return [String]
23
+ # @author Sylvain Daubert
24
+ # @since 0.3.0
13
25
  def self.type
14
26
  'VisibleString'
15
27
  end
data/lib/rasn1/types.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'types/constrained'
4
+
3
5
  module RASN1
4
6
  # This modules is a namesapce for all ASN.1 type classes.
5
7
  # @author Sylvain Daubert
@@ -33,17 +35,17 @@ module RASN1
33
35
  def self.decode_identifier_octets(der)
34
36
  first_octet = der.unpack1('C').to_i
35
37
  asn1_class = Types::Base::CLASSES.key(first_octet & Types::Base::CLASS_MASK) || :universal
36
- pc = (first_octet & Types::Constructed::ASN1_PC).positive? ? :constructed : :primitive
38
+ pc = first_octet.anybits?(Types::Constructed::ASN1_PC) ? :constructed : :primitive
37
39
  id = first_octet & Types::Base::MULTI_OCTETS_ID
38
40
 
39
41
  size = if id == Types::Base::MULTI_OCTETS_ID
40
42
  id = 0
41
43
  count = 1
42
- der[1..-1].to_s.bytes.each do |octet|
44
+ der[1..].to_s.bytes.each do |octet|
43
45
  count += 1
44
46
 
45
47
  id = (id << 7) | (octet & 0x7f)
46
- break if (octet & 0x80).zero?
48
+ break if octet.nobits?(0x80)
47
49
  end
48
50
  count
49
51
  else
@@ -85,7 +87,7 @@ module RASN1
85
87
 
86
88
  # Define a new ASN.1 type from a base one.
87
89
  # This new type may have a constraint defines on it.
88
- # @param [Symbol,String] name New type name. Must start with a capital letter.
90
+ # @param [Symbol,String] name new type name. Must start with a capital letter.
89
91
  # @param [Types::Base] from class from which inherits
90
92
  # @param [Module] in_module module in which creates new type (default to {RASN1::Types})
91
93
  # @return [Class] newly created class
@@ -135,7 +137,6 @@ require_relative 'types/utf8_string'
135
137
  require_relative 'types/numeric_string'
136
138
  require_relative 'types/printable_string'
137
139
  require_relative 'types/ia5string'
138
- require_relative 'types/visible_string'
139
140
  require_relative 'types/constructed'
140
141
  require_relative 'types/sequence'
141
142
  require_relative 'types/sequence_of'
data/lib/rasn1/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RASN1
4
- VERSION = '0.14.0'
4
+ # RASN1 version number
5
+ VERSION = '0.15.0'
5
6
  end
data/lib/rasn1/wrapper.rb CHANGED
@@ -6,12 +6,13 @@ module RASN1
6
6
  # This class is used to wrap a {Types::Base} or {Model} instance to force its options.
7
7
  #
8
8
  # == Usage
9
- # This class may be used to wrap another RASN1 object by 3 ways:
9
+ # This class may be used to wrap another RASN1 object by 4 ways:
10
10
  # * wrap an object to modify its options,
11
11
  # * implicitly wrap an object (i.e. change its tag),
12
- # * explicitly wrap an object (i.e wrap the object in another explicit ASN.1 tag)
12
+ # * explicitly wrap an object (i.e wrap the object in another explicit ASN.1 tag),
13
+ # * wrap a choice model to reuse it in its own definition.
13
14
  #
14
- # @example
15
+ # @example Wrapping object
15
16
  # # object to wrap
16
17
  # int = RASN1::Types::Integer.new(implicit: 1) # its tag is 0x81
17
18
  # # simple wrapper, change an option
@@ -20,7 +21,18 @@ module RASN1
20
21
  # wrapper = RASN1::Wrapper.new(int, implicit: 3) # wrapped int tag is now 0x83
21
22
  # # explicit wrapper
22
23
  # wrapper = RASN1::Wrapper.new(int, explicit: 4) # int tag is always 0x81, but it is wrapped in a 0x84 tag
24
+ # @example Wrapping a choice to make it recursive
25
+ # class RecursiveChoice < RASN1::Model
26
+ # choice :choice,
27
+ # content: [
28
+ # integer(:value, implicit: 1),
29
+ # wrapper(model(:recursive, RecursiveChoice), implicit:2)
30
+ # ]
31
+ # end
23
32
  # @since 0.12.0
33
+ # @since 0.15.0 Wrappers are lazy. They allocate their inner element only when needed (i.e. when calling {#to_der}, {#parse!} or {#element})
34
+ # @author Sylvain Daubert
35
+ # @author LemonTree55
24
36
  class Wrapper < SimpleDelegator
25
37
  # @private Private class used to build/parse explicit wrappers
26
38
  class ExplicitWrapper < Types::Base
@@ -54,15 +66,16 @@ module RASN1
54
66
  # @param [Types::Base,Model] element element to wrap
55
67
  # @param [Hash] options
56
68
  def initialize(element, options={})
69
+ @lazy = true
57
70
  opts = explicit_implicit(options)
58
71
 
59
72
  if explicit?
60
73
  generate_explicit_wrapper(opts)
61
- element.options = element.options.merge(generate_explicit_wrapper_options(opts))
74
+ @element_options_to_merge = generate_explicit_wrapper_options(opts)
62
75
  @options = opts
63
76
  else
64
- opts[:value] = element.value
65
- element.options = element.options.merge(opts)
77
+ opts[:value] = element.value if element.respond_to?(:value)
78
+ @element_options_to_merge = opts
66
79
  @options = {}
67
80
  end
68
81
  raise RASN1::Error, 'Cannot be implicit and explicit' if explicit? && implicit?
@@ -84,7 +97,10 @@ module RASN1
84
97
 
85
98
  # Convert wrapper and its element to a DER string
86
99
  # @return [String]
100
+ # @since 0.12.0
101
+ # @since 0.15.0 Allocate element (lazy wrapper)
87
102
  def to_der
103
+ lazy_generation
88
104
  if implicit?
89
105
  el = generate_implicit_element
90
106
  el.to_der
@@ -101,7 +117,10 @@ module RASN1
101
117
  # @param [Boolean] ber if +true+, accept BER encoding
102
118
  # @return [Integer] total number of parsed bytes
103
119
  # @raise [ASN1Error] error on parsing
120
+ # @since 0.12.0
121
+ # @since 0.15.0 Allocate element (lazy wrapper)
104
122
  def parse!(der, ber: false)
123
+ lazy_generation
105
124
  if implicit?
106
125
  el = generate_implicit_element
107
126
  parsed = el.parse!(der, ber: ber)
@@ -116,11 +135,25 @@ module RASN1
116
135
  end
117
136
  end
118
137
 
138
+ # @private
139
+ # @see Types::Base#do_parse
140
+ def do_parse(der, ber: false)
141
+ if implicit?
142
+ generate_implicit_element(Types::Base.new(constructed: element.constructed?)).do_parse(der, ber: ber)
143
+ elsif explicit?
144
+ @explicit_wrapper.do_parse(der, ber: ber)
145
+ else
146
+ element.do_parse(der, ber: ber)
147
+ end
148
+ end
149
+
119
150
  # @return [Boolean]
120
151
  # @see Types::Base#value?
121
152
  def value?
122
153
  if explicit?
123
154
  @explicit_wrapper.value?
155
+ elsif __getobj__.is_a?(Class)
156
+ false
124
157
  else
125
158
  __getobj__.value?
126
159
  end
@@ -128,8 +161,10 @@ module RASN1
128
161
 
129
162
  # Return Wrapped element
130
163
  # @return [Types::Base,Model]
164
+ # @since 0.12.0
165
+ # @since 0.15.0 Allocate element (lazy wrapper)
131
166
  def element
132
- __getobj__
167
+ lazy_generation
133
168
  end
134
169
 
135
170
  # @return [::Integer]
@@ -140,14 +175,14 @@ module RASN1
140
175
  elsif explicit?
141
176
  @explicit
142
177
  else
143
- element.id
178
+ lazy_generation(register: false).id
144
179
  end
145
180
  end
146
181
 
147
182
  # @return [Symbol]
148
183
  # @see Types::Base#asn1_class
149
184
  def asn1_class
150
- return element.asn1_class unless @options.key?(:class)
185
+ return lazy_generation(register: false).asn1_class unless @options.key?(:class)
151
186
 
152
187
  @options[:class]
153
188
  end
@@ -155,7 +190,7 @@ module RASN1
155
190
  # @return [Boolean]
156
191
  # @see Types::Base#constructed
157
192
  def constructed?
158
- return element.constructed? unless @options.key?(:constructed)
193
+ return lazy_generation(register: false).constructed? unless @options.key?(:constructed)
159
194
 
160
195
  @options[:constructed]
161
196
  end
@@ -169,9 +204,9 @@ module RASN1
169
204
  # @param [::Integer] level
170
205
  # @return [String]
171
206
  def inspect(level=0)
172
- return super(level) unless explicit?
207
+ return super() unless explicit?
173
208
 
174
- @explicit_wrapper.inspect(level) << ' ' << super(level)
209
+ @explicit_wrapper.inspect(level) << ' ' << super()
175
210
  end
176
211
 
177
212
  private
@@ -196,14 +231,33 @@ module RASN1
196
231
  new_opts
197
232
  end
198
233
 
199
- def generate_implicit_element
200
- el = element.dup
201
- if el.explicit?
202
- el.options = el.options.merge(explicit: @implicit)
234
+ def generate_implicit_element(from=nil)
235
+ el = (from || element).dup
236
+ el.options = if element.explicit?
237
+ el.options.merge(explicit: @implicit)
238
+ else
239
+ el.options.merge(implicit: @implicit)
240
+ end
241
+ el
242
+ end
243
+
244
+ def lazy_generation(register: true)
245
+ return __getobj__ unless @lazy
246
+
247
+ real_element = __getobj__
248
+ case real_element
249
+ when Types::Base, Model
250
+ real_element.options = real_element.options.merge(@element_options_to_merge)
251
+ @lazy = false
252
+ real_element
203
253
  else
204
- el.options = el.options.merge(implicit: @implicit)
254
+ el = real_element.new(@element_options_to_merge)
255
+ if register
256
+ @lazy = false
257
+ __setobj__(el)
258
+ end
259
+ el
205
260
  end
206
- el
207
261
  end
208
262
  end
209
263
  end
data/lib/rasn1.rb CHANGED
@@ -7,6 +7,9 @@ require_relative 'rasn1/model'
7
7
  require_relative 'rasn1/wrapper'
8
8
  require_relative 'rasn1/tracer'
9
9
 
10
+ # Constrained types
11
+ require_relative 'rasn1/types/visible_string'
12
+
10
13
  # Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
11
14
  # @author Sylvain Daubert
12
15
  module RASN1
@@ -33,7 +36,7 @@ module RASN1
33
36
  RASN1.tracer.tracing_level += 1 unless RASN1.tracer.nil?
34
37
  until subder.empty?
35
38
  ary << self.parse(subder)
36
- subder = subder[ary.last.to_der.size..-1]
39
+ subder = subder[ary.last.to_der.size..]
37
40
  end
38
41
  RASN1.tracer.tracing_level -= 1 unless RASN1.tracer.nil?
39
42
  type.value = ary
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.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LemonTree55
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-28 00:00:00.000000000 Z
11
+ date: 2025-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strptime