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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +4 -0
- data/lib/rasn1/errors.rb +55 -0
- data/lib/rasn1/helpers/colorize.rb +76 -0
- data/lib/rasn1/model.rb +831 -0
- data/lib/rasn1/schema_parser.rb +470 -0
- data/lib/rasn1/tracer.rb +200 -0
- data/lib/rasn1/types/any.rb +98 -0
- data/lib/rasn1/types/base.rb +675 -0
- data/lib/rasn1/types/bit_string.rb +100 -0
- data/lib/rasn1/types/bmp_string.rb +22 -0
- data/lib/rasn1/types/boolean.rb +57 -0
- data/lib/rasn1/types/choice.rb +158 -0
- data/lib/rasn1/types/constrained.rb +51 -0
- data/lib/rasn1/types/constructed.rb +51 -0
- data/lib/rasn1/types/enumerated.rb +44 -0
- data/lib/rasn1/types/generalized_time.rb +156 -0
- data/lib/rasn1/types/ia5_string.rb +21 -0
- data/lib/rasn1/types/integer.rb +166 -0
- data/lib/rasn1/types/null.rb +43 -0
- data/lib/rasn1/types/numeric_string.rb +41 -0
- data/lib/rasn1/types/object_id.rb +64 -0
- data/lib/rasn1/types/octet_string.rb +52 -0
- data/lib/rasn1/types/primitive.rb +13 -0
- data/lib/rasn1/types/printable_string.rb +42 -0
- data/lib/rasn1/types/sequence.rb +105 -0
- data/lib/rasn1/types/sequence_of.rb +199 -0
- data/lib/rasn1/types/set.rb +28 -0
- data/lib/rasn1/types/set_of.rb +18 -0
- data/lib/rasn1/types/tag.rb +189 -0
- data/lib/rasn1/types/universal_string.rb +22 -0
- data/lib/rasn1/types/utc_time.rb +67 -0
- data/lib/rasn1/types/utf8_string.rb +21 -0
- data/lib/rasn1/types/visible_string.rb +30 -0
- data/lib/rasn1/types.rb +149 -0
- data/lib/rasn1/value_notation.rb +256 -0
- data/lib/rasn1/version.rb +6 -0
- data/lib/rasn1/wrapper.rb +279 -0
- data/lib/rasn1.rb +51 -0
- metadata +123 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# @abstract This is base class for all ASN.1 types.
|
|
6
|
+
#
|
|
7
|
+
# Subclasses SHOULD define:
|
|
8
|
+
# * an ID constant defining ASN.1 BER/DER identification number,
|
|
9
|
+
# * a method {#der_to_value} converting DER into {#value}.
|
|
10
|
+
# * a private method {#value_to_der} converting its {#value} to DER,
|
|
11
|
+
#
|
|
12
|
+
# ==Define an optional value
|
|
13
|
+
# An optional value may be defined using +:optional+ key from {#initialize}:
|
|
14
|
+
# Integer.new(:int, optional: true)
|
|
15
|
+
# An optional value implies:
|
|
16
|
+
# * while parsing, if decoded ID is not optional expected ID, no {ASN1Error}
|
|
17
|
+
# is raised, and parser tries next field,
|
|
18
|
+
# * while encoding, if {#value} is +nil+, this value is not encoded.
|
|
19
|
+
# ==Define a default value
|
|
20
|
+
# A default value may be defined using +:default+ key from {#initialize}:
|
|
21
|
+
# Integer.new(:int, default: 0)
|
|
22
|
+
# A default value implies:
|
|
23
|
+
# * while parsing, if decoded ID is not expected one, no {ASN1Error} is raised
|
|
24
|
+
# and parser sets default value to this ID. Then parser tries next field,
|
|
25
|
+
# * while encoding, if {#value} is equal to default value, this value is not
|
|
26
|
+
# encoded.
|
|
27
|
+
# ==Define a tagged value
|
|
28
|
+
# ASN.1 permits to define tagged values.
|
|
29
|
+
# By example:
|
|
30
|
+
# -- context specific tag
|
|
31
|
+
# CType ::= [0] EXPLICIT INTEGER
|
|
32
|
+
# -- application specific tag
|
|
33
|
+
# AType ::= [APPLICATION 1] EXPLICIT INTEGER
|
|
34
|
+
# -- private tag
|
|
35
|
+
# PType ::= [PRIVATE 2] EXPLICIT INTEGER
|
|
36
|
+
# These types may be defined as:
|
|
37
|
+
# ctype = RASN1::Types::Integer.new(explicit: 0) # with explicit, default #asn1_class is :context
|
|
38
|
+
# atype = RASN1::Types::Integer.new(explicit: 1, class: :application)
|
|
39
|
+
# ptype = RASN1::Types::Integer.new(explicit: 2, class: :private)
|
|
40
|
+
# Sometimes, an EXPLICIT type should be CONSTRUCTED. To do that, use +:constructed+
|
|
41
|
+
# option:
|
|
42
|
+
# ptype = RASN1::Types::Integer.new(explicit: 2, class: :private, constructed: true)
|
|
43
|
+
#
|
|
44
|
+
# Implicit tagged values may also be defined:
|
|
45
|
+
# ctype_implicit = RASN1::Types::Integer.new(implicit: 0)
|
|
46
|
+
# @author Sylvain Daubert
|
|
47
|
+
class Base # rubocop:disable Metrics/ClassLength
|
|
48
|
+
# Allowed ASN.1 classes
|
|
49
|
+
CLASSES = {
|
|
50
|
+
universal: 0x00,
|
|
51
|
+
application: 0x40,
|
|
52
|
+
context: 0x80,
|
|
53
|
+
private: 0xc0
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
def colorize(msg)
|
|
57
|
+
tracer ? tracer.colorize(msg) : Rainbow.new.wrap(msg)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
include RASN1::Helpers::Colorize
|
|
61
|
+
|
|
62
|
+
# Binary mask to get class
|
|
63
|
+
# @private
|
|
64
|
+
CLASS_MASK = 0xc0
|
|
65
|
+
|
|
66
|
+
# @private first octet identifier for multi-octets identifier
|
|
67
|
+
MULTI_OCTETS_ID = 0x1f
|
|
68
|
+
|
|
69
|
+
# Length value for indefinite length
|
|
70
|
+
INDEFINITE_LENGTH = 0x80
|
|
71
|
+
|
|
72
|
+
# @return [String,nil]
|
|
73
|
+
attr_reader :name
|
|
74
|
+
# @return [Symbol]
|
|
75
|
+
attr_reader :asn1_class
|
|
76
|
+
# @return [Object,nil] default value, if defined
|
|
77
|
+
attr_reader :default
|
|
78
|
+
# @return [Hash[Symbol, Object]]
|
|
79
|
+
attr_reader :options
|
|
80
|
+
# @return [String] raw parsed data
|
|
81
|
+
attr_reader :raw_data
|
|
82
|
+
# @return [String] raw parsed length
|
|
83
|
+
attr_reader :raw_length
|
|
84
|
+
|
|
85
|
+
private :raw_data, :raw_length
|
|
86
|
+
|
|
87
|
+
def tracer
|
|
88
|
+
::RASN1.tracer
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get ASN.1 type
|
|
92
|
+
# @return [String]
|
|
93
|
+
def self.type
|
|
94
|
+
return @type if defined? @type
|
|
95
|
+
|
|
96
|
+
@type = self.to_s.gsub(/.*::/, '').gsub(/([a-z0-9])([A-Z])/, '\1 \2').upcase
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get ASN.1 type used to encode this one
|
|
100
|
+
# @return [String]
|
|
101
|
+
def self.encoded_type
|
|
102
|
+
type
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Parse a DER or BER string
|
|
106
|
+
# @param [String] der_or_ber string to parse
|
|
107
|
+
# @param [Hash] options
|
|
108
|
+
# @return [Base]
|
|
109
|
+
# @option options [Boolean] :ber if +true+, parse a BER string, else a DER one
|
|
110
|
+
# @note More options are supported. See {Base#initialize}.
|
|
111
|
+
def self.parse(der_or_ber, options={})
|
|
112
|
+
obj = self.new(options)
|
|
113
|
+
obj.parse!(der_or_ber, ber: options[:ber])
|
|
114
|
+
obj
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Say if a type is constrained.
|
|
118
|
+
# Always return +false+ for predefined types
|
|
119
|
+
# @return [Booleran]
|
|
120
|
+
def self.constrained?
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @param [Hash] options
|
|
125
|
+
# @option options [Symbol] :class ASN.1 class. Default value is +:universal+.
|
|
126
|
+
# If +:explicit+ or +:implicit:+ is defined, default value is +:context+.
|
|
127
|
+
# @option options [::Boolean] :optional define this tag as optional. Default
|
|
128
|
+
# is +false+
|
|
129
|
+
# @option options [Object] :default default value (ASN.1 DEFAULT)
|
|
130
|
+
# @option options [Object] :value value to set
|
|
131
|
+
# @option options [::Integer] :implicit define an IMPLICIT tagged type
|
|
132
|
+
# @option options [::Integer] :explicit define an EXPLICIT tagged type
|
|
133
|
+
# @option options [::Boolean] :constructed if +true+, set type as constructed.
|
|
134
|
+
# May only be used when +:explicit+ is defined, else it is discarded.
|
|
135
|
+
# @option options [::String] :name name for this node
|
|
136
|
+
def initialize(options={})
|
|
137
|
+
@constructed = nil
|
|
138
|
+
set_value(options.delete(:value))
|
|
139
|
+
self.options = options
|
|
140
|
+
specific_initializer
|
|
141
|
+
@raw_data = ''.b
|
|
142
|
+
@raw_length = ''.b
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @abstract To help subclass initialize itself. Default implementation do nothing.
|
|
146
|
+
def specific_initializer; end
|
|
147
|
+
|
|
148
|
+
# Deep copy @value and @default.
|
|
149
|
+
def initialize_copy(*)
|
|
150
|
+
super
|
|
151
|
+
@value = @value.dup
|
|
152
|
+
@no_value = @no_value.dup
|
|
153
|
+
@default = @default.dup
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get value or default value
|
|
157
|
+
def value
|
|
158
|
+
if value?
|
|
159
|
+
@value
|
|
160
|
+
else
|
|
161
|
+
@default
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Set value. If +val+ is +nil+, unset value
|
|
166
|
+
# @param [Object,nil] val
|
|
167
|
+
def value=(val)
|
|
168
|
+
set_value(val)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @abstract Define 'void' value (i.e. 'value' when no value was set)
|
|
172
|
+
def void_value
|
|
173
|
+
''
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Say if this type is optional
|
|
177
|
+
# @return [::Boolean]
|
|
178
|
+
def optional?
|
|
179
|
+
@optional
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Say if this type is tagged or not
|
|
183
|
+
# @return [::Boolean]
|
|
184
|
+
def tagged?
|
|
185
|
+
!@tag.nil?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Say if a tagged type is explicit
|
|
189
|
+
# @return [::Boolean,nil] return +nil+ if not tagged, return +true+
|
|
190
|
+
# if explicit, else +false+
|
|
191
|
+
def explicit?
|
|
192
|
+
defined?(@tag) ? @tag == :explicit : nil # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Say if a tagged type is implicit
|
|
196
|
+
# @return [::Boolean,nil] return +nil+ if not tagged, return +true+
|
|
197
|
+
# if implicit, else +false+
|
|
198
|
+
def implicit?
|
|
199
|
+
defined?(@tag) ? @tag == :implicit : nil # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @abstract This method SHOULD be partly implemented by subclasses, which
|
|
203
|
+
# SHOULD respond to +#value_to_der+.
|
|
204
|
+
# @return [String] DER-formated string
|
|
205
|
+
def to_der
|
|
206
|
+
build
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @return [::Boolean] +true+ if this is a primitive type
|
|
210
|
+
def primitive?
|
|
211
|
+
(self.class < Primitive) && !@constructed
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @return [::Boolean] +true+ if this is a constructed type
|
|
215
|
+
def constructed?
|
|
216
|
+
(self.class < Constructed) || !!@constructed
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Get ASN.1 type
|
|
220
|
+
# @return [String]
|
|
221
|
+
def type
|
|
222
|
+
self.class.type
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Get identifier value
|
|
226
|
+
# @return [Integer]
|
|
227
|
+
def id
|
|
228
|
+
id_value
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @abstract This method SHOULD be partly implemented by subclasses to parse
|
|
232
|
+
# data. Subclasses SHOULD respond to +#der_to_value+.
|
|
233
|
+
# Parse a DER string. This method updates object.
|
|
234
|
+
# @param [String] der DER string
|
|
235
|
+
# @param [Boolean] ber if +true+, accept BER encoding
|
|
236
|
+
# @return [Integer] total number of parsed bytes
|
|
237
|
+
# @raise [ASN1Error] error on parsing
|
|
238
|
+
def parse!(der, ber: false)
|
|
239
|
+
total_length, data = do_parse(der, ber: ber)
|
|
240
|
+
return 0 if total_length.zero?
|
|
241
|
+
|
|
242
|
+
if explicit?
|
|
243
|
+
do_parse_explicit(data)
|
|
244
|
+
else
|
|
245
|
+
der_to_value(data, ber: ber)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
total_length
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Give size in octets of encoded value
|
|
252
|
+
# @return [Integer]
|
|
253
|
+
def value_size
|
|
254
|
+
value_to_der.size
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @param [Integer] level
|
|
258
|
+
# @return [String]
|
|
259
|
+
def inspect(level=0, color: true)
|
|
260
|
+
str = common_inspect(level)
|
|
261
|
+
str << ' ' << inspect_value
|
|
262
|
+
str << ' OPTIONAL' if optional?
|
|
263
|
+
str << " DEFAULT #{@default}" unless @default.nil?
|
|
264
|
+
str
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Objects are equal if they have same class AND same DER
|
|
268
|
+
# @param [Base] other
|
|
269
|
+
# @return [Boolean]
|
|
270
|
+
def ==(other)
|
|
271
|
+
(other.class == self.class) && (other.to_der == self.to_der)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Set options to this object
|
|
275
|
+
# @param [Hash] options
|
|
276
|
+
# @return [void]
|
|
277
|
+
# @since 0.12
|
|
278
|
+
def options=(options)
|
|
279
|
+
set_class options[:class]
|
|
280
|
+
set_optional options[:optional]
|
|
281
|
+
set_default options[:default]
|
|
282
|
+
set_tag options
|
|
283
|
+
@name = options[:name]
|
|
284
|
+
@options = options
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Say if a value is set
|
|
288
|
+
# @return [Boolean]
|
|
289
|
+
# @since 0.12.0
|
|
290
|
+
def value?
|
|
291
|
+
!@no_value
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Say if DER can be built (not default value, not optional without value, has a value)
|
|
295
|
+
# @return [Boolean]
|
|
296
|
+
# @since 0.12.0
|
|
297
|
+
def can_build?
|
|
298
|
+
value? && (@default.nil? || (@value != @default))
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# @private Tracer private API
|
|
302
|
+
# @return [String]
|
|
303
|
+
def trace
|
|
304
|
+
return trace_real if value?
|
|
305
|
+
|
|
306
|
+
msg = msg_type
|
|
307
|
+
if default.nil? # rubocop:disable Style/ConditionalAssignment
|
|
308
|
+
msg << " #{colorize_nil}"
|
|
309
|
+
else
|
|
310
|
+
msg << " #{colorize_attribute('DEFAULT VALUE')} #{colorize_default(value)}"
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# @private Parse tage and length from binary string. Return data length and binary data.
|
|
315
|
+
# @param [String] der
|
|
316
|
+
# @param [Boolean] ber
|
|
317
|
+
# @return [Array(::Integer, String)]
|
|
318
|
+
# @since 0.15.0 was private before
|
|
319
|
+
def do_parse(der, ber: false)
|
|
320
|
+
return [0, ''] unless check_id(der)
|
|
321
|
+
|
|
322
|
+
id_size = Types.decode_identifier_octets(der).last
|
|
323
|
+
total_length, data = get_data(der[id_size..], ber)
|
|
324
|
+
total_length += id_size
|
|
325
|
+
@no_value = false
|
|
326
|
+
|
|
327
|
+
[total_length, data]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# @private Delegate to #explicit type to generate sub-value
|
|
331
|
+
# @param [String] data
|
|
332
|
+
# @return [void]
|
|
333
|
+
# @since 0.15.0 was private before
|
|
334
|
+
def do_parse_explicit(data)
|
|
335
|
+
type = explicit_type
|
|
336
|
+
type.parse!(data)
|
|
337
|
+
@value = type.value
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Make value from DER/BER string
|
|
341
|
+
# @param [String] der
|
|
342
|
+
# @param [::Boolean] ber
|
|
343
|
+
# @return [void]
|
|
344
|
+
# @since 0.15.0 was private before
|
|
345
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
346
|
+
@value = der
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
private
|
|
350
|
+
|
|
351
|
+
def trace_real
|
|
352
|
+
encoded_id = unpack(encode_identifier_octets)
|
|
353
|
+
data_length = raw_data.length
|
|
354
|
+
encoded_length = unpack(raw_length)
|
|
355
|
+
msg = "#{msg_type} #{parens_hex(encoded_id)}, #{length_specifier(data_length, encoded_length)}"
|
|
356
|
+
msg << trace_data
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def trace_data(data=nil)
|
|
360
|
+
byte_count = 0
|
|
361
|
+
data ||= raw_data
|
|
362
|
+
|
|
363
|
+
lines = []
|
|
364
|
+
str = ''
|
|
365
|
+
data.each_byte do |byte|
|
|
366
|
+
if (byte_count % 16).zero?
|
|
367
|
+
str = trace_format_new_data_line(byte_count)
|
|
368
|
+
lines << str
|
|
369
|
+
end
|
|
370
|
+
str[compute_trace_index(byte_count, 3), 2] = '%02x' % byte
|
|
371
|
+
str[compute_trace_index(byte_count, 1, 49)] = byte.between?(32, 126) ? byte.chr : '.'
|
|
372
|
+
byte_count += 1
|
|
373
|
+
end
|
|
374
|
+
lines.map(&:rstrip).join << "\n"
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def trace_format_new_data_line(count)
|
|
378
|
+
head_line = RASN1.tracer.indent(RASN1.tracer.tracing_level + 1)
|
|
379
|
+
("\n#{head_line}%04x " % count) << ' ' * 68
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def compute_trace_index(byte_count, byte_count_mul=1, offset=0)
|
|
383
|
+
base_idx = 7 + RASN1.tracer.indent(RASN1.tracer.tracing_level + 1).length
|
|
384
|
+
base_idx + offset + (byte_count % 16) * byte_count_mul
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def unpack(binstr)
|
|
388
|
+
binstr.unpack1('H*')
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def asn1_class_to_s
|
|
392
|
+
asn1_class == :universal ? '' : asn1_class.to_s.upcase << ' '
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def msg_type(no_id: false)
|
|
396
|
+
msg = name.nil? ? +'' : "#{colorize_name(name)} "
|
|
397
|
+
msg << "#{colorize_id(id, asn1_class_to_s)} " unless no_id
|
|
398
|
+
msg << if explicit?
|
|
399
|
+
+ "#{colorize_attribute('EXPLICIT')} "
|
|
400
|
+
elsif implicit?
|
|
401
|
+
+ "#{colorize_attribute('IMPLICIT')} "
|
|
402
|
+
else
|
|
403
|
+
+''
|
|
404
|
+
end
|
|
405
|
+
msg << colorize_class(type)
|
|
406
|
+
msg << " #{colorize_attribute('OPTIONAL')}" if optional?
|
|
407
|
+
msg
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def pc_bit
|
|
411
|
+
if @constructed.nil?
|
|
412
|
+
self.class.const_get(:ASN1_PC)
|
|
413
|
+
elsif @constructed # true
|
|
414
|
+
Constructed::ASN1_PC
|
|
415
|
+
else # false
|
|
416
|
+
Primitive::ASN1_PC
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def common_inspect(level)
|
|
421
|
+
lvl = [level, 0].max
|
|
422
|
+
str = ' ' * lvl
|
|
423
|
+
str << "#{@name} " unless @name.nil?
|
|
424
|
+
str << asn1_class_to_s
|
|
425
|
+
str << "[#{id}] EXPLICIT " if explicit?
|
|
426
|
+
str << "[#{id}] IMPLICIT " if implicit?
|
|
427
|
+
str << "#{type}:"
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def inspect_value
|
|
431
|
+
if value?
|
|
432
|
+
value.inspect
|
|
433
|
+
else
|
|
434
|
+
'(NO VALUE)'
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def value_to_der
|
|
439
|
+
case @value
|
|
440
|
+
when Base
|
|
441
|
+
@value.to_der
|
|
442
|
+
else
|
|
443
|
+
@value.to_s
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def set_class(asn1_class) # rubocop:disable Naming/AccessorMethodName
|
|
448
|
+
case asn1_class
|
|
449
|
+
when nil
|
|
450
|
+
@asn1_class = :universal
|
|
451
|
+
when Symbol
|
|
452
|
+
raise ClassError unless CLASSES.key?(asn1_class)
|
|
453
|
+
|
|
454
|
+
@asn1_class = asn1_class
|
|
455
|
+
else
|
|
456
|
+
raise ClassError
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def set_optional(optional) # rubocop:disable Naming/AccessorMethodName
|
|
461
|
+
@optional = !!optional
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def set_default(default) # rubocop:disable Naming/AccessorMethodName
|
|
465
|
+
@default = default
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# handle undocumented option +:tag_value+, used internally by
|
|
469
|
+
# {RASN1.parse} to parse non-universal class tags.
|
|
470
|
+
def set_tag(options) # rubocop:disable Naming/AccessorMethodName
|
|
471
|
+
@constructed = options[:constructed]
|
|
472
|
+
if options[:explicit]
|
|
473
|
+
@tag = :explicit
|
|
474
|
+
@id_value = tag_id_to_integer(options[:explicit])
|
|
475
|
+
elsif options[:implicit]
|
|
476
|
+
@tag = :implicit
|
|
477
|
+
@id_value = tag_id_to_integer(options[:implicit])
|
|
478
|
+
elsif options[:tag_value]
|
|
479
|
+
@id_value = tag_id_to_integer(options[:tag_value])
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@asn1_class = :context if defined?(@tag) && (@asn1_class == :universal)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Convert a tag id to an integer.
|
|
486
|
+
# If given a String or Symbol, interprets the characters as big-endian octets
|
|
487
|
+
# (like Apple 4-character codes / 4CCs). Integers are returned as-is.
|
|
488
|
+
# @param [::Integer, String, Symbol] value tag value
|
|
489
|
+
# @return [::Integer]
|
|
490
|
+
def tag_id_to_integer(value)
|
|
491
|
+
case value
|
|
492
|
+
when ::Integer
|
|
493
|
+
value
|
|
494
|
+
when ::String, ::Symbol
|
|
495
|
+
value.to_s.bytes.reduce(0) { |acc, b| (acc << 8) | b }
|
|
496
|
+
else
|
|
497
|
+
value
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def set_value(value) # rubocop:disable Naming/AccessorMethodName
|
|
502
|
+
if value.nil?
|
|
503
|
+
@no_value = true
|
|
504
|
+
@value = void_value
|
|
505
|
+
else
|
|
506
|
+
@no_value = false
|
|
507
|
+
@value = value
|
|
508
|
+
end
|
|
509
|
+
value
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def build
|
|
513
|
+
if can_build?
|
|
514
|
+
if explicit?
|
|
515
|
+
v = explicit_type
|
|
516
|
+
v.value = @value
|
|
517
|
+
encoded_value = v.to_der
|
|
518
|
+
else
|
|
519
|
+
encoded_value = value_to_der
|
|
520
|
+
end
|
|
521
|
+
encode_identifier_octets << encode_size(encoded_value.size) << encoded_value
|
|
522
|
+
else
|
|
523
|
+
''
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def id_value
|
|
528
|
+
return @id_value if defined?(@id_value) && !@id_value.nil?
|
|
529
|
+
|
|
530
|
+
self.class.const_get(:ID)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def encode_identifier_octets
|
|
534
|
+
id2octets.pack('C*')
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def id2octets
|
|
538
|
+
first_octet = CLASSES[asn1_class] | pc_bit
|
|
539
|
+
if id < MULTI_OCTETS_ID
|
|
540
|
+
[first_octet | id]
|
|
541
|
+
else
|
|
542
|
+
[first_octet | MULTI_OCTETS_ID] + unsigned_to_chained_octets(id)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Encode an unsigned integer on multiple octets.
|
|
547
|
+
# Value is encoded on bit 6-0 of each octet, bit 7(MSB) indicates wether
|
|
548
|
+
# further octets follow.
|
|
549
|
+
def unsigned_to_chained_octets(value)
|
|
550
|
+
ary = []
|
|
551
|
+
while value.positive?
|
|
552
|
+
ary.unshift(value & 0x7f | 0x80)
|
|
553
|
+
value >>= 7
|
|
554
|
+
end
|
|
555
|
+
ary[-1] &= 0x7f
|
|
556
|
+
ary
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def encode_size(size)
|
|
560
|
+
if size >= INDEFINITE_LENGTH
|
|
561
|
+
bytes = []
|
|
562
|
+
while size > 255
|
|
563
|
+
bytes.unshift(size & 0xff)
|
|
564
|
+
size >>= 8
|
|
565
|
+
end
|
|
566
|
+
bytes.unshift(size)
|
|
567
|
+
bytes.unshift(INDEFINITE_LENGTH | bytes.size)
|
|
568
|
+
bytes.pack('C*')
|
|
569
|
+
else
|
|
570
|
+
[size].pack('C')
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Check ID from +der+ is the one expected
|
|
575
|
+
# @return [Boolean] +true+ is ID is expected, +false+ if it is not but +self+ is {#optional?}
|
|
576
|
+
# or has a {#default} value.
|
|
577
|
+
# @raise [ASN1Error] ID was not expected, is not optional and has no default value
|
|
578
|
+
def check_id(der) # rubocop:disable Naming/PredicateMethod
|
|
579
|
+
expected_id = encode_identifier_octets
|
|
580
|
+
real_id = der[0, expected_id.size]
|
|
581
|
+
return true if real_id == expected_id
|
|
582
|
+
|
|
583
|
+
if optional?
|
|
584
|
+
@no_value = true
|
|
585
|
+
@value = void_value
|
|
586
|
+
elsif !@default.nil?
|
|
587
|
+
@value = @default
|
|
588
|
+
else
|
|
589
|
+
raise_id_error(der)
|
|
590
|
+
end
|
|
591
|
+
false
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def get_data(der, ber)
|
|
595
|
+
return [0, ''] if der.nil? || der.empty?
|
|
596
|
+
|
|
597
|
+
length, length_length = get_length(der, ber)
|
|
598
|
+
|
|
599
|
+
data = der[1 + length_length, length]
|
|
600
|
+
@raw_length = der[0, length_length + 1]
|
|
601
|
+
@raw_data = data
|
|
602
|
+
|
|
603
|
+
total_length = 1 + length
|
|
604
|
+
total_length += length_length if length_length.positive?
|
|
605
|
+
|
|
606
|
+
[total_length, data]
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def get_length(der, ber)
|
|
610
|
+
length = der.unpack1('C').to_i
|
|
611
|
+
length_length = 0
|
|
612
|
+
|
|
613
|
+
if length == INDEFINITE_LENGTH
|
|
614
|
+
raise_on_indefinite_length(ber)
|
|
615
|
+
elsif length > INDEFINITE_LENGTH
|
|
616
|
+
length_length = length & 0x7f
|
|
617
|
+
length = der[1, length_length].unpack('C*')
|
|
618
|
+
.reduce(0) { |len, b| (len << 8) | b }
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
[length, length_length]
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def raise_on_indefinite_length(ber)
|
|
625
|
+
if primitive?
|
|
626
|
+
raise ASN1Error, "malformed #{type}: indefinite length " \
|
|
627
|
+
'forbidden for primitive types'
|
|
628
|
+
elsif ber
|
|
629
|
+
raise NotImplementedError, 'indefinite length not supported'
|
|
630
|
+
else
|
|
631
|
+
raise ASN1Error, 'indefinite length forbidden in DER encoding'
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def explicit_type
|
|
636
|
+
self.class.new(name: name)
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def raise_id_error(der)
|
|
640
|
+
msg = name.nil? ? +'' : "#{name}: "
|
|
641
|
+
msg << "Expected #{self2name} but get #{der2name(der)}"
|
|
642
|
+
|
|
643
|
+
raise ASN1Error, msg
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def self2name
|
|
647
|
+
name = "#{colorize_class(asn1_class.to_s.upcase)} #{colorize_attribute(constructed? ? 'CONSTRUCTED' : 'PRIMITIVE')}"
|
|
648
|
+
if implicit? || explicit?
|
|
649
|
+
name << ' 0x%X (0x%s)' % [id, bin2hex(encode_identifier_octets)]
|
|
650
|
+
else
|
|
651
|
+
name << ' ' << colorize_class(self.class.type)
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def der2name(der)
|
|
656
|
+
return 'no ID' if der.nil? || der.empty?
|
|
657
|
+
|
|
658
|
+
asn1_class, pc, id, id_size = Types.decode_identifier_octets(der)
|
|
659
|
+
name = "#{asn1_class.to_s.upcase} #{pc.to_s.upcase}"
|
|
660
|
+
type = find_type(id)
|
|
661
|
+
name << " #{type.nil? ? "#{colorize(bin2hex(der[0...id_size])).magenta}" : colorize(type.encoded_type).green}"
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def find_type(id)
|
|
665
|
+
Types.constants.map { |c| Types.const_get(c) }
|
|
666
|
+
.select { |klass| klass < Primitive || klass < Constructed }
|
|
667
|
+
.find { |klass| id == klass::ID }
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def bin2hex(str)
|
|
671
|
+
str.unpack1('H*')
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
end
|