From 57287ae1d4151ec453ec9d33fafe4f1a4ced37e0 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 1 Nov 2012 01:07:27 -0700 Subject: x.509 support -- added certificate authority creation and server cert creation --- lib/leap_cli/commands/ca.rb | 162 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lib/leap_cli/commands/ca.rb (limited to 'lib/leap_cli/commands/ca.rb') diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb new file mode 100644 index 0000000..9f1d42e --- /dev/null +++ b/lib/leap_cli/commands/ca.rb @@ -0,0 +1,162 @@ +require 'openssl' +require 'certificate_authority' +require 'date' +require 'digest/md5' + +module LeapCli; module Commands + + desc 'Creates the public and private key for your Certificate Authority.' + command :'init-ca' do |c| + c.action do |global_options,options,args| + assert_files_missing! :ca_cert, :ca_key + assert_config! 'provider.ca.name' + assert_config! 'provider.ca.bit_size' + + provider = manager.provider + root = CertificateAuthority::Certificate.new + + # set subject + root.subject.common_name = provider.ca.name + possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address'] + provider.ca.keys.each do |key| + if possible.include?(key) + root.subject.send(key + '=', provider.ca[key]) + end + end + + # set expiration + years = 2 + today = Date.today + root.not_before = Time.gm today.year, today.month, today.day + root.not_after = root.not_before + years * 60 * 60 * 24 * 365 + + # generate private key + root.serial_number.number = 1 + root.key_material.generate_key(provider.ca.bit_size) + + # sign self + root.signing_entity = true + root.parent = root + root.sign!(ca_root_signing_profile) + + # save + write_file!(:ca_key, root.key_material.private_key.to_pem) + write_file!(:ca_cert, root.to_pem) + end + end + + desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes' + arg_name '', :optional => false, :multiple => false + command :'update-cert' do |c| + c.action do |global_options,options,args| + assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them' + assert_config! 'provider.ca.server_certificates.bit_size' + assert_config! 'provider.ca.server_certificates.life_span' + + if args.first == 'all' + bail! 'not supported yet' + else + provider = manager.provider + ca_root = cert_from_files(:ca_cert, :ca_key) + node = get_node_from_args(args) + + # set subject + cert = CertificateAuthority::Certificate.new + cert.subject.common_name = node.domain.full + + # set expiration + years = provider.ca.server_certificates.life_span.to_i + today = Date.today + cert.not_before = Time.gm today.year, today.month, today.day + cert.not_after = cert.not_before + years * 60 * 60 * 24 * 365 + + # generate key + cert.serial_number.number = cert_serial_number(node.domain.full) + cert.key_material.generate_key(provider.ca.server_certificates.bit_size) + + # sign + cert.parent = ca_root + cert.sign!(server_signing_profile(node)) + + # save + write_file!([:node_x509_key, node.name], cert.key_material.private_key.to_pem) + write_file!([:node_x509_cert, node.name], cert.to_pem) + end + end + end + + desc 'Generates Diffie-Hellman parameter file (needed for server-side of TLS connections)' + command :'init-dh' do |c| + c.action do |global_options,options,args| + long_running do + if cmd_exists?('certtool') + progress('Generating DH parameters (takes a long time)...') + output = assert_run!('certtool --generate-dh-params --sec-param high') + write_file!(:dh_params, output) + else + progress('Generating DH parameters (takes a REALLY long time)...') + output = OpenSSL::PKey::DH.generate(3248).to_pem + write_file!(:dh_params, output) + end + end + end + end + + private + + def cert_from_files(crt, key) + crt = read_file!(crt) + key = read_file!(key) + openssl_cert = OpenSSL::X509::Certificate.new(crt) + cert = CertificateAuthority::Certificate.from_openssl(openssl_cert) + cert.key_material.private_key = OpenSSL::PKey::RSA.new(key) # second argument is password, if set + return cert + end + + def ca_root_signing_profile + { + "extensions" => { + "basicConstraints" => {"ca" => true}, + "keyUsage" => { + "usage" => ["critical", "keyCertSign"] + }, + "extendedKeyUsage" => { + "usage" => [] + } + } + } + end + + # + # for keyusage, openvpn server certs can have keyEncipherment or keyAgreement. I am not sure which is preferable. + # going with keyAgreement for now. + # + def server_signing_profile(node) + { + "extensions" => { + "keyUsage" => { + "usage" => ["digitalSignature", "keyAgreement"] + }, + "extendedKeyUsage" => { + "usage" => ["serverAuth"] + }, + "subjectAltName" => { + "uris" => [ + "IP:#{node.ip_address}", + "DNS:#{node.domain.internal}" + ] + } + } + } + end + + # + # For cert serial numbers, we need a non-colliding number less than 160 bits. + # md5 will do nicely, since there is no need for a secure hash, just a short one. + # (md5 is 128 bits) + # + def cert_serial_number(domain_name) + Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16) + end + +end; end -- cgit v1.2.3