rasn2 0.16.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/LICENSE +22 -0
- data/README.md +4 -0
- data/lib/rasn1/errors.rb +55 -0
- data/lib/rasn1/helpers/colorize.rb +76 -0
- data/lib/rasn1/model.rb +831 -0
- data/lib/rasn1/schema_parser.rb +470 -0
- data/lib/rasn1/tracer.rb +200 -0
- data/lib/rasn1/types/any.rb +98 -0
- data/lib/rasn1/types/base.rb +675 -0
- data/lib/rasn1/types/bit_string.rb +100 -0
- data/lib/rasn1/types/bmp_string.rb +22 -0
- data/lib/rasn1/types/boolean.rb +57 -0
- data/lib/rasn1/types/choice.rb +158 -0
- data/lib/rasn1/types/constrained.rb +51 -0
- data/lib/rasn1/types/constructed.rb +51 -0
- data/lib/rasn1/types/enumerated.rb +44 -0
- data/lib/rasn1/types/generalized_time.rb +156 -0
- data/lib/rasn1/types/ia5_string.rb +21 -0
- data/lib/rasn1/types/integer.rb +166 -0
- data/lib/rasn1/types/null.rb +43 -0
- data/lib/rasn1/types/numeric_string.rb +41 -0
- data/lib/rasn1/types/object_id.rb +64 -0
- data/lib/rasn1/types/octet_string.rb +52 -0
- data/lib/rasn1/types/primitive.rb +13 -0
- data/lib/rasn1/types/printable_string.rb +42 -0
- data/lib/rasn1/types/sequence.rb +105 -0
- data/lib/rasn1/types/sequence_of.rb +199 -0
- data/lib/rasn1/types/set.rb +28 -0
- data/lib/rasn1/types/set_of.rb +18 -0
- data/lib/rasn1/types/tag.rb +189 -0
- data/lib/rasn1/types/universal_string.rb +22 -0
- data/lib/rasn1/types/utc_time.rb +67 -0
- data/lib/rasn1/types/utf8_string.rb +21 -0
- data/lib/rasn1/types/visible_string.rb +30 -0
- data/lib/rasn1/types.rb +149 -0
- data/lib/rasn1/value_notation.rb +256 -0
- data/lib/rasn1/version.rb +6 -0
- data/lib/rasn1/wrapper.rb +279 -0
- data/lib/rasn1.rb +51 -0
- metadata +123 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 Bit String
|
|
6
|
+
# @author Sylvain Daubert
|
|
7
|
+
class BitString < Primitive
|
|
8
|
+
# BitString id value
|
|
9
|
+
ID = 3
|
|
10
|
+
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
attr_writer :bit_length
|
|
13
|
+
|
|
14
|
+
# @param [Hash] options
|
|
15
|
+
# @option options [Object] :bit_length default bit_length value. Should be
|
|
16
|
+
# present if +:default+ is set
|
|
17
|
+
# @see Base#initialize common options to all ASN.1 types
|
|
18
|
+
def initialize(options={})
|
|
19
|
+
super
|
|
20
|
+
if @default
|
|
21
|
+
raise ASN1Error, "#{@name}: default bit length is not defined" if @options[:bit_length].nil?
|
|
22
|
+
|
|
23
|
+
@default_bit_length = @options[:bit_length]
|
|
24
|
+
end
|
|
25
|
+
@bit_length = @options[:bit_length]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get bit length
|
|
29
|
+
def bit_length
|
|
30
|
+
if value?
|
|
31
|
+
@bit_length
|
|
32
|
+
else
|
|
33
|
+
@default_bit_length
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param [Integer] level
|
|
38
|
+
# @return [String]
|
|
39
|
+
def inspect(level=0)
|
|
40
|
+
str = common_inspect(level)
|
|
41
|
+
str << " #{value.inspect} (bit length: #{bit_length})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Same as {Base#can_build?} but also check bit_length
|
|
45
|
+
# @see Base#can_build?
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
def can_build?
|
|
48
|
+
super || (!@default.nil? && (@bit_length != @default_bit_length))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Make value from DER/BER string. Also set {#bit_length}.
|
|
52
|
+
# @param [String] der
|
|
53
|
+
# @param [::Boolean] ber
|
|
54
|
+
# @return [void]
|
|
55
|
+
# @see Types::Base#der_to_value
|
|
56
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
57
|
+
unused = der.unpack1('C').to_i
|
|
58
|
+
value = der[1..].to_s
|
|
59
|
+
@bit_length = value.length * 8 - unused
|
|
60
|
+
@value = value
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @author Sylvain Daubert
|
|
66
|
+
# @author adfoster-r7
|
|
67
|
+
def value_to_der
|
|
68
|
+
raise ASN1Error, "#{@name}: bit length is not set" if bit_length.nil?
|
|
69
|
+
|
|
70
|
+
value = generate_value_with_correct_length
|
|
71
|
+
|
|
72
|
+
unused = value.length * 8 - @bit_length.to_i
|
|
73
|
+
der = [unused, value].pack('CA*')
|
|
74
|
+
|
|
75
|
+
if unused.positive?
|
|
76
|
+
last_byte = value[-1].to_s.unpack1('C').to_i
|
|
77
|
+
last_byte &= (0xff >> unused) << unused
|
|
78
|
+
der[-1] = [last_byte].pack('C')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@value = value
|
|
82
|
+
|
|
83
|
+
der
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def generate_value_with_correct_length
|
|
87
|
+
value = (@value || '').dup.force_encoding('BINARY')
|
|
88
|
+
value << "\x00".b while value.length * 8 < @bit_length.to_i
|
|
89
|
+
return value unless value.length * 8 > @bit_length.to_i
|
|
90
|
+
|
|
91
|
+
max_len = @bit_length.to_i / 8 + ((@bit_length.to_i % 8).positive? ? 1 : 0)
|
|
92
|
+
value[0, max_len].to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def explicit_type
|
|
96
|
+
self.class.new(name: name, value: @value, bit_length: @bit_length)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 BmpString
|
|
6
|
+
# @since 0.12.0
|
|
7
|
+
# @author adfoster-r7
|
|
8
|
+
class BmpString < OctetString
|
|
9
|
+
# BmpString id value
|
|
10
|
+
ID = 30
|
|
11
|
+
# UniversalString encoding
|
|
12
|
+
# @since 0.15.0
|
|
13
|
+
ENCODING = Encoding::UTF_16BE
|
|
14
|
+
|
|
15
|
+
# Get ASN.1 type
|
|
16
|
+
# @return [String]
|
|
17
|
+
def self.type
|
|
18
|
+
'BmpString'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 Boolean
|
|
6
|
+
# @author Sylvain Daubert
|
|
7
|
+
class Boolean < Primitive
|
|
8
|
+
# Boolean id value
|
|
9
|
+
ID = 0x01
|
|
10
|
+
|
|
11
|
+
# DER true value
|
|
12
|
+
DER_TRUE = 0xff
|
|
13
|
+
# DER false value
|
|
14
|
+
DER_FALSE = 0
|
|
15
|
+
|
|
16
|
+
# @return [false]
|
|
17
|
+
def void_value # rubocop:disable Naming/PredicateMethod
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Make boolean value from DER/BER string.
|
|
22
|
+
# @param [String] der
|
|
23
|
+
# @param [::Boolean] ber
|
|
24
|
+
# @return [void]
|
|
25
|
+
# @see Types::Base#der_to_value
|
|
26
|
+
# @raise [ASN1Error] +der+ is not 1-byte long
|
|
27
|
+
# @raise [ASN1Error] +der+ is not {DER_TRUE} nor {DER_FALSE} and +ber+ is +false+
|
|
28
|
+
def der_to_value(der, ber: false)
|
|
29
|
+
raise ASN1Error, "tag #{@name}: BOOLEAN should have a length of 1" unless der.size == 1
|
|
30
|
+
|
|
31
|
+
bool = der.unpack1('C')
|
|
32
|
+
case bool
|
|
33
|
+
when DER_FALSE
|
|
34
|
+
@value = false
|
|
35
|
+
when DER_TRUE
|
|
36
|
+
@value = true
|
|
37
|
+
else
|
|
38
|
+
raise ASN1Error, "tag #{@name}: bad value 0x%02x for BOOLEAN" % bool unless ber
|
|
39
|
+
|
|
40
|
+
@value = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def value_to_der
|
|
47
|
+
[@value ? DER_TRUE : DER_FALSE].pack('C')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def trace_data
|
|
51
|
+
return super if explicit?
|
|
52
|
+
|
|
53
|
+
' ' + colorize_bool(raw_data != "\x00".b, raw_data.unpack1('H*'))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# A ASN.1 CHOICE is a choice between different types.
|
|
6
|
+
#
|
|
7
|
+
# == Create a CHOICE
|
|
8
|
+
# A CHOICE is defined this way:
|
|
9
|
+
# choice = Choice.new
|
|
10
|
+
# choice.value = [Integer.new(implicit: 0, class: :context),
|
|
11
|
+
# Integer.new(implicit: 1, class: :context),
|
|
12
|
+
# OctetString.new(implicit: 2, class: :context)]
|
|
13
|
+
# The chosen type may be set this way:
|
|
14
|
+
# choice.chosen = 0 # choose :int1
|
|
15
|
+
# The chosen value may be set these ways:
|
|
16
|
+
# choise.value[choice.chosen].value = 1
|
|
17
|
+
# choise.set_chosen_value 1
|
|
18
|
+
# The chosen value may be got these ways:
|
|
19
|
+
# choise.value[choice.chosen].value # => 1
|
|
20
|
+
# choice.chosen_value # => 1
|
|
21
|
+
#
|
|
22
|
+
# == Encode a CHOICE
|
|
23
|
+
# {#to_der} only encodes the chosen value:
|
|
24
|
+
# choise.to_der # => "\x80\x01\x01"
|
|
25
|
+
#
|
|
26
|
+
# == Parse a CHOICE
|
|
27
|
+
# Parsing a CHOICE set {#chosen} and set value to chosen type. If parsed string does
|
|
28
|
+
# not contain a type from CHOICE, a {RASN1::ASN1Error} is raised.
|
|
29
|
+
# str = "\x04\x03abc"
|
|
30
|
+
# choice.parse! str
|
|
31
|
+
# choice.chosen # => 2
|
|
32
|
+
# choice.chosen_value # => "abc"
|
|
33
|
+
# @author Sylvain Daubert
|
|
34
|
+
class Choice < Base
|
|
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) # rubocop:disable Naming/AccessorMethodName
|
|
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
|
+
case @value[@chosen]
|
|
56
|
+
when Base
|
|
57
|
+
@value[@chosen].value
|
|
58
|
+
when Model
|
|
59
|
+
@value[@chosen]
|
|
60
|
+
when Wrapper
|
|
61
|
+
@value[@chosen].element
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @note {#chosen} MUST be set before calling this method
|
|
66
|
+
# @return [String] DER-formated string
|
|
67
|
+
# @raise [ChoiceError] {#chosen} not set
|
|
68
|
+
def to_der
|
|
69
|
+
check_chosen
|
|
70
|
+
@value[@chosen].to_der
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Parse a DER string. This method updates object by setting {#chosen} and
|
|
74
|
+
# chosen value.
|
|
75
|
+
# @param [String] der DER string
|
|
76
|
+
# @param [Boolean] ber if +true+, accept BER encoding
|
|
77
|
+
# @return [Integer] total number of parsed bytes
|
|
78
|
+
# @raise [ASN1Error] error on parsing
|
|
79
|
+
def parse!(der, ber: false)
|
|
80
|
+
len, data = do_parse(der, ber: ber)
|
|
81
|
+
return 0 if len.zero?
|
|
82
|
+
|
|
83
|
+
element = @value[@chosen]
|
|
84
|
+
if element.explicit?
|
|
85
|
+
element.do_parse_explicit(data)
|
|
86
|
+
else
|
|
87
|
+
element.der_to_value(data, ber: ber)
|
|
88
|
+
end
|
|
89
|
+
len
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @private
|
|
93
|
+
# @see Types::Base#do_parse
|
|
94
|
+
# @since 0.15.0 Specific +#do_parse+ to handle recursivity
|
|
95
|
+
def do_parse(der, ber: false)
|
|
96
|
+
@value.each_with_index do |element, i|
|
|
97
|
+
@chosen = i
|
|
98
|
+
return element.do_parse(der, ber: ber)
|
|
99
|
+
rescue ASN1Error
|
|
100
|
+
@chosen = nil
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
@no_value = true
|
|
105
|
+
@value = void_value
|
|
106
|
+
raise ASN1Error, "CHOICE #{@name}: no type matching #{der.inspect}" unless optional?
|
|
107
|
+
|
|
108
|
+
[0, ''.b]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Make choice value from DER/BER string.
|
|
112
|
+
# @param [String] der
|
|
113
|
+
# @param [::Boolean] ber
|
|
114
|
+
# @return [void]
|
|
115
|
+
# @since 0.15.0 Specific +#der_to_value+ to handle recursivity
|
|
116
|
+
def der_to_value(der, ber: false)
|
|
117
|
+
@value.each_with_index do |element, i|
|
|
118
|
+
@chosen = i
|
|
119
|
+
element.parse!(der, ber: ber)
|
|
120
|
+
break
|
|
121
|
+
rescue ASN1Error
|
|
122
|
+
@chosen = nil
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @param [::Integer] level
|
|
128
|
+
# @return [String]
|
|
129
|
+
def inspect(level=0)
|
|
130
|
+
str = common_inspect(level)
|
|
131
|
+
str << if defined?(@chosen) && value?
|
|
132
|
+
"\n#{@value[@chosen].inspect(level + 1)}"
|
|
133
|
+
else
|
|
134
|
+
' not chosen!'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @private Tracer private API
|
|
139
|
+
# @return [String]
|
|
140
|
+
def trace
|
|
141
|
+
msg_type(no_id: true)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Return empty array
|
|
145
|
+
# @return [Array()]
|
|
146
|
+
# @since 0.15.0
|
|
147
|
+
def void_value
|
|
148
|
+
[]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def check_chosen
|
|
154
|
+
raise ChoiceError.new(self) if !defined?(@chosen) || @chosen.nil?
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# Mixin to add constraints on a RASN1 type.
|
|
6
|
+
# Should not be used directly but through {Types.define_type}.
|
|
7
|
+
# @version 0.11.0
|
|
8
|
+
# @author Sylvain Daubert
|
|
9
|
+
module Constrained
|
|
10
|
+
# Define class/module methods for {Constrained} module
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# @return [Proc] proc to check constraints
|
|
13
|
+
attr_accessor :constraint
|
|
14
|
+
|
|
15
|
+
# Check if a constraint is really defined
|
|
16
|
+
# @return [Boolean]
|
|
17
|
+
def constrained?
|
|
18
|
+
@constraint.is_a?(Proc)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check constraint, if defined
|
|
22
|
+
# @param [Object] value the value of the type to check
|
|
23
|
+
# @raise [ConstraintError] constraint is not verified
|
|
24
|
+
def check_constraint(value)
|
|
25
|
+
return unless constrained?
|
|
26
|
+
raise ConstraintError.new(self) unless @constraint.call(value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
extend ClassMethods
|
|
31
|
+
|
|
32
|
+
# Redefined +#value=+ to check constraint before assigning +val+
|
|
33
|
+
# @see Types::Base#value=
|
|
34
|
+
# @raise [ConstraintError] constraint is not verified
|
|
35
|
+
def value=(val)
|
|
36
|
+
self.class.check_constraint(val)
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Make value from +der+ string and check constraints
|
|
41
|
+
# @param [String] der
|
|
42
|
+
# @param [::Boolean] ber
|
|
43
|
+
# @return [void]
|
|
44
|
+
# @raise [ConstraintError] constraint is not verified
|
|
45
|
+
def der_to_value(der, ber: false)
|
|
46
|
+
super
|
|
47
|
+
self.class.check_constraint(@value)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# @abstract This class SHOULD be used as base class for all ASN.1 primitive
|
|
6
|
+
# types.
|
|
7
|
+
# Base class for all ASN.1 constructed types
|
|
8
|
+
# @author Sylvain Daubert
|
|
9
|
+
class Constructed < Base
|
|
10
|
+
# Constructed value
|
|
11
|
+
ASN1_PC = 0x20
|
|
12
|
+
|
|
13
|
+
# @return [Boolean]
|
|
14
|
+
# @since 0.12.0
|
|
15
|
+
# @see Base#can_build?
|
|
16
|
+
def can_build? # rubocop:disable Metrics/CyclomaticComplexity
|
|
17
|
+
return super unless @value.is_a?(Array) && optional?
|
|
18
|
+
return false unless super
|
|
19
|
+
|
|
20
|
+
@value.any? do |el|
|
|
21
|
+
el.can_build? && (
|
|
22
|
+
el.primitive? ||
|
|
23
|
+
(el.value.respond_to?(:empty?) ? !el.value.empty? : !el.value.nil?))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param [::Integer] level (default: 0)
|
|
28
|
+
# @return [String]
|
|
29
|
+
def inspect(level=0)
|
|
30
|
+
case @value
|
|
31
|
+
when Array
|
|
32
|
+
str = common_inspect(level)
|
|
33
|
+
str << "\n"
|
|
34
|
+
level = level.abs + 1
|
|
35
|
+
@value.each do |item|
|
|
36
|
+
case item
|
|
37
|
+
when Base, Model, Wrapper
|
|
38
|
+
str << "#{item.inspect(level)}\n"
|
|
39
|
+
else
|
|
40
|
+
str << ' ' * level
|
|
41
|
+
str << "#{item.inspect}\n"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
str
|
|
45
|
+
else
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 Enumerated
|
|
6
|
+
#
|
|
7
|
+
# An enumerated type permits to assign names to integer values. It may be defined
|
|
8
|
+
# different ways:
|
|
9
|
+
# enum = RASN1::Types::Enumerated.new(enum: { 'a' => 0, 'b' => 1, 'c' => 2 })
|
|
10
|
+
# enum = RASN1::Types::Enumerated.new(enum: { a: 0, b: 1, c: 2 })
|
|
11
|
+
# Its value should be setting as an Integer or a String/symbol:
|
|
12
|
+
# enum.value = :b
|
|
13
|
+
# enum.value = 1 # equivalent to :b
|
|
14
|
+
# But its value is always stored as named integer:
|
|
15
|
+
# enum.value = :b
|
|
16
|
+
# enum.value # => :b
|
|
17
|
+
# enum.value = 0
|
|
18
|
+
# enum.value # => :a
|
|
19
|
+
# A {EnumeratedError} is raised when set value is not in enumeration.
|
|
20
|
+
# @author Sylvain Daubert
|
|
21
|
+
class Enumerated < Integer
|
|
22
|
+
# @!attribute enum
|
|
23
|
+
# @return [Hash]
|
|
24
|
+
|
|
25
|
+
# Enumerated id value
|
|
26
|
+
ID = 0x0a
|
|
27
|
+
|
|
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
|
+
# @see Base#initialize common options to all ASN.1 types
|
|
33
|
+
def initialize(options={})
|
|
34
|
+
super
|
|
35
|
+
raise EnumeratedError, 'no enumeration given' if @enum.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Hash]
|
|
39
|
+
def to_h
|
|
40
|
+
@enum
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'strptime'
|
|
4
|
+
|
|
5
|
+
module RASN1
|
|
6
|
+
module Types
|
|
7
|
+
# ASN.1 GeneralizedTime
|
|
8
|
+
#
|
|
9
|
+
# +{#value} of a {GeneralizedTime} should be a ruby Time.
|
|
10
|
+
#
|
|
11
|
+
# ===Notes
|
|
12
|
+
# When encoding, resulting string is always a UTC time, appended with +Z+.
|
|
13
|
+
# Minutes and seconds are always generated. Fractions of second are generated
|
|
14
|
+
# if value Time object have them.
|
|
15
|
+
#
|
|
16
|
+
# On parsing, are supported:
|
|
17
|
+
# * UTC times (ending with +Z+),
|
|
18
|
+
# * local times (no suffix),
|
|
19
|
+
# * local times with difference between UTC and this local time (ending with
|
|
20
|
+
# +sHHMM+, where +s+ is +++ or +-+, and +HHMM+ is the time differential
|
|
21
|
+
# betwen UTC and local time).
|
|
22
|
+
# These times may include minutes and seconds. Fractions of hour, minute and
|
|
23
|
+
# second are supported.
|
|
24
|
+
# @author Sylvain Daubert
|
|
25
|
+
class GeneralizedTime < Primitive
|
|
26
|
+
# GeneralizedTime id value
|
|
27
|
+
ID = 24
|
|
28
|
+
|
|
29
|
+
# @private
|
|
30
|
+
HOUR_TO_SEC = 3600
|
|
31
|
+
# @private
|
|
32
|
+
MINUTE_TO_SEC = 60
|
|
33
|
+
# @private
|
|
34
|
+
SECOND_TO_SEC = 1
|
|
35
|
+
|
|
36
|
+
# Get ASN.1 type
|
|
37
|
+
# @return [String]
|
|
38
|
+
def self.type
|
|
39
|
+
'GeneralizedTime'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Time]
|
|
43
|
+
def void_value
|
|
44
|
+
Time.now
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Make time value from +der+ string
|
|
48
|
+
# @param [String] der
|
|
49
|
+
# @param [::Boolean] ber
|
|
50
|
+
# @return [void]
|
|
51
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
52
|
+
date_hour, fraction = der.split('.')
|
|
53
|
+
date_hour = date_hour.to_s
|
|
54
|
+
fraction = fraction.to_s
|
|
55
|
+
|
|
56
|
+
if fraction.empty?
|
|
57
|
+
value_when_fraction_empty(date_hour)
|
|
58
|
+
elsif fraction[-1] == 'Z'
|
|
59
|
+
value_when_fraction_ends_with_z(date_hour, fraction)
|
|
60
|
+
else
|
|
61
|
+
value_on_others_cases(date_hour, fraction)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def value_to_der
|
|
68
|
+
utc_value = @value.getutc
|
|
69
|
+
if utc_value.nsec.positive?
|
|
70
|
+
der = utc_value.strftime('%Y%m%d%H%M%S.%9NZ')
|
|
71
|
+
der.sub(/0+Z/, 'Z')
|
|
72
|
+
else
|
|
73
|
+
utc_value.strftime('%Y%m%d%H%M%SZ')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def value_when_fraction_empty(date_hour)
|
|
78
|
+
tz = compute_tz(date_hour)
|
|
79
|
+
year = date_hour.slice!(0, 4).to_i
|
|
80
|
+
month = date_hour.slice!(0, 2).to_i
|
|
81
|
+
day = date_hour.slice!(0, 2).to_i
|
|
82
|
+
hour = date_hour.slice!(0, 2).to_i
|
|
83
|
+
minute = date_hour.slice!(0, 2).to_i
|
|
84
|
+
second = date_hour.slice!(0, 2).to_i
|
|
85
|
+
@value = Time.new(year, month, day, hour, minute, second, tz)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Ruby 3.0: special handle for timezone
|
|
89
|
+
# From 3.1: "Z" and "-0100" are supported
|
|
90
|
+
# Below 3.1: should be "-01:00" or "+00:00"
|
|
91
|
+
def compute_tz(date_hour)
|
|
92
|
+
if date_hour.end_with?('Z')
|
|
93
|
+
date_hour.slice!(-1, 1)
|
|
94
|
+
'+00:00' # Ruby 3.0: to remove after end-of support of ruby 3.0
|
|
95
|
+
elsif date_hour.match?(/[+-]\d+$/)
|
|
96
|
+
# Ruby 3.0
|
|
97
|
+
# date_hour.slice!(-5, 5)
|
|
98
|
+
zone = date_hour.slice!(-5, 5).to_s
|
|
99
|
+
"#{zone[0, 3]}:#{zone[3, 2]}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def value_when_fraction_ends_with_z(date_hour, fraction)
|
|
104
|
+
fraction = fraction[0...-1]
|
|
105
|
+
date_hour << 'Z'
|
|
106
|
+
frac_base = compute_frac_base(date_hour)
|
|
107
|
+
value_when_fraction_empty(date_hour)
|
|
108
|
+
fix_value(fraction, frac_base)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def value_on_others_cases(date_hour, fraction)
|
|
112
|
+
match = fraction.match(/(\d+)([+-]\d+)/)
|
|
113
|
+
if match
|
|
114
|
+
# fraction contains fraction and timezone info. Split them
|
|
115
|
+
fraction = match[1]
|
|
116
|
+
date_hour << match[2]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
frac_base = compute_frac_base(date_hour)
|
|
120
|
+
value_when_fraction_empty(date_hour)
|
|
121
|
+
fix_value(fraction, frac_base)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def fix_value(fraction, frac_base)
|
|
125
|
+
frac = ".#{fraction}".to_r * frac_base
|
|
126
|
+
@value = (@value + frac) unless fraction.nil?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def compute_frac_base(date_hour)
|
|
130
|
+
case date_hour.size
|
|
131
|
+
when 10, 11
|
|
132
|
+
HOUR_TO_SEC
|
|
133
|
+
when 12, 13, 17
|
|
134
|
+
MINUTE_TO_SEC
|
|
135
|
+
when 15
|
|
136
|
+
if date_hour[-1] == 'Z'
|
|
137
|
+
SECOND_TO_SEC
|
|
138
|
+
else
|
|
139
|
+
HOUR_TO_SEC
|
|
140
|
+
end
|
|
141
|
+
when 14, 19
|
|
142
|
+
SECOND_TO_SEC
|
|
143
|
+
else
|
|
144
|
+
prefix = @name.nil? ? type : "tag #{@name}"
|
|
145
|
+
raise ASN1Error, "#{prefix}: unrecognized format: #{date_hour}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def trace_data
|
|
150
|
+
return super if explicit?
|
|
151
|
+
|
|
152
|
+
+' ' << colorize(Time.parse(raw_data)).dark.green
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 IA5 String
|
|
6
|
+
# @author Sylvain Daubert
|
|
7
|
+
class IA5String < OctetString
|
|
8
|
+
# IA5String id value
|
|
9
|
+
ID = 22
|
|
10
|
+
# UniversalString encoding
|
|
11
|
+
# @since 0.15.0
|
|
12
|
+
ENCODING = Encoding::US_ASCII
|
|
13
|
+
|
|
14
|
+
# Get ASN.1 type
|
|
15
|
+
# @return [String]
|
|
16
|
+
def self.type
|
|
17
|
+
'IA5String'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|