To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
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 |
|