derparse 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 719be66a0a1211105f01fcab6ca73988fd5ca6a173b6c1d39b91101ced07a3da
4
- data.tar.gz: 2e0c0d0241a457f84f80b0be1233002fc0aa2e24ccc4b5d9b5c030a0a524f893
3
+ metadata.gz: a97c66e10d00854afeb8f25016c1e2a1078104fdaffaf0114b0583c83957403c
4
+ data.tar.gz: 9748b530905bddd81be8d7510daa7cff17f7e9fa88b8bedcfd54fc668f42a658
5
5
  SHA512:
6
- metadata.gz: 025a57ef3f0bf26dd8d3387cbf122cf4c285148ed132fe293009122715e12afd923460c52a89eb3f687be40145b48d218a7960667f62127486911279291ee15d
7
- data.tar.gz: ea07d6a6fa38ff4874cb439b7aacf84556fddee24c94a9027d157eae3e9a9461440d6f8947625a56079db18753d66b3fa2e72126546a0e06e2ce7b3ccddfe2c8
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
- {DerParse} has a number of instance methods to help you examine and work with
45
- the parsed ASN.1 data. For example, you can get a string description of the
46
- data and its structure:
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
- ```ruby
49
- p.to_s # => "long, multi-line string of something something"
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
- ```ruby
55
- puts p
56
- ```
52
+ ## Traversing the entire DER tree
57
53
 
58
- If you want to process the data, you can use `#traverse` to walk
59
- through every element of the DER, which will yield a {`DerParse::Node`}
60
- for each ASN.1 object that is encountered, along with info about the
61
- parser state.
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, depth, offset|
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. It also handles a string
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
- Finally, you can try {`DerParse#to_a`}. This attempts to turn a DER blob into
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::IncompatibleDataTypeError`}.
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 not, be supported. Support for additional ASN.1
103
- types are welcomed via a well-tested PR.
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
@@ -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)
@@ -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
@@ -16,6 +16,14 @@ class DerParse
16
16
  end
17
17
  end
18
18
  end
19
+
20
+ def first_child
21
+ if @data.empty?
22
+ DerParse::Node::Nil.new
23
+ else
24
+ DerParse::Node.factory(@data)
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  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.1.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-08 00:00:00.000000000 Z
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