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
@@ -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
|