rasn1 0.6.8 → 0.7.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/.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
|