QuickCert allows you to quickly and easily create SSL certificates. It uses a simple configuration file to generate self-signed client and server certificates.
QuickCert is a compilation of NAKAMURA Hiroshi's post [ruby-talk:89917]
the example scripts referenced in the above post, and gen_csr.rb from Ruby’s OpenSSL examples.
A simple QuickCert configuration file looks like:
full_hostname = %xhostname`.strip domainname = full_hostname.split('.')[1..-1].join('.') hostname = full_hostname.split('.')[0] CA[:hostname] = hostname CA[:domainname] = domainname CA[:CA_dir] = File.join Dir.pwd, "CA" CA[:password] = '1234' CERTS << { :type => 'server', :hostname => 'uriel', :password => '5678', } CERTS << { :type => 'client', :user => 'drbrain', :email => 'drbrain@segment7.net', }
This configuration will create a Certificate Authority in a ‘CA’ directory in the current directory, a server certificate with password ‘5678’ for the server ‘uriel’ in a directory named ‘uriel’, and a client certificate for drbrain in the directory ‘drbrain’ with no password.
There are additional SSL knobs you can tweak in the qc_defaults.rb file. (See `gem which quick_cert/defaults`).
To generate the certificates, simply create a qc_config file where you want the certificate directories to be created, then run QuickCert.
QuickCert Version
Creates a new QuickCert instance using the
Certificate Authority described in ca_config
. If there is no
CA at ca_config, then QuickCert will initialize a new one. Prints out
debugging info if debug
is true.
# File lib/quick_cert.rb, line 62 def initialize(ca_config, debug = false) @ca_config = ca_config @debug = debug create_ca end
Creates a new Certificate Authority from @ca_config if it does not already exist at ca_config.
# File lib/quick_cert.rb, line 83 def create_ca return if File.exist? @ca_config[:CA_dir] Dir.mkdir @ca_config[:CA_dir] Dir.mkdir File.join(@ca_config[:CA_dir], 'private'), 0700 Dir.mkdir File.join(@ca_config[:CA_dir], 'newcerts') Dir.mkdir File.join(@ca_config[:CA_dir], 'crl') open @ca_config[:serial_file], 'w' do |f| f << '1' end warn "Generating CA keypair" if @debug keypair = OpenSSL::PKey::RSA.new @ca_config[:ca_rsa_key_length] cert = OpenSSL::X509::Certificate.new name = @ca_config[:name].dup << ['CN', 'CA'] cert.subject = cert.issuer = OpenSSL::X509::Name.new(name) cert.not_before = Time.now cert.not_after = Time.now + @ca_config[:ca_cert_days] * 24 * 60 * 60 cert.public_key = keypair.public_key cert.serial = 0x0 cert.version = 2 # X509v3 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = cert cert.extensions = [ ef.create_extension("basicConstraints", "CA:TRUE", true), ef.create_extension("nsComment", "Ruby/OpenSSL Generated Certificate"), ef.create_extension("subjectKeyIdentifier", "hash"), ef.create_extension("keyUsage", "cRLSign,keyCertSign", true), ] cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") cert.sign keypair, OpenSSL::Digest::SHA1.new keypair_export = keypair.export OpenSSL::Cipher::DES.new(:EDE3, :CBC), @ca_config[:password] warn "Writing keypair to #{@ca_config[:keypair_file]}" if @debug open @ca_config[:keypair_file], "w", 0400 do |fp| fp << keypair_export end warn "Writing cert to #{@ca_config[:cert_file]}" if @debug open @ca_config[:cert_file], "w", 0644 do |f| f << cert.to_pem end warn "Done generating certificate for #{cert.subject}" if @debug end
Creates a new certificate from cert_config
that is signed by
the CA.
# File lib/quick_cert.rb, line 73 def create_cert(cert_config) cert_keypair = create_key cert_config cert_csr = create_csr cert_config, cert_keypair sign_cert cert_config, cert_keypair, cert_csr end
Creates a new Certificate Signing Request for the keypair in
keypair_file
, generating and saving new keypair if nil.
# File lib/quick_cert.rb, line 168 def create_csr(cert_config, keypair_file = nil) keypair = nil dest = cert_config[:hostname] || cert_config[:user] csr_file = File.join dest, "csr_#{dest}.pem" name = @ca_config[:name].dup case cert_config[:type] when 'server' then name << ['OU', 'CA'] name << ['CN', cert_config[:hostname]] when 'client' then name << ['CN', cert_config[:user]] name << ['emailAddress', cert_config[:email]] end name = OpenSSL::X509::Name.new name if File.exist? keypair_file then keypair = OpenSSL::PKey::RSA.new File.read(keypair_file), cert_config[:password] else keypair = create_key cert_config end warn "Generating CSR for #{name}" if @debug req = OpenSSL::X509::Request.new req.version = 0 req.subject = name req.public_key = keypair.public_key req.sign keypair, OpenSSL::Digest::MD5.new warn "Writing CSR to #{csr_file}" if @debug open csr_file, "w" do |f| f << req.to_pem end csr_file end
Creates a new RSA key from cert_config
.
# File lib/quick_cert.rb, line 138 def create_key(cert_config) dest = cert_config[:hostname] || cert_config[:user] keypair_file = File.join dest, (dest + "_keypair.pem") Dir.mkdir dest, 0700 warn "Generating RSA keypair" if @debug keypair = OpenSSL::PKey::RSA.new 1024 if cert_config[:password].nil? then open keypair_file, "w", 0400 do |f| f << keypair.to_pem end else keypair_export = keypair.export OpenSSL::Cipher::DES.new(:EDE3, :CBC), cert_config[:password] warn "Writing keypair to #{keypair_file}" if @debug open keypair_file, "w", 0400 do |f| f << keypair_export end end keypair_file end
Signs the certificate described in cert_config
and
csr_file
, saving it to cert_file
.
# File lib/quick_cert.rb, line 213 def sign_cert(cert_config, cert_file, csr_file) csr = OpenSSL::X509::Request.new File.read(csr_file) raise "CSR sign verification failed." unless csr.verify csr.public_key raise "Key length too short" if csr.public_key.n.num_bits < @ca_config[:cert_key_length_min] raise "Key length too long" if csr.public_key.n.num_bits > @ca_config[:cert_key_length_max] raise "DN does not match" if csr.subject.to_a[0, @ca_config[:name].size] != @ca_config[:name] # Only checks signature here. You must verify CSR according to your # CP/CPS. # CA setup warn "Reading CA cert from #{@ca_config[:cert_file]}" if @debug ca = OpenSSL::X509::Certificate.new File.read(@ca_config[:cert_file]) warn "Reading CA keypair from #{@ca_config[:keypair_file]}" if @debug ca_keypair = OpenSSL::PKey::RSA.new File.read(@ca_config[:keypair_file]), @ca_config[:password] serial = File.read(@ca_config[:serial_file]).chomp.hex open @ca_config[:serial_file], "w" do |f| f << "%04X" % (serial + 1) end warn "Generating cert" if @debug cert = OpenSSL::X509::Certificate.new from = Time.now cert.subject = csr.subject cert.issuer = ca.subject cert.not_before = from cert.not_after = from + @ca_config[:cert_days] * 24 * 60 * 60 cert.public_key = csr.public_key cert.serial = serial cert.version = 2 # X509v3 basic_constraint = nil key_usage = [] ext_key_usage = [] case cert_config[:type] when "ca" then basic_constraint = "CA:TRUE" key_usage << "cRLSign" << "keyCertSign" when "terminalsubca" then basic_constraint = "CA:TRUE,pathlen:0" key_usage << "cRLSign" << "keyCertSign" when "server" then basic_constraint = "CA:FALSE" key_usage << "digitalSignature" << "keyEncipherment" ext_key_usage << "serverAuth" when "ocsp" then basic_constraint = "CA:FALSE" key_usage << "nonRepudiation" << "digitalSignature" ext_key_usage << "serverAuth" << "OCSPSigning" when "client" then basic_constraint = "CA:FALSE" key_usage << "nonRepudiation" << "digitalSignature" << "keyEncipherment" ext_key_usage << "clientAuth" << "emailProtection" else raise "unknonw cert type \"#{cert_config[:type]}\"" end ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = ca 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 cert.sign ca_keypair, OpenSSL::Digest::SHA1.new backup_cert_file = @ca_config[:new_certs_dir] + "/cert_#{cert.serial}.pem" warn "Writing backup cert to #{backup_cert_file}" if @debug open backup_cert_file, "w", 0644 do |f| f << cert.to_pem end # Write cert dest = cert_config[:hostname] || cert_config[:user] cert_file = File.join dest, "cert_#{dest}.pem" warn "Writing cert to #{cert_file}" if @debug open cert_file, "w", 0644 do |f| f << cert.to_pem end cert_file end