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