#!/usr/bin/env ruby

require 'openssl.so'
require 'openssl/buffering'
require 'thread'

module OpenSSL
  module PKey
if defined? DSA
    class DSA
      def DSA::new(arg, pass=nil)
        if arg.kind_of? Fixnum
	  DSA::generate(arg) {|p,n|
	    if block_given? then yield [p,n] end
	  }
	else
	  DSA::new_from_pem(arg, pass)
	end
      end # DSA::new
      #
      # DSA::new_from_pem(PEM string, pass) is built-in
      # DSA::new_from_fixnum(size) is an alias to DSA::generate(size)
      # DSA::generate(size) is built-in; yields p,n
      # 
      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})"
	end
	self.sign_digest digest.update(data.to_s).digest
      end # sign
      
      def verify(digest, signature, data)
	unless digest.kind_of? OpenSSL::Digest::ANY
	  raise TypeError, "digest alg needed! (got #{digest.class})"
	end
	unless signature.class == String
	  raise TypeError, "Signature as String expected (got #{sign.class})"
	end
	self.verify_digest(digest.update(data.to_s).digest, signature)
      end # verify
    end # DSA
end #defined? DSA
if defined? RSA
    class RSA
      def RSA::new(arg, pass=nil)
        if arg.kind_of? Fixnum
	  RSA::generate(arg) {|p,n|
	    if block_given? then yield [p,n] end
	  }
	else
	  RSA::new_from_pem(arg, pass)
	end
      end # RSA::new
      #
      # RSA::new_from_pem(PEM string, pass) is built-in
      # RSA::new_from_fixnum(size) is an alias to RSA::generate(size)
      # RSA::generate(size) is built-in; yields p,n
      # 
      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})"
	end
	self.private_encrypt digest.update(data.to_s).digest
      end # sign
      
      def verify(digest, signature, data)
	unless digest.kind_of? OpenSSL::Digest::ANY
	  raise TypeError, "digest alg needed! (got #{digest.class})"
	end
	unless signature.class == String
	  raise TypeError, "Signature as String expected (got #{sign.class})"
	end
	md_s = self.public_decrypt signature
	md_d = digest.update(data.to_s).digest
	md_s == md_d
      end # verify
    end # RSA
end # defined? RSA
if defined? DH
    class DH
      def DH::new(arg, gen = 2)
        if arg.kind_of? Fixnum
	  DH::generate(arg, gen) {|p,n|
	    if block_given? then yield [p,n] end
	  }
	else
	  DH::new_from_pem(arg)
	end
      end # DH::new
      #
      # DH::new_from_pem(PEM string, pass) is built-in
      # DH::new_from_fixnum(size, gen) is an alias to DH::generate(size, gen)
      # DH::generate(size, gen) is built-in; yields p,n
      #
    end # DH
end # defined? DH
  end # PKey

  module SSL
    class SSLSocket
      include Buffering
      CallbackMutex = Mutex.new
      
      def connect
        CallbackMutex.synchronize{ __connect }
      end
      
      def accept
        CallbackMutex.synchronize{ __accept }
      end
    end # SSLSocket
  end # SSL
  
  module X509
    class Name
      def Name::new(arg)
        type = arg.class
	while type
	  method = "new_from_#{type.name.downcase}".intern
	  return Name::send(method, arg) if Name::respond_to? method
	  type = type.superclass
	end
	raise TypeError, "Don't how to make new #{self} from #{arg.class}"
        ###Name::send("new_from_#{arg.class.name.downcase}", arg)
      end
      #
      # Name::new_from_hash(hash) is built-in method
      # 
      def Name::new_from_string(str) # we're expecting string like "/A=B/C=D/E=F"
        hash = Hash::new
	key = val = nil # speed optim.
	ary = str.split("/")
	ary.shift # first item is "" - so skip it
	ary.each {|item|
	  key, val = item.split("=")
	  hash[key] = val
	}
	Name::new_from_hash(hash)
	###ary.collect! {|item| item.split("=") }
	###Name::new_from_array(ary)
      end
      
      def Name::new_from_array(ary) # [["A","B"],["C","D"],["E","F"]]
	hash = Hash::new
	ary.each {|key, val|
	  hash[key] = val
	}
	Name::new_from_hash(hash)
      end
      #
      # to_h is built-in method
      # 
      def to_s # "/A=B/C=D/E=F"
        hash = self.to_h
	str = ""
	hash.keys.each do |key|
	  str += "/" + key + "=" + hash[key]
	end
	str
      end
      
      def to_a # [["A","B"],["C","D"],["E","F"]]
        self.to_h.to_a
      end
    end # Name
    
    class ExtensionFactory
      def create_extension(*arg)
        if arg.size == 1 then arg = arg[0] end
	type = arg.class
	while type
	  method = "create_ext_from_#{type.name.downcase}".intern
	  return send(method, arg) if respond_to? method
	  type = type.superclass
	end
	raise TypeError, "Don't how to create ext from #{arg.class}"
	###send("create_ext_from_#{arg.class.name.downcase}", arg)
      end
      #
      # create_ext_from_array is built-in
      #
      def create_ext_from_string(str) # "oid = critical, value"
	unless str =~ /\s*=\s*/
	  raise ArgumentError, "string in format \"oid = value\" expected"
	end
        ary = []
	ary << $`.sub(/^\s*/,"") # delete whitespaces from the beginning
	rest = $'.sub(/\s*$/,"") # delete them from the end
	if rest =~ /^critical,\s*/ # handle 'critical' option
	  ary << $'
	  ary << true
	else
	  ary << rest
	end
	create_ext_from_array(ary)
      end
      
      def create_ext_from_hash(hash) # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
	unless (hash.has_key? "oid" and hash.has_key? "value")
	  raise ArgumentError, "hash in format {\"oid\"=>..., \"value\"=>...} expected"
	end
        ary = []
	ary << hash["oid"]
	ary << hash["value"]
	ary << hash["critical"] if hash.has_key? "critical"
        create_ext_from_array(ary)
      end
    end # ExtensionFactory
    
    class Extension
      # note: Extension.new is UNDEFed! - use ExtensionFactory.create_extension
      #
      # to_a is built-in
      # 
      def to_s # "oid = critical, value"
        ary = self.to_a
	str = ary[0] + " = "
	str += "critical, " if ary[2] == true
	str += ary[1]
      end
      
      def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
        ary = self.to_a
	{"oid"=>ary[0],"value"=>ary[1],"critical"=>ary[2]}
      end

      def oid
        self.to_a[0]
      end

      def value
        self.to_a[1]
      end
      
      def critical?
        self.to_a[2]
      end
    end # Extension
    
    class Attribute
      def Attribute::new(arg)
        type = arg.class
	while type
	  method = "new_from_#{type.name.downcase}".intern
	  return Attribute::send(method, arg) if Attribute::respond_to? method
	  type = type.superclass
	end
	raise "Don't how to make new #{self} from #{arg.class}"
        ###Attribute::send("new_from_#{arg.class.name.downcase}", arg)
      end
      #
      # Attribute::new_from_array(ary) is built-in method
      #
      def Attribute::new_from_string(str) # "oid = value"
	unless str =~ /\s*=\s*/
	  raise ArgumentError, "string in format \"oid = value\" expected"
	end
        ary = []
	ary << $`.sub(/^\s*/,"") # delete whitespaces from the beginning
	ary << $'.sub(/\s*$/,"") # delete them from the end
	Attribute::new_from_array(ary)
      end

      def Attribute::new_from_hash(hash) # {"oid"=>"...", "value"=>"..."}
	unless (hash.has_key? "oid" and hash.has_key? "value")
	  raise ArgumentError, "hash in format {\"oid\"=>..., \"value\"=>...} expected"
	end
        ary = []
	ary << hash["oid"]
	ary << hash["value"]
	Attribute::new_from_array(ary)
      end
    end # Attribute
  end # X509

  class BN
    def initialize(arg=nil, type="dec")
    	return if arg.nil?
	t = arg.class
	while t
	  method = "from_#{t.name.downcase.split('::').last}".intern
	  return send(method, arg, type) if respond_to?(method, true)
	  t = t.superclass
	end
	raise "Don't how to init #{self.class.name} from #{arg.class}"
    end

    def from_bn(arg, dummy=nil)
      copy(arg)
    end

    def from_integer(arg, type="dec")
      from_string(arg.to_s, type)
    end

    def from_string(arg, type="dec")
      send("from_s_#{type.downcase}", arg)
    end

    private :from_bn, :from_integer, :from_string
    
    def to_s(type="dec")
      send("to_s_#{type.downcase}")
    end

    def to_i
      self.to_s.to_i
    end
  end # BN
end # OpenSSL

class Integer
  def to_bn
    OpenSSL::BN::new(self)
  end
end # Integer

