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,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 SEQUENCE OF
|
|
6
|
+
#
|
|
7
|
+
# A SEQUENCE OF is an array of one ASN.1 type.
|
|
8
|
+
#
|
|
9
|
+
# == Use with {Primitive} types
|
|
10
|
+
# To encode this ASN.1 example:
|
|
11
|
+
# Integers ::= SEQUENCE OF INTEGER
|
|
12
|
+
# do:
|
|
13
|
+
# # Create a SEQUENCE OF INTEGER
|
|
14
|
+
# seqof = RASN1::Types::SequenceOf.new(RASN1::Types::Integer)
|
|
15
|
+
# # Set integer values
|
|
16
|
+
# seqof.value = [1, 2, 3, 4].map { |i| RASN1::Types::Integer.new(value: i) }
|
|
17
|
+
# seqof << 5 # infer a RASN1::Types::Integer with given value
|
|
18
|
+
#
|
|
19
|
+
# == Use with {Constructed} types
|
|
20
|
+
# SEQUENCE OF may be used to create sequence of composed types. For example:
|
|
21
|
+
# composed_type = RASN1::Types::Sequence.new
|
|
22
|
+
# commposed_type.value = [RASN1::Types::Integer,
|
|
23
|
+
# RASN1::Types::OctetString]
|
|
24
|
+
# seqof = RASN1::Types::SequenceOf.new(composed_type)
|
|
25
|
+
# seqof << [0, 'data0']
|
|
26
|
+
# seqof << [1, 'data1']
|
|
27
|
+
#
|
|
28
|
+
# == Use with {Model}
|
|
29
|
+
# SEQUENCE OF may also be used with a Model type:
|
|
30
|
+
# class MyModel < RASN1::Model
|
|
31
|
+
# sequence :seq,
|
|
32
|
+
# content: [boolean(:bool), integer(:int)]
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# seqof = RASN1::Types::SequenceOf.new(:record, MyModel)
|
|
36
|
+
# # set values
|
|
37
|
+
# seqof << { bool: true, int: 12 }
|
|
38
|
+
# seqof << { bool: false, int: 65535 }
|
|
39
|
+
# # Generate DER string
|
|
40
|
+
# der = seqof.to_der # => String
|
|
41
|
+
# # parse
|
|
42
|
+
# seqof.parse! der
|
|
43
|
+
#
|
|
44
|
+
# After parsing, a SEQUENCE OF may be accessed as an Array:
|
|
45
|
+
# seqof[0] # => MyModel
|
|
46
|
+
# seqof[0][:bool].value # => true
|
|
47
|
+
# seqof[0][:int].value # => 12
|
|
48
|
+
# @author Sylvain Daubert
|
|
49
|
+
class SequenceOf < Constructed
|
|
50
|
+
# SequenceOf id value
|
|
51
|
+
ID = Sequence::ID
|
|
52
|
+
|
|
53
|
+
# @return [Class, Base]
|
|
54
|
+
attr_reader :of_type
|
|
55
|
+
|
|
56
|
+
# A SEQUENCE OF is encoded as a SEQUENCE.
|
|
57
|
+
# @return ['SEQUENCE']
|
|
58
|
+
def self.encoded_type
|
|
59
|
+
Sequence.encoded_type
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param [Class, Base] of_type base type for sequence of
|
|
63
|
+
# @see Base#initialize
|
|
64
|
+
def initialize(of_type, options={})
|
|
65
|
+
super(options)
|
|
66
|
+
@of_type = of_type
|
|
67
|
+
@no_value = false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Clone @#of_type and values
|
|
71
|
+
def initialize_copy(*)
|
|
72
|
+
super
|
|
73
|
+
@of_type = @of_type.dup
|
|
74
|
+
@value = @value.map(&:dup)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Array]
|
|
78
|
+
def void_value
|
|
79
|
+
[]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Add an item to SEQUENCE OF
|
|
83
|
+
# @param [Object] obj
|
|
84
|
+
# @note
|
|
85
|
+
# * if a SEQUENCE OF primitive type, +obj+ is an instance of primitive type or equivalent Ruby type
|
|
86
|
+
# * if a SEQUENCE OF composed type, +obj+ is an array of ruby type instances
|
|
87
|
+
# * if a SEQUENCE OF model, +obj+ is either a Model or a Hash
|
|
88
|
+
def <<(obj)
|
|
89
|
+
return push_primitive(obj) if of_type_class < Primitive
|
|
90
|
+
return push_composed_array(obj) if composed_of_type?
|
|
91
|
+
|
|
92
|
+
push_model(obj)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get element of index +idx+
|
|
96
|
+
# @param [Integer] idx
|
|
97
|
+
# @return [Base]
|
|
98
|
+
def [](idx)
|
|
99
|
+
@value[idx]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get length of SEQUENCE OF (ie number of elements)
|
|
103
|
+
# @return [Integer]
|
|
104
|
+
def length
|
|
105
|
+
@value.length
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @param [::Integer] level
|
|
109
|
+
# @return [String]
|
|
110
|
+
def inspect(level=0)
|
|
111
|
+
str = common_inspect(level)
|
|
112
|
+
str << "\n"
|
|
113
|
+
level = level.abs + 1
|
|
114
|
+
@value.each do |item|
|
|
115
|
+
case item
|
|
116
|
+
when Base, Model
|
|
117
|
+
next if item.optional? && item.value.nil?
|
|
118
|
+
|
|
119
|
+
str << item.inspect(level)
|
|
120
|
+
str << "\n" unless str.end_with?("\n")
|
|
121
|
+
else
|
|
122
|
+
str << (' ' * level) << "#{item.inspect}\n"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
str
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Make sequence of value from +der+ string
|
|
129
|
+
# @param [String] der
|
|
130
|
+
# @param [::Boolean] ber
|
|
131
|
+
# @return [void]
|
|
132
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
133
|
+
@value = []
|
|
134
|
+
nb_bytes = 0
|
|
135
|
+
|
|
136
|
+
while nb_bytes < der.length
|
|
137
|
+
type = of_type_class.new
|
|
138
|
+
nb_bytes += type.parse!(der[nb_bytes, der.length])
|
|
139
|
+
@value << type
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def of_type_class
|
|
146
|
+
@of_type.is_a?(Class) ? @of_type : @of_type.class
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def composed_of_type?
|
|
150
|
+
!@of_type.is_a?(Class) && [Sequence, Set].include?(of_type_class)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def value_to_der
|
|
154
|
+
@value.map(&:to_der).join
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def explicit_type
|
|
158
|
+
self.class.new(self.of_type, name: name)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def push_primitive(obj)
|
|
162
|
+
@value <<
|
|
163
|
+
case obj
|
|
164
|
+
when Primitive
|
|
165
|
+
obj
|
|
166
|
+
else
|
|
167
|
+
of_type_class.new(value: obj)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def push_composed_array(obj)
|
|
172
|
+
raise ASN1Error, 'object to add should be an Array' unless obj.is_a?(Array)
|
|
173
|
+
|
|
174
|
+
new_value = of_type_class.new
|
|
175
|
+
@of_type.value.each_with_index do |type, i|
|
|
176
|
+
type2 = type.dup
|
|
177
|
+
type2.value = obj[i]
|
|
178
|
+
new_value.value << type2
|
|
179
|
+
end
|
|
180
|
+
@value << new_value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def push_model(obj)
|
|
184
|
+
case obj
|
|
185
|
+
when Hash
|
|
186
|
+
@value << of_type_class.new(obj)
|
|
187
|
+
when of_type_class
|
|
188
|
+
@value << obj
|
|
189
|
+
else
|
|
190
|
+
raise ASN1Error, "object to add should be a #{of_type_class} or a Hash"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def trace_data
|
|
195
|
+
''
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 set
|
|
6
|
+
#
|
|
7
|
+
# A set is a collection of another ASN.1 types.
|
|
8
|
+
#
|
|
9
|
+
# To encode this ASN.1 example:
|
|
10
|
+
# Record ::= SET {
|
|
11
|
+
# id INTEGER,
|
|
12
|
+
# room [0] INTEGER OPTIONAL,
|
|
13
|
+
# house [1] IMPLICIT INTEGER DEFAULT 0
|
|
14
|
+
# }
|
|
15
|
+
# do:
|
|
16
|
+
# set = RASN1::Types::Set.new
|
|
17
|
+
# set.value = [
|
|
18
|
+
# RASN1::Types::Integer
|
|
19
|
+
# RASN1::Types::Integer(explicit: 0, optional: true),
|
|
20
|
+
# RASN1::Types::Integer(implicit: 1, default: 0)
|
|
21
|
+
# ]
|
|
22
|
+
# @author Sylvain Daubert
|
|
23
|
+
class Set < Sequence
|
|
24
|
+
# Set id value
|
|
25
|
+
ID = 0x11
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 SET OF
|
|
6
|
+
# @author Sylvain Daubert
|
|
7
|
+
class SetOf < SequenceOf
|
|
8
|
+
# SetOf id value
|
|
9
|
+
ID = Set::ID
|
|
10
|
+
|
|
11
|
+
# A SET OF is encoded as a SET.
|
|
12
|
+
# @return ['SET']
|
|
13
|
+
def self.encoded_type
|
|
14
|
+
Set.encoded_type
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 Tag constructed type.
|
|
6
|
+
#
|
|
7
|
+
# A Tag type is a constructed element with schematized content
|
|
8
|
+
# (child elements, like a {Sequence}) that can be bound to flexible tag
|
|
9
|
+
# criteria instead of a fixed universal tag:
|
|
10
|
+
# * a single tag number,
|
|
11
|
+
# * a list of possible tag numbers,
|
|
12
|
+
# * any private-class tag.
|
|
13
|
+
#
|
|
14
|
+
# == Binding to a single tag
|
|
15
|
+
# # Context-class tag 5 (default class is :context)
|
|
16
|
+
# generic = Tag.new(tag: 5, value: [Integer.new, OctetString.new])
|
|
17
|
+
# # Application-class tag 10
|
|
18
|
+
# generic = Tag.new(tag: 10, class: :application, value: [Integer.new])
|
|
19
|
+
#
|
|
20
|
+
# == Binding to a list of tags
|
|
21
|
+
# generic = Tag.new(tag: [5, 6, 7], value: [Integer.new, OctetString.new])
|
|
22
|
+
#
|
|
23
|
+
# == Binding to any private tag
|
|
24
|
+
# generic = Tag.new(tag: :private, value: [Integer.new, OctetString.new])
|
|
25
|
+
# # or equivalently
|
|
26
|
+
# generic = Tag.new(any_private: true, value: [Integer.new])
|
|
27
|
+
class Tag < Constructed
|
|
28
|
+
# Placeholder ID — not used directly; tag is always dynamic.
|
|
29
|
+
ID = 0
|
|
30
|
+
|
|
31
|
+
# @return [Integer, nil] the tag number that was matched during parsing
|
|
32
|
+
attr_reader :matched_tag
|
|
33
|
+
|
|
34
|
+
# @param [Hash] options
|
|
35
|
+
# @option options [Integer, Array<Integer>, :private] :tag tag number(s) to
|
|
36
|
+
# bind to, or +:private+ to accept any private-class tag
|
|
37
|
+
# @option options [Boolean] :any_private if +true+, accept any private-class tag
|
|
38
|
+
# @see Base#initialize
|
|
39
|
+
def initialize(options={})
|
|
40
|
+
extract_tag_binding(options)
|
|
41
|
+
super
|
|
42
|
+
@no_value = false
|
|
43
|
+
@value ||= []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Deep copy
|
|
47
|
+
def initialize_copy(*)
|
|
48
|
+
super
|
|
49
|
+
@value = case @value
|
|
50
|
+
when Array then @value.map(&:dup)
|
|
51
|
+
else @value.dup
|
|
52
|
+
end
|
|
53
|
+
@accepted_tags = @accepted_tags.dup
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Array]
|
|
57
|
+
def void_value
|
|
58
|
+
[]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get element by index or name
|
|
62
|
+
# @param [Integer, String, Symbol] idx_or_name
|
|
63
|
+
# @return [Object, nil]
|
|
64
|
+
def [](idx_or_name)
|
|
65
|
+
return unless @value.is_a?(Array)
|
|
66
|
+
|
|
67
|
+
case idx_or_name
|
|
68
|
+
when ::Integer
|
|
69
|
+
@value[idx_or_name.to_i]
|
|
70
|
+
when String, Symbol
|
|
71
|
+
@value.find { |elt| elt.name == idx_or_name }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Parse constructed content from DER
|
|
76
|
+
# @param [String] der
|
|
77
|
+
# @param [Boolean] ber
|
|
78
|
+
# @return [void]
|
|
79
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
80
|
+
if @value.is_a?(Array) && !@value.empty?
|
|
81
|
+
nb_bytes = 0
|
|
82
|
+
@value.each do |element|
|
|
83
|
+
nb_bytes += element.parse!(der[nb_bytes..])
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
@value = der
|
|
87
|
+
der.length
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def extract_tag_binding(options)
|
|
94
|
+
tag = options.delete(:tag)
|
|
95
|
+
@any_private = !options.delete(:any_private).nil?
|
|
96
|
+
|
|
97
|
+
@accepted_tags = case tag
|
|
98
|
+
when Array then tag
|
|
99
|
+
when ::Integer then [tag]
|
|
100
|
+
when :private
|
|
101
|
+
@any_private = true
|
|
102
|
+
[]
|
|
103
|
+
else
|
|
104
|
+
[]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if @any_private
|
|
108
|
+
options[:class] ||= :private
|
|
109
|
+
options[:implicit] = @accepted_tags.first if @accepted_tags.any?
|
|
110
|
+
elsif @accepted_tags.any?
|
|
111
|
+
options[:class] ||= :context
|
|
112
|
+
options[:implicit] = @accepted_tags.first
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def check_id(der)
|
|
117
|
+
return check_any_private_id(der) if @any_private && @accepted_tags.empty?
|
|
118
|
+
return check_multi_tag_id(der) if @accepted_tags.length > 1
|
|
119
|
+
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def check_any_private_id(der)
|
|
124
|
+
return no_match(der) if der.nil? || der.empty?
|
|
125
|
+
|
|
126
|
+
first_octet = der.unpack1('C').to_i
|
|
127
|
+
asn1_class_bits = first_octet & CLASS_MASK
|
|
128
|
+
if asn1_class_bits == CLASSES[:private]
|
|
129
|
+
_, _, tag_id, = Types.decode_identifier_octets(der)
|
|
130
|
+
@id_value = tag_id
|
|
131
|
+
@matched_tag = tag_id
|
|
132
|
+
true
|
|
133
|
+
else
|
|
134
|
+
no_match(der)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def check_multi_tag_id(der)
|
|
139
|
+
original_id = @id_value
|
|
140
|
+
@accepted_tags.each do |tag|
|
|
141
|
+
@id_value = tag
|
|
142
|
+
expected_id = encode_identifier_octets
|
|
143
|
+
real_id = der[0, expected_id.size]
|
|
144
|
+
if real_id == expected_id
|
|
145
|
+
@matched_tag = tag
|
|
146
|
+
return true
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
@id_value = original_id
|
|
150
|
+
no_match(der)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def no_match(der)
|
|
154
|
+
if optional?
|
|
155
|
+
@no_value = true
|
|
156
|
+
@value = void_value
|
|
157
|
+
elsif !@default.nil?
|
|
158
|
+
@value = @default
|
|
159
|
+
else
|
|
160
|
+
raise_id_error(der)
|
|
161
|
+
end
|
|
162
|
+
false
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def value_to_der
|
|
166
|
+
case @value
|
|
167
|
+
when Array then @value.map(&:to_der).join
|
|
168
|
+
else @value.to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def trace_data
|
|
173
|
+
''
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def explicit_type
|
|
177
|
+
opts = { name: name, value: @value }
|
|
178
|
+
if @accepted_tags.length == 1
|
|
179
|
+
opts[:tag] = @accepted_tags.first
|
|
180
|
+
elsif @accepted_tags.length > 1
|
|
181
|
+
opts[:tag] = @accepted_tags
|
|
182
|
+
end
|
|
183
|
+
opts[:any_private] = true if @any_private
|
|
184
|
+
opts[:class] = @asn1_class unless @asn1_class == :universal
|
|
185
|
+
self.class.new(opts)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 UniversalString
|
|
6
|
+
# @since 0.13.0
|
|
7
|
+
# @author zeroSteiner
|
|
8
|
+
class UniversalString < OctetString
|
|
9
|
+
# UniversalString id value
|
|
10
|
+
ID = 28
|
|
11
|
+
# UniversalString encoding
|
|
12
|
+
# @since 0.15.0
|
|
13
|
+
ENCODING = Encoding::UTF_32BE
|
|
14
|
+
|
|
15
|
+
# Get ASN.1 type
|
|
16
|
+
# @return [String]
|
|
17
|
+
def self.type
|
|
18
|
+
'UniversalString'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module RASN1
|
|
6
|
+
module Types
|
|
7
|
+
# ASN.1 UTCTime
|
|
8
|
+
#
|
|
9
|
+
# +{#value} of a +UtcTime+ should be a ruby Time.
|
|
10
|
+
#
|
|
11
|
+
# ===Notes
|
|
12
|
+
# When encoding, resulting string is always a UTC time, appended with +Z+.
|
|
13
|
+
# Seconds are always generated.
|
|
14
|
+
#
|
|
15
|
+
# On parsing, UTC times (ending with +Z+) and local times (ending with
|
|
16
|
+
# +sHHMM+, where +s+ is +++ or +-+, and +HHMM+ is the time differential
|
|
17
|
+
# betwen UTC and local time) are both supported. Seconds may be present or
|
|
18
|
+
# not.
|
|
19
|
+
# @author Sylvain Daubert
|
|
20
|
+
class UtcTime < Primitive
|
|
21
|
+
# UtcTime id value
|
|
22
|
+
ID = 23
|
|
23
|
+
|
|
24
|
+
# Get ASN.1 type
|
|
25
|
+
# @return [String]
|
|
26
|
+
def self.type
|
|
27
|
+
'UTCTime'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Make time value from +der+ string
|
|
31
|
+
# @param [String] der
|
|
32
|
+
# @param [::Boolean] ber
|
|
33
|
+
# @return [void]
|
|
34
|
+
# @raise [ASN1Error] unrecognized time format
|
|
35
|
+
def der_to_value(der, ber: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
36
|
+
format = case der.size
|
|
37
|
+
when 11, 15
|
|
38
|
+
'%Y%m%d%H%M%z'
|
|
39
|
+
when 13, 17
|
|
40
|
+
'%Y%m%d%H%M%S%z'
|
|
41
|
+
else
|
|
42
|
+
prefix = @name.nil? ? type : "tag #{@name}"
|
|
43
|
+
raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
|
|
44
|
+
end
|
|
45
|
+
century = (Time.now.year / 100).to_s
|
|
46
|
+
begin
|
|
47
|
+
@value = Strptime.new(format).exec(century + der)
|
|
48
|
+
rescue ArgumentError
|
|
49
|
+
prefix = @name.nil? ? type : "tag #{@name}"
|
|
50
|
+
raise ASN1Error, "#{prefix}: unrecognized format: #{der}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def value_to_der
|
|
57
|
+
@value.getutc.strftime('%y%m%d%H%M%SZ')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def trace_data
|
|
61
|
+
return super if explicit?
|
|
62
|
+
|
|
63
|
+
+' ' << raw_data
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# ASN.1 UTF8 String
|
|
6
|
+
# @author Sylvain Daubert
|
|
7
|
+
class Utf8String < OctetString
|
|
8
|
+
# Utf8String id value
|
|
9
|
+
ID = 12
|
|
10
|
+
# Utf8String encoding
|
|
11
|
+
# @since 0.15.0
|
|
12
|
+
ENCODING = Encoding::UTF_8
|
|
13
|
+
|
|
14
|
+
# Get ASN.1 type
|
|
15
|
+
# @return [String]
|
|
16
|
+
def self.type
|
|
17
|
+
'UTF8String'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RASN1
|
|
4
|
+
module Types
|
|
5
|
+
# Create VisibleString as a Constrained subclass of IA5String
|
|
6
|
+
Types.define_type(:VisibleString, from: IA5String) do |value|
|
|
7
|
+
value.nil? || value.chars.all? { |c| c.ord.between?(32, 126) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# ASN.1 Visible String
|
|
11
|
+
#
|
|
12
|
+
# VisibleString may only contain ASCII characters from range 32 to 126.
|
|
13
|
+
# @author LemonTree55
|
|
14
|
+
# @since 0.3.0
|
|
15
|
+
# @since 0.15.0 VisibleString is defined as a constrained {IA5String}. It will now raise if value contains a non-visible character.
|
|
16
|
+
class VisibleString
|
|
17
|
+
# VisibleString id value
|
|
18
|
+
# @since 0.3.0
|
|
19
|
+
ID = 26
|
|
20
|
+
|
|
21
|
+
# Get ASN.1 type
|
|
22
|
+
# @return [String]
|
|
23
|
+
# @author Sylvain Daubert
|
|
24
|
+
# @since 0.3.0
|
|
25
|
+
def self.type
|
|
26
|
+
'VisibleString'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|