rasn1 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/Rakefile +12 -0
- data/lib/rasn1.rb +32 -0
- data/lib/rasn1/model.rb +197 -0
- data/lib/rasn1/types.rb +37 -0
- data/lib/rasn1/types/base.rb +347 -0
- data/lib/rasn1/types/bit_string.rb +70 -0
- data/lib/rasn1/types/boolean.rb +36 -0
- data/lib/rasn1/types/choice.rb +97 -0
- data/lib/rasn1/types/constructed.rb +13 -0
- data/lib/rasn1/types/enumerated.rb +119 -0
- data/lib/rasn1/types/integer.rb +42 -0
- data/lib/rasn1/types/null.rb +19 -0
- data/lib/rasn1/types/object_id.rb +79 -0
- data/lib/rasn1/types/octet_string.rb +33 -0
- data/lib/rasn1/types/primitive.rb +12 -0
- data/lib/rasn1/types/sequence.rb +39 -0
- data/lib/rasn1/types/sequence_of.rb +85 -0
- data/lib/rasn1/types/set.rb +26 -0
- data/lib/rasn1/types/set_of.rb +11 -0
- data/lib/rasn1/types/set_spec.rb +61 -0
- data/lib/rasn1/types/utf8_string.rb +20 -0
- data/lib/rasn1/version.rb +3 -0
- data/rasn1.gemspec +29 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 66f3cf5edaaabbd17a920296e943f8e46f5de81c
|
4
|
+
data.tar.gz: a83fb189ba31da35c1dc1743a01675fb3ac270d1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa8459ba40745b387cdde298ff577707c63fd41ee4a2646b639878fcbb695638050910b94ec638ce7f010b8053140e30aa305a0634f3508c6fb8e6c7a8a337c7
|
7
|
+
data.tar.gz: a7bc698f83d23585dfd75b27cc46429c9f2ec84b6c36d77980fbb3d0423b8ead411cd9a700a4700b2164e117d247837d54771e66a5f8aaf03fa198b1336cb9b7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Sylvain Daubert
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Rasn1
|
2
|
+
|
3
|
+
Rasn1 will be a ruby ASN.1 library to encode, parse and decode ASN.1 data in DER format.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rasn1'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install rasn1
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
All examples below will be based on:
|
23
|
+
|
24
|
+
```
|
25
|
+
Record ::= SEQUENCE {
|
26
|
+
id INTEGER,
|
27
|
+
room [0] INTEGER OPTIONAL,
|
28
|
+
house [1] INTEGER DEFAULT 0
|
29
|
+
}
|
30
|
+
```
|
31
|
+
|
32
|
+
## Create a ASN.1 model
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class Record < RASN1::Model
|
36
|
+
sequence :record,
|
37
|
+
content: [integer(:id),
|
38
|
+
integer(:room, implicit: 0, optional: true),
|
39
|
+
integer(:house, implicit: 1, default: 0)]
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
More comple classes may be designed by nesting simple classes. For example:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class ComplexRecord < RASN1::Model
|
47
|
+
sequence :cplx_record,
|
48
|
+
content: [boolean(:bool),
|
49
|
+
octet_string(:data, explicit: 0),
|
50
|
+
Record]
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
## Parse a DER-encoded string
|
55
|
+
```ruby
|
56
|
+
record = Record.parse(der_string)
|
57
|
+
record[:id] # => RASN1::Types::Integer
|
58
|
+
record[:id].value # => Integer
|
59
|
+
record[:id].to_i # => Integer
|
60
|
+
record[:id].asn1_class # => Symbol
|
61
|
+
record[:id].optional? # => false
|
62
|
+
record[:id].default # => nil
|
63
|
+
record[:room].optional # => true
|
64
|
+
record[:house].default # => 0
|
65
|
+
|
66
|
+
record[:id].to_der # => String
|
67
|
+
```
|
68
|
+
|
69
|
+
## Generate a DER-encoded string
|
70
|
+
```ruby
|
71
|
+
record = Record.new(id: 12)
|
72
|
+
record[:id].to_i # => 12
|
73
|
+
record[:room] # => nil
|
74
|
+
record[:house] # => 0
|
75
|
+
|
76
|
+
# Set one value
|
77
|
+
record[:room] = 43
|
78
|
+
record[:room] # => 43
|
79
|
+
|
80
|
+
# Set mulitple values
|
81
|
+
record.set id: 124, house: 155
|
82
|
+
|
83
|
+
record.to_der # => String
|
84
|
+
```
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/rasn1.
|
89
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
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
|
+
|
data/lib/rasn1.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rasn1/version'
|
2
|
+
require 'rasn1/types'
|
3
|
+
require 'rasn1/model'
|
4
|
+
|
5
|
+
# Rasn1 is a pure ruby library to parse, decode and encode ASN.1 data.
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
module RASN1
|
8
|
+
|
9
|
+
# Base error class
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
# ASN.1 encoding/decoding error
|
13
|
+
class ASN1Error < Error; end
|
14
|
+
|
15
|
+
# ASN.1 class error
|
16
|
+
class ClassError < Error
|
17
|
+
# @return [String]
|
18
|
+
def message
|
19
|
+
"Tag class should be a symbol: #{Types::Base::CLASSES.keys.join(', ')}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enumerated error
|
24
|
+
class EnumeratedError < Error; end
|
25
|
+
|
26
|
+
# CHOICE error: #chosen not set
|
27
|
+
class ChoiceError < RASN1::Error
|
28
|
+
def message
|
29
|
+
"CHOICE #@name: #chosen not set"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/rasn1/model.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
module RASN1
|
2
|
+
|
3
|
+
# @abstract
|
4
|
+
# {Model} class is a base class to define ASN.1 models.
|
5
|
+
# == Create a simple ASN.1 model
|
6
|
+
# Given this ASN.1 example:
|
7
|
+
# Record ::= SEQUENCE {
|
8
|
+
# id INTEGER,
|
9
|
+
# room [0] IMPLICIT INTEGER OPTIONAL,
|
10
|
+
# house [1] EXPLICIT INTEGER DEFAULT 0
|
11
|
+
# }
|
12
|
+
# you may create your model like this:
|
13
|
+
# class Record < RASN1::Model
|
14
|
+
# sequence(:record,
|
15
|
+
# content: [integer(:id),
|
16
|
+
# integer(:room, implicit: 0, optional: true),
|
17
|
+
# integer(:house, explicit: 1, default: 0)])
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# === Parse a DER-encoded string
|
21
|
+
# record = Record.parse(der_string)
|
22
|
+
# record[:id] # => RASN1::Types::Integer
|
23
|
+
# record[:id].value # => Integer
|
24
|
+
# record[:id].to_i # => Integer
|
25
|
+
# record[:id].asn1_class # => Symbol
|
26
|
+
# record[:id].optional? # => false
|
27
|
+
# record[:id].default # => nil
|
28
|
+
# record[:room].optional # => true
|
29
|
+
# record[:house].default # => 0
|
30
|
+
#
|
31
|
+
# You may also parse a BER-encoded string this way:
|
32
|
+
# record = Record.parse(der_string, ber: true)
|
33
|
+
#
|
34
|
+
# === Generate a DER-encoded string
|
35
|
+
# record = Record.new(id: 12, room: 24)
|
36
|
+
# record.to_der
|
37
|
+
#
|
38
|
+
# == Create a more complex model
|
39
|
+
# Models may be nested. For example:
|
40
|
+
# class Record2 < RASN1::Model
|
41
|
+
# sequence(:record2,
|
42
|
+
# content: [boolean(:rented, default: false),
|
43
|
+
# Record])
|
44
|
+
# end
|
45
|
+
# Set values like this:
|
46
|
+
# record2 = Record2.new
|
47
|
+
# record2[:rented] = true
|
48
|
+
# record2[:record][:id] = 65537
|
49
|
+
# record2[:record][:room] = 43
|
50
|
+
# or like this:
|
51
|
+
# record2 = Record2.new(rented: true, record: { id: 65537, room: 43 })
|
52
|
+
# @author Sylvain Daubert
|
53
|
+
class Model
|
54
|
+
|
55
|
+
class << self
|
56
|
+
|
57
|
+
# Define a SEQUENCE type. Should be used to define a new subclass of {Model}.
|
58
|
+
# @param [Hash] options
|
59
|
+
# @option options [Array] :content
|
60
|
+
# @see Types::Sequence#initialize
|
61
|
+
# @return [Types::Sequence]
|
62
|
+
def sequence(name, options={})
|
63
|
+
@records ||= {}
|
64
|
+
@records[name] = Proc.new do
|
65
|
+
seq = Types::Sequence.new(name, options)
|
66
|
+
seq.value = options[:content] if options[:content]
|
67
|
+
seq
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# define all class methods to instance a ASN.1 TAG
|
72
|
+
Types.primitives.each do |prim|
|
73
|
+
class_eval "def #{prim.type.downcase.gsub(/\s+/, '_')}(name, options={})\n" \
|
74
|
+
" Proc.new { #{prim.to_s}.new(name, options) }\n" \
|
75
|
+
"end"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Parse a DER/BER encoded string
|
79
|
+
# @param [String] str
|
80
|
+
# @param [Boolean] ber accept BER encoding or not
|
81
|
+
# @return [Model]
|
82
|
+
def parse(str, ber: false)
|
83
|
+
model = new
|
84
|
+
model.parse! str, ber: ber
|
85
|
+
model
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Create a new instance of a {Model}
|
90
|
+
# @param [Hash] args
|
91
|
+
def initialize(args={})
|
92
|
+
set_elements
|
93
|
+
initialize_elements self, args
|
94
|
+
end
|
95
|
+
|
96
|
+
# Give access to element +name+ in model
|
97
|
+
# @param [String,Symbol] name
|
98
|
+
# @return [Types::Base]
|
99
|
+
def [](name)
|
100
|
+
@elements[name]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get name frm root type
|
104
|
+
# @return [String,Symbol]
|
105
|
+
def name
|
106
|
+
@elements[@root].name
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return a hash image of model
|
110
|
+
# @return [Hash]
|
111
|
+
def to_h
|
112
|
+
{ @root => private_to_h(@elements[@root]) }
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String]
|
116
|
+
def to_der
|
117
|
+
@elements[@root].to_der
|
118
|
+
end
|
119
|
+
|
120
|
+
# Parse a DER/BER encoded string, and modify object in-place.
|
121
|
+
# @param [String] str
|
122
|
+
# @param [Boolean] ber accept BER encoding or not
|
123
|
+
# @return [Integer] number of parsed bytes
|
124
|
+
def parse!(str, ber: false)
|
125
|
+
@elements[@root].parse!(str, ber: ber)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def set_elements(element=nil)
|
131
|
+
if element.nil?
|
132
|
+
records = self.class.class_eval { @records }
|
133
|
+
@root = records.keys.first
|
134
|
+
@elements = {}
|
135
|
+
@elements[@root] = records[@root].call
|
136
|
+
if @elements[@root].value.is_a? Array
|
137
|
+
@elements[@root].value = @elements[@root].value.map do |subel|
|
138
|
+
se = case subel
|
139
|
+
when Proc
|
140
|
+
subel.call
|
141
|
+
when Class
|
142
|
+
subel.new
|
143
|
+
end
|
144
|
+
@elements[se.name] = se
|
145
|
+
set_elements se if se.is_a? Types::Sequence
|
146
|
+
se
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
element.value.map! do |subel|
|
151
|
+
se = case subel
|
152
|
+
when Proc
|
153
|
+
subel.call
|
154
|
+
when Class
|
155
|
+
subel.new
|
156
|
+
end
|
157
|
+
@elements[se.name] = se
|
158
|
+
set_elements se if se.is_a? Types::Sequence
|
159
|
+
se
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def initialize_elements(obj, args)
|
165
|
+
args.each do |name, value|
|
166
|
+
if obj[name]
|
167
|
+
if value.is_a? Hash
|
168
|
+
if obj[name].is_a? Model
|
169
|
+
initialize_elements obj[name], value
|
170
|
+
else
|
171
|
+
raise ArgumentError, "element #{name}: may only pass a Hash for Sequence elements"
|
172
|
+
end
|
173
|
+
else
|
174
|
+
obj[name].value = value
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def private_to_h(element)
|
181
|
+
h = {}
|
182
|
+
element.value.each do |subel|
|
183
|
+
h[subel.name] = case subel
|
184
|
+
when Types::Sequence
|
185
|
+
private_to_h(subel)
|
186
|
+
when Model
|
187
|
+
subel.to_h[subel.name]
|
188
|
+
else
|
189
|
+
next if subel.value.nil? and subel.optional?
|
190
|
+
subel.value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
h
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
data/lib/rasn1/types.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module RASN1
|
2
|
+
# This modules is a namesapce for all ASN.1 type classes.
|
3
|
+
# @author Sylvain Daubert
|
4
|
+
module Types
|
5
|
+
|
6
|
+
# Give all primitive types
|
7
|
+
# @return [Array<Types::Primitive>]
|
8
|
+
def self.primitives
|
9
|
+
self.constants.map { |c| Types.const_get(c) }.
|
10
|
+
select { |klass| klass < Primitive }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Give all constructed types
|
14
|
+
# @return [Array<Types::Constructed>]
|
15
|
+
def self.constructed
|
16
|
+
self.constants.map { |c| Types.const_get(c) }.
|
17
|
+
select { |klass| klass < Constructed }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require_relative 'types/base'
|
23
|
+
require_relative 'types/primitive'
|
24
|
+
require_relative 'types/boolean'
|
25
|
+
require_relative 'types/integer'
|
26
|
+
require_relative 'types/bit_string'
|
27
|
+
require_relative 'types/octet_string'
|
28
|
+
require_relative 'types/null'
|
29
|
+
require_relative 'types/object_id'
|
30
|
+
require_relative 'types/enumerated'
|
31
|
+
require_relative 'types/utf8_string'
|
32
|
+
require_relative 'types/constructed'
|
33
|
+
require_relative 'types/sequence'
|
34
|
+
require_relative 'types/sequence_of'
|
35
|
+
require_relative 'types/set'
|
36
|
+
require_relative 'types/set_of'
|
37
|
+
require_relative 'types/choice'
|
@@ -0,0 +1,347 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# @abstract This is base class for all ASN.1 types.
|
5
|
+
#
|
6
|
+
# Subclasses SHOULD define:
|
7
|
+
# * a TAG constant defining ASN.1 tag number,
|
8
|
+
# * a private method {#value_to_der} converting its {#value} to DER,
|
9
|
+
# * a private method {#der_to_value} converting DER into {#value}.
|
10
|
+
#
|
11
|
+
# ==Define an optional value
|
12
|
+
# An optional value may be defined using +:optional+ key from {#initialize}:
|
13
|
+
# Integer.new(:int, optional: true)
|
14
|
+
# An optional value implies:
|
15
|
+
# * while parsing, if decoded tag is not optional expected tag, no {ASN1Error}
|
16
|
+
# is raised, and parser tries net tag,
|
17
|
+
# * while encoding, if {#value} is +nil+, this value is not encoded.
|
18
|
+
# ==Define a default value
|
19
|
+
# A default value may be defined using +:default+ key from {#initialize}:
|
20
|
+
# Integer.new(:int, default: 0)
|
21
|
+
# A default value implies:
|
22
|
+
# * while parsing, if decoded tag is not expected tag, no {ASN1Error} is raised
|
23
|
+
# and parser sets default value to this tag. Then parser tries nex tag,
|
24
|
+
# * while encoding, if {#value} is equal to default value, this value is not
|
25
|
+
# encoded.
|
26
|
+
# ==Define a tagged value
|
27
|
+
# ASN.1 permits to define tagged values.
|
28
|
+
# By example:
|
29
|
+
# -- context specific tag
|
30
|
+
# CType ::= [0] EXPLICIT INTEGER
|
31
|
+
# -- application specific tag
|
32
|
+
# AType ::= [APPLICATION 1] EXPLICIT INTEGER
|
33
|
+
# -- private tag
|
34
|
+
# PType ::= [PRIVATE 2] EXPLICIT INTEGER
|
35
|
+
# These types may be defined as:
|
36
|
+
# ctype = RASN1::Types::Integer.new(:ctype, explicit: 0) # with explicit, default #asn1_class is :context
|
37
|
+
# atype = RASN1::Types::Integer.new(:atype, explicit: 1, class: :application)
|
38
|
+
# ptype = RASN1::Types::Integer.new(:ptype, explicit: 2, class: :private)
|
39
|
+
#
|
40
|
+
# Implicit tagged values may also be defined:
|
41
|
+
# ctype_implicit = RASN1::Types::Integer.new(:ctype, implicit: 0)
|
42
|
+
# @author Sylvain Daubert
|
43
|
+
class Base
|
44
|
+
# Allowed ASN.1 tag classes
|
45
|
+
CLASSES = {
|
46
|
+
universal: 0x00,
|
47
|
+
application: 0x40,
|
48
|
+
context: 0x80,
|
49
|
+
private: 0xc0
|
50
|
+
}
|
51
|
+
|
52
|
+
# Maximum ASN.1 tag number
|
53
|
+
MAX_TAG = 0x1e
|
54
|
+
|
55
|
+
# Length value for indefinite length
|
56
|
+
INDEFINITE_LENGTH = 0x80
|
57
|
+
|
58
|
+
# @return [Symbol, String]
|
59
|
+
attr_reader :name
|
60
|
+
# @return [Symbol]
|
61
|
+
attr_reader :asn1_class
|
62
|
+
# @return [Object,nil] default value, if defined
|
63
|
+
attr_reader :default
|
64
|
+
# @return [Object]
|
65
|
+
attr_writer :value
|
66
|
+
|
67
|
+
# Get ASN.1 type
|
68
|
+
# @return [String]
|
69
|
+
def self.type
|
70
|
+
return @type if @type
|
71
|
+
@type = self.to_s.gsub(/.*::/, '').gsub(/([a-z0-9])([A-Z])/, '\1 \2').upcase
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# @param [Symbol, String] name name for this tag in grammar
|
76
|
+
# @param [Hash] options
|
77
|
+
# @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+
|
78
|
+
# @option options [::Boolean] :optional define this tag as optional. Default
|
79
|
+
# is +false+
|
80
|
+
# @option options [Object] :default default value for DEFAULT tag
|
81
|
+
# @option options [Object] :value value to set
|
82
|
+
def initialize(name, options={})
|
83
|
+
@name = name
|
84
|
+
|
85
|
+
set_options options
|
86
|
+
end
|
87
|
+
|
88
|
+
# Used by +#dup+ and +#clone+. Deep copy @value.
|
89
|
+
def initialize_copy(other)
|
90
|
+
@value = @value.nil? ? nil : @value.dup
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get value or default value
|
94
|
+
def value
|
95
|
+
if @value.nil?
|
96
|
+
@default
|
97
|
+
else
|
98
|
+
@value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [::Boolean]
|
103
|
+
def optional?
|
104
|
+
@optional
|
105
|
+
end
|
106
|
+
|
107
|
+
# Say if this type is tagged or not
|
108
|
+
# @return [::Boolean]
|
109
|
+
def tagged?
|
110
|
+
!@tag.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
# Say if a tagged type is explicit
|
114
|
+
# @return [::Boolean,nil] return +nil+ if not tagged, return +true+
|
115
|
+
# if explicit, else +false+
|
116
|
+
def explicit?
|
117
|
+
@tag.nil? ? @tag : @tag == :explicit
|
118
|
+
end
|
119
|
+
|
120
|
+
# Say if a tagged type is implicit
|
121
|
+
# @return [::Boolean,nil] return +nil+ if not tagged, return +true+
|
122
|
+
# if implicit, else +false+
|
123
|
+
def implicit?
|
124
|
+
@tag.nil? ? @tag : @tag == :implicit
|
125
|
+
end
|
126
|
+
|
127
|
+
# @abstract This method SHOULD be partly implemented by subclasses, which
|
128
|
+
# SHOULD respond to +#value_to_der+.
|
129
|
+
# @return [String] DER-formated string
|
130
|
+
def to_der
|
131
|
+
if self.class.const_defined?('TAG')
|
132
|
+
build_tag
|
133
|
+
else
|
134
|
+
raise NotImplementedError, 'should be implemented by subclasses'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [::Boolean] +true+ if this is a primitive type
|
139
|
+
def primitive?
|
140
|
+
if self.class < Primitive
|
141
|
+
true
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [::Boolean] +true+ if this is a constructed type
|
148
|
+
def constructed?
|
149
|
+
if self.class < Constructed
|
150
|
+
true
|
151
|
+
else
|
152
|
+
false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get ASN.1 type
|
157
|
+
# @return [String]
|
158
|
+
def type
|
159
|
+
self.class.type
|
160
|
+
end
|
161
|
+
|
162
|
+
# Get tag value
|
163
|
+
# @return [Integer]
|
164
|
+
def tag
|
165
|
+
(@tag_value || self.class::TAG) | CLASSES[@asn1_class] | self.class::ASN1_PC
|
166
|
+
end
|
167
|
+
|
168
|
+
# @abstract This method SHOULD be partly implemented by subclasses to parse
|
169
|
+
# data. Subclasses SHOULD respond to +#der_to_value+.
|
170
|
+
# Parse a DER string. This method updates object.
|
171
|
+
# @param [String] der DER string
|
172
|
+
# @param [Boolean] ber if +true+, accept BER encoding
|
173
|
+
# @return [Integer] total number of parsed bytes
|
174
|
+
# @raise [ASN1Error] error on parsing
|
175
|
+
def parse!(der, ber: false)
|
176
|
+
return 0 unless check_tag(der)
|
177
|
+
|
178
|
+
total_length, data = get_data(der, ber)
|
179
|
+
if explicit?
|
180
|
+
type = self.class.new(@name)
|
181
|
+
type.parse!(data)
|
182
|
+
@value = type.value
|
183
|
+
else
|
184
|
+
der_to_value(data, ber: ber)
|
185
|
+
end
|
186
|
+
|
187
|
+
total_length
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def set_options(options)
|
194
|
+
set_class options[:class]
|
195
|
+
set_optional options[:optional]
|
196
|
+
set_default options[:default]
|
197
|
+
set_tag options
|
198
|
+
@value = options[:value]
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_class(asn1_class)
|
202
|
+
case asn1_class
|
203
|
+
when nil
|
204
|
+
@asn1_class = :universal
|
205
|
+
when Symbol
|
206
|
+
if CLASSES.keys.include? asn1_class
|
207
|
+
@asn1_class = asn1_class
|
208
|
+
else
|
209
|
+
raise ClassError
|
210
|
+
end
|
211
|
+
else
|
212
|
+
raise ClassError
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def set_optional(optional)
|
217
|
+
@optional = !!optional
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_default(default)
|
221
|
+
@default = default
|
222
|
+
end
|
223
|
+
|
224
|
+
def set_tag(options)
|
225
|
+
if options[:explicit]
|
226
|
+
@tag = :explicit
|
227
|
+
@tag_value = options[:explicit]
|
228
|
+
elsif options[:implicit]
|
229
|
+
@tag = :implicit
|
230
|
+
@tag_value = options[:implicit]
|
231
|
+
end
|
232
|
+
|
233
|
+
@asn1_class = :context if @tag and @asn1_class == :universal
|
234
|
+
end
|
235
|
+
|
236
|
+
def build_tag?
|
237
|
+
!(!@default.nil? and (@value.nil? or @value == @default)) and
|
238
|
+
!(optional? and @value.nil?)
|
239
|
+
end
|
240
|
+
|
241
|
+
def build_tag
|
242
|
+
if build_tag?
|
243
|
+
if explicit?
|
244
|
+
v = self.class.new(nil)
|
245
|
+
v.value = @value
|
246
|
+
encoded_value = v.to_der
|
247
|
+
else
|
248
|
+
encoded_value = value_to_der
|
249
|
+
end
|
250
|
+
encode_tag << encode_size(encoded_value.size) << encoded_value
|
251
|
+
else
|
252
|
+
''
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def encode_tag
|
257
|
+
if (@tag_value || self.class::TAG) <= MAX_TAG
|
258
|
+
[tag].pack('C')
|
259
|
+
else
|
260
|
+
raise ASN1Error, 'multi-byte tag value are not supported'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def encode_size(size)
|
265
|
+
if size >= INDEFINITE_LENGTH
|
266
|
+
bytes = []
|
267
|
+
while size > 255
|
268
|
+
bytes << (size & 0xff)
|
269
|
+
size >>= 8
|
270
|
+
end
|
271
|
+
bytes << size
|
272
|
+
bytes.reverse!
|
273
|
+
bytes.unshift(INDEFINITE_LENGTH | bytes.size)
|
274
|
+
bytes.pack('C*')
|
275
|
+
else
|
276
|
+
[size].pack('C')
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def check_tag(der)
|
281
|
+
tag = der[0, 1]
|
282
|
+
if tag != encode_tag
|
283
|
+
if optional?
|
284
|
+
@value = nil
|
285
|
+
elsif !@default.nil?
|
286
|
+
@value = @default
|
287
|
+
else
|
288
|
+
raise_tag_error(encode_tag, tag)
|
289
|
+
end
|
290
|
+
false
|
291
|
+
else
|
292
|
+
true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def get_data(der, ber)
|
297
|
+
length = der[1, 1].unpack('C').first
|
298
|
+
length_length = 0
|
299
|
+
|
300
|
+
if length == INDEFINITE_LENGTH
|
301
|
+
if primitive?
|
302
|
+
raise ASN1Error, "malformed #{type} TAG (#@name): indefinite length " \
|
303
|
+
"forbidden for primitive types"
|
304
|
+
else
|
305
|
+
if ber
|
306
|
+
raise NotImplementedError, "TAG #@name: indefinite length not " \
|
307
|
+
"supported yet"
|
308
|
+
else
|
309
|
+
raise ASN1Error, "TAG #@name: indefinite length forbidden in DER " \
|
310
|
+
"encoding"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
elsif length < INDEFINITE_LENGTH
|
314
|
+
data = der[2, length]
|
315
|
+
else
|
316
|
+
length_length = length & 0x7f
|
317
|
+
length = der[2, length_length].unpack('C*').
|
318
|
+
reduce(0) { |len, b| (len << 8) | b }
|
319
|
+
data = der[2 + length_length, length]
|
320
|
+
end
|
321
|
+
|
322
|
+
total_length = 2 + length
|
323
|
+
total_length += length_length if length_length > 0
|
324
|
+
|
325
|
+
[total_length, data]
|
326
|
+
end
|
327
|
+
|
328
|
+
def raise_tag_error(expected_tag, tag)
|
329
|
+
msg = "Expected tag #{tag2name(expected_tag)} but get #{tag2name(tag)}"
|
330
|
+
msg << " for #@name"
|
331
|
+
raise ASN1Error, msg
|
332
|
+
end
|
333
|
+
|
334
|
+
def tag2name(tag)
|
335
|
+
return 'no tag' if tag.nil? or tag.empty?
|
336
|
+
|
337
|
+
itag = tag.unpack('C').first
|
338
|
+
name = CLASSES.key(itag & 0xc0).to_s.upcase
|
339
|
+
name << " #{itag & Constructed::ASN1_PC > 0 ? 'CONSTRUCTED' : 'PRIMITIVE'}"
|
340
|
+
type = Types.constants.map { |c| Types.const_get(c) }.
|
341
|
+
select { |klass| klass < Primitive || klass < Constructed }.
|
342
|
+
find { |klass| klass::TAG == itag & 0x1f }
|
343
|
+
name << " #{type.nil? ? "0x%02X" % (itag & 0x1f) : type.type }"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|