rasn1 0.6.8 → 0.9.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.
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 SEQUENCE OF
5
6
  #
6
7
  # A SEQUENCE OF is an array of one ASN.1 type.
@@ -45,7 +46,8 @@ module RASN1
45
46
  # seqof[0][:int].value # => 12
46
47
  # @author Sylvain Daubert
47
48
  class SequenceOf < Constructed
48
- TAG = Sequence::TAG
49
+ # SequenceOf id value
50
+ ID = Sequence::ID
49
51
 
50
52
  # @return [Class, Base]
51
53
  attr_reader :of_type
@@ -62,40 +64,27 @@ module RASN1
62
64
  def initialize(of_type, options={})
63
65
  super(options)
64
66
  @of_type = of_type
65
- @value = []
67
+ @no_value = false
66
68
  end
67
69
 
68
70
  def initialize_copy(other)
69
71
  super
70
72
  @of_type = @of_type.dup
71
- @value = @value.map { |v| v.dup }
73
+ @value = @value.map(&:dup)
74
+ end
75
+
76
+ # @return [Array]
77
+ def void_value
78
+ []
72
79
  end
73
80
 
74
81
  # Add an item to SEQUENCE OF
75
82
  # @param [Array,Hash, Model]
76
83
  def <<(obj)
77
- if of_type_class < Primitive
78
- raise ASN1Error, 'object to add should be an Array' unless obj.is_a?(Array)
79
- @value += obj.map { |item| @of_type.new(item) }
80
- elsif composed_of_type?
81
- raise ASN1Error, 'object to add should be an Array' unless obj.is_a?(Array)
82
- new_value = of_type_class.new
83
- @of_type.value.each_with_index do |type, i|
84
- type2 = type.dup
85
- type2.value = obj[i]
86
- new_value.value << type2
87
- end
88
- @value << new_value
89
- elsif of_type_class < Model
90
- case obj
91
- when Hash
92
- @value << @of_type.new(obj)
93
- when of_type_class
94
- @value << obj
95
- else
96
- raise ASN1Error, "object to add should be a #{of_type_class} or a Hash"
97
- end
98
- end
84
+ return push_array_of_primitive(obj) if of_type_class < Primitive
85
+ return push_composed_array(obj) if composed_of_type?
86
+
87
+ push_model(obj)
99
88
  end
100
89
 
101
90
  # Get element of index +idx+
@@ -112,17 +101,15 @@ module RASN1
112
101
  end
113
102
 
114
103
  def inspect(level=0)
115
- str = ''
116
- str << ' ' * level if level > 0
117
- str << "#{@name} " unless @name.nil?
118
- level = level.abs
119
- str << "#{type}:\n"
120
- level += 1
104
+ str = common_inspect(level)
105
+ str << "\n"
106
+ level = level.abs + 1
121
107
  @value.each do |item|
122
108
  case item
123
109
  when Base, Model
124
- next if item.optional? and item.value.nil?
125
- str << "#{item.inspect(level)}"
110
+ next if item.optional? && item.value.nil?
111
+
112
+ str << item.inspect(level)
126
113
  str << "\n" unless str.end_with?("\n")
127
114
  else
128
115
  str << ' ' * level + "#{item.inspect}\n"
@@ -138,24 +125,22 @@ module RASN1
138
125
  end
139
126
 
140
127
  def composed_of_type?
141
- [Sequence, Set].include? of_type_class
128
+ !@of_type.is_a?(Class) && [Sequence, Set].include?(of_type_class)
142
129
  end
143
130
 
144
131
  def value_to_der
145
- @value.map { |v| v.to_der }.join
132
+ @value.map(&:to_der).join
146
133
  end
147
134
 
148
- def der_to_value(der, ber:false)
135
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
149
136
  @value = []
150
137
  nb_bytes = 0
151
138
 
152
139
  while nb_bytes < der.length
153
- type = if composed_of_type?
140
+ type = if composed_of_type? && !@of_type.is_a?(Class)
154
141
  @of_type.dup
155
- elsif of_type_class < Model
156
- of_type_class.new
157
142
  else
158
- of_type_class.new(:t)
143
+ of_type_class.new
159
144
  end
160
145
  nb_bytes += type.parse!(der[nb_bytes, der.length])
161
146
  @value << type
@@ -165,6 +150,35 @@ module RASN1
165
150
  def explicit_type
166
151
  self.class.new(self.of_type)
167
152
  end
153
+
154
+ def push_array_of_primitive(obj)
155
+ raise ASN1Error, 'object to add should be an Array' unless obj.is_a?(Array)
156
+
157
+ @value += obj.map { |item| of_type_class.new(value: item) }
158
+ end
159
+
160
+ def push_composed_array(obj)
161
+ raise ASN1Error, 'object to add should be an Array' unless obj.is_a?(Array)
162
+
163
+ new_value = of_type_class.new
164
+ @of_type.value.each_with_index do |type, i|
165
+ type2 = type.dup
166
+ type2.value = obj[i]
167
+ new_value.value << type2
168
+ end
169
+ @value << new_value
170
+ end
171
+
172
+ def push_model(obj)
173
+ case obj
174
+ when Hash
175
+ @value << of_type_class.new(obj)
176
+ when of_type_class
177
+ @value << obj
178
+ else
179
+ raise ASN1Error, "object to add should be a #{of_type_class} or a Hash"
180
+ end
181
+ end
168
182
  end
169
183
  end
170
184
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 set
5
6
  #
6
7
  # A set is a collection of another ASN.1 types.
@@ -20,7 +21,8 @@ module RASN1
20
21
  # ]
21
22
  # @author Sylvain Daubert
22
23
  class Set < Sequence
23
- TAG = 0x11
24
+ # Set id value
25
+ ID = 0x11
24
26
  end
25
27
  end
26
28
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 SET OF
5
6
  # @author Sylvain Daubert
6
7
  class SetOf < SequenceOf
7
- TAG = Set::TAG
8
+ # SetOf id value
9
+ ID = Set::ID
8
10
 
9
11
  # A SET OF is encoded as a SET.
10
12
  # @return ['SET']
@@ -14,4 +16,3 @@ module RASN1
14
16
  end
15
17
  end
16
18
  end
17
-
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
 
3
5
  module RASN1
4
6
  module Types
5
-
6
7
  # ASN.1 UTCTime
7
8
  #
8
9
  # +{#value} of a +UtcTime+ should be a ruby Time.
@@ -17,7 +18,8 @@ module RASN1
17
18
  # not.
18
19
  # @author Sylvain Daubert
19
20
  class UtcTime < Primitive
20
- TAG = 23
21
+ # UtcTime id value
22
+ ID = 23
21
23
 
22
24
  # Get ASN.1 type
23
25
  # @return [String]
@@ -26,12 +28,12 @@ module RASN1
26
28
  end
27
29
 
28
30
  private
29
-
31
+
30
32
  def value_to_der
31
33
  @value.getutc.strftime('%y%m%d%H%M%SZ')
32
34
  end
33
-
34
- def der_to_value(der, ber: false)
35
+
36
+ def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
35
37
  format = case der.size
36
38
  when 11
37
39
  '%Y%m%d%H%MZ'
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 UTF8 String
5
6
  # @author Sylvain Daubert
6
7
  class Utf8String < OctetString
7
- TAG = 12
8
+ # Utf8String id value
9
+ ID = 12
8
10
 
9
11
  # Get ASN.1 type
10
12
  # @return [String]
@@ -13,14 +15,14 @@ module RASN1
13
15
  end
14
16
 
15
17
  private
16
-
18
+
17
19
  def value_to_der
18
20
  @value.to_s.force_encoding('UTF-8').force_encoding('BINARY')
19
21
  end
20
22
 
21
- def der_to_value(der, ber:false)
23
+ def der_to_value(der, ber: false)
22
24
  super
23
- @value.force_encoding('UTF-8')
25
+ @value = der.force_encoding('UTF-8')
24
26
  end
25
27
  end
26
28
  end
@@ -1,17 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
4
  module Types
3
-
4
5
  # ASN.1 Visible String
5
6
  # @author Sylvain Daubert
6
7
  class VisibleString < IA5String
7
- TAG = 26
8
+ # VisibleString id value
9
+ ID = 26
8
10
 
9
11
  # Get ASN.1 type
10
12
  # @return [String]
11
13
  def self.type
12
14
  'VisibleString'
13
15
  end
14
-
15
16
  end
16
17
  end
17
18
  end
data/lib/rasn1/types.rb CHANGED
@@ -1,48 +1,78 @@
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
- select { |klass| klass < Primitive }
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
- select { |klass| klass < Constructed }
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.unpack1('C').to_i
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
+ der.bytes.each_with_index do |octet, i|
35
+ next if i.zero?
36
+
37
+ id = (id << 7) | (octet & 0x7f)
38
+ break i + 1 if (octet & 0x80).zero?
39
+ end
40
+ else
41
+ 1
42
+ end
43
+
44
+ [asn1_class, pc, id, size]
18
45
  end
19
46
 
20
- # Give ASN.1 type from an integer. If +tag+ is unknown, return a {Types::Base}
47
+ # Give ASN.1 type from a DER string. If ID is unknown, return a {Types::Base}
21
48
  # object.
22
- # @param [Integer] tag
49
+ # @param [String] der
23
50
  # @return [Types::Base]
24
51
  # @raise [ASN1Error] +tag+ is out of range
25
- def self.tag2type(tag)
26
- raise ASN1Error, "tag is out of range" if tag > 0xff
27
-
28
- if !defined? @tag2types
29
- constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
30
- primitives = self.primitives - [Types::Enumerated]
31
- ary = [primitives, constructed].flatten.map do |type|
32
- next unless type.const_defined? :TAG
33
- [type::TAG, type]
34
- end
35
- @tag2types = Hash[ary]
36
- @tag2types.default = Types::Base
37
- end
52
+ def self.id2type(der)
53
+ # Define a cache for well-known ASN.1 types
54
+ self.generate_id2type_cache unless defined? @id2types
38
55
 
39
- klass = @tag2types[tag & 0xdf] # Remove CONSTRUCTED bit
40
- is_constructed = (tag & 0x20) == 0x20
41
- asn1class = Types::Base::CLASSES.key(tag & 0xc0)
56
+ asn1class, pc, id, = self.decode_identifier_octets(der)
57
+ # cache_id: check versus class and 5 LSB bits
58
+ cache_id = der.unpack1('C') & 0xdf
59
+ klass = cache_id < Types::Base::MULTI_OCTETS_ID ? @id2types[id] : Types::Base
60
+ is_constructed = (pc == :constructed)
42
61
  options = { class: asn1class, constructed: is_constructed }
43
- options[:tag_value] = (tag & 0x1f) if klass == Types::Base
62
+ options[:tag_value] = id if klass == Types::Base
44
63
  klass.new(options)
45
64
  end
65
+
66
+ # @private Generate cache for {.id2type}
67
+ def self.generate_id2type_cache
68
+ constructed = self.constructed - [Types::SequenceOf, Types::SetOf]
69
+ primitives = self.primitives - [Types::Enumerated]
70
+ ary = (primitives + constructed).select { |type| type.const_defined? :ID }
71
+ .map { |type| [type::ID, type] }
72
+ @id2types = ary.to_h
73
+ @id2types.default = Types::Base
74
+ @id2types.freeze
75
+ end
46
76
  end
47
77
  end
48
78
 
data/lib/rasn1/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RASN1
2
- VERSION = '0.6.8'
4
+ VERSION = '0.9.0'
3
5
  end
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 #@name: #chosen not set"
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
- while der.size > 0
46
- type = Types.tag2type(der[0].ord)
46
+ until der.empty?
47
+ type = Types.id2type(der)
47
48
  type.parse!(der, ber: ber)
48
- root = type if root.nil?
49
+ root ||= type
49
50
 
50
51
  if [Types::Sequence, Types::Set].include? type.class
51
52
  subder = type.value
52
53
  ary = []
53
- while subder.size > 0
54
+ until subder.empty?
54
55
  ary << self.parse(subder)
55
56
  subder = subder[ary.last.to_der.size..-1]
56
57
  end
metadata CHANGED
@@ -1,57 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasn1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.8
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvain Daubert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-11 00:00:00.000000000 Z
11
+ date: 2021-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: yard
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.9'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.9'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.13'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.13'
41
13
  - !ruby/object:Gem::Dependency
42
14
  name: rake
43
15
  requirement: !ruby/object:Gem::Requirement
44
16
  requirements:
45
17
  - - "~>"
46
18
  - !ruby/object:Gem::Version
47
- version: '10.0'
19
+ version: '12.3'
48
20
  type: :development
49
21
  prerelease: false
50
22
  version_requirements: !ruby/object:Gem::Requirement
51
23
  requirements:
52
24
  - - "~>"
53
25
  - !ruby/object:Gem::Version
54
- version: '10.0'
26
+ version: '12.3'
55
27
  - !ruby/object:Gem::Dependency
56
28
  name: rspec
57
29
  requirement: !ruby/object:Gem::Requirement
@@ -67,36 +39,32 @@ dependencies:
67
39
  - !ruby/object:Gem::Version
68
40
  version: '3.0'
69
41
  - !ruby/object:Gem::Dependency
70
- name: simplecov
42
+ name: yard
71
43
  requirement: !ruby/object:Gem::Requirement
72
44
  requirements:
73
45
  - - "~>"
74
46
  - !ruby/object:Gem::Version
75
- version: '0.14'
47
+ version: '0.9'
76
48
  type: :development
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
80
52
  - - "~>"
81
53
  - !ruby/object:Gem::Version
82
- version: '0.14'
83
- description: |2
84
-
85
- RASN1 is a pure ruby ASN.1 library. It may encode and decode DER and BER
86
- encodings.
54
+ version: '0.9'
55
+ description: |
56
+ RASN1 is a pure ruby ASN.1 library. It may encode and decode DER and BER
57
+ encodings.
87
58
  email:
88
59
  - sylvain.daubert@laposte.net
89
60
  executables: []
90
61
  extensions: []
91
- extra_rdoc_files: []
62
+ extra_rdoc_files:
63
+ - README.md
64
+ - LICENSE
92
65
  files:
93
- - ".gitignore"
94
- - ".rubocop.yml"
95
- - ".travis.yml"
96
- - Gemfile
97
66
  - LICENSE
98
67
  - README.md
99
- - Rakefile
100
68
  - lib/rasn1.rb
101
69
  - lib/rasn1/model.rb
102
70
  - lib/rasn1/types.rb
@@ -124,28 +92,36 @@ files:
124
92
  - lib/rasn1/types/utf8_string.rb
125
93
  - lib/rasn1/types/visible_string.rb
126
94
  - lib/rasn1/version.rb
127
- - rasn1.gemspec
128
95
  homepage: https://github.com/sdaubert/rasn1
129
96
  licenses:
130
97
  - MIT
131
- metadata: {}
98
+ metadata:
99
+ homepage_uri: https://github.com/sdaubert/rasn1
100
+ source_code_uri: https://github.com/sdaubert/rasn1
101
+ bug_tracker_uri: https://github.com/sdaubert/rasn1/issues
102
+ documentation_uri: https://www.rubydoc.info/gems/rasn1
132
103
  post_install_message:
133
- rdoc_options: []
104
+ rdoc_options:
105
+ - "--title"
106
+ - RASN1 - A pure ruby ASN.1 library
107
+ - "--main"
108
+ - README.md
109
+ - "--inline-source"
110
+ - "--quiet"
134
111
  require_paths:
135
112
  - lib
136
113
  required_ruby_version: !ruby/object:Gem::Requirement
137
114
  requirements:
138
115
  - - ">="
139
116
  - !ruby/object:Gem::Version
140
- version: 2.3.0
117
+ version: 2.5.0
141
118
  required_rubygems_version: !ruby/object:Gem::Requirement
142
119
  requirements:
143
120
  - - ">="
144
121
  - !ruby/object:Gem::Version
145
122
  version: '0'
146
123
  requirements: []
147
- rubyforge_project:
148
- rubygems_version: 2.7.6
124
+ rubygems_version: 3.2.5
149
125
  signing_key:
150
126
  specification_version: 4
151
127
  summary: Ruby ASN.1 library
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- *~
2
- /.bundle/
3
- /.yardoc
4
- /Gemfile.lock
5
- /_yardoc/
6
- /coverage/
7
- /doc/
8
- /pkg/
9
- /spec/reports/
10
- /tmp/
11
- /vendor/
data/.rubocop.yml DELETED
@@ -1,28 +0,0 @@
1
- Layout/SpaceAroundEqualsInParameterDefault:
2
- EnforcedStyle: no_space
3
- Lint/EmptyWhen:
4
- Enabled: false
5
- Lint/Void:
6
- Enabled: false
7
- Metrics:
8
- Enabled: false
9
- Style/AsciiComments:
10
- Enabled: false
11
- Style/Encoding:
12
- Enabled: false
13
- Style/EvalWithLocation:
14
- Enabled: false
15
- Style/FormatString:
16
- EnforcedStyle: percent
17
- Style/FormatStringToken:
18
- EnforcedStyle: unannotated
19
- Style/PerlBackrefs:
20
- Enabled: false
21
- Style/RedundantSelf:
22
- Enabled: false
23
- Style/StructInheritance:
24
- Enabled: false
25
- Style/TrailingCommaInArrayLiteral:
26
- Enabled: false
27
- Style/TrailingCommaInHashLiteral:
28
- Enabled: false
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- install:
7
- - bundle install --path vendor/bundle --jobs=3 --retry=3
8
- script:
9
- - bundle exec rake
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rasn1.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
- require 'yard'
4
-
5
- RSpec::Core::RakeTask.new
6
- YARD::Rake::YardocTask.new do |t|
7
- t.files = ['lib/**/*.rb', '-', 'README.md', 'LICENSE']
8
- t.options = %w(--no-private)
9
- end
10
-
11
- task :default => :spec
12
-