derparse 0.1.0 → 0.2.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/README.md +81 -27
- data/lib/derparse.rb +31 -1
- data/lib/derparse/node.rb +15 -1
- data/lib/derparse/node/nil.rb +31 -0
- data/lib/derparse/node/sequence.rb +8 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a97c66e10d00854afeb8f25016c1e2a1078104fdaffaf0114b0583c83957403c
|
4
|
+
data.tar.gz: 9748b530905bddd81be8d7510daa7cff17f7e9fa88b8bedcfd54fc668f42a658
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3f09d136141f1d2c7e16fed6348ac044836d258ebf5f1c1b7e2be39e799f439b6bec67f27b0de32a6418f95f24f6e9d306d41a8f17a08a687e7f97ec6fbeb1f
|
7
|
+
data.tar.gz: 39c6a09d9f6fd69ee6f915116bbe8d38aafaa8ef400406e696599d46278c484a9cf2e09de9bd89fa16aa8c8a1f96ff51c050cae5aa335de41fd031c863459d37
|
data/README.md
CHANGED
@@ -41,38 +41,50 @@ to parse:
|
|
41
41
|
p = DerParse.new("0\x81\x80\x02\x01\x2a\x04\x44\x65\x72\x50\x61\x72\x73\x65\x02\x01\x45\x6e\x69\x63\x65")
|
42
42
|
```
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
If you've got multiple DER-encoded strings concatenated, that's no problem --
|
45
|
+
`DerParse` will handle that. If you're traversing, you'll get multiple nodes
|
46
|
+
of depth `0`.
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
```
|
48
|
+
The {`DerParse`} class has a number of instance methods to help you examine and
|
49
|
+
work with the parsed ASN.1 data.
|
51
50
|
|
52
|
-
which means, of course, that you can print it to screen:
|
53
51
|
|
54
|
-
|
55
|
-
puts p
|
56
|
-
```
|
52
|
+
## Traversing the entire DER tree
|
57
53
|
|
58
|
-
If you want to
|
59
|
-
through every element of the DER,
|
60
|
-
for each ASN.1 object that is encountered
|
61
|
-
|
54
|
+
If you want to examine every value in the DER, digging down into sequences
|
55
|
+
and such, then you want `#traverse`. It walks through every element of the DER, yielding
|
56
|
+
a {`DerParse::Node`} for each ASN.1 object that is encountered. When a constructed
|
57
|
+
element (structure, set) is encountered, a node for the sequence or set will be
|
58
|
+
yielded, followed by a node for each of the ASN.1 objects in the sequence of set.
|
62
59
|
|
63
60
|
```ruby
|
64
|
-
p.traverse do |node
|
65
|
-
puts "Parsing depth: #{depth} Offset: #{offset}"
|
61
|
+
p.traverse do |node|
|
62
|
+
puts "Parsing depth: #{node.depth} Offset: #{node.offset}"
|
66
63
|
puts "Header length: #{node.header_length} Data length: #{node.data_length}"
|
67
|
-
puts "Tag number: #{node.tag} Tag class: #{node.tag_class} Constructed: #{node.constructed
|
68
|
-
puts "Value: #{node.value}"
|
64
|
+
puts "Tag number: #{node.tag} Tag class: #{node.tag_class} Constructed: #{node.constructed?.inspect}"
|
65
|
+
puts "Value: #{node.value rescue nil}"
|
69
66
|
puts "INCOMPLETE!" unless node.complete?
|
70
67
|
end
|
71
68
|
```
|
72
69
|
|
70
|
+
If you call `#traverse` without a block, it'll return an `Enumerator`, which
|
71
|
+
returns the next node in the DER structure for each call to `#next`. It is
|
72
|
+
more convenient when you want to construct parsing state machines.
|
73
|
+
|
74
|
+
|
75
|
+
## Walking around nodes
|
76
|
+
|
77
|
+
If you want more control about how you travel through the tree of nodes, you
|
78
|
+
can get the first node in the DER tree with {`DerParse#first_node`}. Every
|
79
|
+
node has {`DerParse#next_node`} and {`DerParse#first_child`} methods, which do
|
80
|
+
what they say on the tin. In both cases, if there is no relevant node, a
|
81
|
+
{`DerParse::Node::Nil`} will be returned.
|
82
|
+
|
83
|
+
|
84
|
+
## Examining a node
|
85
|
+
|
73
86
|
There are a whole bunch of methods which {`DerParse::Node`} has to help you
|
74
|
-
query what sort of ASN.1 object you're dealing with.
|
75
|
-
in which there are multiple ASN.1 objects encoded sequentially.
|
87
|
+
query what sort of ASN.1 object you're dealing with.
|
76
88
|
|
77
89
|
It's is particularly important to note the {`DerParse::Node#complete?`} method.
|
78
90
|
Because `DerParse` tries to give you as much data as it can, even if the DER is
|
@@ -82,15 +94,56 @@ is known to not be "correct", in a strict sense. What counts as an
|
|
82
94
|
get complicated. For full details, see the documentation for
|
83
95
|
{DerParse#traverse}.
|
84
96
|
|
85
|
-
If you call `#traverse` without a block, it'll return an iterator, which allows
|
86
|
-
you to be more flexible with your iteration, like constructing complicated handlers
|
87
|
-
for parsed DER structures.
|
88
97
|
|
89
|
-
|
98
|
+
## Finding objects in a corrupted DER
|
99
|
+
|
100
|
+
If you're working with a string that is missing its beginning, or has its early
|
101
|
+
parts corrupted, you can try using the {`DerParse#resync`} method to try and "resync"
|
102
|
+
the parser, based on knowing that an object of a particular type is in there
|
103
|
+
*somewhere*.
|
104
|
+
|
105
|
+
It works by looking for an encoded ASN.1 object of a type you specify, and returning
|
106
|
+
a node instance for that object, which you can then traverse using `#next_node`
|
107
|
+
and `#first_child` (as appropriate).
|
108
|
+
|
109
|
+
There is always the risk of a false-positive when using `#resync`, so you
|
110
|
+
should never 100% trust the values it gives back. However, to try and minimise
|
111
|
+
the risk, we assume that the ASN.1 object you're looking for is complete (not
|
112
|
+
truncated), and that the rest of the string is valid DER. This means that all
|
113
|
+
of the lengths remaining in the DER must line up, which isn't particularly
|
114
|
+
likely to happen at random.
|
115
|
+
|
116
|
+
The downside of this validation approach is that if the corruption extends to
|
117
|
+
having extra "garbage" data at the end of the DER, then `#resync` won't find
|
118
|
+
anything. You can turn this off by passing `strict: false` to `#resync`, but
|
119
|
+
be aware that your false positive rate will skyrocket.
|
120
|
+
|
121
|
+
|
122
|
+
## Rendering as a string
|
123
|
+
|
124
|
+
This is quite straightforward. If a DER consists entirely of object types that
|
125
|
+
`DerParse` knows how to decode into Ruby values, you can just use `#to_s`:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
p.to_s # => "long, multi-line string of something something"
|
129
|
+
```
|
130
|
+
|
131
|
+
which means, of course, that you can print it to screen:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
puts p
|
135
|
+
```
|
136
|
+
|
137
|
+
If your DER contains an ASN.1 value that `DerParse` doesn't know how to
|
138
|
+
render, you'll get a {`DerParse::IncompatibleDatatypeError`}.
|
139
|
+
|
140
|
+
## A collection of Ruby objects
|
141
|
+
|
142
|
+
For this, you can use {`DerParse#to_a`}. This attempts to turn a DER blob into
|
90
143
|
a collection of Ruby objects. It handles many common ASN.1 data types,
|
91
144
|
including integers, octet and bit strings, sequences and sets (turns them into
|
92
145
|
arrays), and so on. If it hits an ASN.1 type it can't handle, it'll give up
|
93
|
-
and raise {`DerParse::
|
146
|
+
and raise {`DerParse::IncompatibleDatatypeError`}.
|
94
147
|
|
95
148
|
|
96
149
|
# Compatibility and Coverage
|
@@ -99,8 +152,9 @@ The parts of ASN.1 that `DerParse` best supports are those which I've needed
|
|
99
152
|
for my own purposes -- mainly sequences, integers, octet strings, and to a
|
100
153
|
lesser extent OIDs. In particular, it's important to note that `DerParse` is
|
101
154
|
for ***DER*** structures, so any BER-specific things like indefinite lengths
|
102
|
-
are not, and probably will
|
103
|
-
types are welcomed via a
|
155
|
+
are not, and probably will never, be supported. Support for rendering
|
156
|
+
additional ASN.1 types into specific Ruby objects are welcomed via a
|
157
|
+
well-tested PR.
|
104
158
|
|
105
159
|
|
106
160
|
# Security
|
data/lib/derparse.rb
CHANGED
@@ -6,7 +6,7 @@ class DerParse
|
|
6
6
|
|
7
7
|
def initialize(s)
|
8
8
|
if s.respond_to?(:to_str)
|
9
|
-
@s = s.to_str
|
9
|
+
@s = s.to_str.dup.force_encoding("BINARY")
|
10
10
|
else
|
11
11
|
raise ArgumentError,
|
12
12
|
"Must provide string to parse"
|
@@ -34,6 +34,36 @@ class DerParse
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def resync(tag, strict: true)
|
38
|
+
i = @s.index(tag)
|
39
|
+
|
40
|
+
until i.nil?
|
41
|
+
n = DerParse::Node.factory(@s[i..], offset: i)
|
42
|
+
|
43
|
+
if n.complete?
|
44
|
+
if !strict || n.next_node.nil?
|
45
|
+
return n
|
46
|
+
else
|
47
|
+
nn = n.next_node
|
48
|
+
while nn.complete? && !nn.next_node.nil?
|
49
|
+
nn = nn.next_node
|
50
|
+
end
|
51
|
+
if nn.complete?
|
52
|
+
return n
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
i = (ni = @s[i+1..].index(tag)) ? ni + i + 1: nil
|
58
|
+
end
|
59
|
+
|
60
|
+
DerParse::Node::Nil.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def first_node
|
64
|
+
node(@s, 0, 0) { |n| return n }
|
65
|
+
end
|
66
|
+
|
37
67
|
private
|
38
68
|
|
39
69
|
def node(der, depth, offset, &blk)
|
data/lib/derparse/node.rb
CHANGED
@@ -25,6 +25,7 @@ class DerParse
|
|
25
25
|
@rest = parse_data(r)
|
26
26
|
rescue IncompleteDer
|
27
27
|
@complete = false
|
28
|
+
@rest = ""
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -56,8 +57,20 @@ class DerParse
|
|
56
57
|
@complete
|
57
58
|
end
|
58
59
|
|
60
|
+
def next_node
|
61
|
+
if @rest.empty?
|
62
|
+
DerParse::Node::Nil.new
|
63
|
+
else
|
64
|
+
DerParse::Node.factory(@rest)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def first_child
|
69
|
+
DerParse::Node::Nil.new
|
70
|
+
end
|
71
|
+
|
59
72
|
def value
|
60
|
-
raise IncompatibleDatatypeError
|
73
|
+
raise IncompatibleDatatypeError, "No known way to render tag #{@tag} into a value"
|
61
74
|
end
|
62
75
|
|
63
76
|
private
|
@@ -138,5 +151,6 @@ end
|
|
138
151
|
|
139
152
|
require_relative "./node/integer"
|
140
153
|
require_relative "./node/octet_string"
|
154
|
+
require_relative "./node/nil"
|
141
155
|
require_relative "./node/null"
|
142
156
|
require_relative "./node/sequence"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class DerParse
|
2
|
+
class Node
|
3
|
+
# This class does not represent an ASN.1 object, but instead is a special
|
4
|
+
# node that is returned when no other node would be suitable. It is, shall
|
5
|
+
# we say, "`nil`-compatible".
|
6
|
+
class Nil < Node
|
7
|
+
def initialize(*_)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.handles?(_)
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def nil?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_node
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def first_child
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: derparse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- lib/derparse.rb
|
161
161
|
- lib/derparse/node.rb
|
162
162
|
- lib/derparse/node/integer.rb
|
163
|
+
- lib/derparse/node/nil.rb
|
163
164
|
- lib/derparse/node/null.rb
|
164
165
|
- lib/derparse/node/octet_string.rb
|
165
166
|
- lib/derparse/node/sequence.rb
|