aboutsummaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands/ca.rb
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2012-11-01 01:07:27 -0700
committerelijah <elijah@riseup.net>2012-11-01 01:07:27 -0700
commit57287ae1d4151ec453ec9d33fafe4f1a4ced37e0 (patch)
treefe63a8de11c41d247fc3634277bc49c1ca7cd689 /lib/leap_cli/commands/ca.rb
parentf339e7b94ab8920fd1e271c50145b5d2d1a8ac9d (diff)
downloadleap_cli-57287ae1d4151ec453ec9d33fafe4f1a4ced37e0.tar.gz
leap_cli-57287ae1d4151ec453ec9d33fafe4f1a4ced37e0.tar.bz2
x.509 support -- added certificate authority creation and server cert creation
Diffstat (limited to 'lib/leap_cli/commands/ca.rb')
-rw-r--r--lib/leap_cli/commands/ca.rb162
1 files changed, 162 insertions, 0 deletions
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 '<node-name | "all">', :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