To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 29 / 2916f748eef73ff691bcfc74e7c1ac23960d090b.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (6.99 KB)

1
# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $
2
#
3
# NET::BER
4
# Mixes ASN.1/BER convenience methods into several standard classes.
5
# Also provides BER parsing functionality.
6
#
7
#----------------------------------------------------------------------------
8
#
9
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
10
#
11
# Gmail: garbagecat10
12
#
13
# This program is free software; you can redistribute it and/or modify
14
# it under the terms of the GNU General Public License as published by
15
# the Free Software Foundation; either version 2 of the License, or
16
# (at your option) any later version.
17
#
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
# GNU General Public License for more details.
22
#
23
# You should have received a copy of the GNU General Public License
24
# along with this program; if not, write to the Free Software
25
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
26
#
27
#---------------------------------------------------------------------------
28
#
29
#
30

    
31

    
32

    
33

    
34
module Net
35

    
36
  module BER
37

    
38
  class BerError < Exception; end
39

    
40

    
41
  # This module is for mixing into IO and IO-like objects.
42
  module BERParser
43

    
44
    # The order of these follows the class-codes in BER.
45
    # Maybe this should have been a hash.
46
    TagClasses = [:universal, :application, :context_specific, :private]
47

    
48
    BuiltinSyntax = {
49
      :universal => {
50
        :primitive => {
51
          1 => :boolean,
52
          2 => :integer,
53
          4 => :string,
54
          10 => :integer,
55
        },
56
        :constructed => {
57
          16 => :array,
58
          17 => :array
59
        }
60
      }
61
    }
62

    
63
    #
64
    # read_ber
65
    # TODO: clean this up so it works properly with partial
66
    # packets coming from streams that don't block when
67
    # we ask for more data (like StringIOs). At it is,
68
    # this can throw TypeErrors and other nasties.
69
    #
70
    def read_ber syntax=nil
71
      return nil if (StringIO == self.class) and  eof?
72

    
73
      id = getc  # don't trash this value, we'll use it later
74
      tag = id & 31
75
      tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
76
      tagclass = TagClasses[ id >> 6 ]
77
      encoding = (id & 0x20 != 0) ? :constructed : :primitive
78

    
79
      n = getc
80
      lengthlength,contentlength = if n <= 127
81
        [1,n]
82
      else
83
        j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
84
        [1 + (n & 127), j]
85
      end
86

    
87
      newobj = read contentlength
88

    
89
      objtype = nil
90
      [syntax, BuiltinSyntax].each {|syn|
91
        if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
92
          objtype = ot[tag]
93
          break
94
        end
95
      }
96
      
97
      obj = case objtype
98
      when :boolean
99
        newobj != "\000"
100
      when :string
101
        (newobj || "").dup
102
      when :integer
103
        j = 0
104
        newobj.each_byte {|b| j = (j << 8) + b}
105
        j
106
      when :array
107
        seq = []
108
        sio = StringIO.new( newobj || "" )
109
        # Interpret the subobject, but note how the loop
110
        # is built: nil ends the loop, but false (a valid
111
        # BER value) does not!
112
        while (e = sio.read_ber(syntax)) != nil
113
          seq << e
114
        end
115
        seq
116
      else
117
        raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
118
      end
119

    
120
      # Add the identifier bits into the object if it's a String or an Array.
121
      # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
122
      obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
123
      obj
124

    
125
    end
126

    
127
  end # module BERParser
128
  end # module BER
129

    
130
end # module Net
131

    
132

    
133
class IO
134
  include Net::BER::BERParser
135
end
136

    
137
require "stringio"
138
class StringIO
139
  include Net::BER::BERParser
140
end
141

    
142
begin
143
  require 'openssl'
144
  class OpenSSL::SSL::SSLSocket
145
    include Net::BER::BERParser
146
  end
147
rescue LoadError
148
# Ignore LoadError.
149
# DON'T ignore NameError, which means the SSLSocket class
150
# is somehow unavailable on this implementation of Ruby's openssl.
151
# This may be WRONG, however, because we don't yet know how Ruby's
152
# openssl behaves on machines with no OpenSSL library. I suppose
153
# it's possible they do not fail to require 'openssl' but do not
154
# create the classes. So this code is provisional.
155
# Also, you might think that OpenSSL::SSL::SSLSocket inherits from
156
# IO so we'd pick it up above. But you'd be wrong.
157
end
158

    
159
class String
160
  def read_ber syntax=nil
161
    StringIO.new(self).read_ber(syntax)
162
  end
163
end
164

    
165

    
166

    
167
#----------------------------------------------
168

    
169

    
170
class FalseClass
171
  #
172
  # to_ber
173
  #
174
  def to_ber
175
    "\001\001\000"
176
  end
177
end
178

    
179

    
180
class TrueClass
181
  #
182
  # to_ber
183
  #
184
  def to_ber
185
    "\001\001\001"
186
  end
187
end
188

    
189

    
190

    
191
class Fixnum
192
  #
193
  # to_ber
194
  #
195
  def to_ber
196
    i = [self].pack('w')
197
    [2, i.length].pack("CC") + i
198
  end
199

    
200
  #
201
  # to_ber_enumerated
202
  #
203
  def to_ber_enumerated
204
    i = [self].pack('w')
205
    [10, i.length].pack("CC") + i
206
  end
207

    
208
  #
209
  # to_ber_length_encoding
210
  #
211
  def to_ber_length_encoding
212
    if self <= 127
213
      [self].pack('C')
214
    else
215
      i = [self].pack('N').sub(/^[\0]+/,"")
216
      [0x80 + i.length].pack('C') + i
217
    end
218
  end
219

    
220
end # class Fixnum
221

    
222

    
223
class Bignum
224

    
225
  def to_ber
226
    i = [self].pack('w')
227
    i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
228
    [2, i.length].pack("CC") + i
229
  end
230

    
231
end
232

    
233

    
234

    
235
class String
236
  #
237
  # to_ber
238
  # A universal octet-string is tag number 4,
239
  # but others are possible depending on the context, so we
240
  # let the caller give us one.
241
  # The preferred way to do this in user code is via to_ber_application_sring
242
  # and to_ber_contextspecific.
243
  #
244
  def to_ber code = 4
245
    [code].pack('C') + length.to_ber_length_encoding + self
246
  end
247

    
248
  #
249
  # to_ber_application_string
250
  #
251
  def to_ber_application_string code
252
    to_ber( 0x40 + code )
253
  end
254

    
255
  #
256
  # to_ber_contextspecific
257
  #
258
  def to_ber_contextspecific code
259
    to_ber( 0x80 + code )
260
  end
261

    
262
end # class String
263

    
264

    
265

    
266
class Array
267
  #
268
  # to_ber_appsequence
269
  # An application-specific sequence usually gets assigned
270
  # a tag that is meaningful to the particular protocol being used.
271
  # This is different from the universal sequence, which usually
272
  # gets a tag value of 16.
273
  # Now here's an interesting thing: We're adding the X.690
274
  # "application constructed" code at the top of the tag byte (0x60),
275
  # but some clients, notably ldapsearch, send "context-specific
276
  # constructed" (0xA0). The latter would appear to violate RFC-1777,
277
  # but what do I know? We may need to change this.
278
  #
279

    
280
  def to_ber                 id = 0; to_ber_seq_internal( 0x30 + id ); end
281
  def to_ber_set             id = 0; to_ber_seq_internal( 0x31 + id ); end
282
  def to_ber_sequence        id = 0; to_ber_seq_internal( 0x30 + id ); end
283
  def to_ber_appsequence     id = 0; to_ber_seq_internal( 0x60 + id ); end
284
  def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
285

    
286
  private
287
  def to_ber_seq_internal code
288
    s = self.to_s
289
    [code].pack('C') + s.length.to_ber_length_encoding + s
290
  end
291

    
292
end # class Array
293

    
294