diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-01-24 06:01:58 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-01-24 06:01:58 +0000 |
| commit | ae2575b45de1e8f4c0ec956cebe0eed2bafbcf57 (patch) | |
| tree | 9c2b7c839087c285c228374f525315e55c392a34 /lib/puppet/sslcertificates | |
| parent | 18e8e74a2e3b4c5d092fc0aae38bbc5455d4db48 (diff) | |
| download | puppet-ae2575b45de1e8f4c0ec956cebe0eed2bafbcf57.tar.gz puppet-ae2575b45de1e8f4c0ec956cebe0eed2bafbcf57.tar.xz puppet-ae2575b45de1e8f4c0ec956cebe0eed2bafbcf57.zip | |
Adding the event-loop stuff to the repository and switching to using it. Also, breaking many classes out into their own class files.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@848 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet/sslcertificates')
| -rw-r--r-- | lib/puppet/sslcertificates/ca.rb | 286 | ||||
| -rw-r--r-- | lib/puppet/sslcertificates/certificate.rb | 283 |
2 files changed, 569 insertions, 0 deletions
diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb new file mode 100644 index 000000000..0137e15eb --- /dev/null +++ b/lib/puppet/sslcertificates/ca.rb @@ -0,0 +1,286 @@ +class Puppet::SSLCertificates::CA + Certificate = Puppet::SSLCertificates::Certificate + attr_accessor :keyfile, :file, :config, :dir, :cert + + @@params = [ + :certdir, + :publickeydir, + :privatekeydir, + :cadir, + :cakey, + :cacert, + :capass, + :capub, + :csrdir, + :signeddir, + :serial, + :privatedir, + :ca_crl_days, + :ca_days, + :ca_md, + :req_bits, + :keylength, + :autosign + ] + + @@defaults = { + :certdir => [:ssldir, "certs"], + :publickeydir => [:ssldir, "public_keys"], + :privatekeydir => [:ssldir, "private_keys"], + :cadir => [:ssldir, "ca"], + :cacert => [:cadir, "ca_crt.pem"], + :cakey => [:cadir, "ca_key.pem"], + :capub => [:cadir, "ca_pub.pem"], + :csrdir => [:cadir, "requests"], + :signeddir => [:cadir, "signed"], + :capass => [:cadir, "ca.pass"], + :serial => [:cadir, "serial"], + :privatedir => [:ssldir, "private"], + :passfile => [:privatedir, "password"], + :autosign => [:puppetconf, "autosign.conf"], + :ca_crl_days => 365, + :ca_days => 1825, + :ca_md => "md5", + :req_bits => 2048, + :keylength => 1024, + } + + @@params.each { |param| + Puppet.setdefault(param,@@defaults[param]) + } + + def certfile + @config[:cacert] + end + + def host2csrfile(hostname) + File.join(Puppet[:csrdir], [hostname, "pem"].join(".")) + end + + # this stores signed certs in a directory unrelated to + # normal client certs + def host2certfile(hostname) + File.join(Puppet[:signeddir], [hostname, "pem"].join(".")) + end + + def thing2name(thing) + thing.subject.to_a.find { |ary| + ary[0] == "CN" + }[1] + end + + def initialize(hash = {}) + self.setconfig(hash) + + self.getcert + unless FileTest.exists?(@config[:serial]) + File.open(@config[:serial], "w") { |f| + f << "%04X" % 1 + } + end + + if Puppet[:capass] and ! FileTest.exists?(Puppet[:capass]) + self.genpass + end + end + + def genpass + pass = "" + 20.times { pass += (rand(74) + 48).chr } + + unless @config[:capass] + raise "No passfile" + end + Puppet::SSLCertificates.mkdir(File.dirname(@config[:capass])) + File.open(@config[:capass], "w", 0600) { |f| f.print pass } + return pass + end + + def getcert + if FileTest.exists?(@config[:cacert]) + @cert = OpenSSL::X509::Certificate.new( + File.read(@config[:cacert]) + ) + else + self.mkrootcert + end + end + + def getclientcsr(host) + csrfile = host2csrfile(host) + unless File.exists?(csrfile) + return nil + end + + return OpenSSL::X509::Request.new(File.read(csrfile)) + end + + def getclientcert(host) + certfile = host2certfile(host) + unless File.exists?(certfile) + return [nil, nil] + end + + return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert] + end + + def list + return Dir.entries(Puppet[:csrdir]).reject { |file| + file =~ /^\.+$/ + }.collect { |file| + file.sub(/\.pem$/, '') + } + end + + def mkrootcert + cert = Certificate.new( + :name => "CAcert", + :cert => @config[:cacert], + :encrypt => @config[:passfile], + :key => @config[:cakey], + :selfsign => true, + :length => 1825, + :type => :ca + ) + @cert = cert.mkselfsigned + File.open(@config[:cacert], "w", 0660) { |f| + f.puts @cert.to_pem + } + @key = cert.key + return cert + end + + def removeclientcsr(host) + csrfile = host2csrfile(host) + unless File.exists?(csrfile) + raise Puppet::Error, "No certificate request for %s" % host + end + + File.unlink(csrfile) + end + + def setconfig(hash) + @config = {} + @@params.each { |param| + if hash.include?(param) + begin + @config[param] = hash[param] + Puppet[param] = hash[param] + hash.delete(param) + rescue => detail + puts detail + exit + end + else + begin + @config[param] = Puppet[param] + rescue => detail + puts detail + exit + end + end + } + + if hash.include?(:password) + @config[:password] = hash[:password] + hash.delete(:password) + end + + if hash.length > 0 + raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",") + end + + [:cadir, :csrdir, :signeddir].each { |dir| + unless @config[dir] + raise "%s is undefined" % dir + end + unless FileTest.exists?(@config[dir]) + Puppet::SSLCertificates.mkdir(@config[dir]) + end + } + end + + def sign(csr) + unless csr.is_a?(OpenSSL::X509::Request) + raise Puppet::Error, + "CA#sign only accepts OpenSSL::X509::Request objects, not %s" % + csr.class + end + + unless csr.verify(csr.public_key) + raise Puppet::Error, "CSR sign verification failed" + end + + # i should probably check key length... + + # read the ca cert in + cacert = OpenSSL::X509::Certificate.new( + File.read(@config[:cacert]) + ) + + cakey = nil + if @config[:password] + cakey = OpenSSL::PKey::RSA.new( + File.read(@config[:cakey]), @config[:password] + ) + else + cakey = OpenSSL::PKey::RSA.new( + File.read(@config[:cakey]) + ) + end + + unless cacert.check_private_key(cakey) + raise Puppet::Error, "CA Certificate is invalid" + end + + serial = File.read(@config[:serial]).chomp.hex + newcert = Puppet::SSLCertificates.mkcert( + :type => :server, + :name => csr.subject, + :days => @config[:ca_days], + :issuer => cacert, + :serial => serial, + :publickey => csr.public_key + ) + + # increment the serial + File.open(@config[:serial], "w") { |f| + f << "%04X" % (serial + 1) + } + + newcert.sign(cakey, OpenSSL::Digest::SHA1.new) + + self.storeclientcert(newcert) + + return [newcert, cacert] + end + + def storeclientcsr(csr) + host = thing2name(csr) + + csrfile = host2csrfile(host) + if File.exists?(csrfile) + raise Puppet::Error, "Certificate request for %s already exists" % host + end + + File.open(csrfile, "w", 0660) { |f| + f.print csr.to_pem + } + end + + def storeclientcert(cert) + host = thing2name(cert) + + certfile = host2certfile(host) + if File.exists?(certfile) + Puppet.notice "Overwriting signed certificate %s for %s" % + [certfile, host] + end + + File.open(certfile, "w", 0660) { |f| + f.print cert.to_pem + } + end +end + +# $Id$ diff --git a/lib/puppet/sslcertificates/certificate.rb b/lib/puppet/sslcertificates/certificate.rb new file mode 100644 index 000000000..65ceb44b9 --- /dev/null +++ b/lib/puppet/sslcertificates/certificate.rb @@ -0,0 +1,283 @@ +class Puppet::SSLCertificates::Certificate + SSLCertificates = Puppet::SSLCertificates + + attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type + attr_accessor :key, :cert, :csr, :cacert + + @@params2names = { + :name => "CN", + :state => "ST", + :country => "C", + :email => "emailAddress", + :org => "O", + :city => "L", + :ou => "OU" + } + + def certname + OpenSSL::X509::Name.new self.subject + end + + def delete + [@certfile,@keyfile].each { |file| + if FileTest.exists?(file) + File.unlink(file) + end + } + + if defined? @hash and @hash + if FileTest.symlink?(@hash) + File.unlink(@hash) + end + end + end + + def exists? + return FileTest.exists?(@certfile) + end + + def getkey + unless FileTest.exists?(@keyfile) + self.mkkey() + end + if @password + @key = OpenSSL::PKey::RSA.new( + File.read(@keyfile), + @password + ) + else + @key = OpenSSL::PKey::RSA.new( + File.read(@keyfile) + ) + end + end + + def initialize(hash) + unless hash.include?(:name) + raise "You must specify the common name for the certificate" + end + @name = hash[:name] + + # init a few variables + @cert = @key = @csr = nil + + if hash.include?(:cert) + @certfile = hash[:cert] + @dir = File.dirname(@certfile) + else + @dir = hash[:dir] || Puppet[:certdir] + @certfile = File.join(@dir, @name) + end + + @cacertfile ||= File.join(Puppet[:certdir], "ca.pem") + + unless FileTest.directory?(@dir) + Puppet::SSLCertificates.mkdir(@dir) + end + + unless @certfile =~ /\.pem$/ + @certfile += ".pem" + end + @keyfile = hash[:key] || File.join( + Puppet[:privatekeydir], [@name,"pem"].join(".") + ) + unless FileTest.directory?(@dir) + Puppet::SSLCertificates.mkdir(@dir) + end + + [@keyfile].each { |file| + dir = File.dirname(file) + + unless FileTest.directory?(dir) + Puppet::SSLCertificates.mkdir(dir) + end + } + + @days = hash[:length] || 365 + @selfsign = hash[:selfsign] || false + @encrypt = hash[:encrypt] || false + @replace = hash[:replace] || false + @issuer = hash[:issuer] || nil + + if hash.include?(:type) + case hash[:type] + when :ca, :client, :server: @type = hash[:type] + else + raise "Invalid Cert type %s" % hash[:type] + end + else + @type = :client + end + + @params = {:name => @name} + [:state, :country, :email, :org, :ou].each { |param| + if hash.include?(param) + @params[param] = hash[param] + end + } + + if @encrypt + if @encrypt =~ /^\// + File.open(@encrypt) { |f| + @password = f.read.chomp + } + else + raise ":encrypt must be a path to a pass phrase file" + end + else + @password = nil + end + + if hash.include?(:selfsign) + @selfsign = hash[:selfsign] + else + @selfsign = false + end + end + + # this only works for servers, not for users + def mkcsr + unless defined? @key and @key + self.getkey + end + + name = OpenSSL::X509::Name.new self.subject + + @csr = OpenSSL::X509::Request.new + @csr.version = 0 + @csr.subject = name + @csr.public_key = @key.public_key + @csr.sign(@key, OpenSSL::Digest::SHA1.new) + + #File.open(@csrfile, "w") { |f| + # f << @csr.to_pem + #} + + unless @csr.verify(@key.public_key) + raise Puppet::Error, "CSR sign verification failed" + end + + return @csr + end + + def mkkey + # @key is the file + + @key = OpenSSL::PKey::RSA.new(1024) +# { |p,n| +# case p +# when 0; Puppet.info "key info: ." # BN_generate_prime +# when 1; Puppet.info "key info: +" # BN_generate_prime +# when 2; Puppet.info "key info: *" # searching good prime, +# # n = #of try, +# # but also data from BN_generate_prime +# when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q, +# # but also data from BN_generate_prime +# else; Puppet.info "key info: *" # BN_generate_prime +# end +# } + + if @password + #passwdproc = proc { @password } + keytext = @key.export( + OpenSSL::Cipher::DES.new(:EDE3, :CBC), + @password + ) + File.open(@keyfile, "w", 0400) { |f| + f << keytext + } + else + File.open(@keyfile, "w", 0400) { |f| + f << @key.to_pem + } + end + + #cmd = "#{ossl} genrsa -out #{@key} 1024" + end + + def mkselfsigned + unless defined? @key and @key + self.getkey + end + + if defined? @cert and @cert + raise Puppet::Error, "Cannot replace existing certificate" + end + + args = { + :name => self.certname, + :days => @days, + :issuer => nil, + :serial => 0x0, + :publickey => @key.public_key + } + if @type + args[:type] = @type + else + args[:type] = :server + end + @cert = SSLCertificates.mkcert(args) + + @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign + + return @cert + end + + def subject(string = false) + subj = @@params2names.collect { |param, name| + if @params.include?(param) + [name, @params[param]] + end + }.reject { |ary| ary.nil? } + + if string + return "/" + subj.collect { |ary| + "%s=%s" % ary + }.join("/") + "/" + else + return subj + end + end + + # verify that we can track down the cert chain or whatever + def verify + "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem" + end + + def write + files = { + @certfile => @cert, + @keyfile => @key, + } + if defined? @cacert + files[@cacertfile] = @cacert + end + + files.each { |file,thing| + if defined? thing and thing + if FileTest.exists?(file) + next + end + + text = nil + + if thing.is_a?(OpenSSL::PKey::RSA) and @password + text = thing.export( + OpenSSL::Cipher::DES.new(:EDE3, :CBC), + @password + ) + else + text = thing.to_pem + end + + File.open(file, "w", 0660) { |f| f.print text } + end + } + + if defined? @cacert + SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile) + end + end +end + +# $Id$ |
