rasn1 0.6.6 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|