From 76a3a736cfb50cb1c6d926d1e3afb0f504818157 Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 16 Nov 2012 14:30:20 -0800 Subject: added CSR ability (and vendored certificate_authority gem, so we can get the unreleased fixes we need). --- lib/leap_cli/commands/ca.rb | 90 ++++++++++++++++++++++++++++++++++++++++++++- lib/leap_cli/log.rb | 14 ++++++- lib/leap_cli/path.rb | 3 ++ lib/leap_cli/util.rb | 28 +++++++------- lib/leap_cli/version.rb | 2 +- 5 files changed, 119 insertions(+), 18 deletions(-) (limited to 'lib/leap_cli') diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb index 5b556a3..1763ba3 100644 --- a/lib/leap_cli/commands/ca.rb +++ b/lib/leap_cli/commands/ca.rb @@ -50,6 +50,7 @@ module LeapCli; module Commands 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.digest' assert_config! 'provider.ca.server_certificates.life_span' assert_config! 'common.x509.use' @@ -82,6 +83,72 @@ module LeapCli; module Commands end end + # + # hints: + # + # inspect CSR: + # openssl req -noout -text -in files/cert/x.csr + # + # generate CSR with openssl to see how it compares: + # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr + # + # validate a CSR: + # http://certlogik.com/decoder/ + # + # nice details about CSRs: + # http://www.redkestrel.co.uk/Articles/CSR.html + # + desc 'Creates a Certificate Signing Request for use in purchasing a commercial x509 certificate' + command :'init-csr' do |c| + c.switch 'sign', :desc => 'additionally creates a cert that is signed by your own CA (recommended only for testing)', :negatable => false + c.action do |global_options,options,args| + assert_config! 'provider.domain' + assert_config! 'provider.name' + assert_config! 'provider.default_language' + assert_config! 'provider.ca.server_certificates.bit_size' + assert_config! 'provider.ca.server_certificates.digest' + assert_files_missing! [:commercial_key, manager.provider.domain], [:commercial_csr, manager.provider.domain], :msg => 'If you really want to create a new key and CSR, remove these files first.' + if options[:sign] + assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them' + end + + # RSA key + keypair = CertificateAuthority::MemoryKeyMaterial.new + log :generating, "%s bit RSA key" % manager.provider.ca.server_certificates.bit_size do + keypair.generate_key(manager.provider.ca.server_certificates.bit_size) + write_file! [:commercial_key, manager.provider.domain], keypair.private_key.to_pem + end + + # CSR + dn = CertificateAuthority::DistinguishedName.new + csr = CertificateAuthority::SigningRequest.new + dn.common_name = manager.provider.domain + dn.organization = manager.provider.name[manager.provider.default_language] + log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do + csr.distinguished_name = dn + csr.key_material = keypair + csr.digest = manager.provider.ca.server_certificates.digest + request = csr.to_x509_csr + write_file! [:commercial_csr, manager.provider.domain], csr.to_pem + end + + # Sign using our own CA, for use in testing but hopefully not production. + # It is not that commerical CAs are so secure, it is just that signing your own certs is + # a total drag for the user because they must click through dire warnings. + if options[:sign] + log :generating, "x509 server certificate for testing purposes" do + cert = csr.to_cert + cert.serial_number.number = cert_serial_number(manager.provider.domain) + cert.not_before = today + cert.not_after = years_from_today(1) + cert.parent = ca_root + cert.sign! test_cert_signing_profile + write_file! [:commercial_cert, manager.provider.domain], cert.to_pem + end + end + end + end + private def cert_needs_updating?(node) @@ -182,11 +249,11 @@ module LeapCli; module Commands # for keyusage, openvpn server certs can have keyEncipherment or keyAgreement. I am not sure which is preferable. # going with keyAgreement for now. # - # digest options: SHA512, SHA1 + # digest options: SHA512, SHA256, SHA1 # def server_signing_profile(node) { - "digest" => "SHA256", + "digest" => manager.provider.ca.server_certificates.digest, "extensions" => { "keyUsage" => { "usage" => ["digitalSignature", "keyAgreement"] @@ -202,6 +269,25 @@ module LeapCli; module Commands } end + # + # This is used when signing the main cert for the provider's domain + # with our own CA (for testing purposes). Typically, this cert would + # be purchased from a commercial CA, and not signed this way. + # + def test_cert_signing_profile + { + "digest" => "SHA256", + "extensions" => { + "keyUsage" => { + "usage" => ["digitalSignature", "keyAgreement"] + }, + "extendedKeyUsage" => { + "usage" => ["serverAuth"] + } + } + } + end + def dns_names_for_node(node) names = [node.domain.internal] if node['dns'] && node.dns['aliases'] && node.dns.aliases.any? diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 1cc1c6a..0821177 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -8,6 +8,12 @@ module LeapCli def log_level=(value) @log_level = value end + def indent_level + @indent_level ||= 0 + end + def indent_level=(value) + @indent_level = value + end end ## @@ -34,7 +40,8 @@ def log(*args) level = args.grep(Integer).first || 1 title = args.grep(Symbol).first message = args.grep(String).first - options = args.grep(Hash).first || {:indent => 0} + options = args.grep(Hash).first || {} + options[:indent] ||= LeapCli.indent_level if message && LeapCli.log_level >= level print " " * (options[:indent]+1) if options[:indent] > 0 @@ -66,5 +73,10 @@ def log(*args) end end puts "#{message}" + if block_given? + LeapCli.indent_level += 1 + yield + LeapCli.indent_level -= 1 + end end end diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index bf4c89f..de01fdb 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -34,6 +34,9 @@ module LeapCli; module Path :ca_key => 'files/ca/ca.key', :ca_cert => 'files/ca/ca.crt', :dh_params => 'files/ca/dh.pem', + :commercial_key => 'files/cert/#{arg}.key', + :commercial_csr => 'files/cert/#{arg}.csr', + :commercial_cert => 'files/cert/#{arg}.crt', :node_x509_key => 'files/nodes/#{arg}/#{arg}.key', :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt', :vagrantfile => 'test/Vagrantfile' diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 9b04894..0b0fb9e 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -55,7 +55,7 @@ module LeapCli # def assert_bin!(cmd_name) assert! `which #{cmd_name}`.strip.any? do - log 0, :missing, "command '%s'" % cmd_name + log :missing, "command '%s'" % cmd_name end end @@ -68,9 +68,9 @@ module LeapCli output = `#{cmd}` unless $?.success? bail! do - log 0, :run, cmd - log 0, :failed, "(exit #{$?.exitstatus}) #{output}", :indent => 1 - log 0, message, :indent => 1 if message + log :run, cmd + log :failed, "(exit #{$?.exitstatus}) #{output}", :indent => 1 + log message, :indent => 1 if message end else log 2, :ran, cmd @@ -86,13 +86,13 @@ module LeapCli }.compact if file_list.length > 1 bail! do - log 0, :error, "Sorry, we can't continue because these files already exist: #{file_list.join(', ')}." - log 0, options[:msg] if options[:msg] + log :error, "Sorry, we can't continue because these files already exist: #{file_list.join(', ')}." + log options[:msg] if options[:msg] end elsif file_list.length == 1 bail! do - log 0, :error, "Sorry, we can't continue because this file already exists: #{file_list.first}." - log 0, options[:msg] if options[:msg] + log :error, "Sorry, we can't continue because this file already exists: #{file_list.first}." + log options[:msg] if options[:msg] end end end @@ -104,8 +104,8 @@ module LeapCli rescue NoMethodError rescue NameError end - assert! !value.nil? do - log 0, :missing, "configuration value for #{conf_path}" + assert! !value.nil? && value != "REQUIRED" do + log :missing, "required configuration value for #{conf_path}" end end @@ -117,13 +117,13 @@ module LeapCli }.compact if file_list.length > 1 bail! do - log 0, :missing, "these files: #{file_list.join(', ')}" - log 0, options[:msg] if options[:msg] + log :missing, "these files: #{file_list.join(', ')}" + log options[:msg] if options[:msg] end elsif file_list.length == 1 bail! do - log 0, :missing, "file #{file_list.first}" - log 0, options[:msg] if options[:msg] + log :missing, "file #{file_list.first}" + log options[:msg] if options[:msg] end end end diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 83c2159..0dbd215 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -3,6 +3,6 @@ module LeapCli VERSION = '0.1.3' SUMMARY = 'Command line interface to the LEAP platform' DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' - REQUIRE_PATHS = ['lib', 'vendor/supply_drop/lib'] + REQUIRE_PATHS = ['lib', 'vendor/supply_drop/lib', 'vendor/certificate_authority/lib'] end end -- cgit v1.2.3