Created
November 24, 2009 22:07
-
-
Save QNENet/242281 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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