rasn1 0.6.8 → 0.7.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 +4 -4
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -3
- data/Changelog.md +11 -0
- data/README.md +11 -2
- data/lib/rasn1.rb +6 -5
- data/lib/rasn1/model.rb +83 -33
- data/lib/rasn1/types.rb +11 -8
- data/lib/rasn1/types/any.rb +16 -16
- data/lib/rasn1/types/base.rb +73 -66
- data/lib/rasn1/types/bit_string.rb +14 -16
- data/lib/rasn1/types/boolean.rb +15 -12
- data/lib/rasn1/types/choice.rb +9 -14
- data/lib/rasn1/types/constructed.rb +8 -9
- data/lib/rasn1/types/enumerated.rb +3 -1
- data/lib/rasn1/types/generalized_time.rb +23 -25
- data/lib/rasn1/types/ia5string.rb +6 -4
- data/lib/rasn1/types/integer.rb +21 -19
- data/lib/rasn1/types/null.rb +7 -7
- data/lib/rasn1/types/numeric_string.rb +5 -3
- data/lib/rasn1/types/object_id.rb +12 -11
- data/lib/rasn1/types/octet_string.rb +5 -5
- data/lib/rasn1/types/primitive.rb +2 -1
- data/lib/rasn1/types/printable_string.rb +5 -3
- data/lib/rasn1/types/sequence.rb +20 -6
- data/lib/rasn1/types/sequence_of.rb +13 -11
- data/lib/rasn1/types/set.rb +3 -1
- data/lib/rasn1/types/set_of.rb +3 -2
- data/lib/rasn1/types/utc_time.rb +5 -3
- data/lib/rasn1/types/utf8_string.rb +5 -3
- data/lib/rasn1/types/visible_string.rb +3 -2
- data/lib/rasn1/version.rb +3 -1
- data/rasn1.gemspec +1 -1
- metadata +6 -5
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Enumerated
|
5
6
|
#
|
6
7
|
# An enumerated type permits to assign names to integer values. It may be defined
|
@@ -21,6 +22,7 @@ module RASN1
|
|
21
22
|
# @!attribute enum
|
22
23
|
# @return [Hash]
|
23
24
|
|
25
|
+
# Enumerated tag value
|
24
26
|
TAG = 0x0a
|
25
27
|
|
26
28
|
# @overload initialize(options={})
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 GeneralizedTime
|
5
6
|
#
|
6
7
|
# +{#value} of a {GeneralizedTime} should be a ruby Time.
|
@@ -20,6 +21,7 @@ module RASN1
|
|
20
21
|
# second are supported.
|
21
22
|
# @author Sylvain Daubert
|
22
23
|
class GeneralizedTime < Primitive
|
24
|
+
# GeneralizedTime tag value
|
23
25
|
TAG = 24
|
24
26
|
|
25
27
|
# Get ASN.1 type
|
@@ -29,50 +31,48 @@ module RASN1
|
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
32
|
-
|
34
|
+
|
33
35
|
def value_to_der
|
34
|
-
if @value.nsec
|
36
|
+
if @value.nsec.positive?
|
35
37
|
der = @value.getutc.strftime('%Y%m%d%H%M%S.%9NZ')
|
36
38
|
der.sub(/0+Z/, 'Z')
|
37
39
|
else
|
38
40
|
@value.getutc.strftime('%Y%m%d%H%M%SZ')
|
39
41
|
end
|
40
42
|
end
|
41
|
-
|
43
|
+
|
42
44
|
def der_to_value(der, ber: false)
|
43
45
|
date_hour, fraction = der.split('.')
|
44
46
|
utc_offset_forced = false
|
45
47
|
if fraction.nil?
|
46
|
-
if date_hour[-1] != 'Z'
|
48
|
+
if (date_hour[-1] != 'Z') && (date_hour !~ /[+-]\d+$/)
|
47
49
|
# If not UTC, have to add offset with UTC to force
|
48
50
|
# DateTime#strptime to generate a local time. But this difference
|
49
51
|
# may be errored because of DST.
|
50
52
|
date_hour << Time.now.strftime('%z')
|
51
53
|
utc_offset_forced = true
|
52
54
|
end
|
55
|
+
elsif fraction[-1] == 'Z'
|
56
|
+
fraction = fraction[0...-1]
|
57
|
+
date_hour << 'Z'
|
53
58
|
else
|
54
|
-
|
55
|
-
|
56
|
-
|
59
|
+
match = fraction.match(/(\d+)([+-]\d+)/)
|
60
|
+
if match
|
61
|
+
# fraction contains fraction and timezone info. Split them
|
62
|
+
fraction = match[1]
|
63
|
+
date_hour << match[2]
|
57
64
|
else
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# fraction only contains fraction.
|
65
|
-
# Have to add offset with UTC to force DateTime#strptime to
|
66
|
-
# generate a local time. But this difference may be errored
|
67
|
-
# because of DST.
|
68
|
-
date_hour << Time.now.strftime('%z')
|
69
|
-
utc_offset_forced = true
|
70
|
-
end
|
65
|
+
# fraction only contains fraction.
|
66
|
+
# Have to add offset with UTC to force DateTime#strptime to
|
67
|
+
# generate a local time. But this difference may be errored
|
68
|
+
# because of DST.
|
69
|
+
date_hour << Time.now.strftime('%z')
|
70
|
+
utc_offset_forced = true
|
71
71
|
end
|
72
72
|
end
|
73
73
|
format = case date_hour.size
|
74
74
|
when 11
|
75
|
-
frac_base = 60*60
|
75
|
+
frac_base = 60 * 60
|
76
76
|
'%Y%m%d%HZ'
|
77
77
|
when 13
|
78
78
|
frac_base = 60
|
@@ -100,9 +100,7 @@ module RASN1
|
|
100
100
|
# Check DST. There may be a shift of one hour...
|
101
101
|
if utc_offset_forced
|
102
102
|
compare_time = Time.new(*@value.to_a[0..5].reverse)
|
103
|
-
if compare_time.utc_offset != @value.utc_offset
|
104
|
-
@value = compare_time
|
105
|
-
end
|
103
|
+
@value = compare_time if compare_time.utc_offset != @value.utc_offset
|
106
104
|
end
|
107
105
|
@value += ".#{fraction}".to_r * frac_base unless fraction.nil?
|
108
106
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 IA5 String
|
5
6
|
# @author Sylvain Daubert
|
6
7
|
class IA5String < OctetString
|
8
|
+
# IA5String tag value
|
7
9
|
TAG = 22
|
8
10
|
|
9
11
|
# Get ASN.1 type
|
@@ -13,15 +15,15 @@ module RASN1
|
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
16
|
-
|
18
|
+
|
17
19
|
def value_to_der
|
18
20
|
@value.to_s.force_encoding('US-ASCII').force_encoding('BINARY')
|
19
21
|
end
|
20
22
|
|
21
|
-
def der_to_value(der, ber:false)
|
23
|
+
def der_to_value(der, ber: false)
|
22
24
|
super
|
23
25
|
@value.force_encoding('US-ASCII')
|
24
26
|
end
|
25
|
-
|
27
|
+
end
|
26
28
|
end
|
27
29
|
end
|
data/lib/rasn1/types/integer.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Integer
|
5
6
|
# @author Sylvain Daubert
|
6
7
|
class Integer < Primitive
|
7
8
|
# @return [Hash,nil]
|
8
9
|
attr_reader :enum
|
9
10
|
|
11
|
+
# Integer tag value
|
10
12
|
TAG = 0x02
|
11
13
|
|
12
14
|
# @overload initialize(options={})
|
@@ -30,19 +32,19 @@ module RASN1
|
|
30
32
|
self.value = @value
|
31
33
|
|
32
34
|
case @default
|
33
|
-
when String,Symbol
|
34
|
-
unless @enum.
|
35
|
-
raise EnumeratedError, "TAG
|
35
|
+
when String, Symbol
|
36
|
+
unless @enum.key? @default
|
37
|
+
raise EnumeratedError, "TAG #{@name}: unknwon enumerated default value #@{default}"
|
36
38
|
end
|
37
39
|
when ::Integer
|
38
|
-
if @enum.
|
40
|
+
if @enum.value? @default
|
39
41
|
@default = @enum.key(@default)
|
40
42
|
else
|
41
|
-
raise EnumeratedError, "TAG
|
43
|
+
raise EnumeratedError, "TAG #{@name}: default value #@{default} not in enumeration"
|
42
44
|
end
|
43
45
|
when nil
|
44
46
|
else
|
45
|
-
raise TypeError, "TAG
|
47
|
+
raise TypeError, "TAG #{@name}: #{@default.class} not handled as default value"
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -51,24 +53,24 @@ module RASN1
|
|
51
53
|
def value=(v)
|
52
54
|
case v
|
53
55
|
when String,Symbol
|
54
|
-
raise EnumeratedError,
|
56
|
+
raise EnumeratedError, "TAG #{@name} has no :enum" if @enum.nil?
|
55
57
|
|
56
|
-
unless @enum.
|
57
|
-
raise EnumeratedError, "TAG
|
58
|
+
unless @enum.key? v
|
59
|
+
raise EnumeratedError, "TAG #{@name}: unknwon enumerated value #{v}"
|
58
60
|
end
|
59
61
|
@value = v
|
60
62
|
when ::Integer
|
61
63
|
if @enum.nil?
|
62
64
|
@value = v
|
63
|
-
elsif @enum.
|
65
|
+
elsif @enum.value? v
|
64
66
|
@value = @enum.key(v)
|
65
67
|
else
|
66
|
-
raise EnumeratedError, "TAG
|
68
|
+
raise EnumeratedError, "TAG #{@name}: #{v} not in enumeration"
|
67
69
|
end
|
68
70
|
when nil
|
69
71
|
@value = nil
|
70
72
|
else
|
71
|
-
raise EnumeratedError, "TAG
|
73
|
+
raise EnumeratedError, "TAG #{@name}: not in enumeration"
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
@@ -86,15 +88,15 @@ module RASN1
|
|
86
88
|
|
87
89
|
def int_value_to_der(value=nil)
|
88
90
|
v = value || @value
|
89
|
-
size = v.bit_length / 8 + (v.bit_length % 8
|
90
|
-
size = 1 if size
|
91
|
-
comp_value = if v
|
91
|
+
size = v.bit_length / 8 + ((v.bit_length % 8).positive? ? 1 : 0)
|
92
|
+
size = 1 if size.zero?
|
93
|
+
comp_value = if v.positive?
|
92
94
|
# If MSB is 1, increment size to set initial octet
|
93
95
|
# to 0 to amrk it as a positive integer
|
94
96
|
size += 1 if v >> (size * 8 - 1) == 1
|
95
97
|
v
|
96
98
|
else
|
97
|
-
~
|
99
|
+
~v.abs + 1
|
98
100
|
end
|
99
101
|
ary = []
|
100
102
|
size.times { ary << (comp_value & 0xff); comp_value >>= 8 }
|
@@ -108,7 +110,7 @@ module RASN1
|
|
108
110
|
when ::Integer
|
109
111
|
int_value_to_der
|
110
112
|
else
|
111
|
-
raise TypeError, "TAG
|
113
|
+
raise TypeError, "TAG #{@name}: #{@value.class} not handled"
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
@@ -126,7 +128,7 @@ module RASN1
|
|
126
128
|
return if @enum.nil?
|
127
129
|
|
128
130
|
@value = @enum.key(@value)
|
129
|
-
raise EnumeratedError, "TAG
|
131
|
+
raise EnumeratedError, "TAG #{@name}: value #{v} not in enumeration" if @value.nil?
|
130
132
|
end
|
131
133
|
|
132
134
|
def explicit_type
|
data/lib/rasn1/types/null.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Null
|
5
6
|
# @author Sylvain Daubert
|
6
7
|
class Null < Primitive
|
8
|
+
# Null tag value
|
7
9
|
TAG = 0x05
|
8
10
|
|
9
11
|
# @return [String]
|
10
12
|
def inspect(level=0)
|
11
|
-
str = ''
|
12
|
-
str << '
|
13
|
-
str << "#{@name} " unless @name.nil?
|
14
|
-
str << "#{type}"
|
15
|
-
str << " OPTIONAL" if optional?
|
13
|
+
str = common_inspect(level)[0..-2] # remove terminal ':'
|
14
|
+
str << ' OPTIONAL' if optional?
|
16
15
|
str
|
17
16
|
end
|
18
17
|
|
@@ -23,7 +22,8 @@ module RASN1
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def der_to_value(der, ber: false)
|
26
|
-
raise ASN1Error,
|
25
|
+
raise ASN1Error, 'NULL TAG should not have content!' if der.length.positive?
|
26
|
+
|
27
27
|
@value = nil
|
28
28
|
end
|
29
29
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Numeric String
|
5
6
|
# @author Sylvain Daubert
|
6
7
|
class NumericString < OctetString
|
8
|
+
# NumericString tag value
|
7
9
|
TAG = 18
|
8
10
|
|
9
11
|
# Get ASN.1 type
|
@@ -13,7 +15,7 @@ module RASN1
|
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
16
|
-
|
18
|
+
|
17
19
|
def value_to_der
|
18
20
|
check_characters
|
19
21
|
@value.to_s.force_encoding('BINARY')
|
@@ -26,7 +28,7 @@ module RASN1
|
|
26
28
|
|
27
29
|
def check_characters
|
28
30
|
if @value.to_s =~ /([^0-9 ])/
|
29
|
-
raise ASN1Error, "NUMERIC STRING
|
31
|
+
raise ASN1Error, "NUMERIC STRING #{@name}: invalid character: '#{$1}'"
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -1,20 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
5
|
|
4
6
|
# ASN.1 Object ID
|
5
7
|
# @author Sylvain Daubert
|
6
8
|
class ObjectId < Primitive
|
9
|
+
# ObjectId tag value
|
7
10
|
TAG = 6
|
8
11
|
|
9
12
|
private
|
10
13
|
|
11
14
|
def value_to_der
|
12
|
-
ids = @value.split('.').map!
|
15
|
+
ids = @value.split('.').map!(&:to_i)
|
13
16
|
|
14
17
|
if ids[0] > 2
|
15
18
|
raise ASN1Error, 'OBJECT ID #@name: first subidentifier should be less than 3'
|
16
19
|
end
|
17
|
-
if ids[0] < 2
|
20
|
+
if (ids[0] < 2) && (ids[1] > 39)
|
18
21
|
raise ASN1Error, 'OBJECT ID #@name: second subidentifier should be less than 40'
|
19
22
|
end
|
20
23
|
|
@@ -23,8 +26,8 @@ module RASN1
|
|
23
26
|
next v if v < 128
|
24
27
|
|
25
28
|
ary = []
|
26
|
-
while v
|
27
|
-
ary.unshift
|
29
|
+
while v.positive?
|
30
|
+
ary.unshift((v & 0x7f) | 0x80)
|
28
31
|
v >>= 7
|
29
32
|
end
|
30
33
|
ary[-1] &= 0x7f
|
@@ -35,17 +38,15 @@ module RASN1
|
|
35
38
|
|
36
39
|
def der_to_value(der, ber: false)
|
37
40
|
bytes = der.unpack('C*')
|
41
|
+
nr_bytes_to_remove = 1
|
38
42
|
ids = if bytes[0] < 80
|
39
|
-
|
40
|
-
[ bytes[0] / 40, bytes[0] % 40]
|
43
|
+
[bytes[0] / 40, bytes[0] % 40]
|
41
44
|
elsif bytes[0] < 128
|
42
|
-
remove = 1
|
43
45
|
[2, bytes[0] - 80]
|
44
46
|
else
|
45
|
-
remove = 1
|
46
47
|
second_id = bytes[0] & 0x7f
|
47
48
|
bytes[1..-1].each do |byte|
|
48
|
-
|
49
|
+
nr_bytes_to_remove += 1
|
49
50
|
second_id <<= 7
|
50
51
|
if byte < 128
|
51
52
|
second_id |= byte
|
@@ -58,10 +59,10 @@ module RASN1
|
|
58
59
|
end
|
59
60
|
|
60
61
|
id = 0
|
61
|
-
bytes.shift(
|
62
|
+
bytes.shift(nr_bytes_to_remove)
|
62
63
|
bytes.each do |byte|
|
63
64
|
if byte < 128
|
64
|
-
if id
|
65
|
+
if id.zero?
|
65
66
|
ids << byte
|
66
67
|
else
|
67
68
|
ids << ((id << 7) | byte)
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Octet String
|
5
6
|
#
|
6
7
|
# An OCTET STRINT may contain another primtive object:
|
@@ -11,13 +12,12 @@ module RASN1
|
|
11
12
|
# os.to_der # => DER string with INTEGER in OCTET STRING
|
12
13
|
# @author Sylvain Daubert
|
13
14
|
class OctetString < Primitive
|
15
|
+
# OctetString tag value
|
14
16
|
TAG = 0x04
|
15
17
|
|
16
18
|
def inspect(level=0)
|
17
|
-
str =
|
18
|
-
str <<
|
19
|
-
str << "#{@name} " unless @name.nil?
|
20
|
-
str << "#{type}: #{value.inspect}"
|
19
|
+
str = common_inspect(level)
|
20
|
+
str << " #{value.inspect}"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RASN1
|
2
4
|
module Types
|
3
|
-
|
4
5
|
# ASN.1 Printable String
|
5
6
|
# @author Sylvain Daubert
|
6
7
|
class PrintableString < OctetString
|
8
|
+
# PrintableString tag value
|
7
9
|
TAG = 19
|
8
10
|
|
9
11
|
# Get ASN.1 type
|
@@ -13,7 +15,7 @@ module RASN1
|
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
16
|
-
|
18
|
+
|
17
19
|
def value_to_der
|
18
20
|
check_characters
|
19
21
|
@value.to_s.force_encoding('BINARY')
|
@@ -26,7 +28,7 @@ module RASN1
|
|
26
28
|
|
27
29
|
def check_characters
|
28
30
|
if @value.to_s =~ /([^a-zA-Z0-9 '=\(\)\+,\-\.\/:\?])/
|
29
|
-
raise ASN1Error, "PRINTABLE STRING
|
31
|
+
raise ASN1Error, "PRINTABLE STRING #{@name}: invalid character: '#{$1}'"
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|