rasn1 0.6.8 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -3
- data/Changelog.md +11 -0
- data/README.md +11 -2
- data/lib/rasn1.rb +6 -5
- data/lib/rasn1/model.rb +83 -33
- data/lib/rasn1/types.rb +11 -8
- data/lib/rasn1/types/any.rb +16 -16
- data/lib/rasn1/types/base.rb +73 -66
- data/lib/rasn1/types/bit_string.rb +14 -16
- data/lib/rasn1/types/boolean.rb +15 -12
- data/lib/rasn1/types/choice.rb +9 -14
- data/lib/rasn1/types/constructed.rb +8 -9
- data/lib/rasn1/types/enumerated.rb +3 -1
- data/lib/rasn1/types/generalized_time.rb +23 -25
- data/lib/rasn1/types/ia5string.rb +6 -4
- data/lib/rasn1/types/integer.rb +21 -19
- data/lib/rasn1/types/null.rb +7 -7
- data/lib/rasn1/types/numeric_string.rb +5 -3
- data/lib/rasn1/types/object_id.rb +12 -11
- data/lib/rasn1/types/octet_string.rb +5 -5
- data/lib/rasn1/types/primitive.rb +2 -1
- data/lib/rasn1/types/printable_string.rb +5 -3
- data/lib/rasn1/types/sequence.rb +20 -6
- data/lib/rasn1/types/sequence_of.rb +13 -11
- data/lib/rasn1/types/set.rb +3 -1
- data/lib/rasn1/types/set_of.rb +3 -2
- data/lib/rasn1/types/utc_time.rb +5 -3
- data/lib/rasn1/types/utf8_string.rb +5 -3
- data/lib/rasn1/types/visible_string.rb +3 -2
- data/lib/rasn1/version.rb +3 -1
- data/rasn1.gemspec +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc411854088bf4835982774db3a4037c1760c9cb2088653fb2dc92d82d7788c
|
4
|
+
data.tar.gz: e81586c34344dfa3c3c26868e6689ebf841c964eff6c80dd0705288617818143
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aecf47fe0ebb5d79546cf63c514359d4477fbd0c3a543aaebe1619f45cd4f8604beeff8ca614a59444a68ddc6aa8901ead9160b3cb7026ead301ea2d6935ed27
|
7
|
+
data.tar.gz: edea58f24e5931deb04a5f038d8c9f0b2945bb4c90892acfa264b29c9f530b0e6534b25a41173a57fd002f7da7bfa52f1820d172dfe91ddaf4257a73df5ab9d1
|
data/.rubocop.yml
CHANGED
@@ -6,6 +6,8 @@ Lint/Void:
|
|
6
6
|
Enabled: false
|
7
7
|
Metrics:
|
8
8
|
Enabled: false
|
9
|
+
Naming/AccessorMethodName:
|
10
|
+
Enabled: false
|
9
11
|
Style/AsciiComments:
|
10
12
|
Enabled: false
|
11
13
|
Style/Encoding:
|
@@ -20,6 +22,8 @@ Style/PerlBackrefs:
|
|
20
22
|
Enabled: false
|
21
23
|
Style/RedundantSelf:
|
22
24
|
Enabled: false
|
25
|
+
Style/RegexpLiteral:
|
26
|
+
EnforcedStyle: slashes
|
23
27
|
Style/StructInheritance:
|
24
28
|
Enabled: false
|
25
29
|
Style/TrailingCommaInArrayLiteral:
|
data/.travis.yml
CHANGED
data/Changelog.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Rasn1 Changelog
|
2
|
+
|
3
|
+
## 0.7.0
|
4
|
+
|
5
|
+
* add RASN1::Model#value to get value of a (potentially nested) element.
|
6
|
+
* add RASN1::Types::Sequence#[]. Access to element by index or by name.
|
7
|
+
* add frozen_string literal on all ruby files.
|
8
|
+
* optimize RASN1::Types.tag2type.
|
9
|
+
* refactoring of RASN1::Types::Base and RASN1::Types::Boolean.
|
10
|
+
* fix bugs:
|
11
|
+
* RASN1::Types::Base#initialize_copy raises on ruby 2.3 when @value and/or @default were nil, true, false of Integer.
|
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,7 +43,7 @@ module RASN1
|
|
42
43
|
# @return [Types::Base]
|
43
44
|
def self.parse(der, ber: false)
|
44
45
|
root = nil
|
45
|
-
|
46
|
+
until der.empty?
|
46
47
|
type = Types.tag2type(der[0].ord)
|
47
48
|
type.parse!(der, ber: ber)
|
48
49
|
root = type if root.nil?
|
@@ -50,7 +51,7 @@ module RASN1
|
|
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,7 +59,6 @@ module RASN1
|
|
58
59
|
# this method.
|
59
60
|
# @author Sylvain Daubert
|
60
61
|
class Model
|
61
|
-
|
62
62
|
class << self
|
63
63
|
# @return [Hash]
|
64
64
|
attr_reader :options
|
@@ -92,7 +92,7 @@ module RASN1
|
|
92
92
|
# @return [void]
|
93
93
|
def inherited(klass)
|
94
94
|
super
|
95
|
-
root =
|
95
|
+
root = @root
|
96
96
|
klass.class_eval { @root = root }
|
97
97
|
end
|
98
98
|
|
@@ -108,16 +108,16 @@ module RASN1
|
|
108
108
|
# @param [Symbol,String] name name of object in model
|
109
109
|
# @param [Hash] options
|
110
110
|
# @see Types::Choice#initialize
|
111
|
-
%w
|
111
|
+
%w[sequence set choice].each do |type|
|
112
112
|
class_eval "def #{type}(name, options={})\n" \
|
113
113
|
" options.merge!(name: name)\n" \
|
114
|
-
" proc =
|
114
|
+
" proc = proc do |opts|\n" \
|
115
115
|
" Types::#{type.capitalize}.new(options.merge(opts))\n" \
|
116
116
|
" end\n" \
|
117
117
|
" @root = [name, proc]\n" \
|
118
118
|
" @root << options[:content] unless options[:content].nil?\n" \
|
119
119
|
" @root\n" \
|
120
|
-
|
120
|
+
'end'
|
121
121
|
end
|
122
122
|
|
123
123
|
# @method sequence_of(name, type, options)
|
@@ -130,15 +130,15 @@ module RASN1
|
|
130
130
|
# @param [Model, Types::Base] type type for SET OF
|
131
131
|
# @param [Hash] options
|
132
132
|
# @see Types::SetOf#initialize
|
133
|
-
%w
|
133
|
+
%w[sequence set].each do |type|
|
134
134
|
klass_name = "Types::#{type.capitalize}Of"
|
135
135
|
class_eval "def #{type}_of(name, type, options={})\n" \
|
136
136
|
" options.merge!(name: name)\n" \
|
137
|
-
" proc =
|
137
|
+
" proc = proc do |opts|\n" \
|
138
138
|
" #{klass_name}.new(type, options.merge(opts))\n" \
|
139
139
|
" end\n" \
|
140
140
|
" @root = [name, proc]\n" \
|
141
|
-
|
141
|
+
'end'
|
142
142
|
end
|
143
143
|
|
144
144
|
# @method boolean(name, options)
|
@@ -187,14 +187,15 @@ module RASN1
|
|
187
187
|
# @see Types::IA5String#initialize
|
188
188
|
Types.primitives.each do |prim|
|
189
189
|
next if prim == Types::ObjectId
|
190
|
+
|
190
191
|
method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
|
191
192
|
class_eval "def #{method_name}(name, options={})\n" \
|
192
193
|
" options.merge!(name: name)\n" \
|
193
|
-
" proc =
|
194
|
-
" #{prim
|
194
|
+
" proc = proc do |opts|\n" \
|
195
|
+
" #{prim}.new(options.merge(opts))\n" \
|
195
196
|
" end\n" \
|
196
197
|
" @root = [name, proc]\n" \
|
197
|
-
|
198
|
+
'end'
|
198
199
|
end
|
199
200
|
|
200
201
|
# @param [Symbol,String] name name of object in model
|
@@ -204,7 +205,7 @@ module RASN1
|
|
204
205
|
# @see Types::ObjectId#initialize
|
205
206
|
def objectid(name, options={})
|
206
207
|
options.merge!(name: name)
|
207
|
-
proc =
|
208
|
+
proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
|
208
209
|
@root = [name, proc]
|
209
210
|
end
|
210
211
|
|
@@ -213,7 +214,7 @@ module RASN1
|
|
213
214
|
# @see Types::Any#initialize
|
214
215
|
def any(name, options={})
|
215
216
|
options.merge!(name: name)
|
216
|
-
proc =
|
217
|
+
proc = proc { |opts| Types::Any.new(options.merge(opts)) }
|
217
218
|
@root = [name, proc]
|
218
219
|
end
|
219
220
|
|
@@ -221,6 +222,7 @@ module RASN1
|
|
221
222
|
# @return [String]
|
222
223
|
def type
|
223
224
|
return @type if defined? @type
|
225
|
+
|
224
226
|
@type = self.to_s.gsub(/.*::/, '')
|
225
227
|
end
|
226
228
|
|
@@ -256,7 +258,8 @@ module RASN1
|
|
256
258
|
# @param [Object] value
|
257
259
|
# @return [Object] value
|
258
260
|
def []=(name, value)
|
259
|
-
raise Error,
|
261
|
+
raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
|
262
|
+
|
260
263
|
@elements[name].value = value
|
261
264
|
end
|
262
265
|
|
@@ -304,6 +307,32 @@ module RASN1
|
|
304
307
|
@elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber)
|
305
308
|
end
|
306
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
|
+
|
307
336
|
# Delegate some methods to root element
|
308
337
|
# @param [Symbol] meth
|
309
338
|
def method_missing(meth, *args)
|
@@ -314,6 +343,11 @@ module RASN1
|
|
314
343
|
end
|
315
344
|
end
|
316
345
|
|
346
|
+
# @return [Boolean]
|
347
|
+
def respond_to_missing?(meth, *)
|
348
|
+
@elements[@root].respond_to?(meth) || super
|
349
|
+
end
|
350
|
+
|
317
351
|
# @return [String]
|
318
352
|
def inspect(level=0)
|
319
353
|
' ' * level + "(#{type}) #{root.inspect(-level)}"
|
@@ -326,14 +360,29 @@ module RASN1
|
|
326
360
|
(other.class == self.class) && (other.to_der == self.to_der)
|
327
361
|
end
|
328
362
|
|
329
|
-
|
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
|
+
keys.each 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
|
330
378
|
|
331
|
-
|
332
|
-
[Types::Sequence, Types::Set].include? el.class
|
379
|
+
nil
|
333
380
|
end
|
334
381
|
|
335
|
-
|
336
|
-
|
382
|
+
private
|
383
|
+
|
384
|
+
def composed?(elt)
|
385
|
+
[Types::Sequence, Types::Set].include? elt.class
|
337
386
|
end
|
338
387
|
|
339
388
|
def get_type(proc_or_class, options={})
|
@@ -353,16 +402,16 @@ module RASN1
|
|
353
402
|
root
|
354
403
|
end
|
355
404
|
|
356
|
-
def set_elements(name,
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
subel
|
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
|
+
if composed?(subel) && content2.is_a?(Array)
|
412
|
+
set_elements(name2, proc_or_class, content2)
|
365
413
|
end
|
414
|
+
subel
|
366
415
|
end
|
367
416
|
end
|
368
417
|
|
@@ -410,10 +459,11 @@ module RASN1
|
|
410
459
|
end
|
411
460
|
when Types::Sequence
|
412
461
|
seq = my_element.value.map do |el|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
462
|
+
next if el.optional? && el.value.nil?
|
463
|
+
|
464
|
+
name = el.is_a?(Model) ? @elements.key(el) : el.name
|
465
|
+
[name, private_to_h(el)]
|
466
|
+
end
|
417
467
|
seq.compact!
|
418
468
|
Hash[seq]
|
419
469
|
else
|
data/lib/rasn1/types.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
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 }
|
18
19
|
end
|
19
20
|
|
20
21
|
# Give ASN.1 type from an integer. If +tag+ is unknown, return a {Types::Base}
|
@@ -23,17 +24,19 @@ module RASN1
|
|
23
24
|
# @return [Types::Base]
|
24
25
|
# @raise [ASN1Error] +tag+ is out of range
|
25
26
|
def self.tag2type(tag)
|
26
|
-
raise ASN1Error,
|
27
|
+
raise ASN1Error, 'tag is out of range' if tag > 0xff
|
27
28
|
|
28
|
-
|
29
|
+
unless defined? @tag2types
|
29
30
|
constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
|
30
31
|
primitives = self.primitives - [Types::Enumerated]
|
31
|
-
ary =
|
32
|
+
ary = (primitives + constructed).map do |type|
|
32
33
|
next unless type.const_defined? :TAG
|
34
|
+
|
33
35
|
[type::TAG, type]
|
34
36
|
end
|
35
37
|
@tag2types = Hash[ary]
|
36
38
|
@tag2types.default = Types::Base
|
39
|
+
@tag2types.freeze
|
37
40
|
end
|
38
41
|
|
39
42
|
klass = @tag2types[tag & 0xdf] # Remove CONSTRUCTED bit
|
data/lib/rasn1/types/any.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 ANY: accepts any types
|
5
6
|
#
|
6
7
|
# If `any#value` is `nil` and Any object is not {#optional?}, `any` will be encoded as a {Null} object.
|
7
8
|
# @author Sylvain Daubert
|
8
9
|
class Any < Base
|
9
|
-
|
10
10
|
# @return [String] DER-formated string
|
11
11
|
def to_der
|
12
12
|
case @value
|
@@ -25,30 +25,30 @@ module RASN1
|
|
25
25
|
# @param [Boolean] ber if +true+, accept BER encoding
|
26
26
|
# @return [Integer] total number of parsed bytes
|
27
27
|
def parse!(der, ber: false)
|
28
|
-
if der.nil?
|
28
|
+
if der.nil? || der.empty?
|
29
29
|
return 0 if optional?
|
30
30
|
|
31
|
-
raise ASN1Error,
|
31
|
+
raise ASN1Error, 'Expected ANY but get nothing'
|
32
32
|
end
|
33
33
|
|
34
|
-
total_length,
|
34
|
+
total_length, = get_data(der, ber)
|
35
35
|
@value = der[0, total_length]
|
36
36
|
total_length
|
37
37
|
end
|
38
38
|
|
39
39
|
def inspect(level=0)
|
40
|
-
|
41
|
-
str
|
40
|
+
lvl = level >= 0 ? level : 0
|
41
|
+
str = ' ' * lvl
|
42
42
|
str << "#{@name} " unless @name.nil?
|
43
|
-
if @value.nil?
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
43
|
+
str << if @value.nil?
|
44
|
+
'(ANY) NULL'
|
45
|
+
elsif @value.is_a?(OctetString) || @value.is_a?(BitString)
|
46
|
+
"(ANY) #{@value.type}: #{value.value.inspect}"
|
47
|
+
elsif @value.class < Base
|
48
|
+
"(ANY) #{@value.type}: #{value.value}"
|
49
|
+
else
|
50
|
+
"ANY: #{value.to_s.inspect}"
|
51
|
+
end
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|