aboutsummaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands/ca.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/leap_cli/commands/ca.rb')
-rw-r--r--lib/leap_cli/commands/ca.rb106
1 files changed, 88 insertions, 18 deletions
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
index 46e3494..579e305 100644
--- a/lib/leap_cli/commands/ca.rb
+++ b/lib/leap_cli/commands/ca.rb
@@ -1,6 +1,6 @@
-require 'openssl'
-require 'certificate_authority'
-require 'date'
+autoload :OpenSSL, 'openssl'
+autoload :CertificateAuthority, 'certificate_authority'
+autoload :Date, 'date'
require 'digest/md5'
module LeapCli; module Commands
@@ -36,6 +36,7 @@ module LeapCli; module Commands
nodes = manager.filter!(args)
nodes.each_node do |node|
+ warn_if_commercial_cert_will_soon_expire(node)
if !node.x509.use
remove_file!([:node_x509_key, node.name])
remove_file!([:node_x509_cert, node.name])
@@ -81,9 +82,19 @@ module LeapCli; module Commands
# http://www.redkestrel.co.uk/Articles/CSR.html
#
cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
- cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. The properties used for this CSR come from `provider.ca.server_certificates`."
+ cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
+ "The properties used for this CSR come from `provider.ca.server_certificates`, "+
+ "but may be overridden here."
cert.command :csr do |csr|
csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
+ csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
+ csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name."
+ csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name."
+ csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name."
+ csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name."
+ csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name."
+ csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
+ csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
csr.action do |global_options,options,args|
assert_config! 'provider.domain'
assert_config! 'provider.name'
@@ -97,24 +108,25 @@ module LeapCli; module Commands
# RSA key
keypair = CertificateAuthority::MemoryKeyMaterial.new
- log :generating, "%s bit RSA key" % server_certificates.bit_size do
- keypair.generate_key(server_certificates.bit_size)
+ bit_size = (options[:bits] || server_certificates.bit_size).to_i
+ log :generating, "%s bit RSA key" % bit_size do
+ keypair.generate_key(bit_size)
write_file! [:commercial_key, domain], keypair.private_key.to_pem
end
# CSR
dn = CertificateAuthority::DistinguishedName.new
- csr = CertificateAuthority::SigningRequest.new
- dn.common_name = domain
- dn.organization = provider.name[provider.default_language]
- dn.country = server_certificates['country'] # optional
- dn.state = server_certificates['state'] # optional
- dn.locality = server_certificates['locality'] # optional
-
- log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do
- csr.distinguished_name = dn
- csr.key_material = keypair
- csr.digest = server_certificates.digest
+ dn.common_name = domain
+ dn.organization = options[:organization] || provider.name[provider.default_language]
+ dn.ou = options[:organizational_unit] # optional
+ dn.email_address = options[:email] # optional
+ dn.country = options[:country] || server_certificates['country'] # optional
+ dn.state = options[:state] || server_certificates['state'] # optional
+ dn.locality = options[:locality] || server_certificates['locality'] # optional
+
+ digest = options[:digest] || server_certificates.digest
+ log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
+ csr = create_csr(dn, keypair, digest)
request = csr.to_x509_csr
write_file! [:commercial_csr, domain], csr.to_pem
end
@@ -191,7 +203,7 @@ module LeapCli; module Commands
return true
else
cert = load_certificate_file([:node_x509_cert, node.name])
- if cert.not_after < months_from_yesterday(1)
+ if cert.not_after < months_from_yesterday(2)
log :updating, "cert for node '#{node.name}' because it will expire soon"
return true
end
@@ -222,6 +234,18 @@ module LeapCli; module Commands
return false
end
+ def warn_if_commercial_cert_will_soon_expire(node)
+ dns_names_for_node(node).each do |domain|
+ if file_exists?([:commercial_cert, domain])
+ cert = load_certificate_file([:commercial_cert, domain])
+ if cert.not_after < months_from_yesterday(2)
+ log :warning, "the commercial certificate '#{Path.relative_path([:commercial_cert, domain])}' will expire soon. "+
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ end
+ end
+ end
+ end
+
def generate_cert_for_node(node)
return if node.x509.use == false
@@ -262,6 +286,43 @@ module LeapCli; module Commands
yield cert.key_material.private_key.to_pem, cert.to_pem
end
+ #
+ # creates a CSR and returns it.
+ # with the correct extReq attribute so that the CA
+ # doens't generate certs with extensions we don't want.
+ #
+ def create_csr(dn, keypair, digest)
+ csr = CertificateAuthority::SigningRequest.new
+ csr.distinguished_name = dn
+ csr.key_material = keypair
+ csr.digest = digest
+
+ # define extensions manually (library doesn't support setting these on CSRs)
+ extensions = []
+ extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
+ basic.ca = false
+ }
+ extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
+ keyusage.usage = ["digitalSignature", "keyEncipherment"]
+ }
+ extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
+ extkeyusage.usage = [ "serverAuth"]
+ }
+
+ # convert extensions to attribute 'extReq'
+ # aka "Requested Extensions"
+ factory = OpenSSL::X509::ExtensionFactory.new
+ attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
+ extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
+ )])
+ attrs = [
+ OpenSSL::X509::Attribute.new("extReq", attrval),
+ ]
+ csr.attributes = attrs
+
+ return csr
+ end
+
def ca_root
@ca_root ||= begin
load_certificate_file(:ca_cert, :ca_key)
@@ -406,6 +467,15 @@ module LeapCli; module Commands
cert_serial_number(domain_name).to_s(36)
end
+ # prints CertificateAuthority::DistinguishedName fields
+ def print_dn(dn)
+ fields = {}
+ [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
+ fields[attr] = dn.send(attr) if dn.send(attr)
+ end
+ fields.inspect
+ end
+
##
## TIME HELPERS
##