diff options
author | Michal Rokos <m.rokos@sh.cvut.cz> | 2001-11-16 11:20:13 +0000 |
---|---|---|
committer | Michal Rokos <m.rokos@sh.cvut.cz> | 2001-11-16 11:20:13 +0000 |
commit | 8903e757c052d3a99aada758bb628ec135360e60 (patch) | |
tree | 5f0e99803c1fc5c6909fd7d325d0f279265b97cf /lib | |
download | ruby-openssl-history-8903e757c052d3a99aada758bb628ec135360e60.tar.gz |
Initial revision
Diffstat (limited to 'lib')
-rw-r--r-- | lib/buffering.rb | 179 | ||||
-rw-r--r-- | lib/net/https.rb | 146 | ||||
-rw-r--r-- | lib/net/protocols.rb | 51 | ||||
-rw-r--r-- | lib/net/telnets.rb | 224 | ||||
-rw-r--r-- | lib/openssl.rb | 97 |
5 files changed, 697 insertions, 0 deletions
diff --git a/lib/buffering.rb b/lib/buffering.rb new file mode 100644 index 0000000..5832025 --- /dev/null +++ b/lib/buffering.rb @@ -0,0 +1,179 @@ +=begin + += buffering.rb -- Buffering mix-in module. + + Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> + + $IPR: buffering.rb,v 1.13 2001/09/13 16:42:49 gotoyuzo Exp $ + +=end + +module Buffering + include Enumerable + attr_accessor :sync + BLOCK_SIZE = 1024 + + # + # for reading. + # + private + + def fill_rbuff + @rbuffer = "" unless defined? @rbuffer + begin + if self.respond_to?(:to_io) + IO.select([self.to_io], nil, nil) + end + @rbuffer << self.sysread(BLOCK_SIZE) + rescue EOFError + @eof = true + end + end + + def consume_rbuff(size=nil) + if @rbuffer.size == 0 + @eof = nil + nil + else + size = @rbuffer.size unless size + ret = @rbuffer[0, size] + @rbuffer[0, size] = "" + ret + end + end + + public + + def read(size=nil) + fill_rbuff unless defined? @rbuffer + until @eof + break if size && size <= @rbuffer.size + fill_rbuff + end + consume_rbuff(size) + end + + def gets(eol=$/) + fill_rbuff unless defined? @rbuffer + idx = @rbuffer.index(eol) + until @eof + break if idx + fill_rbuff + idx = @rbuffer.index(eol) + end + if eol.is_a?(Regexp) + size = idx ? idx+$&.size : nil + else + size = idx ? idx+eol.size : nil + end + consume_rbuff(size) + end + + def each(eol=$/) + while line = self.gets(eol?) + yield line + end + end + alias each_line each + + def readlines(eol=$/) + ary = [] + while line = self.gets(eol) + ary << line + end + ary + end + + def readline(eol=$/) + raise EOFErorr if eof? + gets(eol) + end + + def getc + c = read(1) + c ? c.to_i : nil + end + + def each_byte + while c = getc + yield(c) + end + end + + def readchar + raise EOFErorr if eof? + getc + end + + def ungetc(c) + @buffer[0,0] = c.chr + end + + def eof? + @eof && @rbuffer.size == 0 + end + alias eof eof? + + # + # for writing. + # + private + + def do_write(s) + @wbuffer = "" unless defined? @wbuffer + @wbuffer << s + if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/) + remain = idx ? idx + $/.size : @wbuffer.length + nwritten = 0 + while remain > 0 + nwrote = syswrite(@wbuffer[nwritten,remain]) + remain -= nwrote + nwritten += nwrote + end + @wbuffer = "" + end + end + + public + + def write(s) + do_write(s) + s.length + end + + def << (s) + do_write(s) + self + end + + def puts(*args) + s = "" + args.each{ |arg| s << arg.to_s + $/ } + do_write(s) + nil + end + + def print(*args) + s = "" + args.each{ |arg| s << arg.to_s } + do_write(s) + nil + end + + def printf(s, *args) + do_write(s % args) + nil + end + + def flush + osync = @sync + @sync = true + do_write "" + @sync = osync + end + + def close + flush + sysclose + end +end diff --git a/lib/net/https.rb b/lib/net/https.rb new file mode 100644 index 0000000..e1c43e5 --- /dev/null +++ b/lib/net/https.rb @@ -0,0 +1,146 @@ +=begin + += https.rb -- SSL/TLS enhancement for Net::HTTP. + + Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> + + This program requires Net 1.2.0 or higher version. + You can get it from RAA or Ruby's CVS repository. + + $IPR: https.rb,v 1.5 2001/07/15 22:24:05 gotoyuzo Exp $ + +== class Net::HTTP + +== Example + +Simple HTTP client is here: + + require 'net/http' + host, port, path = "localhost", 80, "/" + if %r!http://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0] + host = $1 + port = $2.to_i if $2 + path = $3 + end + h = Net::HTTP.new(host, port) + h.get2(path){ |resp| print resp.body } + +It can be replaced by follow one: + + require 'net/https' + host, port, path = "localhost", 80, "/" + if %r!(https?)://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0] + scheme = $1 + host = $2 + port = $3 ? $3.to_i : ((scheme == "http") ? 80 : 443) + path = $4 + end + h = Net::HTTP.new(host, port) + h.use_ssl = true if scheme == "https" # enable SSL/TLS + h.get2(path){ |resp| print resp.body } + +=== Instance Methods + +: use_ssl + returns ture if use SSL/TLS with HTTP. + +: use_ssl=((|true_or_false|)) + sets use_ssl. + +: peer_cert + return the X.509 certificates the server presented. + +: key=((|path|)) + Sets private key file to use in PEM format. + Key_file is not required if the cert_file bundles private key. + +: cert=((|path|)) + Sets pathname of a X.509 certification file in PEM format. + +: ca_file=((|path|)) + Sets path of a CA certification file in PEM format. + The file can contrain several CA certificats. + +: ca_path=((|path|)) + Sets path of a CA certification directory containing certifications + in PEM format. + +: verify_mode=((|mode|)) + Sets the flags for server the certification verification at + begining of SSL/TLS session. + +: verify_callback=((|proc|)) + Sets the verify callback for the server certification verification. + +: verify_depth=((|num|)) + Sets the maximum depth for the certificate chain verification. + +=end + +require 'net/protocols' +require 'net/http' + +module Net + class HTTP + protocol_param :socket_type, ::Net::NetPrivate::SSLSocket + + attr_accessor :use_ssl + attr_writer :key, :cert, :ca_file, :ca_path, :timeout + attr_writer :verify_mode, :verify_callback, :verify_depth + attr_reader :peer_cert + + class Conn < ::Net::NetPrivate::HTTPRequest + REQUEST_HAS_BODY=false + RESPONSE_HAS_BODY=false + METHOD="connect" + + def initialize + super nil, nil + end + + def exec( sock, addr, port, ver ) + @socket = sock + request addr, port, ver + @response = get_response(sock) + @response + end + + def request( addr, port, ver ) + @socket.writeline sprintf('CONNECT %s:%s HTTP/%s', addr, port, ver) + @socket.writeline '' + end + end + + def on_connect + if use_ssl + if proxy? + resp = Conn.new.exec(@socket, @address, @port, "1.0") + if resp.code != '200' + raise resp.message + end + end + @socket.key = @key_file + @socket.cert = @cert_file + @socket.ca_file = @ca_file + @socket.ca_path = @ca_path + @socket.verify_mode = @verify_mode + @socket.verify_callback = @verify_callback + @socket.verify_depth = @verify_depth + @socket.timeout = @timeout + @socket.ssl_connect + @peer_cert = socket.peer_cert + end + end + + module ProxyMod + def edit_path( path ) + if use_ssl + 'https://' + addr_port + path + else + 'http://' + addr_port + path + end + end + end + + end +end diff --git a/lib/net/protocols.rb b/lib/net/protocols.rb new file mode 100644 index 0000000..349407c --- /dev/null +++ b/lib/net/protocols.rb @@ -0,0 +1,51 @@ +=begin + += protocols.rb -- SSL/TLS enhancement for Net. + + Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> + + This program requires Net 1.2.0 or higher version. + You can get it from RAA or Ruby's CVS repository. + + $IPR: protocols.rb,v 1.1 2001/06/17 14:30:22 gotoyuzo Exp $ + +=end + +require 'net/protocol' +require 'forwardable' +require 'openssl' + +module Net + module NetPrivate + + class SSLSocket < Socket + extend Forwardable + + def_delegators(:@socket, + :key=, :cert=, :ca_file=, :ca_path=, + :verify_mode=, :verify_callback=, :verify_depth=, + :timeout=) + + def initialize(addr, port, otime = nil, rtime = nil, pipe = nil) + super + @raw_socket = @socket + @socket = OpenSSL::SSL::SSLSocket.new(@socket, @cert_file, @key_file) + end + + def reopen(tout=nil) + super + @raw_socket = @socket + @socket = OpenSSL::SSL::SSLSocket.new(@socket, @cert_file, @key_file) + end + + def close + super + @raw_socket.close + end + + def peer_cert; @socket.peer_cert; end + def ssl_connect; @socket.connect; end + + end + end +end diff --git a/lib/net/telnets.rb b/lib/net/telnets.rb new file mode 100644 index 0000000..923181f --- /dev/null +++ b/lib/net/telnets.rb @@ -0,0 +1,224 @@ +=begin + += https.rb -- SSL/TLS enhancement for Net::Telnet. + + Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> + + $IPR: telnets.rb,v 1.5 2001/09/13 16:42:50 gotoyuzo Exp $ + +== class Net::Telnet + +This class will initiate SSL/TLS session automaticaly if the server +sent OPT_STARTTLS. Some options are added for SSL/TLS. + + host = Net::Telnet::new({ + "Host" => "localhost", + "Port" => "telnets", + # follows are new options. + 'Cert' => "user.crt", + 'Key' => "user.key", + 'CAFile' => "/some/where/certs/casert.pem", + 'CAPath' => "/some/where/caserts", + 'VerifyMode' => SSL::VERIFY_PEER, + 'VerifyCallback' => verify_proc + }) + +This class is expected to be a superset of usual Net::Telnet. + +=end + +require "net/telnet" +require "OpenSSL" + +module Net + class Telnet + attr_reader :ssl + + OPT_STARTTLS = 46.chr # "\056" # "\x2e" # Start TLS + TLS_FOLLOWS = 1.chr # "\001" # "\x01" # FOLLOWS (for STARTTLS) + + alias preprocess_orig preprocess + + def ssl?; @ssl; end + + def preprocess(string) + # combine CR+NULL into CR + string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"] + + # combine EOL into "\n" + string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"] + + string.gsub(/#{IAC}( + [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]| + [#{DO}#{DONT}#{WILL}#{WONT}][#{OPT_BINARY}-#{OPT_EXOPL}]| + #{SB}[#{OPT_BINARY}-#{OPT_EXOPL}] + (#{IAC}#{IAC}|[^#{IAC}])+#{IAC}#{SE} + )/xno) do + if IAC == $1 # handle escaped IAC characters + IAC + elsif AYT == $1 # respond to "IAC AYT" (are you there) + self.write("nobody here but us pigeons" + EOL) + '' + elsif DO[0] == $1[0] # respond to "IAC DO x" + if OPT_BINARY[0] == $1[1] + @telnet_option["BINARY"] = true + self.write(IAC + WILL + OPT_BINARY) + elsif OPT_STARTTLS[0] == $1[1] + self.write(IAC + WILL + OPT_STARTTLS) + self.write(IAC + SB + OPT_STARTTLS + TLS_FOLLOWS + IAC + SE) + else + self.write(IAC + WONT + $1[1..1]) + end + '' + elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x" + self.write(IAC + WONT + $1[1..1]) + '' + elsif WILL[0] == $1[0] # respond to "IAC WILL x" + if OPT_BINARY[0] == $1[1] + self.write(IAC + DO + OPT_BINARY) + elsif OPT_ECHO[0] == $1[1] + self.write(IAC + DO + OPT_ECHO) + elsif OPT_SGA[0] == $1[1] + @telnet_option["SGA"] = true + self.write(IAC + DO + OPT_SGA) + else + self.write(IAC + DONT + $1[1..1]) + end + '' + elsif WONT[0] == $1[0] # respond to "IAC WON'T x" + if OPT_ECHO[0] == $1[1] + self.write(IAC + DONT + OPT_ECHO) + elsif OPT_SGA[0] == $1[1] + @telnet_option["SGA"] = false + self.write(IAC + DONT + OPT_SGA) + else + self.write(IAC + DONT + $1[1..1]) + end + '' + elsif SB[0] == $1[0] # respond to "IAC SB xxx IAC SE" + if OPT_STARTTLS[0] == $1[1] && TLS_FOLLOWS[0] == $2[0] + @sock = OpenSSL::SSL::SSLSocket.new(@sock) + @sock.cert = @options['cert'] || @options['Cert'] + @sock.key = @options['key'] || @options['Key'] + @sock.ca_file = @options['ca_file'] || @options['CAFile'] + @sock.ca_path = @options['ca_path'] || @options['CAPath'] + @sock.timeout = @options['timeout'] || @options['Timeout'] + @sock.verify_mode = + @options['verify_mode'] || @options['VerifyMode'] + @sock.verify_callback = + @options['verify_callback'] || @options['VerifyCallback'] + @sock.verify_depth = + @options['verify_depth'] || @options['VerifyDepth'] + @sock.connect + @ssl = true + end + '' + else + '' + end + end + end # preprocess + + alias waitfor_org waitfor + + def waitfor(options) + time_out = @options["Timeout"] + waittime = @options["Waittime"] + + if options.kind_of?(Hash) + prompt = if options.has_key?("Match") + options["Match"] + elsif options.has_key?("Prompt") + options["Prompt"] + elsif options.has_key?("String") + Regexp.new( Regexp.quote(options["String"]) ) + end + time_out = options["Timeout"] if options.has_key?("Timeout") + waittime = options["Waittime"] if options.has_key?("Waittime") + else + prompt = options + end + + if time_out == false + time_out = nil + end + + line = '' + buf = '' + @rest = '' unless @rest + + until(prompt === line and not IO::select([@sock], nil, nil, waittime)) + unless IO::select([@sock], nil, nil, time_out) + raise TimeoutError, "timed-out; wait for the next data" + end + begin + c = @rest + @sock.sysread(1024 * 1024) + @dumplog.log_dump('<', c) if @options.has_key?("Dump_log") + if @options["Telnetmode"] + pos = 0 + catch(:next){ + while true + case c[pos] + when IAC[0] + case c[pos+1] + when DO[0], DONT[0], WILL[0], WONT[0] + throw :next unless c[pos+2] + pos += 3 + when SB[0] + ret = detect_sub_negotiation(c, pos) + throw :next unless ret + pos = ret + when nil + throw :next + else + pos += 2 + end + when nil + throw :next + else + pos += 1 + end + end + } + + buf = preprocess(c[0...pos]) + @rest = c[pos..-1] + end + @log.print(buf) if @options.has_key?("Output_log") + line.concat(buf) + yield buf if block_given? + rescue EOFError # End of file reached + if line == '' + line = nil + yield nil if block_given? + end + break + end + end + line + end + + private + + def detect_sub_negotiation(data, pos) + return nil if data.length < pos+6 # IAC SB x param IAC SE + pos += 3 + while true + case data[pos] + when IAC[0] + if data[pos+1] == SE[0] + pos += 2 + return pos + else + pos += 2 + end + when nil + return nil + else + pos += 1 + end + end + end + + end +end diff --git a/lib/openssl.rb b/lib/openssl.rb new file mode 100644 index 0000000..1d6f72f --- /dev/null +++ b/lib/openssl.rb @@ -0,0 +1,97 @@ +#!/usr/bin/env ruby + +require 'openssl.so' +require 'buffering' + +module OpenSSL + module PKey + class DSA + def sign(digest, data) + unless self.private? + raise OpenSSL::PKey::DSAError, "Cannot sign with public key!" + end + unless digest.kind_of? OpenSSL::Digest::ANY + raise TypeError, "digest alg needed! (got #{digest.class.name})" + end + txt = "" + if data.kind_of? String + txt = data + else + begin + txt = data.to_s + rescue + raise TypeError, "string needed! (got #{data.class.name})" + end + end + self.sign_digest digest.update(txt).digest + end #sign + def verify(digest, signature, data) + unless digest.kind_of? OpenSSL::Digest::ANY + raise TypeError, "digest alg needed! (got #{digest.class.name})" + end + txt = "" + if data.kind_of? String + txt = data + else + begin + txt = data.to_s + rescue + raise TypeError, "string needed! (got #{data.class.name})" + end + end + unless signature.type == String + raise TypeError, "Signature as String expected (got #{sign.class.name})" + end + self.verify_digest(digest.update(txt).digest, signature) + end #verify + end #DSA + class RSA + def sign(digest, data) + unless self.private? + raise OpenSSL::PKey::RSAError, "Cannot sign with public key!" + end + unless digest.kind_of? OpenSSL::Digest::ANY + raise TypeError, "digest alg needed! (got #{digest.class.name})" + end + txt = "" + if data.kind_of? String + txt = data + else + begin + txt = data.to_s + rescue + raise TypeError, "string needed! (got #{data.class.name})" + end + end + self.private_encrypt digest.update(txt).digest + end #sign + def verify(digest, signature, data) + unless digest.kind_of? OpenSSL::Digest::ANY + raise TypeError, "digest alg needed! (got #{digest.class.name})" + end + txt = "" + if data.kind_of? String + txt = data + else + begin + txt = data.to_s + rescue + raise TypeError, "string needed! (got #{data.class.name})" + end + end + unless signature.type == String + raise TypeError, "Signature as String expected (got #{sign.class.name})" + end + hash_s = self.public_decrypt signature + hash_d = digest.update(txt).digest + hash_s == hash_d + end #verify + end #RSA + end #PKey + module SSL + class SSLSocket + include Buffering + end #SSLSocket + end #SSL +end #OpenSSL + |