require 'puppet/sslcertificates'

# A module to handle reading of certificates.
module Puppet::SSLCertificates::Support
    class MissingCertificate < Puppet::Error; end
    class InvalidCertificate < Puppet::Error; end

    attr_reader :cacert

    # Some metaprogramming to create methods for retrieving and creating keys.
    # This probably isn't fewer lines than defining each separately...
    def self.keytype(name, options, &block)
        var = "@%s" % name

        maker = "mk_%s" % name
        reader = "read_%s" % name

        unless param = options[:param]
            raise ArgumentError, "You must specify the parameter for the key"
        end

        unless klass = options[:class]
            raise ArgumentError, "You must specify the class for the key"
        end

        # Define the method that creates it.
        define_method(maker, &block)

        # Define the reading method.
        define_method(reader) do
            return nil unless FileTest.exists?(Puppet[param])
            begin
                instance_variable_set(var, klass.new(File.read(Puppet[param])))
            rescue => detail
                raise InvalidCertificate, "Could not read %s: %s" % [param, detail]
            end
        end

        # Define the overall method, which just calls the reader and maker
        # as appropriate.
        define_method(name) do
            unless cert = instance_variable_get(var)
                unless cert = send(reader)
                    cert = send(maker)
                    Puppet.settings.write(param) { |f| f.puts cert.to_pem }
                end
                instance_variable_set(var, cert)
            end
            cert
        end
    end

    # The key pair.
    keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do
        Puppet.info "Creating a new SSL key at %s" % Puppet[:hostprivkey]
        key = OpenSSL::PKey::RSA.new(Puppet[:keylength])

        # Our key meta programming can only handle one file, so we have
        # to separately write out the public key.
        Puppet.settings.write(:hostpubkey) do |f|
            f.print key.public_key.to_pem
        end
        return key
    end

    # Our certificate request
    keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do
        Puppet.info "Creating a new certificate request for %s" %
            Puppet[:certname]

        csr = OpenSSL::X509::Request.new
        csr.version = 0
        csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]])
        csr.public_key = key.public_key
        csr.sign(key, OpenSSL::Digest::MD5.new)

        return csr
    end

    keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do
        raise MissingCertificate, "No host certificate"
    end

    keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do
        raise MissingCertificate, "No CA certificate"
    end

    # Request a certificate from the remote system.  This does all of the work
    # of creating the cert request, contacting the remote system, and
    # storing the cert locally.
    def requestcert
        begin
            cert, cacert = caclient.getcert(@csr.to_pem)
        rescue => detail
            if Puppet[:trace]
                puts detail.backtrace
            end
            raise Puppet::Error.new("Certificate retrieval failed: %s" %
                detail)
        end

        if cert.nil? or cert == ""
            return nil
        end
        Puppet.settings.write(:hostcert) do |f| f.print cert end
        Puppet.settings.write(:localcacert) do |f| f.print cacert end
        #File.open(@certfile, "w", 0644) { |f| f.print cert }
        #File.open(@cacertfile, "w", 0644) { |f| f.print cacert }
        begin
            @cert = OpenSSL::X509::Certificate.new(cert)
            @cacert = OpenSSL::X509::Certificate.new(cacert)
            retrieved = true
        rescue => detail
            raise Puppet::Error.new(
                "Invalid certificate: %s" % detail
            )
        end

        unless @cert.check_private_key(@key)
            raise Puppet::DevError, "Received invalid certificate"
        end
        return retrieved
    end
end

