rasn1 0.6.6 → 0.8.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 +4 -4
- data/README.md +11 -2
- data/lib/rasn1.rb +7 -6
- data/lib/rasn1/model.rb +108 -59
- data/lib/rasn1/types.rb +49 -20
- data/lib/rasn1/types/any.rb +26 -16
- data/lib/rasn1/types/base.rb +146 -115
- data/lib/rasn1/types/bit_string.rb +20 -24
- data/lib/rasn1/types/boolean.rb +17 -14
- data/lib/rasn1/types/choice.rb +11 -16
- data/lib/rasn1/types/constructed.rb +9 -9
- data/lib/rasn1/types/enumerated.rb +4 -2
- data/lib/rasn1/types/generalized_time.rb +24 -26
- data/lib/rasn1/types/ia5string.rb +7 -5
- data/lib/rasn1/types/integer.rb +32 -45
- data/lib/rasn1/types/null.rb +8 -8
- data/lib/rasn1/types/numeric_string.rb +7 -7
- data/lib/rasn1/types/object_id.rb +18 -48
- data/lib/rasn1/types/octet_string.rb +6 -6
- data/lib/rasn1/types/primitive.rb +2 -1
- data/lib/rasn1/types/printable_string.rb +8 -7
- data/lib/rasn1/types/sequence.rb +21 -7
- data/lib/rasn1/types/sequence_of.rb +15 -13
- data/lib/rasn1/types/set.rb +4 -2
- data/lib/rasn1/types/set_of.rb +4 -3
- data/lib/rasn1/types/utc_time.rb +6 -4
- data/lib/rasn1/types/utf8_string.rb +6 -4
- data/lib/rasn1/types/visible_string.rb +4 -3
- data/lib/rasn1/version.rb +3 -1
- metadata +27 -50
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -29
- data/.travis.yml +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -12
- data/rasn1.gemspec +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4eb9957878c25e54a394fee02aab5a42aa1201ad6250f7f6a2a3992128c87d6
|
4
|
+
data.tar.gz: 3b3d3c85d619ee7f78923d7f7c839874f329087b0b1a4094839b059daf94c2ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9eb501aacf629355e56a4103dfc3e354bfa035c031a23f2dcc61f8add191a13960cd64ea68db36340694f3b20aba316c0088ebe91943d795405b11489be9342e
|
7
|
+
data.tar.gz: 115cef9248e5251a24ec0bb6d6e977084cfb4ff78118a7f5be31ee8fea05a0c831b935fa72c18bdc08e71bb729a85cff2b394f4a84aafd56245f70908f0a4dc6
|
data/README.md
CHANGED
@@ -15,13 +15,22 @@ gem 'rasn1'
|
|
15
15
|
|
16
16
|
And then execute:
|
17
17
|
|
18
|
-
$ bundle
|
18
|
+
$ bundle install
|
19
19
|
|
20
20
|
Or install it yourself as:
|
21
21
|
|
22
22
|
$ gem install rasn1
|
23
23
|
|
24
|
-
##
|
24
|
+
## Simple usage
|
25
|
+
|
26
|
+
To decode a DER/BER string without checking a model, do:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
decoded_der = RASN1.parse(der_string)
|
30
|
+
decoded_ber = RASN1.parse(ber_string, ber: true)
|
31
|
+
```
|
32
|
+
|
33
|
+
## Advanced usage
|
25
34
|
All examples below will be based on:
|
26
35
|
|
27
36
|
```
|
data/lib/rasn1.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rasn1/version'
|
2
4
|
require 'rasn1/types'
|
3
5
|
require 'rasn1/model'
|
@@ -5,7 +7,6 @@ require 'rasn1/model'
|
|
5
7
|
# Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
|
6
8
|
# @author Sylvain Daubert
|
7
9
|
module RASN1
|
8
|
-
|
9
10
|
# Base error class
|
10
11
|
class Error < StandardError; end
|
11
12
|
|
@@ -26,10 +27,10 @@ module RASN1
|
|
26
27
|
# CHOICE error: #chosen not set
|
27
28
|
class ChoiceError < RASN1::Error
|
28
29
|
def message
|
29
|
-
"CHOICE
|
30
|
+
"CHOICE #{@name}: #chosen not set"
|
30
31
|
end
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
# Parse a DER/BER string without checking a model
|
34
35
|
# @note If you want to check ASN.1 grammary, you should define a {Model}
|
35
36
|
# and use {Model#parse}.
|
@@ -42,15 +43,15 @@ module RASN1
|
|
42
43
|
# @return [Types::Base]
|
43
44
|
def self.parse(der, ber: false)
|
44
45
|
root = nil
|
45
|
-
|
46
|
-
type = Types.
|
46
|
+
until der.empty?
|
47
|
+
type = Types.id2type(der)
|
47
48
|
type.parse!(der, ber: ber)
|
48
49
|
root = type if root.nil?
|
49
50
|
|
50
51
|
if [Types::Sequence, Types::Set].include? type.class
|
51
52
|
subder = type.value
|
52
53
|
ary = []
|
53
|
-
|
54
|
+
until subder.empty?
|
54
55
|
ary << self.parse(subder)
|
55
56
|
subder = subder[ary.last.to_der.size..-1]
|
56
57
|
end
|
data/lib/rasn1/model.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module RASN1
|
3
4
|
# @abstract
|
4
5
|
# {Model} class is a base class to define ASN.1 models.
|
5
6
|
# == Create a simple ASN.1 model
|
@@ -58,8 +59,9 @@ module RASN1
|
|
58
59
|
# this method.
|
59
60
|
# @author Sylvain Daubert
|
60
61
|
class Model
|
61
|
-
|
62
62
|
class << self
|
63
|
+
# @return [Hash]
|
64
|
+
attr_reader :options
|
63
65
|
|
64
66
|
# Use another model in this model
|
65
67
|
# @param [String,Symbol] name
|
@@ -106,16 +108,16 @@ module RASN1
|
|
106
108
|
# @param [Symbol,String] name name of object in model
|
107
109
|
# @param [Hash] options
|
108
110
|
# @see Types::Choice#initialize
|
109
|
-
%w
|
111
|
+
%w[sequence set choice].each do |type|
|
110
112
|
class_eval "def #{type}(name, options={})\n" \
|
111
113
|
" options.merge!(name: name)\n" \
|
112
|
-
" proc =
|
114
|
+
" proc = proc do |opts|\n" \
|
113
115
|
" Types::#{type.capitalize}.new(options.merge(opts))\n" \
|
114
116
|
" end\n" \
|
115
117
|
" @root = [name, proc]\n" \
|
116
118
|
" @root << options[:content] unless options[:content].nil?\n" \
|
117
119
|
" @root\n" \
|
118
|
-
|
120
|
+
'end'
|
119
121
|
end
|
120
122
|
|
121
123
|
# @method sequence_of(name, type, options)
|
@@ -128,15 +130,15 @@ module RASN1
|
|
128
130
|
# @param [Model, Types::Base] type type for SET OF
|
129
131
|
# @param [Hash] options
|
130
132
|
# @see Types::SetOf#initialize
|
131
|
-
%w
|
133
|
+
%w[sequence set].each do |type|
|
132
134
|
klass_name = "Types::#{type.capitalize}Of"
|
133
135
|
class_eval "def #{type}_of(name, type, options={})\n" \
|
134
136
|
" options.merge!(name: name)\n" \
|
135
|
-
" proc =
|
137
|
+
" proc = proc do |opts|\n" \
|
136
138
|
" #{klass_name}.new(type, options.merge(opts))\n" \
|
137
139
|
" end\n" \
|
138
140
|
" @root = [name, proc]\n" \
|
139
|
-
|
141
|
+
'end'
|
140
142
|
end
|
141
143
|
|
142
144
|
# @method boolean(name, options)
|
@@ -185,14 +187,15 @@ module RASN1
|
|
185
187
|
# @see Types::IA5String#initialize
|
186
188
|
Types.primitives.each do |prim|
|
187
189
|
next if prim == Types::ObjectId
|
190
|
+
|
188
191
|
method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
|
189
192
|
class_eval "def #{method_name}(name, options={})\n" \
|
190
193
|
" options.merge!(name: name)\n" \
|
191
|
-
" proc =
|
192
|
-
" #{prim
|
194
|
+
" proc = proc do |opts|\n" \
|
195
|
+
" #{prim}.new(options.merge(opts))\n" \
|
193
196
|
" end\n" \
|
194
197
|
" @root = [name, proc]\n" \
|
195
|
-
|
198
|
+
'end'
|
196
199
|
end
|
197
200
|
|
198
201
|
# @param [Symbol,String] name name of object in model
|
@@ -201,8 +204,8 @@ module RASN1
|
|
201
204
|
# +Object#object_id+.
|
202
205
|
# @see Types::ObjectId#initialize
|
203
206
|
def objectid(name, options={})
|
204
|
-
options
|
205
|
-
proc =
|
207
|
+
options[:name] = name
|
208
|
+
proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
|
206
209
|
@root = [name, proc]
|
207
210
|
end
|
208
211
|
|
@@ -210,15 +213,16 @@ module RASN1
|
|
210
213
|
# @param [Hash] options
|
211
214
|
# @see Types::Any#initialize
|
212
215
|
def any(name, options={})
|
213
|
-
options
|
214
|
-
proc =
|
216
|
+
options[:name] = name
|
217
|
+
proc = proc { |opts| Types::Any.new(options.merge(opts)) }
|
215
218
|
@root = [name, proc]
|
216
219
|
end
|
217
220
|
|
218
221
|
# Give type name (aka class name)
|
219
222
|
# @return [String]
|
220
223
|
def type
|
221
|
-
return @type if @type
|
224
|
+
return @type if defined? @type
|
225
|
+
|
222
226
|
@type = self.to_s.gsub(/.*::/, '')
|
223
227
|
end
|
224
228
|
|
@@ -254,7 +258,8 @@ module RASN1
|
|
254
258
|
# @param [Object] value
|
255
259
|
# @return [Object] value
|
256
260
|
def []=(name, value)
|
257
|
-
raise Error,
|
261
|
+
raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
|
262
|
+
|
258
263
|
@elements[name].value = value
|
259
264
|
end
|
260
265
|
|
@@ -302,6 +307,32 @@ module RASN1
|
|
302
307
|
@elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber)
|
303
308
|
end
|
304
309
|
|
310
|
+
# @overload value
|
311
|
+
# Get value of root element
|
312
|
+
# @return [Object,nil]
|
313
|
+
# @overload value(name)
|
314
|
+
# Direct access to the value of +name+ (nested) element of model.
|
315
|
+
# @param [String,Symbol] name
|
316
|
+
# @return [Object,nil]
|
317
|
+
# @return [Object,nil]
|
318
|
+
def value(name=nil, *args)
|
319
|
+
if name.nil?
|
320
|
+
@elements[@root].value
|
321
|
+
else
|
322
|
+
elt = by_name(name)
|
323
|
+
|
324
|
+
unless args.empty?
|
325
|
+
elt = elt.root if elt.is_a?(Model)
|
326
|
+
args.each do |arg|
|
327
|
+
elt = elt.root if elt.is_a?(Model)
|
328
|
+
elt = elt[arg]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
elt.value
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
305
336
|
# Delegate some methods to root element
|
306
337
|
# @param [Symbol] meth
|
307
338
|
def method_missing(meth, *args)
|
@@ -312,6 +343,11 @@ module RASN1
|
|
312
343
|
end
|
313
344
|
end
|
314
345
|
|
346
|
+
# @return [Boolean]
|
347
|
+
def respond_to_missing?(meth, *)
|
348
|
+
@elements[@root].respond_to?(meth) || super
|
349
|
+
end
|
350
|
+
|
315
351
|
# @return [String]
|
316
352
|
def inspect(level=0)
|
317
353
|
' ' * level + "(#{type}) #{root.inspect(-level)}"
|
@@ -324,14 +360,29 @@ module RASN1
|
|
324
360
|
(other.class == self.class) && (other.to_der == self.to_der)
|
325
361
|
end
|
326
362
|
|
327
|
-
|
363
|
+
protected
|
364
|
+
|
365
|
+
# Give a (nested) element from its name
|
366
|
+
# @param [String, Symbol] name
|
367
|
+
# @return [Model, Types::Base]
|
368
|
+
def by_name(name)
|
369
|
+
elt = self[name]
|
370
|
+
return elt unless elt.nil?
|
371
|
+
|
372
|
+
@elements.each_key do |subelt_name|
|
373
|
+
if self[subelt_name].is_a?(Model)
|
374
|
+
elt = self[subelt_name][name]
|
375
|
+
return elt unless elt.nil?
|
376
|
+
end
|
377
|
+
end
|
328
378
|
|
329
|
-
|
330
|
-
[Types::Sequence, Types::Set].include? el.class
|
379
|
+
nil
|
331
380
|
end
|
332
381
|
|
333
|
-
|
334
|
-
|
382
|
+
private
|
383
|
+
|
384
|
+
def composed?(elt)
|
385
|
+
[Types::Sequence, Types::Set].include? elt.class
|
335
386
|
end
|
336
387
|
|
337
388
|
def get_type(proc_or_class, options={})
|
@@ -347,50 +398,47 @@ module RASN1
|
|
347
398
|
root = self.class.class_eval { @root }
|
348
399
|
@root = root[0]
|
349
400
|
@elements = {}
|
350
|
-
@elements[@root] = get_type(root[1], self.class.
|
401
|
+
@elements[@root] = get_type(root[1], self.class.options || {})
|
351
402
|
root
|
352
403
|
end
|
353
404
|
|
354
|
-
def set_elements(name,
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
subel
|
363
|
-
end
|
405
|
+
def set_elements(name, _elt, content=nil)
|
406
|
+
return unless content.is_a? Array
|
407
|
+
|
408
|
+
@elements[name].value = content.map do |name2, proc_or_class, content2|
|
409
|
+
subel = get_type(proc_or_class)
|
410
|
+
@elements[name2] = subel
|
411
|
+
set_elements(name2, proc_or_class, content2) if composed?(subel) && content2.is_a?(Array)
|
412
|
+
subel
|
364
413
|
end
|
365
414
|
end
|
366
415
|
|
367
416
|
def initialize_elements(obj, args)
|
368
417
|
args.each do |name, value|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
end
|
386
|
-
else
|
387
|
-
value.each do |el|
|
388
|
-
obj[name] << el
|
389
|
-
end
|
418
|
+
next unless obj[name]
|
419
|
+
|
420
|
+
case value
|
421
|
+
when Hash
|
422
|
+
raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model
|
423
|
+
|
424
|
+
initialize_elements obj[name], value
|
425
|
+
when Array
|
426
|
+
composed = if obj[name].is_a? Model
|
427
|
+
obj[name].root
|
428
|
+
else
|
429
|
+
obj[name]
|
430
|
+
end
|
431
|
+
if composed.of_type.is_a? Model
|
432
|
+
value.each do |el|
|
433
|
+
composed << initialize_elements(composed.of_type.class.new, el)
|
390
434
|
end
|
391
435
|
else
|
392
|
-
|
436
|
+
value.each do |el|
|
437
|
+
obj[name] << el
|
438
|
+
end
|
393
439
|
end
|
440
|
+
else
|
441
|
+
obj[name].value = value
|
394
442
|
end
|
395
443
|
end
|
396
444
|
end
|
@@ -408,10 +456,11 @@ module RASN1
|
|
408
456
|
end
|
409
457
|
when Types::Sequence
|
410
458
|
seq = my_element.value.map do |el|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
459
|
+
next if el.optional? && el.value.nil?
|
460
|
+
|
461
|
+
name = el.is_a?(Model) ? @elements.key(el) : el.name
|
462
|
+
[name, private_to_h(el)]
|
463
|
+
end
|
415
464
|
seq.compact!
|
416
465
|
Hash[seq]
|
417
466
|
else
|
data/lib/rasn1/types.rb
CHANGED
@@ -1,46 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
# This modules is a namesapce for all ASN.1 type classes.
|
3
5
|
# @author Sylvain Daubert
|
4
6
|
module Types
|
5
|
-
|
6
7
|
# Give all primitive types
|
7
8
|
# @return [Array<Types::Primitive>]
|
8
9
|
def self.primitives
|
9
|
-
@primitives ||= self.constants.map { |c| Types.const_get(c) }
|
10
|
-
|
10
|
+
@primitives ||= self.constants.map { |c| Types.const_get(c) }
|
11
|
+
.select { |klass| klass < Primitive }
|
11
12
|
end
|
12
13
|
|
13
14
|
# Give all constructed types
|
14
15
|
# @return [Array<Types::Constructed>]
|
15
16
|
def self.constructed
|
16
|
-
@constructed ||= self.constants.map { |c| Types.const_get(c) }
|
17
|
-
|
17
|
+
@constructed ||= self.constants.map { |c| Types.const_get(c) }
|
18
|
+
.select { |klass| klass < Constructed }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
# Decode a DER string to extract identifier octets.
|
23
|
+
# @param [String] der
|
24
|
+
# @return [Array] Return ASN.1 class as Symbol, contructed/primitive as Symbol,
|
25
|
+
# ID and size of identifier octets
|
26
|
+
def self.decode_identifier_octets(der)
|
27
|
+
first_octet = der[0].unpack1('C')
|
28
|
+
asn1_class = Types::Base::CLASSES.key(first_octet & Types::Base::CLASS_MASK)
|
29
|
+
pc = (first_octet & Types::Constructed::ASN1_PC).positive? ? :constructed : :primitive
|
30
|
+
id = first_octet & Types::Base::MULTI_OCTETS_ID
|
31
|
+
|
32
|
+
size = if id == Types::Base::MULTI_OCTETS_ID
|
33
|
+
id = 0
|
34
|
+
1.upto(der.size - 1) do |i|
|
35
|
+
octet = der[i].unpack1('C')
|
36
|
+
id = (id << 7) | (octet & 0x7f)
|
37
|
+
break i + 1 if (octet & 0x80).zero?
|
38
|
+
end
|
39
|
+
else
|
40
|
+
1
|
41
|
+
end
|
42
|
+
|
43
|
+
[asn1_class, pc, id, size]
|
18
44
|
end
|
19
45
|
|
20
|
-
# Give ASN.1 type from
|
46
|
+
# Give ASN.1 type from a DER string. If ID is unknown, return a {Types::Base}
|
21
47
|
# object.
|
22
|
-
# @param [
|
48
|
+
# @param [String] der
|
23
49
|
# @return [Types::Base]
|
24
50
|
# @raise [ASN1Error] +tag+ is out of range
|
25
|
-
def self.
|
26
|
-
|
27
|
-
|
28
|
-
if !defined? @tag2types
|
51
|
+
def self.id2type(der)
|
52
|
+
# Define a cache for well-known ASN.1 types
|
53
|
+
unless defined? @id2types
|
29
54
|
constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
|
30
55
|
primitives = self.primitives - [Types::Enumerated]
|
31
|
-
ary =
|
32
|
-
next unless type.const_defined? :
|
33
|
-
|
56
|
+
ary = (primitives + constructed).map do |type|
|
57
|
+
next unless type.const_defined? :ID
|
58
|
+
|
59
|
+
[type::ID, type]
|
34
60
|
end
|
35
|
-
@
|
36
|
-
@
|
61
|
+
@id2types = Hash[ary]
|
62
|
+
@id2types.default = Types::Base
|
63
|
+
@id2types.freeze
|
37
64
|
end
|
38
65
|
|
39
|
-
|
40
|
-
|
41
|
-
|
66
|
+
asn1class, pc, id, = self.decode_identifier_octets(der)
|
67
|
+
# cache_id: check versus class and 5 LSB bits
|
68
|
+
cache_id = der.unpack1('C') & 0xdf
|
69
|
+
klass = cache_id < Types::Base::MULTI_OCTETS_ID ? @id2types[id] : Types::Base
|
70
|
+
is_constructed = (pc == :constructed)
|
42
71
|
options = { class: asn1class, constructed: is_constructed }
|
43
|
-
options[:tag_value] =
|
72
|
+
options[:tag_value] = id if klass == Types::Base
|
44
73
|
klass.new(options)
|
45
74
|
end
|
46
75
|
end
|