You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ber.rb 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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. module Net
  31. module BER
  32. class BerError < Exception; end
  33. # This module is for mixing into IO and IO-like objects.
  34. module BERParser
  35. # The order of these follows the class-codes in BER.
  36. # Maybe this should have been a hash.
  37. TagClasses = [:universal, :application, :context_specific, :private]
  38. BuiltinSyntax = {
  39. :universal => {
  40. :primitive => {
  41. 1 => :boolean,
  42. 2 => :integer,
  43. 4 => :string,
  44. 10 => :integer,
  45. },
  46. :constructed => {
  47. 16 => :array,
  48. 17 => :array
  49. }
  50. }
  51. }
  52. #
  53. # read_ber
  54. # TODO: clean this up so it works properly with partial
  55. # packets coming from streams that don't block when
  56. # we ask for more data (like StringIOs). At it is,
  57. # this can throw TypeErrors and other nasties.
  58. #
  59. def read_ber syntax=nil
  60. return nil if (StringIO == self.class) and eof?
  61. id = getc # don't trash this value, we'll use it later
  62. tag = id & 31
  63. tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
  64. tagclass = TagClasses[ id >> 6 ]
  65. encoding = (id & 0x20 != 0) ? :constructed : :primitive
  66. n = getc
  67. lengthlength,contentlength = if n <= 127
  68. [1,n]
  69. else
  70. j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
  71. [1 + (n & 127), j]
  72. end
  73. newobj = read contentlength
  74. objtype = nil
  75. [syntax, BuiltinSyntax].each {|syn|
  76. if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
  77. objtype = ot[tag]
  78. break
  79. end
  80. }
  81. obj = case objtype
  82. when :boolean
  83. newobj != "\000"
  84. when :string
  85. (newobj || "").dup
  86. when :integer
  87. j = 0
  88. newobj.each_byte {|b| j = (j << 8) + b}
  89. j
  90. when :array
  91. seq = []
  92. sio = StringIO.new( newobj || "" )
  93. # Interpret the subobject, but note how the loop
  94. # is built: nil ends the loop, but false (a valid
  95. # BER value) does not!
  96. while (e = sio.read_ber(syntax)) != nil
  97. seq << e
  98. end
  99. seq
  100. else
  101. raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
  102. end
  103. # Add the identifier bits into the object if it's a String or an Array.
  104. # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
  105. obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
  106. obj
  107. end
  108. end # module BERParser
  109. end # module BER
  110. end # module Net
  111. class IO
  112. include Net::BER::BERParser
  113. end
  114. require "stringio"
  115. class StringIO
  116. include Net::BER::BERParser
  117. end
  118. begin
  119. require 'openssl'
  120. class OpenSSL::SSL::SSLSocket
  121. include Net::BER::BERParser
  122. end
  123. rescue LoadError
  124. # Ignore LoadError.
  125. # DON'T ignore NameError, which means the SSLSocket class
  126. # is somehow unavailable on this implementation of Ruby's openssl.
  127. # This may be WRONG, however, because we don't yet know how Ruby's
  128. # openssl behaves on machines with no OpenSSL library. I suppose
  129. # it's possible they do not fail to require 'openssl' but do not
  130. # create the classes. So this code is provisional.
  131. # Also, you might think that OpenSSL::SSL::SSLSocket inherits from
  132. # IO so we'd pick it up above. But you'd be wrong.
  133. end
  134. class String
  135. def read_ber syntax=nil
  136. StringIO.new(self).read_ber(syntax)
  137. end
  138. end
  139. #----------------------------------------------
  140. class FalseClass
  141. #
  142. # to_ber
  143. #
  144. def to_ber
  145. "\001\001\000"
  146. end
  147. end
  148. class TrueClass
  149. #
  150. # to_ber
  151. #
  152. def to_ber
  153. "\001\001\001"
  154. end
  155. end
  156. class Fixnum
  157. #
  158. # to_ber
  159. #
  160. def to_ber
  161. i = [self].pack('w')
  162. [2, i.length].pack("CC") + i
  163. end
  164. #
  165. # to_ber_enumerated
  166. #
  167. def to_ber_enumerated
  168. i = [self].pack('w')
  169. [10, i.length].pack("CC") + i
  170. end
  171. #
  172. # to_ber_length_encoding
  173. #
  174. def to_ber_length_encoding
  175. if self <= 127
  176. [self].pack('C')
  177. else
  178. i = [self].pack('N').sub(/^[\0]+/,"")
  179. [0x80 + i.length].pack('C') + i
  180. end
  181. end
  182. end # class Fixnum
  183. class Bignum
  184. def to_ber
  185. i = [self].pack('w')
  186. i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
  187. [2, i.length].pack("CC") + i
  188. end
  189. end
  190. class String
  191. #
  192. # to_ber
  193. # A universal octet-string is tag number 4,
  194. # but others are possible depending on the context, so we
  195. # let the caller give us one.
  196. # The preferred way to do this in user code is via to_ber_application_sring
  197. # and to_ber_contextspecific.
  198. #
  199. def to_ber code = 4
  200. [code].pack('C') + length.to_ber_length_encoding + self
  201. end
  202. #
  203. # to_ber_application_string
  204. #
  205. def to_ber_application_string code
  206. to_ber( 0x40 + code )
  207. end
  208. #
  209. # to_ber_contextspecific
  210. #
  211. def to_ber_contextspecific code
  212. to_ber( 0x80 + code )
  213. end
  214. end # class String
  215. class Array
  216. #
  217. # to_ber_appsequence
  218. # An application-specific sequence usually gets assigned
  219. # a tag that is meaningful to the particular protocol being used.
  220. # This is different from the universal sequence, which usually
  221. # gets a tag value of 16.
  222. # Now here's an interesting thing: We're adding the X.690
  223. # "application constructed" code at the top of the tag byte (0x60),
  224. # but some clients, notably ldapsearch, send "context-specific
  225. # constructed" (0xA0). The latter would appear to violate RFC-1777,
  226. # but what do I know? We may need to change this.
  227. #
  228. def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
  229. def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
  230. def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
  231. def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
  232. def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
  233. private
  234. def to_ber_seq_internal code
  235. s = self.to_s
  236. [code].pack('C') + s.length.to_ber_length_encoding + s
  237. end
  238. end # class Array