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
@@ -0,0 +1,70 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Bit String
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class BitString < Primitive
|
7
|
+
TAG = 0x03
|
8
|
+
|
9
|
+
# @return [Integer] bit length of bit string
|
10
|
+
attr_accessor :bit_length
|
11
|
+
|
12
|
+
# @param [Symbol, String] name name for this tag in grammar
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+
|
15
|
+
# @option options [::Boolean] :optional define this tag as optional. Default
|
16
|
+
# is +false+
|
17
|
+
# @option options [Object] :default default value for DEFAULT tag
|
18
|
+
# @option options [Object] :bit_length default bit_length value. Should be
|
19
|
+
# present if +:default+ is set
|
20
|
+
def initialize(name, options={})
|
21
|
+
super
|
22
|
+
if @default
|
23
|
+
if options[:bit_length].nil?
|
24
|
+
raise ASN1Error, "TAG #@name: default bit length is not defined"
|
25
|
+
end
|
26
|
+
@default_bit_length = options[:bit_length]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_tag?
|
33
|
+
!(!@default.nil? and (@value.nil? or @value == @default and
|
34
|
+
@bit_length == @default_bit_length)) and
|
35
|
+
!(optional? and @value.nil?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def value_to_der
|
39
|
+
raise ASN1Error, "TAG #@name: bit length is not set" if @bit_length.nil?
|
40
|
+
|
41
|
+
while @value.length * 8 < @bit_length
|
42
|
+
@value << "\x00"
|
43
|
+
end
|
44
|
+
@value.force_encoding('BINARY')
|
45
|
+
|
46
|
+
if @value.length * 8 > @bit_length
|
47
|
+
max_len = @bit_length / 8 + (@bit_length % 8 > 0 ? 1 : 0)
|
48
|
+
@value = @value[0, max_len]
|
49
|
+
end
|
50
|
+
|
51
|
+
unused = @value.length * 8 - @bit_length
|
52
|
+
der = [unused, @value].pack('CA*')
|
53
|
+
|
54
|
+
if unused > 0
|
55
|
+
last_byte = @value[-1].unpack('C').first
|
56
|
+
last_byte &= (0xff >> unused) << unused
|
57
|
+
der[-1] = [last_byte].pack('C')
|
58
|
+
end
|
59
|
+
|
60
|
+
der
|
61
|
+
end
|
62
|
+
|
63
|
+
def der_to_value(der, ber:false)
|
64
|
+
unused, @value = der.unpack('CA*')
|
65
|
+
@bit_length = @value.length * 8 - unused
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Boolean
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class Boolean < Primitive
|
7
|
+
TAG = 0x01
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def value_to_der
|
12
|
+
[@value ? 0xff : 0x00].pack('C')
|
13
|
+
end
|
14
|
+
|
15
|
+
def der_to_value(der, ber: false)
|
16
|
+
unless der.size == 1
|
17
|
+
raise ASN1Error, "tag #@name: BOOLEAN should have a length of 1"
|
18
|
+
end
|
19
|
+
|
20
|
+
bool = der.unpack('C').first
|
21
|
+
case bool
|
22
|
+
when 0
|
23
|
+
@value = false
|
24
|
+
when 0xff
|
25
|
+
@value = true
|
26
|
+
else
|
27
|
+
if ber
|
28
|
+
@value = true
|
29
|
+
else
|
30
|
+
raise ASN1Error, "tag #@name: bad value 0x%02x for BOOLEAN" % bool
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# A ASN.1 CHOICE is a choice between different types.
|
5
|
+
#
|
6
|
+
# == Create a CHOICE
|
7
|
+
# A CHOICE is defined this way:
|
8
|
+
# choice = Choice.new(:a_choice)
|
9
|
+
# choice.value = [Integer.new(:int1, implicit: 0, class: :context),
|
10
|
+
# Integer.new(:int2, implicit: 1, class: :context),
|
11
|
+
# OctetString.new(:os, implicit: 2, class: :context)]
|
12
|
+
# The chosen type may be set this way:
|
13
|
+
# choice.chosen = 0 # choose :int1
|
14
|
+
# The chosen value may be set these ways:
|
15
|
+
# choise.value[choice.chosen].value = 1
|
16
|
+
# choise.set_chosen_value 1
|
17
|
+
# The chosen value may be got these ways:
|
18
|
+
# choise.value[choice.chosen].value # => 1
|
19
|
+
# choice.chosen_value # => 1
|
20
|
+
#
|
21
|
+
# == Encode a CHOICE
|
22
|
+
# {#to_der} only encodes the chosen value:
|
23
|
+
# choise.to_der # => "\x80\x01\x01"
|
24
|
+
#
|
25
|
+
# == Parse a CHOICE
|
26
|
+
# Parsing a CHOICE set {#chosen} and set value to chosen type. If parsed string does
|
27
|
+
# not contain a type from CHOICE, a {RASN1::ASN1Error} is raised.
|
28
|
+
# str = "\x04\x03abc"
|
29
|
+
# choice.parse! str
|
30
|
+
# choice.chosen # => 2
|
31
|
+
# choice.chosen_value # => "abc"
|
32
|
+
# @author Sylvain Daubert
|
33
|
+
class Choice < Base
|
34
|
+
|
35
|
+
# Chosen type
|
36
|
+
# @return [Integer] index of type in choice value
|
37
|
+
attr_accessor :chosen
|
38
|
+
|
39
|
+
# Set chosen value.
|
40
|
+
# @note {#chosen} MUST be set before calling this method
|
41
|
+
# @param [Object] value
|
42
|
+
# @return [Object] value
|
43
|
+
# @raise [ChoiceError] {#chosen} not set
|
44
|
+
def set_chosen_value(value)
|
45
|
+
check_chosen
|
46
|
+
@value[@chosen].value = value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get chosen value
|
50
|
+
# @note {#chosen} MUST be set before calling this method
|
51
|
+
# @return [Object] value
|
52
|
+
# @raise [ChoiceError] {#chosen} not set
|
53
|
+
def chosen_value
|
54
|
+
check_chosen
|
55
|
+
@value[@chosen].value
|
56
|
+
end
|
57
|
+
|
58
|
+
# @note {#chosen} MUST be set before calling this method
|
59
|
+
# @return [String] DER-formated string
|
60
|
+
# @raise [ChoiceError] {#chosen} not set
|
61
|
+
def to_der
|
62
|
+
check_chosen
|
63
|
+
@value[@chosen].to_der
|
64
|
+
end
|
65
|
+
|
66
|
+
# Parse a DER string. This method updates object by setting {#chosen} and
|
67
|
+
# chosen value.
|
68
|
+
# @param [String] der DER string
|
69
|
+
# @param [Boolean] ber if +true+, accept BER encoding
|
70
|
+
# @return [Integer] total number of parsed bytes
|
71
|
+
# @raise [ASN1Error] error on parsing
|
72
|
+
def parse!(der, ber: false)
|
73
|
+
parsed = false
|
74
|
+
@value.each_with_index do |element, i|
|
75
|
+
begin
|
76
|
+
@chosen = i
|
77
|
+
nb_bytes = element.parse!(der)
|
78
|
+
parsed = true
|
79
|
+
break nb_bytes
|
80
|
+
rescue ASN1Error
|
81
|
+
@chosen = nil
|
82
|
+
next
|
83
|
+
end
|
84
|
+
end
|
85
|
+
raise ASN1Error, "CHOICE #@name: no type matching #{der.inspect}" unless parsed
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def check_chosen
|
91
|
+
raise ChoiceError if @chosen.nil?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# @abstract This class SHOULD be used as base class for all ASN.1 primitive
|
5
|
+
# types.
|
6
|
+
# Base class for all ASN.1 constructed types
|
7
|
+
# @author Sylvain Daubert
|
8
|
+
class Constructed < Base
|
9
|
+
# Constructed value
|
10
|
+
ASN1_PC = 0x20
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Enumerated
|
5
|
+
#
|
6
|
+
# An enumerated type permits to assign names to integer values. It may be defined
|
7
|
+
# different ways:
|
8
|
+
# enum = RASN1::Types::Enumerated.new(:name, enum: { 'a' => 0, 'b' => 1, 'c' => 2 })
|
9
|
+
# enum = RASN1::Types::Enumerated.new(:name, enum: { a: 0, b: 1, c: 2 })
|
10
|
+
# Its value should be setting as an Integer or a String/symbol:
|
11
|
+
# enum.value = :b
|
12
|
+
# enum.value = 1 # equivalent to :b
|
13
|
+
# But its value is always stored as named integer:
|
14
|
+
# enum.value = :b
|
15
|
+
# enum.value # => :b
|
16
|
+
# enum.value = 0
|
17
|
+
# enum.value # => :a
|
18
|
+
# A {EnumeratedError} is raised when set value is not in enumeration.
|
19
|
+
# @author Sylvain Daubert
|
20
|
+
class Enumerated < Integer
|
21
|
+
|
22
|
+
# @param [Symbol, String] name name for this tag in grammar
|
23
|
+
# @param [Hash] options
|
24
|
+
# @option options [Symbol] :class ASN.1 tag class. Default value is +:universal+
|
25
|
+
# @option options [::Boolean] :optional define this tag as optional. Default
|
26
|
+
# is +false+
|
27
|
+
# @option options [Object] :default default value for DEFAULT tag
|
28
|
+
# @option options [Hash] :enum enumeration hash. Keys are names, and values
|
29
|
+
# are integers. This key is mandatory.
|
30
|
+
# @raise [EnumeratedError] +:enum+ key is not present
|
31
|
+
# @raise [EnumeratedError] +:default+ value is unknown
|
32
|
+
def initialize(name, options={})
|
33
|
+
super
|
34
|
+
raise EnumeratedError, 'no enumeration given' unless options.has_key? :enum
|
35
|
+
@enum = options[:enum]
|
36
|
+
|
37
|
+
case @default
|
38
|
+
when String,Symbol
|
39
|
+
unless @enum.has_key? @default
|
40
|
+
raise EnumeratedError, "TAG #@name: unknwon enumerated default value #@default"
|
41
|
+
end
|
42
|
+
when ::Integer
|
43
|
+
if @enum.has_value? @default
|
44
|
+
@default = @enum.key(@default)
|
45
|
+
else
|
46
|
+
raise EnumeratedError, "TAG #@name: default value #@defalt not in enumeration"
|
47
|
+
end
|
48
|
+
when nil
|
49
|
+
else
|
50
|
+
raise TypeError, "TAG #@name: #{@value.class} not handled as default value"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [Integer,String,Symbol,nil] v
|
55
|
+
# @return [String,Symbol,nil]
|
56
|
+
def value=(v)
|
57
|
+
case v
|
58
|
+
when String,Symbol
|
59
|
+
unless @enum.has_key? v
|
60
|
+
raise EnumeratedError, "TAG #@name: unknwon enumerated value #{v}"
|
61
|
+
end
|
62
|
+
@value = v
|
63
|
+
when ::Integer
|
64
|
+
unless @enum.has_value? v
|
65
|
+
raise EnumeratedError, "TAG #@name: #{v} not in enumeration"
|
66
|
+
end
|
67
|
+
@value = @enum.key(v)
|
68
|
+
when nil
|
69
|
+
@value = nil
|
70
|
+
else
|
71
|
+
raise EnumeratedError, "TAG #@name: not in enumeration"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Integer]
|
76
|
+
def to_i
|
77
|
+
case @value
|
78
|
+
when String, Symbol
|
79
|
+
@enum[@value]
|
80
|
+
when ::Integer
|
81
|
+
super
|
82
|
+
when nil
|
83
|
+
if @default
|
84
|
+
@enum[@default]
|
85
|
+
else
|
86
|
+
0
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise TypeError, "TAG #@name: #{@value.class} not handled"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Hash]
|
94
|
+
def to_h
|
95
|
+
@enum
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def value_to_der
|
101
|
+
case @value
|
102
|
+
when String, Symbol
|
103
|
+
super @enum[@value]
|
104
|
+
when ::Integer
|
105
|
+
super
|
106
|
+
else
|
107
|
+
raise TypeError, "TAG #@name: #{@value.class} not handled"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def der_to_value(der, ber:false)
|
112
|
+
super
|
113
|
+
v = @value
|
114
|
+
@value = @enum.key(v)
|
115
|
+
raise EnumeratedError, "TAG #@name: value #{v} not in enumeration" if @value.nil?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Integer
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class Integer < Primitive
|
7
|
+
TAG = 0x02
|
8
|
+
|
9
|
+
# @return [Integer]
|
10
|
+
def to_i
|
11
|
+
@value || @default || 0
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def value_to_der(value=nil)
|
17
|
+
v = value || @value
|
18
|
+
size = v.bit_length / 8 + (v.bit_length % 8 > 0 ? 1 : 0)
|
19
|
+
size = 1 if size == 0
|
20
|
+
comp_value = if v > 0
|
21
|
+
# If MSB is 1, increment size to set initial octet
|
22
|
+
# to 0 to amrk it as a positive integer
|
23
|
+
size += 1 if v >> (size * 8 - 1) == 1
|
24
|
+
v
|
25
|
+
else
|
26
|
+
~(v.abs) + 1
|
27
|
+
end
|
28
|
+
ary = []
|
29
|
+
size.times { ary << (comp_value & 0xff); comp_value >>= 8 }
|
30
|
+
ary.reverse.pack('C*')
|
31
|
+
end
|
32
|
+
|
33
|
+
def der_to_value(der, ber: false)
|
34
|
+
ary = der.unpack('C*')
|
35
|
+
@value = ary.reduce(0) { |len, b| (len << 8) | b }
|
36
|
+
if ary[0] & 0x80 == 0x80
|
37
|
+
@value = -((~@value & ((1 << @value.bit_length) - 1)) + 1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Null
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class Null < Primitive
|
7
|
+
TAG = 0x05
|
8
|
+
|
9
|
+
def value_to_der
|
10
|
+
''
|
11
|
+
end
|
12
|
+
|
13
|
+
def der_to_value(der, ber: false)
|
14
|
+
raise ASN1Error, "NULL TAG should not have content!" if der.length > 0
|
15
|
+
@value = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module RASN1
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# ASN.1 Object ID
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class ObjectId < Primitive
|
7
|
+
TAG = 6
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def value_to_der
|
12
|
+
ids = @value.split('.').map! { |str| str.to_i }
|
13
|
+
|
14
|
+
if ids[0] > 2
|
15
|
+
raise ASN1Error, 'OBJECT ID #@name: first subidentifier should be less than 3'
|
16
|
+
end
|
17
|
+
if ids[0] < 2 and ids[1] > 39
|
18
|
+
raise ASN1Error, 'OBJECT ID #@name: second subidentifier should be less than 40'
|
19
|
+
end
|
20
|
+
|
21
|
+
ids[0, 2] = ids[0] * 40 + ids[1]
|
22
|
+
ids.map! do |v|
|
23
|
+
next v if v < 128
|
24
|
+
|
25
|
+
ary = []
|
26
|
+
while v > 0
|
27
|
+
ary.unshift (v & 0x7f) | 0x80
|
28
|
+
v >>= 7
|
29
|
+
end
|
30
|
+
ary[-1] &= 0x7f
|
31
|
+
ary
|
32
|
+
end
|
33
|
+
ids.flatten.pack('C*')
|
34
|
+
end
|
35
|
+
|
36
|
+
def der_to_value(der, ber: false)
|
37
|
+
bytes = der.unpack('C*')
|
38
|
+
ids = if bytes[0] < 80
|
39
|
+
remove = 1
|
40
|
+
[ bytes[0] / 40, bytes[0] % 40]
|
41
|
+
elsif bytes[0] < 128
|
42
|
+
remove = 1
|
43
|
+
[2, bytes[0] - 80]
|
44
|
+
else
|
45
|
+
remove = 1
|
46
|
+
second_id = bytes[0] & 0x7f
|
47
|
+
bytes[1..-1].each do |byte|
|
48
|
+
remove += 1
|
49
|
+
second_id <<= 7
|
50
|
+
if byte < 128
|
51
|
+
second_id |= byte
|
52
|
+
break
|
53
|
+
else
|
54
|
+
second_id |= byte & 0x7f
|
55
|
+
end
|
56
|
+
end
|
57
|
+
[2, second_id - 80]
|
58
|
+
end
|
59
|
+
|
60
|
+
id = 0
|
61
|
+
bytes.shift(remove)
|
62
|
+
bytes.each do |byte|
|
63
|
+
if byte < 128
|
64
|
+
if id == 0
|
65
|
+
ids << byte
|
66
|
+
else
|
67
|
+
ids << ((id << 7) | byte)
|
68
|
+
id = 0
|
69
|
+
end
|
70
|
+
else
|
71
|
+
id = (id << 7) | (byte & 0x7f)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@value = ids.join('.')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|