rasn1 0.14.0 → 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.
@@ -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.between?(32, 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.16.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
@@ -23,7 +26,7 @@ module RASN1
23
26
  # @param [String] der binary string to parse
24
27
  # @param [Boolean] ber if +true+, decode a BER string, else a DER one
25
28
  # @return [Types::Base]
26
- def self.parse(der, ber: false) # rubocop:disable Metrics:AbcSize
29
+ def self.parse(der, ber: false) # rubocop:disable Metrics/AbcSize
27
30
  type = Types.id2type(der)
28
31
  type.parse!(der, ber: ber)
29
32
 
@@ -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.16.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-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strptime