#! /usr/bin/env ruby require 'optparse' require 'fileutils' require 'yaml' require 'zip' require_relative '../lib/version.rb' require_relative '../lib/functions.rb' SSL_CONF = 'openssl.ini' APP_CONF = 'ovpn-key.yml' options = {} OptionParser.new do |opts| opts.banner = "Usage: #{File.basename $0} [--nopass]" opts.on("--init [directory]", "Init a CA directory (defaults to current)") do |v| if v options[:init] = v else options[:init] = "." end end opts.on("--ca", "Generate a CA (ca.crt)") do |v| check_crt('ca') options[:generate_ca] = v end opts.on("--dh", "Generate a DH keyfile (dh.pem)") do |v| options[:generate_dh] = v end opts.on("--server [name]", "Generate a server key (defaults to 'server')") do |v| if v options[:generate_server] = v else options[:generate_server] = "server" end check_crt(options[:generate_server]) end opts.on("--client [name]", "Generate a client key and pack it to ZIP") do |v| abort "Error: client should have an alphanumeric name" unless v check_crt(v) options[:generate_client] = v end opts.on("--revoke [name]", "Revoke a certificate (using crl.pem) and delete it") do |v| abort "Please specify what certificate to revoke" unless v options[:revoke] = v end opts.on("--nopass", "Don't protect .key files with a password") do |v| options[:no_password] = v end end.parse! if ARGV.length > 0 abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $0} -h` for help" end unless options[:init] || options[:generate_ca] || options[:generate_dh] \ || options[:generate_server] || options[:generate_client] || options[:revoke] abort "See `#{File.basename $0} -h` for usage" end File.umask 0077 if options[:init] unless options[:init] == '.' create_dir options[:init] Dir.chdir options[:init] end ['certs', 'meta'].each {|dir| create_dir dir} ['meta/index.txt', 'meta/index.txt.attr', 'meta/serial', SSL_CONF, APP_CONF].each {|file| unless File.exist? file FileUtils.copy_file(File.expand_path("defaults/#{file}", "#{__dir__}/.."), "./#{file}") puts "Created file: #{file}" end } elsif !File.exist? 'ovpn-key.yml' begin rc = YAML.load_file(File.expand_path '~/.ovpn-key.yml') rescue Errno::ENOENT end Dir.chdir File.expand_path(rc['cd']) if rc && rc['cd'] end begin settings = YAML.load_file(APP_CONF) rescue Errno::ENOENT abort "Run `#{File.basename $0} --init` before generating certificates" end ZIP_DIR = settings['zip_dir'] || '~' OPENSSL = settings['openssl'] || 'openssl' KEY_SIZE = settings['key_size'] || 2048 ENCRYPT = settings['encrypt'] || 'aes128' CN_CA = settings['ca_name'] || 'Certification Authority' REQ = settings['details'] if options[:generate_ca] genrsa('ca', 'ca', options[:no_password]) req('ca', 'ca', CN_CA) gen_crl end if options[:generate_dh] exe "#{OPENSSL} dhparam -out dh.pem #{KEY_SIZE}" end if options[:generate_server] genrsa('server', options[:generate_server], options[:no_password]) req('server', options[:generate_server], options[:generate_server]) end if options[:generate_client] ovpn_files = Dir['*.ovpn'] case ovpn_files.length when 1 ovpn_file = ovpn_files.first when 0 abort "No .ovpn file in current directory, please add one" else abort "More than one .ovpn files in current directory, aborting" end genrsa('client', options[:generate_client], options[:no_password]) req('client', options[:generate_client], options[:generate_client]) zip_file = File.join(File.expand_path(ZIP_DIR), "#{File.basename ovpn_file, '.ovpn'}.tblk.zip") File.delete(zip_file) if File.exist?(zip_file) Zip::File.open(zip_file, Zip::File::CREATE) do |zip| zip.get_output_stream(ovpn_file) {|f| File.open(ovpn_file).each {|line| f.write line} f.write "cert #{options[:generate_client]}.crt\nkey #{options[:generate_client]}.key\n" } [ 'ca.crt', "#{options[:generate_client]}.crt", "#{options[:generate_client]}.key"].each {|i| zip.add(i, i) } end end if options[:revoke] exe "#{OPENSSL} ca -revoke '#{options[:revoke]}.crt' -config #{SSL_CONF}" gen_crl ['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"} end