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