summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2005-08-04 06:41:59 +0000
committerLuke Kanies <luke@madstop.com>2005-08-04 06:41:59 +0000
commit0fce775b26393dfcb913883cad3605961acd544c (patch)
treefc71407f1cbebdf2aa377904f82624c46d1bab15
parenta1d206a126e53434b2ba4ada68d05f5890cc1ce2 (diff)
downloadpuppet-0fce775b26393dfcb913883cad3605961acd544c.tar.gz
puppet-0fce775b26393dfcb913883cad3605961acd544c.tar.xz
puppet-0fce775b26393dfcb913883cad3605961acd544c.zip
okay, switch all cert management to the library, instead of using the openssl binary
git-svn-id: https://reductivelabs.com/svn/puppet/library/trunk@491 980ebf18-57e1-0310-9a29-db15c13687c0
-rwxr-xr-xlib/puppet/openssl.rb471
1 files changed, 392 insertions, 79 deletions
diff --git a/lib/puppet/openssl.rb b/lib/puppet/openssl.rb
index 78bec8bd6..db9a6ad72 100755
--- a/lib/puppet/openssl.rb
+++ b/lib/puppet/openssl.rb
@@ -7,6 +7,7 @@
require 'puppet'
+require 'openssl'
require 'getoptlong'
module Puppet
@@ -26,6 +27,7 @@ module OpenSSL
#puts "\n\n%s\n\n" % cmd
unless $? == 0
+ puts cmd
raise output
end
return output
@@ -327,12 +329,12 @@ basicConstraints = CA:true
certdir = @config["local_ca"]["new_certs_dir"].value.chomp
Puppet::OpenSSL.mkdir(certdir)
- serial = @config["local_ca"]["serial"].value.chomp
- unless FileTest.exists?(serial)
- unless FileTest.exists?(File.dirname(serial))
- Puppet::OpenSSL.mkdir(File.dirname(serial))
+ @serial = @config["local_ca"]["serial"].value.chomp
+ unless FileTest.exists?(@serial)
+ unless FileTest.exists?(File.dirname(@serial))
+ Puppet::OpenSSL.mkdir(File.dirname(@serial))
end
- File.open(serial, "w", 0600) { |f| f.puts "01" }
+ File.open(@serial, "w", 0600) { |f| f.puts "01" }
end
database = @config["local_ca"]["database"].value.chomp
@@ -342,6 +344,8 @@ basicConstraints = CA:true
end
File.open(database, "w", 0600) { |f| f.print "" }
end
+
+ @days = @config["local_ca"]["default_crl_days"].value.to_i || 365
unless @certfile
raise "could not retrieve cert path"
@@ -355,7 +359,7 @@ basicConstraints = CA:true
@password = hash[:password]
else
@passfile = hash[:passfile] || File.join(@dir, "private", "phrase")
- self.genpass
+ @password = self.genpass
end
@cert = self.getcert
@@ -401,10 +405,12 @@ basicConstraints = CA:true
:cert => @certfile,
:encrypt => @passfile,
:key => @keyfile,
+ :selfsign => true,
:length => 1825
)
+ @cert = cert.mkselfsigned
- cert.mkselfsigned
+ #cert.mkselfsigned
return cert
end
@@ -430,24 +436,150 @@ basicConstraints = CA:true
raise "CA#sign only accepts Puppet::OpenSSL::Certificate objects"
end
- ossl = Puppet::OpenSSL.findopenssl
- sign = [ossl]
- sign << "ca"
- sign << "-batch"
- sign << ["-config", self.file]
- sign << ["-passin", "file:%s" % @passfile]
- sign << ["-out", cert.cert]
- sign << ["-infiles", cert.csr]
+ csr = ::OpenSSL::X509::Request.new(
+ File.read(cert.csrfile)
+ )
+
+ unless csr.verify(csr.public_key)
+ raise "CSR sign verification failed"
+ end
+
+ # i should probably check key length...
+
+ # read the ca cert in
+ if cert.exists?
+ raise "Cannot sign existing certificates"
+ end
+
+ cacert = ::OpenSSL::X509::Certificate.new(
+ File.read(@certfile)
+ )
+
+ ca_keypair = ::OpenSSL::PKey::RSA.new(
+ File.read(@keyfile), @password
+ )
- Puppet::OpenSSL.exec(sign.flatten.join(" "))
- # and then verify it
+ serial = File.read(@serial).chomp.hex
+ newcert = cert.mkcert(
+ cacert, cacert.subject, serial, ca_keypair.public_key
+ )
- #verify = "%s verify -CAfile %s %s" %
- # [ossl, @cert, cert.cert]
- verify = "%s verify %s" %
- [ossl, cert.cert]
+ # increment the serial
+ File.open(@serial, "w") { |f|
+ f << "%04X" % (serial + 1)
+ }
- Puppet::OpenSSL.exec(verify)
+# newcert = ::OpenSSL::X509::Certificate.new
+# from = Time.now
+#
+# newcert.subject = csr.subject
+# newcert.issuer = csr.subject
+# newcert.not_before = from
+# newcert.not_after = from + (@days * 24 * 60 * 60)
+# newcert.public_key = csr.public_key
+# newcert.serial = serial
+# newcert.version = 2 # X509v3
+#
+# basic_constraint = nil
+# key_usage = []
+# ext_key_usage = []
+#
+# case cert.type
+# when :ca:
+# basic_constraint = "CA:TRUE"
+# key_usage.push %w{cRLSign keyCertSign}
+# when :terminalsubca:
+# basic_constraint = "CA:TRUE,pathlen:0"
+# key_usage %w{cRLSign keyCertSign}
+# when :server:
+# basic_constraint = "CA:FALSE"
+# key_usage << %w{digitalSignature keyEncipherment}
+# ext_key_usage << "serverAuth"
+# when :ocsp:
+# basic_constraint = "CA:FALSE"
+# key_usage << %w{nonRepudiation digitalSignature}
+# ext_key_usage << %w{serverAuth OCSPSigning}
+# when :client:
+# basic_constraint = "CA:FALSE"
+# key_usage << %w{nonRepudiation digitalSignature keyEncipherment}
+# ext_key_usage << %w{clientAuth emailProtection}
+# else
+# raise "unknonwn cert type '%s'" % cert.type
+# end
+#
+# key_usage.flatten!
+# ext_key_usage.flatten!
+#
+# ef = ::OpenSSL::X509::ExtensionFactory.new
+#
+# ef.subject_certificate = newcert
+# ef.issuer_certificate = cacert
+#
+# ex = []
+# ex << ef.create_extension("basicConstraints", basic_constraint, true)
+# ex << ef.create_extension("nsComment",
+# "Ruby/OpenSSL Generated Certificate")
+# ex << ef.create_extension("subjectKeyIdentifier", "hash")
+# #ex << ef.create_extension("nsCertType", "client,email")
+# unless key_usage.empty? then
+# ex << ef.create_extension("keyUsage", key_usage.join(","))
+# end
+# #ex << ef.create_extension("authorityKeyIdentifier",
+# # "keyid:always,issuer:always")
+# #ex << ef.create_extension("authorityKeyIdentifier", "keyid:always")
+# unless ext_key_usage.empty? then
+# ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(","))
+# end
+#
+# #if @ca_config[:cdp_location] then
+# # ex << ef.create_extension("crlDistributionPoints",
+# # @ca_config[:cdp_location])
+# #end
+#
+# #if @ca_config[:ocsp_location] then
+# # ex << ef.create_extension("authorityInfoAccess",
+# # "OCSP;" << @ca_config[:ocsp_location])
+# #end
+# newcert.extensions = ex
+ newcert.sign(ca_keypair, ::OpenSSL::Digest::SHA1.new)
+
+ File.open(cert.certfile, "w", 0644) { |f|
+ f << newcert.to_pem
+ }
+ #backup_cert_file = @ca_config[:new_certs_dir] + "/cert_#{cert.serial}.pem"
+ #puts "Writing backup cert to #{backup_cert_file}" if $DEBUG
+ #File.open backup_cert_file, "w", 0644 do |f|
+ # f << newcert.to_pem
+ #end
+
+ # Write cert
+ #dest = cert_config[:hostname] || cert_config[:user]
+ #cert_file = File.join dest, "cert_#{dest}.pem"
+ #puts "Writing cert to #{cert_file}" if $DEBUG
+ #File.open cert_file, "w", 0644 do |f|
+ # f << cert.to_pem
+ #end
+
+ return newcert
+
+# ossl = Puppet::OpenSSL.findopenssl
+# sign = [ossl]
+# sign << "ca"
+# sign << "-batch"
+# sign << ["-config", self.file]
+# sign << ["-passin", "file:%s" % @passfile]
+# sign << ["-out", cert.cert]
+# sign << ["-infiles", cert.csr]
+#
+# Puppet::OpenSSL.exec(sign.flatten.join(" "))
+# # and then verify it
+#
+# #verify = "%s verify -CAfile %s %s" %
+# # [ossl, @certfile, cert.cert]
+# verify = "%s verify %s" %
+# [ossl, cert.cert]
+#
+# Puppet::OpenSSL.exec(verify)
#openssl ca -config ca.config -out $CERT -infiles $CSR
#echo "CA verifying: $CERT <-> CA cert"
#openssl verify -CAfile ca.crt $CERT
@@ -455,7 +587,8 @@ basicConstraints = CA:true
end
class Certificate
- attr_accessor :cert, :key, :name, :dir, :hash, :csr
+ attr_accessor :certfile, :keyfile, :name, :dir, :hash, :csrfile, :type
+ attr_accessor :issuer
@@params2names = {
:name => "CN",
@@ -467,8 +600,12 @@ basicConstraints = CA:true
:ou => "OU"
}
+ def certname
+ ::OpenSSL::X509::Name.new self.subject
+ end
+
def delete
- [@cert,@key,@csr].each { |file|
+ [@certfile,@keyfile,@csrfile].each { |file|
FileTest.exists?(file) and File.unlink(file)
}
@@ -480,7 +617,25 @@ basicConstraints = CA:true
end
def exists?
- FileTest.exists?(@cert)
+ puts "testing for %s" % @certfile
+ system("find %s" % @dir)
+ 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)
@@ -490,25 +645,36 @@ basicConstraints = CA:true
@name = hash[:name]
if hash.include?(:cert)
- @cert = hash[:cert]
- @dir = File.dirname(@cert)
+ @certfile = hash[:cert]
+ @dir = File.dirname(@certfile)
else
@dir = hash[:dir]
unless hash.include?(:dir)
raise "You must specify the directory in which to store certs"
end
- @cert = File.join(@dir, @name)
+ @certfile = File.join(@dir, @name)
end
- unless @cert =~ /\.pem$/
- @cert += ".pem"
+ unless @certfile =~ /\.pem$/
+ @certfile += ".pem"
end
- @key = hash[:key] || File.join(@dir, @name + "_key.pem")
- @csr = hash[:csr] || File.join(@dir, @name + "_csr.pem")
+ @keyfile = hash[:key] || File.join(@dir, @name + "_key.pem")
+ @csrfile = hash[:csr] || File.join(@dir, @name + "_csr.pem")
@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|
@@ -518,9 +684,15 @@ basicConstraints = CA:true
}
if @encrypt
- unless @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)
@@ -532,39 +704,149 @@ basicConstraints = CA:true
@ossl = Puppet::OpenSSL.findopenssl
end
- #def mkcert
- # cmd = "#{ossl} req -nodes -new -x509 -keyout %s -out %s -config %s" %
- # [@key, @cert, Puppet::OpenSSL.config]
- #end
+ def mkcert(issuercert, issuername, serial, publickey)
+ puts "called mkcert on %s" % @certfile
+ #puts caller
+ unless issuercert or @selfsign
+ raise "Certs must either have an issuer or must be self-signed"
+ end
+ @cert = ::OpenSSL::X509::Certificate.new
+ from = Time.now
+
+ @cert.subject = self.certname
+ @cert.issuer = issuername
+ @cert.not_before = from
+ @cert.not_after = from + (@days * 24 * 60 * 60)
+ @cert.version = 2 # X509v3
+
+ @cert.public_key = publickey
+ @cert.serial = serial
+
+ basic_constraint = nil
+ key_usage = []
+ ext_key_usage = []
+
+ case @type
+ when :ca:
+ basic_constraint = "CA:TRUE"
+ key_usage.push %w{cRLSign keyCertSign}
+ when :terminalsubca:
+ basic_constraint = "CA:TRUE,pathlen:0"
+ key_usage %w{cRLSign keyCertSign}
+ when :server:
+ basic_constraint = "CA:FALSE"
+ key_usage << %w{digitalSignature keyEncipherment}
+ ext_key_usage << "serverAuth"
+ when :ocsp:
+ basic_constraint = "CA:FALSE"
+ key_usage << %w{nonRepudiation digitalSignature}
+ ext_key_usage << %w{serverAuth OCSPSigning}
+ when :client:
+ basic_constraint = "CA:FALSE"
+ key_usage << %w{nonRepudiation digitalSignature keyEncipherment}
+ ext_key_usage << %w{clientAuth emailProtection}
+ else
+ raise "unknonwn cert type '%s'" % @type
+ end
+
+ key_usage.flatten!
+ ext_key_usage.flatten!
+
+ ef = ::OpenSSL::X509::ExtensionFactory.new
+
+ if issuercert
+ ef.issuer_certificate = issuercert
+ else
+ ef.issuer_certificate = @cert
+ end
+
+ ef.subject_certificate = @cert
+
+ ex = []
+ ex << ef.create_extension("basicConstraints", basic_constraint, true)
+ ex << ef.create_extension("nsComment",
+ "Ruby/OpenSSL Generated Certificate")
+ ex << ef.create_extension("subjectKeyIdentifier", "hash")
+ #ex << ef.create_extension("nsCertType", "client,email")
+ unless key_usage.empty? then
+ ex << ef.create_extension("keyUsage", key_usage.join(","))
+ end
+ #ex << ef.create_extension("authorityKeyIdentifier",
+ # "keyid:always,issuer:always")
+ #ex << ef.create_extension("authorityKeyIdentifier", "keyid:always")
+ unless ext_key_usage.empty? then
+ ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(","))
+ end
+
+ #if @ca_config[:cdp_location] then
+ # ex << ef.create_extension("crlDistributionPoints",
+ # @ca_config[:cdp_location])
+ #end
+
+ #if @ca_config[:ocsp_location] then
+ # ex << ef.create_extension("authorityInfoAccess",
+ # "OCSP;" << @ca_config[:ocsp_location])
+ #end
+ @cert.extensions = ex
+
+ #cmd = "#{ossl} req -nodes -new -x509 -keyout %s -out %s -config %s" %
+ # [@key, @certfile, Puppet::OpenSSL.config]
+
+ @cert.sign(@key, ::OpenSSL::Digest::SHA1.new) if @selfsign
+
+ # write the cert out
+ File.open(@certfile, "w") { |f| f << @cert.to_pem }
+
+ return @cert
+ end
+
+ # this only works for servers, not for users
def mkcsr
- #cmd = "#{ossl} req -new -key #{@key} -out #{@csr}"
+ #cmd = "#{ossl} req -new -key #{@key} -out #{@csrfile}"
#self.class.exec(exec)
- cmd = [@ossl, "req"]
- cmd << "-batch"
- cmd << "-new"
- cmd << ["-newkey", "rsa:1024"]
- cmd << ["-subj", self.subject]
- cmd << ["-keyout", @key]
- cmd << ["-out", @csr]
- if @encrypt
- cmd << ["-passout", "file:" + @encrypt]
- else
- cmd << "-nodes"
+ unless @key
+ self.getkey
end
- Puppet::OpenSSL.exec(cmd.flatten.join(" "))
+ 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::MD5.new)
+
+ File.open(@csrfile, "w") { |f|
+ f << csr.to_pem
+ }
+
+# cmd = [@ossl, "req"]
+# cmd << "-batch"
+# cmd << "-new"
+# cmd << ["-newkey", "rsa:1024"]
+# cmd << ["-subj", self.subject]
+# cmd << ["-keyout", @key]
+# cmd << ["-out", @csrfile]
+#
+# if @encrypt
+# cmd << ["-passout", "file:" + @encrypt]
+# else
+# cmd << "-nodes"
+# end
+#
+# Puppet::OpenSSL.exec(cmd.flatten.join(" "))
end
def mkhash
- hash = Puppet::OpenSSL.exec("openssl x509 -noout -hash -in %s" % @cert).chomp
+ hash = Puppet::OpenSSL.exec("openssl x509 -noout -hash -in %s" % @certfile).chomp
10.times { |i|
path = File.join(@dir, "%s.%s" % [hash, i])
if FileTest.exists?(path)
if FileTest.symlink?(path)
dest = File.readlink(path)
- if dest == @cert
+ if dest == @certfile
# the correct link already exists
puts "hash already exists"
@hash = path
@@ -577,49 +859,80 @@ basicConstraints = CA:true
end
end
- File.symlink(@cert, path)
+ File.symlink(@certfile, path)
@hash = path
break
}
end
- #def mkkey
- # cmd = "#{ossl} genrsa -out #{@key} 1024"
- #end
+ def mkkey
+ # @key is the file
- def mkselfsigned
- if self.exists?
- unless @replace
- raise "Certificate exists"
- end
+ @key = ::OpenSSL::PKey::RSA.new 1024
+
+ 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, "req"]
- cmd << "-batch"
- cmd << ["-subj", self.subject]
- cmd << "-new"
- cmd << "-x509"
- cmd << ["-keyout", @key]
- cmd << ["-out", @cert]
+ #cmd = "#{ossl} genrsa -out #{@key} 1024"
+ end
- if @encrypt
- cmd << ["-passout", "file:" + @encrypt]
- else
- cmd << "-nodes"
+ def mkselfsigned
+ unless FileTest.exists?(@keyfile)
+ self.mkkey
end
- Puppet::OpenSSL.exec(cmd.flatten.join(" "))
- self.mkhash
+ self.mkcert(nil, self.certname, 0x0, @key.public_key)
+# if self.exists?
+# unless @replace
+# raise "Certificate exists"
+# end
+# end
+#
+# cmd = [@ossl, "req"]
+# cmd << "-batch"
+# cmd << ["-subj", self.subject(true)]
+# cmd << "-new"
+# cmd << "-x509"
+# cmd << ["-keyout", @keyfile]
+# cmd << ["-out", @certfile]
+#
+# if @encrypt
+# cmd << ["-passout", "file:" + @encrypt]
+# else
+# cmd << "-nodes"
+# end
+#
+# Puppet::OpenSSL.exec(cmd.flatten.join(" "))
+# self.mkhash
end
- def subject
- subj = "/" + @@params2names.collect { |param, name|
+ def subject(string = false)
+ subj = @@params2names.collect { |param, name|
if @params.include?(param)
- "%s=%s" % [name, @params[param]]
+ [name, @params[param]]
end
- }.reject { |s| s.nil? }.join("/") + "/"
- return subj
+ }.reject { |ary| ary.nil? }
+
+ if string
+ return "/" + subj.collect { |ary|
+ "%s=%s" % ary
+ }.join("/") + "/"
+ else
+ return subj
+ end
end
end
end