lib/functions.rb in ovpn-key-0.7.7 vs lib/functions.rb in ovpn-key-0.8

- old
+ new

@@ -10,37 +10,131 @@ abort 'Error: client should have an alphanumeric name' unless name check_crt(name) end def exe(cmd) - system(cmd) or abort "error executing: #{cmd}" + system(cmd) || abort("error executing: #{cmd}") end -def gen_and_sign(type, certname, no_password) - gen_key(certname, no_password) - sign_key(type, certname, certname) +def ask_password(name) + password = '' + loop do + print "Enter password for #{name}.key: " + password = $stdin.noecho(&:gets).chomp + puts # trailing newline + break unless password.empty? + end + password end -def gen_key(certname, no_password) - if no_password - exe "#{OPENSSL} genrsa -out '#{certname}.key' #{KEY_SIZE}" - else - exe "#{OPENSSL} genrsa -#{ENCRYPT} -out '#{certname}.key' #{KEY_SIZE}" +def gen_and_sign(type, certname, password) + gen_key(certname, password) + sign_key(type, certname, password) +end + +def gen_key(certname, password) + key = OpenSSL::PKey::RSA.new(KEY_SIZE) + File.open("#{certname}.key", 'w') do |f| + f.write password ? key.to_pem(OpenSSL::Cipher.new(ENCRYPT), password) : key end end -def sign_key(type, certname, cn) - if certname == 'ca' - exe "#{OPENSSL} req -new -x509 -key '#{certname}.key' -out '#{certname}.crt' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type} -days #{CA_DAYS}" - else - exe "#{OPENSSL} req -new -key '#{certname}.key' -out '#{certname}.csr' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type}" - exe "#{OPENSSL} ca -in '#{certname}.csr' -out '#{certname}.crt' -config #{SSL_CONF} -extensions ext.#{type} -batch" - File.delete "#{certname}.csr" +# type is one of: 'ca', 'server', 'client' +# rubocop:disable Naming/MethodParameterName +def sign_key(type, cn, password) + # rubocop:enable Naming/MethodParameterName + certname = type == 'ca' ? 'ca' : cn + key = OpenSSL::PKey::RSA.new File.read("#{certname}.key"), password + subj = OpenSSL::X509::Name.new([['CN', cn]] + REQ.to_a) + serial = begin + File.read(SERIAL_FILE).to_i + rescue Errno::ENOENT + 0 + end + 1 + + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = serial + cert.not_before = Time.now + cert.not_after = + Time.now + + case type + when 'ca' + EXPIRE['ca'] + when 'server' + EXPIRE['server'] + when 'client' + EXPIRE['client'] + # days to seconds + end * 86_400 + cert.public_key = key.public_key + cert.subject = subj + cert.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a) + + ef = OpenSSL::X509::ExtensionFactory.new nil, cert + ef.issuer_certificate = cert + + cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash') + cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid,issuer:always') + cert.add_extension ef.create_extension('basicConstraints', type == 'ca' ? 'CA:true' : 'CA:false') + + case type + when 'ca' + cert.add_extension ef.create_extension('keyUsage', 'cRLSign,keyCertSign') + cert.sign key, OpenSSL::Digest.new(DIGEST) + when 'server' + cert.add_extension ef.create_extension('keyUsage', 'keyEncipherment,digitalSignature') + cert.add_extension ef.create_extension('extendedKeyUsage', 'serverAuth') + when 'client' + cert.add_extension ef.create_extension('keyUsage', 'digitalSignature') + cert.add_extension ef.create_extension('extendedKeyUsage', 'clientAuth') end + unless type == 'ca' + ca_key = begin + OpenSSL::PKey::RSA.new File.read('ca.key'), ask_password('ca') + rescue OpenSSL::PKey::RSAError + retry + end + cert.sign ca_key, OpenSSL::Digest.new(DIGEST) + end + + File.open(SERIAL_FILE, 'w') {|f| f.write serial } + File.open("#{certname}.crt", 'w') {|f| f.write cert.to_pem } end -def gen_crl - exe "#{OPENSSL} ca -gencrl -out crl.pem -config #{SSL_CONF}" +def revoke(certname) + crl = OpenSSL::X509::CRL.new(File.read(CRL_FILE)) + cert = OpenSSL::X509::Certificate.new(File.read("#{certname}.crt")) + revoke = OpenSSL::X509::Revoked.new.tap {|rev| + rev.serial = cert.serial + rev.time = Time.now + } + crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds + crl.add_revoked(revoke) + begin + update_crl(crl, ask_password('ca')) + rescue OpenSSL::PKey::RSAError + retry + end + + %w[crt key].each {|ext| File.delete "#{certname}.#{ext}" } +end + +def gen_crl(ca_pass) + return if File.exist? CRL_FILE + + crl = OpenSSL::X509::CRL.new + crl.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a) + update_crl(crl, ca_pass) +end + +def update_crl(crl, ca_pass) + ca_key = OpenSSL::PKey::RSA.new File.read('ca.key'), ca_pass + crl.last_update = Time.now + crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds + crl.version = crl.version + 1 + crl.sign(ca_key, OpenSSL::Digest.new(DIGEST)) + File.open(CRL_FILE, 'w') {|f| f.write crl.to_pem } end def create_dir(name) return if Dir.exist? name