Skip to content

Instantly share code, notes, and snippets.

@QNENet
Created November 24, 2009 22:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save QNENet/242281 to your computer and use it in GitHub Desktop.
Save QNENet/242281 to your computer and use it in GitHub Desktop.
require 'openssl'
class QuickCert
VERSION = "1.0.2"
##
# Creates a new QuickCert instance using the Certificate
# Authority described in +cfg+. If there is no CA at
# cfg[:certificate_authority_dir], then QuickCert will initialize a new one.
def initialize(new_certificate_opts_authority)
@new_certificate_opts_authority = new_certificate_opts_authority
create_certificate_authority
end
##
# Creates a new Certificate Authority from @new_certificate_opts_authority if it
# does not already exist at new_certificate_opts_authority[:certificate_authority_dir].
def create_certificate_authority
return if File.exists? @new_certificate_opts_authority[:certificate_authority_dir]
Dir.mkdir @new_certificate_opts_authority[:certificate_authority_dir]
Dir.mkdir(File.join(@new_certificate_opts_authority[:certificate_authority_dir], 'private'), 0700)
@new_certificate_opts_authority[:new_certificates_dir] = File.join(@new_certificate_opts_authority[:certificate_authority_dir], 'newcertificates')
Dir.mkdir(@new_certificate_opts_authority[:new_certificates_dir])
Dir.mkdir(File.join(@new_certificate_opts_authority[:certificate_authority_dir], 'certificate_revokation_list'))
File.open @new_certificate_opts_authority[:serial_file], 'w' do |f| f << '1' end
puts "Generating CA keypair" if $DEBUG
keypair = OpenSSL::PKey::RSA.new @new_certificate_opts_authority[:ca_rsa_key_length]
certificate = OpenSSL::X509::Certificate.new
name = @new_certificate_opts_authority[:name].dup << ['CN', 'CA']
certificate.subject = certificate.issuer = OpenSSL::X509::Name.new(name)
certificate.not_before = Time.now
certificate.not_after = Time.now + @new_certificate_opts_authority[:ca_cert_days] * 24 * 60 * 60
certificate.public_key = keypair.public_key
certificate.serial = 12345
certificate.version = 2 # X509v3
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = certificate
ef.issuer_certificate = certificate
certificate.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),
ef.create_extension("authorityKeyIdentifier", "keyid:always, issuer:always")
]
# certificate.extensions.each {|e| puts e}
# abort
# cert.addExtension(ext)
# certificate.addExtension ef.create_extension("authorityKeyIdentifier", "keyid:always, issuer:always")
certificate.sign keypair, OpenSSL::Digest::SHA1.new
cb = proc do @new_certificate_opts_authority[:password] end
keypair_export = (keypair.export OpenSSL::Cipher::DES.new(:EDE3, :CBC), &cb)
puts "Writing keypair to #{@new_certificate_opts_authority[:keypair_file]}" if $DEBUG
File.open @new_certificate_opts_authority[:keypair_file], "w", 0400 do |fp|
fp << keypair_export
end
puts "Writing certificate to #{@new_certificate_opts_authority[:cert_file]}" if $DEBUG
File.open @new_certificate_opts_authority[:cert_file], "w", 0644 do |f|
f << certificate.to_pem
end
puts "Done generating certificate for #{certificate.subject}" if $DEBUG
end
##
# Creates a new certificate from +new_certificate_opts+ that is signed
# by the CA.
def create_certificate(new_certificate_opts)
@dest = new_certificate_opts[:hostname] || new_certificate_opts[:user]
new_certificate_opts[:new_dir] = File.join(@new_certificate_opts_authority[:new_certificates_dir], @dest)
Dir.mkdir(new_certificate_opts[:new_dir], 0700) # 0700 = 000 111 000 000 I think owner read/write/execute
@keypair_file = File.join(new_certificate_opts[:new_dir], (@dest + "_keypair.pem"))
certificate_keypair = create_key new_certificate_opts
certificate_csr = create_certificate_signing_request new_certificate_opts, certificate_keypair
sign_certificate new_certificate_opts, certificate_keypair, certificate_csr
end
##
# Creates a new RSA key from +new_certificate_opts+.
def create_key(new_certificate_opts)
passwd_cb = nil
# @dest = new_certificate_opts[:hostname] || new_certificate_opts[:user]
# keypair_file = File.join(ldest, (dest + "_keypair.pem"))
# new_dir = File.join(@new_certificate_opts_authority[:new_certificates_dir], dest)
# Dir.mkdir new_dir, 0700
puts "Generating RSA keypair" if $DEBUG
keypair = OpenSSL::PKey::RSA.new(1024)
if new_certificate_opts[:password].nil? then
File.open @keypair_file, "w", 0400 do |f| # 0400 = 000 100 000 000 owner read only ??
f << keypair.to_pem
end
else
passwd_cb = proc do
new_certificate_opts[:password]
end
cipher = OpenSSL::Cipher::DES.new(:EDE3, :CBC)
keypair_export = keypair.export(cipher, new_certificate_opts[:password])
puts "Writing keypair to #{@keypair_file}" if $DEBUG
File.open @keypair_file, "w", 0400 do |f|
f << keypair_export
end
end
return @keypair_file
end
##
# Creates a new Certificate Signing Request for the keypair in
# +keypair_file+, generating and saving new keypair if nil.
def create_certificate_signing_request(new_certificate_opts, keypair_file = nil)
keypair = nil
# dest = new_certificate_opts[:hostname] || new_certificate_opts[:user]
csr_file = File.join new_certificate_opts[:new_dir], "csr_#{@dest}.pem"
name = @new_certificate_opts_authority[:name].dup
case new_certificate_opts[:type]
when 'server' then
name << ['OU', 'CA']
name << ['CN', new_certificate_opts[:hostname]]
when 'client' then
name << ['CN', new_certificate_opts[:user]]
name << ['emailAddress', new_certificate_opts[:email]]
end
name = OpenSSL::X509::Name.new(name)
if File.exists? keypair_file then
keypair = OpenSSL::PKey::RSA.new(File.read(keypair_file), new_certificate_opts[:password])
else
keypair = create_key(new_certificate_opts)
end
puts "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
puts "Writing CSR to #{csr_file}" if $DEBUG
File.open csr_file, "w" do |f|
f << req.to_pem
end
return csr_file
end
##
# Signs the certificate described in +new_certificate_opts+ and
# +csr_file+, saving it to +cert_file+.
def sign_certificate(new_certificate_opts, cert_file, csr_file)
csr = OpenSSL::X509::Request.new File.read(csr_file)
raise "CSR sign verification failed." unless csr.verify csr.public_key
if csr.public_key.n.num_bits < @new_certificate_opts_authority[:cert_key_length_min] then
raise "Key length too short"
end
if csr.public_key.n.num_bits > @new_certificate_opts_authority[:cert_key_length_max] then
raise "Key length too long"
end
if csr.subject.to_a[0, @new_certificate_opts_authority[:name].size] != @new_certificate_opts_authority[:name] then
raise "DN does not match"
end
# Only checks signature here. You must verify CSR according to your
# CP/CPS.
# CA setup
puts "Reading CA certificate from #{@new_certificate_opts_authority[:cert_file]}" if $DEBUG
ca = OpenSSL::X509::Certificate.new(File.read(@new_certificate_opts_authority[:cert_file]))
puts "Reading CA keypair from #{@new_certificate_opts_authority[:keypair_file]}" if $DEBUG
ca_keypair = OpenSSL::PKey::RSA.new(File.read(@new_certificate_opts_authority[:keypair_file]), @new_certificate_opts_authority[:password])
serial = File.read(@new_certificate_opts_authority[:serial_file]).chomp.hex
File.open @new_certificate_opts_authority[:serial_file], "w" do |f|
f << "%04X" % (serial + 1)
end
puts "Generating certificate" if $DEBUG
certificate = OpenSSL::X509::Certificate.new
from = Time.now
certificate.subject = csr.subject
certificate.issuer = ca.subject
certificate.not_before = from
certificate.not_after = from + @new_certificate_opts_authority[:cert_days] * 24 * 60 * 60
certificate.public_key = csr.public_key
certificate.serial = serial
certificate.version = 2 # X509v3
basic_constraint = nil
key_usage = []
ext_key_usage = []
case new_certificate_opts[: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 certificate type \"#{new_certificate_opts[:type]}\""
end
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = certificate
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 @new_certificate_opts_authority[:certificate_revokation_list_distribution_point_location] then
ex << ef.create_extension("crlDistributionPoints",
@new_certificate_opts_authority[:certificate_revokation_list_distribution_point_location])
end
if @new_certificate_opts_authority[:online_certificate_status_protocol_utility_location] then
ex << ef.create_extension("authorityInfoAccess",
"OCSP;" << @new_certificate_opts_authority[:online_certificate_status_protocol_utility_location])
end
certificate.extensions = ex
certificate.sign(ca_keypair, OpenSSL::Digest::SHA1.new)
backup_certificate_file = @new_certificate_opts_authority[:new_certs_dir] + "/cert_#{certificate.serial}.pem"
puts "Writing backup certificate to #{backup_certificate_file}" if $DEBUG
File.open backup_certificate_file, "w", 0644 do |f|
f << certificate.to_pem
end
# Write certificate
dest = new_certificate_opts[:hostname] || new_certificate_opts[:user]
certificate_file = File.join dest, "certificate_#{dest}.pem"
puts "Writing certificate to #{certificate_file}" if $DEBUG
File.open certificate_file, "w", 0644 do |f|
f << certificate.to_pem
end
return certificate_file
end
end # class QuickCert
#if __FILE__ == $0 then
$DEBUG = true
CA = {}
CERTS = []
#load ARGV.shift || 'qc_config'
#CA[:certificate_authority_dir] = File.join($info[:program_directory], 'certificates')
CA[:certificate_authority_dir] = File.join(File.dirname(__FILE__) , 'certificates')
CA[:keypair_file] ||= File.join CA[:certificate_authority_dir], "private/cakeypair.pem"
CA[:cert_file] ||= File.join CA[:certificate_authority_dir], "cacert.pem"
CA[:serial_file] ||= File.join CA[:certificate_authority_dir], "serial"
CA[:new_certs_dir] ||= File.join CA[:certificate_authority_dir], "newcerts"
CA[:new_keypair_dir] ||= File.join CA[:certificate_authority_dir], "private/keypair_backup"
CA[:crl_dir] ||= File.join CA[:certificate_authority_dir], "crl"
CA[:ca_cert_days] ||= 5 * 365 # five years
CA[:ca_rsa_key_length] ||= 2048
CA[:cert_days] ||= 365 # one year
CA[:cert_key_length_min] ||= 1024
CA[:cert_key_length_max] ||= 2048
CA[:crl_file] ||= File.join CA[:crl_dir], "#{CA[:hostname]}.crl"
CA[:crl_pem_file] ||= File.join CA[:crl_dir], "#{CA[:hostname]}.pem"
CA[:crl_days] ||= 14
if CA[:name].nil?
CA[:name] = [
['C', 'US', OpenSSL::ASN1::PRINTABLESTRING],
['O', CA[:domainname], OpenSSL::ASN1::UTF8STRING],
['OU', CA[:hostname], OpenSSL::ASN1::UTF8STRING],
]
end
CERTS << {
:type => 'server',
:hostname => 'uriel',
:password => '5678',
}
CERTS << {
:type => 'client',
:user => 'drbrain',
:email => 'drbrain@segment7.net',
}
qc = QuickCert.new(CA)
CERTS.each do |new_certificate_opts|
qc.create_certificate(new_certificate_opts)
end
#end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment