Chris@0
|
1 # $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $
|
Chris@0
|
2 #
|
Chris@0
|
3 # NET::BER
|
Chris@0
|
4 # Mixes ASN.1/BER convenience methods into several standard classes.
|
Chris@0
|
5 # Also provides BER parsing functionality.
|
Chris@0
|
6 #
|
Chris@0
|
7 #----------------------------------------------------------------------------
|
Chris@0
|
8 #
|
Chris@0
|
9 # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
Chris@0
|
10 #
|
Chris@0
|
11 # Gmail: garbagecat10
|
Chris@0
|
12 #
|
Chris@0
|
13 # This program is free software; you can redistribute it and/or modify
|
Chris@0
|
14 # it under the terms of the GNU General Public License as published by
|
Chris@0
|
15 # the Free Software Foundation; either version 2 of the License, or
|
Chris@0
|
16 # (at your option) any later version.
|
Chris@0
|
17 #
|
Chris@0
|
18 # This program is distributed in the hope that it will be useful,
|
Chris@0
|
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Chris@0
|
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Chris@0
|
21 # GNU General Public License for more details.
|
Chris@0
|
22 #
|
Chris@0
|
23 # You should have received a copy of the GNU General Public License
|
Chris@0
|
24 # along with this program; if not, write to the Free Software
|
Chris@0
|
25 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Chris@0
|
26 #
|
Chris@0
|
27 #---------------------------------------------------------------------------
|
Chris@0
|
28 #
|
Chris@0
|
29 #
|
Chris@0
|
30
|
Chris@0
|
31
|
Chris@0
|
32
|
Chris@0
|
33
|
Chris@0
|
34 module Net
|
Chris@0
|
35
|
Chris@0
|
36 module BER
|
Chris@0
|
37
|
Chris@0
|
38 class BerError < Exception; end
|
Chris@0
|
39
|
Chris@0
|
40
|
Chris@0
|
41 # This module is for mixing into IO and IO-like objects.
|
Chris@0
|
42 module BERParser
|
Chris@0
|
43
|
Chris@0
|
44 # The order of these follows the class-codes in BER.
|
Chris@0
|
45 # Maybe this should have been a hash.
|
Chris@0
|
46 TagClasses = [:universal, :application, :context_specific, :private]
|
Chris@0
|
47
|
Chris@0
|
48 BuiltinSyntax = {
|
Chris@0
|
49 :universal => {
|
Chris@0
|
50 :primitive => {
|
Chris@0
|
51 1 => :boolean,
|
Chris@0
|
52 2 => :integer,
|
Chris@0
|
53 4 => :string,
|
Chris@0
|
54 10 => :integer,
|
Chris@0
|
55 },
|
Chris@0
|
56 :constructed => {
|
Chris@0
|
57 16 => :array,
|
Chris@0
|
58 17 => :array
|
Chris@0
|
59 }
|
Chris@0
|
60 }
|
Chris@0
|
61 }
|
Chris@0
|
62
|
Chris@0
|
63 #
|
Chris@0
|
64 # read_ber
|
Chris@0
|
65 # TODO: clean this up so it works properly with partial
|
Chris@0
|
66 # packets coming from streams that don't block when
|
Chris@0
|
67 # we ask for more data (like StringIOs). At it is,
|
Chris@0
|
68 # this can throw TypeErrors and other nasties.
|
Chris@0
|
69 #
|
Chris@0
|
70 def read_ber syntax=nil
|
Chris@0
|
71 return nil if (StringIO == self.class) and eof?
|
Chris@0
|
72
|
Chris@0
|
73 id = getc # don't trash this value, we'll use it later
|
Chris@0
|
74 tag = id & 31
|
Chris@0
|
75 tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
|
Chris@0
|
76 tagclass = TagClasses[ id >> 6 ]
|
Chris@0
|
77 encoding = (id & 0x20 != 0) ? :constructed : :primitive
|
Chris@0
|
78
|
Chris@0
|
79 n = getc
|
Chris@0
|
80 lengthlength,contentlength = if n <= 127
|
Chris@0
|
81 [1,n]
|
Chris@0
|
82 else
|
Chris@0
|
83 j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
|
Chris@0
|
84 [1 + (n & 127), j]
|
Chris@0
|
85 end
|
Chris@0
|
86
|
Chris@0
|
87 newobj = read contentlength
|
Chris@0
|
88
|
Chris@0
|
89 objtype = nil
|
Chris@0
|
90 [syntax, BuiltinSyntax].each {|syn|
|
Chris@0
|
91 if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
|
Chris@0
|
92 objtype = ot[tag]
|
Chris@0
|
93 break
|
Chris@0
|
94 end
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 obj = case objtype
|
Chris@0
|
98 when :boolean
|
Chris@0
|
99 newobj != "\000"
|
Chris@0
|
100 when :string
|
Chris@0
|
101 (newobj || "").dup
|
Chris@0
|
102 when :integer
|
Chris@0
|
103 j = 0
|
Chris@0
|
104 newobj.each_byte {|b| j = (j << 8) + b}
|
Chris@0
|
105 j
|
Chris@0
|
106 when :array
|
Chris@0
|
107 seq = []
|
Chris@0
|
108 sio = StringIO.new( newobj || "" )
|
Chris@0
|
109 # Interpret the subobject, but note how the loop
|
Chris@0
|
110 # is built: nil ends the loop, but false (a valid
|
Chris@0
|
111 # BER value) does not!
|
Chris@0
|
112 while (e = sio.read_ber(syntax)) != nil
|
Chris@0
|
113 seq << e
|
Chris@0
|
114 end
|
Chris@0
|
115 seq
|
Chris@0
|
116 else
|
Chris@0
|
117 raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
|
Chris@0
|
118 end
|
Chris@0
|
119
|
Chris@0
|
120 # Add the identifier bits into the object if it's a String or an Array.
|
Chris@0
|
121 # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
|
Chris@0
|
122 obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
|
Chris@0
|
123 obj
|
Chris@0
|
124
|
Chris@0
|
125 end
|
Chris@0
|
126
|
Chris@0
|
127 end # module BERParser
|
Chris@0
|
128 end # module BER
|
Chris@0
|
129
|
Chris@0
|
130 end # module Net
|
Chris@0
|
131
|
Chris@0
|
132
|
Chris@0
|
133 class IO
|
Chris@0
|
134 include Net::BER::BERParser
|
Chris@0
|
135 end
|
Chris@0
|
136
|
Chris@0
|
137 require "stringio"
|
Chris@0
|
138 class StringIO
|
Chris@0
|
139 include Net::BER::BERParser
|
Chris@0
|
140 end
|
Chris@0
|
141
|
Chris@0
|
142 begin
|
Chris@0
|
143 require 'openssl'
|
Chris@0
|
144 class OpenSSL::SSL::SSLSocket
|
Chris@0
|
145 include Net::BER::BERParser
|
Chris@0
|
146 end
|
Chris@0
|
147 rescue LoadError
|
Chris@0
|
148 # Ignore LoadError.
|
Chris@0
|
149 # DON'T ignore NameError, which means the SSLSocket class
|
Chris@0
|
150 # is somehow unavailable on this implementation of Ruby's openssl.
|
Chris@0
|
151 # This may be WRONG, however, because we don't yet know how Ruby's
|
Chris@0
|
152 # openssl behaves on machines with no OpenSSL library. I suppose
|
Chris@0
|
153 # it's possible they do not fail to require 'openssl' but do not
|
Chris@0
|
154 # create the classes. So this code is provisional.
|
Chris@0
|
155 # Also, you might think that OpenSSL::SSL::SSLSocket inherits from
|
Chris@0
|
156 # IO so we'd pick it up above. But you'd be wrong.
|
Chris@0
|
157 end
|
Chris@0
|
158
|
Chris@0
|
159 class String
|
Chris@0
|
160 def read_ber syntax=nil
|
Chris@0
|
161 StringIO.new(self).read_ber(syntax)
|
Chris@0
|
162 end
|
Chris@0
|
163 end
|
Chris@0
|
164
|
Chris@0
|
165
|
Chris@0
|
166
|
Chris@0
|
167 #----------------------------------------------
|
Chris@0
|
168
|
Chris@0
|
169
|
Chris@0
|
170 class FalseClass
|
Chris@0
|
171 #
|
Chris@0
|
172 # to_ber
|
Chris@0
|
173 #
|
Chris@0
|
174 def to_ber
|
Chris@0
|
175 "\001\001\000"
|
Chris@0
|
176 end
|
Chris@0
|
177 end
|
Chris@0
|
178
|
Chris@0
|
179
|
Chris@0
|
180 class TrueClass
|
Chris@0
|
181 #
|
Chris@0
|
182 # to_ber
|
Chris@0
|
183 #
|
Chris@0
|
184 def to_ber
|
Chris@0
|
185 "\001\001\001"
|
Chris@0
|
186 end
|
Chris@0
|
187 end
|
Chris@0
|
188
|
Chris@0
|
189
|
Chris@0
|
190
|
Chris@0
|
191 class Fixnum
|
Chris@0
|
192 #
|
Chris@0
|
193 # to_ber
|
Chris@0
|
194 #
|
Chris@0
|
195 def to_ber
|
Chris@0
|
196 i = [self].pack('w')
|
Chris@0
|
197 [2, i.length].pack("CC") + i
|
Chris@0
|
198 end
|
Chris@0
|
199
|
Chris@0
|
200 #
|
Chris@0
|
201 # to_ber_enumerated
|
Chris@0
|
202 #
|
Chris@0
|
203 def to_ber_enumerated
|
Chris@0
|
204 i = [self].pack('w')
|
Chris@0
|
205 [10, i.length].pack("CC") + i
|
Chris@0
|
206 end
|
Chris@0
|
207
|
Chris@0
|
208 #
|
Chris@0
|
209 # to_ber_length_encoding
|
Chris@0
|
210 #
|
Chris@0
|
211 def to_ber_length_encoding
|
Chris@0
|
212 if self <= 127
|
Chris@0
|
213 [self].pack('C')
|
Chris@0
|
214 else
|
Chris@0
|
215 i = [self].pack('N').sub(/^[\0]+/,"")
|
Chris@0
|
216 [0x80 + i.length].pack('C') + i
|
Chris@0
|
217 end
|
Chris@0
|
218 end
|
Chris@0
|
219
|
Chris@0
|
220 end # class Fixnum
|
Chris@0
|
221
|
Chris@0
|
222
|
Chris@0
|
223 class Bignum
|
Chris@0
|
224
|
Chris@0
|
225 def to_ber
|
Chris@0
|
226 i = [self].pack('w')
|
Chris@0
|
227 i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
|
Chris@0
|
228 [2, i.length].pack("CC") + i
|
Chris@0
|
229 end
|
Chris@0
|
230
|
Chris@0
|
231 end
|
Chris@0
|
232
|
Chris@0
|
233
|
Chris@0
|
234
|
Chris@0
|
235 class String
|
Chris@0
|
236 #
|
Chris@0
|
237 # to_ber
|
Chris@0
|
238 # A universal octet-string is tag number 4,
|
Chris@0
|
239 # but others are possible depending on the context, so we
|
Chris@0
|
240 # let the caller give us one.
|
Chris@0
|
241 # The preferred way to do this in user code is via to_ber_application_sring
|
Chris@0
|
242 # and to_ber_contextspecific.
|
Chris@0
|
243 #
|
Chris@0
|
244 def to_ber code = 4
|
Chris@0
|
245 [code].pack('C') + length.to_ber_length_encoding + self
|
Chris@0
|
246 end
|
Chris@0
|
247
|
Chris@0
|
248 #
|
Chris@0
|
249 # to_ber_application_string
|
Chris@0
|
250 #
|
Chris@0
|
251 def to_ber_application_string code
|
Chris@0
|
252 to_ber( 0x40 + code )
|
Chris@0
|
253 end
|
Chris@0
|
254
|
Chris@0
|
255 #
|
Chris@0
|
256 # to_ber_contextspecific
|
Chris@0
|
257 #
|
Chris@0
|
258 def to_ber_contextspecific code
|
Chris@0
|
259 to_ber( 0x80 + code )
|
Chris@0
|
260 end
|
Chris@0
|
261
|
Chris@0
|
262 end # class String
|
Chris@0
|
263
|
Chris@0
|
264
|
Chris@0
|
265
|
Chris@0
|
266 class Array
|
Chris@0
|
267 #
|
Chris@0
|
268 # to_ber_appsequence
|
Chris@0
|
269 # An application-specific sequence usually gets assigned
|
Chris@0
|
270 # a tag that is meaningful to the particular protocol being used.
|
Chris@0
|
271 # This is different from the universal sequence, which usually
|
Chris@0
|
272 # gets a tag value of 16.
|
Chris@0
|
273 # Now here's an interesting thing: We're adding the X.690
|
Chris@0
|
274 # "application constructed" code at the top of the tag byte (0x60),
|
Chris@0
|
275 # but some clients, notably ldapsearch, send "context-specific
|
Chris@0
|
276 # constructed" (0xA0). The latter would appear to violate RFC-1777,
|
Chris@0
|
277 # but what do I know? We may need to change this.
|
Chris@0
|
278 #
|
Chris@0
|
279
|
Chris@0
|
280 def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
|
Chris@0
|
281 def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
|
Chris@0
|
282 def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
|
Chris@0
|
283 def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
|
Chris@0
|
284 def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
|
Chris@0
|
285
|
Chris@0
|
286 private
|
Chris@0
|
287 def to_ber_seq_internal code
|
Chris@0
|
288 s = self.to_s
|
Chris@0
|
289 [code].pack('C') + s.length.to_ber_length_encoding + s
|
Chris@0
|
290 end
|
Chris@0
|
291
|
Chris@0
|
292 end # class Array
|
Chris@0
|
293
|
Chris@0
|
294
|