ovpn-key 0.7.4 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/NOTICE +1 -1
- data/README.md +9 -8
- data/bin/ovpn-key +106 -55
- data/defaults/ovpn-key.yml +13 -4
- data/lib/functions.rb +149 -27
- data/lib/version.rb +3 -1
- metadata +10 -15
- data/defaults/meta/index.txt +0 -0
- data/defaults/meta/index.txt.attr +0 -1
- data/defaults/meta/serial +0 -1
- data/defaults/openssl.ini +0 -48
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: af60a802bc319338bb17b74b6add7c6cd78d7bdc7a96021ad105a0928a59d491
         | 
| 4 | 
            +
              data.tar.gz: 3b9aef64a304187f18a963ebf703a360f5790c9705af15864372796abdb353c8
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: bf1d15c25d12102fa3ac5429e40fae565e6ebeb2bc4fe32edea884eca9e9ffbe58bb5a15ce979a7d5403ef0ce67ceca28e38d7da5ea54f0890d1938e4ef6015a
         | 
| 7 | 
            +
              data.tar.gz: 5855147ac84b0a8dc29b02b65d730a39caa46ad755ed69f92b845770d115478956045b99f40159abdb221ec2e5799abca1abd75c698a236612cfe4c10cf78421
         | 
    
        data/NOTICE
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -2,7 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            This utility is designed as [easy-rsa](https://github.com/OpenVPN/easy-rsa) replacement suitable for one exact use case.
         | 
| 4 4 |  | 
| 5 | 
            -
            It's basically a wrapper around  | 
| 5 | 
            +
            It's basically a wrapper around OpenSSL API to:
         | 
| 6 6 | 
             
            * create a self-signed CA
         | 
| 7 7 | 
             
            * create client and server certificates and pack them to ZIP files along with the OpenVPN config
         | 
| 8 8 | 
             
            * revoke the certificates
         | 
| @@ -28,14 +28,15 @@ If you're brave, [let me know](https://github.com/chillum/ovpn-key/issues), wher | |
| 28 28 | 
             
            ### Usage
         | 
| 29 29 |  | 
| 30 30 | 
             
            1. `ovpn-key --init`
         | 
| 31 | 
            -
            2. edit `ovpn-key.yml` | 
| 32 | 
            -
            3. `ovpn-key --ca --dh | 
| 33 | 
            -
            4. `ovpn-key -- | 
| 34 | 
            -
            5. `ovpn-key -- | 
| 35 | 
            -
            6. `ovpn-key -- | 
| 36 | 
            -
            7.  | 
| 31 | 
            +
            2. edit `ovpn-key.yml`
         | 
| 32 | 
            +
            3. `ovpn-key --ca --dh`
         | 
| 33 | 
            +
            4. `ovpn-key --server --nopass`
         | 
| 34 | 
            +
            5. `ovpn-key --client somebody [--nopass]`
         | 
| 35 | 
            +
            6. `ovpn-key --revoke somebody`
         | 
| 36 | 
            +
            7. `ovpn-key --static` (generates `ta.key`)
         | 
| 37 | 
            +
            8. add a file with `.ovpn` extension to the directory  
         | 
| 37 38 | 
             
               it should contain every setting except for `cert` and `key`
         | 
| 38 | 
            -
             | 
| 39 | 
            +
            9. `ovpn-key --zip somebody-else [--nopass]`
         | 
| 39 40 |  | 
| 40 41 | 
             
            ### Configuration
         | 
| 41 42 |  | 
    
        data/bin/ovpn-key
    CHANGED
    
    | @@ -1,80 +1,84 @@ | |
| 1 | 
            -
            #! /usr/bin/env ruby
         | 
| 2 | 
            -
             | 
| 1 | 
            +
            #! /usr/bin/env ruby -w
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 3 4 | 
             
            require 'fileutils'
         | 
| 5 | 
            +
            require 'io/console'
         | 
| 6 | 
            +
            require 'openssl'
         | 
| 7 | 
            +
            require 'optparse'
         | 
| 4 8 | 
             
            require 'yaml'
         | 
| 5 9 | 
             
            require 'zip'
         | 
| 6 | 
            -
            require_relative '../lib/version | 
| 7 | 
            -
            require_relative '../lib/functions | 
| 10 | 
            +
            require_relative '../lib/version'
         | 
| 11 | 
            +
            require_relative '../lib/functions'
         | 
| 8 12 |  | 
| 9 | 
            -
            SSL_CONF = 'openssl.ini'
         | 
| 10 13 | 
             
            APP_CONF = 'ovpn-key.yml'
         | 
| 14 | 
            +
            CRL_FILE = 'crl.pem'
         | 
| 15 | 
            +
            SERIAL_FILE = 'serial'
         | 
| 11 16 |  | 
| 12 17 | 
             
            options = {}
         | 
| 18 | 
            +
            # rubocop:disable Metrics/BlockLength
         | 
| 13 19 | 
             
            OptionParser.new do |opts|
         | 
| 14 | 
            -
               | 
| 15 | 
            -
              opts. | 
| 16 | 
            -
             | 
| 20 | 
            +
              # rubocop:enable Metrics/BlockLength
         | 
| 21 | 
            +
              opts.banner = "Usage: #{File.basename $PROGRAM_NAME} <options> [--nopass]"
         | 
| 22 | 
            +
              opts.on('--init [directory]', 'Init a CA directory (defaults to current)') do |v|
         | 
| 23 | 
            +
                options[:init] = v || '.'
         | 
| 17 24 | 
             
              end
         | 
| 18 | 
            -
              opts.on( | 
| 25 | 
            +
              opts.on('--ca', 'Generate a CA (ca.crt)') do |v|
         | 
| 19 26 | 
             
                check_crt('ca')
         | 
| 20 27 | 
             
                options[:generate_ca] = v
         | 
| 21 28 | 
             
              end
         | 
| 22 | 
            -
              opts.on( | 
| 29 | 
            +
              opts.on('--dh', 'Generate a DH keyfile (dh.pem)') do |v|
         | 
| 23 30 | 
             
                # it's safe to overwrite this file
         | 
| 24 31 | 
             
                options[:generate_dh] = v
         | 
| 25 32 | 
             
              end
         | 
| 26 | 
            -
              opts.on( | 
| 27 | 
            -
                options[:generate_static] = v | 
| 28 | 
            -
                check_crt( | 
| 33 | 
            +
              opts.on('--static', 'Generate OpenVPN static key (ta.key)') do |v|
         | 
| 34 | 
            +
                options[:generate_static] = v
         | 
| 35 | 
            +
                check_crt('ta')
         | 
| 29 36 | 
             
              end
         | 
| 30 | 
            -
              opts.on( | 
| 31 | 
            -
                options[:generate_server] = v  | 
| 37 | 
            +
              opts.on('--server [name]', "Generate a server key (defaults to 'server')") do |v|
         | 
| 38 | 
            +
                options[:generate_server] = v || 'server'
         | 
| 32 39 | 
             
                check_crt(options[:generate_server])
         | 
| 33 40 | 
             
              end
         | 
| 34 | 
            -
              opts.on( | 
| 41 | 
            +
              opts.on('--client [name]', 'Generate a client key and sign it') do |v|
         | 
| 35 42 | 
             
                check_client(v)
         | 
| 36 43 | 
             
                options[:generate_client] = v
         | 
| 37 44 | 
             
              end
         | 
| 38 | 
            -
              opts.on( | 
| 45 | 
            +
              opts.on('--zip    [name]', 'Ditto plus pack it to ZIP with OpenVPN config') do |v|
         | 
| 39 46 | 
             
                check_client(v)
         | 
| 40 47 | 
             
                options[:generate_zip] = v
         | 
| 41 48 | 
             
              end
         | 
| 42 | 
            -
              opts.on( | 
| 43 | 
            -
                abort  | 
| 49 | 
            +
              opts.on('--revoke [name]', "Revoke a certificate (using #{CRL_FILE}) and delete it") do |v|
         | 
| 50 | 
            +
                abort 'Please specify what certificate to revoke' unless v
         | 
| 44 51 | 
             
                options[:revoke] = v
         | 
| 45 52 | 
             
              end
         | 
| 46 | 
            -
              opts.on( | 
| 53 | 
            +
              opts.on('--nopass', "Don't protect .key files with a password") do |v|
         | 
| 47 54 | 
             
                options[:no_password] = v
         | 
| 48 55 | 
             
              end
         | 
| 49 56 | 
             
            end.parse!
         | 
| 50 | 
            -
            if ARGV.length | 
| 51 | 
            -
              abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $ | 
| 57 | 
            +
            if ARGV.length.positive?
         | 
| 58 | 
            +
              abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $PROGRAM_NAME} -h` for help"
         | 
| 52 59 | 
             
            end
         | 
| 53 60 | 
             
            unless options[:init] || options[:generate_ca] || options[:generate_dh] || options[:generate_static] \
         | 
| 54 61 | 
             
              || options[:generate_server] || options[:generate_client] || options[:generate_zip] || options[:revoke]
         | 
| 55 | 
            -
              abort "See `#{File.basename $ | 
| 62 | 
            +
              abort "See `#{File.basename $PROGRAM_NAME} -h` for usage"
         | 
| 56 63 | 
             
            end
         | 
| 57 | 
            -
            if options[:generate_client]  | 
| 64 | 
            +
            if options[:generate_client] && options[:generate_zip]
         | 
| 58 65 | 
             
              # I assume that user likely wants one of them and is confused with usage
         | 
| 59 | 
            -
              abort  | 
| 66 | 
            +
              abort 'There can be only one: --client or --zip'
         | 
| 60 67 | 
             
            end
         | 
| 61 | 
            -
            umask = File.umask  | 
| 68 | 
            +
            umask = File.umask 0o077
         | 
| 62 69 |  | 
| 63 70 | 
             
            if options[:init]
         | 
| 64 71 | 
             
              unless options[:init] == '.'
         | 
| 65 72 | 
             
                create_dir options[:init]
         | 
| 66 73 | 
             
                Dir.chdir options[:init]
         | 
| 67 74 | 
             
              end
         | 
| 68 | 
            -
               | 
| 69 | 
            -
             | 
| 70 | 
            -
                 | 
| 71 | 
            -
             | 
| 72 | 
            -
                  puts "Created file: #{file}"
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
              }
         | 
| 75 | 
            +
              unless File.exist? APP_CONF
         | 
| 76 | 
            +
                FileUtils.copy_file(File.expand_path("defaults/#{APP_CONF}", "#{__dir__}/.."), "./#{APP_CONF}")
         | 
| 77 | 
            +
                puts "Created file: #{APP_CONF}"
         | 
| 78 | 
            +
              end
         | 
| 75 79 | 
             
            elsif !File.exist? APP_CONF
         | 
| 76 80 | 
             
              begin
         | 
| 77 | 
            -
                rc = YAML.load_file(File.expand_path | 
| 81 | 
            +
                rc = YAML.load_file(File.expand_path("~/.#{APP_CONF}"))
         | 
| 78 82 | 
             
              rescue Errno::ENOENT
         | 
| 79 83 | 
             
                # no configuration file in home directory is not an error
         | 
| 80 84 | 
             
              end
         | 
| @@ -84,33 +88,82 @@ end | |
| 84 88 | 
             
            begin
         | 
| 85 89 | 
             
              settings = YAML.load_file(APP_CONF)
         | 
| 86 90 | 
             
            rescue Errno::ENOENT
         | 
| 87 | 
            -
              abort "Run `#{File.basename $ | 
| 91 | 
            +
              abort "Run `#{File.basename $PROGRAM_NAME} --init` before generating certificates"
         | 
| 88 92 | 
             
            end
         | 
| 89 93 | 
             
            ZIP_DIR  = settings['zip_dir']  || '~'
         | 
| 90 94 | 
             
            OPENVPN  = settings['openvpn']  || 'openvpn'
         | 
| 91 | 
            -
            OPENSSL  = settings['openssl']  || 'openssl'
         | 
| 92 | 
            -
            KEY_SIZE = settings['key_size'] || 2048
         | 
| 93 95 | 
             
            ENCRYPT  = settings['encrypt']  || 'aes128'
         | 
| 94 | 
            -
             | 
| 96 | 
            +
            DIGEST   = settings['digest']   || 'sha256'
         | 
| 97 | 
            +
            KEY_SIZE = settings['key_size'] || 2048
         | 
| 95 98 | 
             
            CN_CA    = settings['ca_name']  || 'Certification Authority'
         | 
| 96 | 
            -
             | 
| 99 | 
            +
             | 
| 100 | 
            +
            unless settings['ca_days'].nil?
         | 
| 101 | 
            +
              if settings['expire'].nil?
         | 
| 102 | 
            +
                puts 'Migrating pre-0.8 configuration to new format: ca_days'
         | 
| 103 | 
            +
                puts "WARNING: if you tweaked `default_days` or `default_days_crl` in #{SSL_CONF}, edit #{APP_CONF}"
         | 
| 104 | 
            +
                File.open(APP_CONF, 'a') do |f|
         | 
| 105 | 
            +
                  f.write "# ca_days is not used anymore, you can remove it\nexpire:\n"
         | 
| 106 | 
            +
                  f.write "  ca:     #{settings['ca_days']}\n  crl:    3650\n  server: 3650\n  client: 3650\n"
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
              else
         | 
| 109 | 
            +
                puts "WARNING: `ca_days` setting is deprecated, remove it from #{APP_CONF}"
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
            end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            settings['expire']           ||= {}
         | 
| 114 | 
            +
            settings['expire']['ca']     ||= settings['ca_days'] || 3650
         | 
| 115 | 
            +
            settings['expire']['crl']    ||= 3650
         | 
| 116 | 
            +
            settings['expire']['server'] ||= 3650
         | 
| 117 | 
            +
            settings['expire']['client'] ||= 3650
         | 
| 118 | 
            +
            EXPIRE = settings['expire']
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            if settings['x509'].nil? && !settings['details'].nil?
         | 
| 121 | 
            +
              puts 'Migrating pre-0.8 configuration to new format: details'
         | 
| 122 | 
            +
              REQ = OpenSSL::X509::Name.parse(settings['details']).to_a
         | 
| 123 | 
            +
              File.open(APP_CONF, 'a') do |f|
         | 
| 124 | 
            +
                f.write "# details is not used anymore, you can remove it\nx509:\n"
         | 
| 125 | 
            +
                REQ.map {|i, j| f.write "  #{i}: #{j}\n" }
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
            else
         | 
| 128 | 
            +
              REQ = settings['x509']
         | 
| 129 | 
            +
              puts "WARNING: `details` section is deprecated, remove it from #{APP_CONF}" unless settings['details'].nil?
         | 
| 130 | 
            +
            end
         | 
| 131 | 
            +
            if settings['openssl']
         | 
| 132 | 
            +
              puts "WARNING: `openssl` setting is deprecated, remove it from #{APP_CONF}"
         | 
| 133 | 
            +
            end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            if !File.exist?(SERIAL_FILE) && File.exist?('meta/serial')
         | 
| 136 | 
            +
              FileUtils.copy_file('meta/serial', SERIAL_FILE)
         | 
| 137 | 
            +
              puts 'Copied meta/serial to serial'
         | 
| 138 | 
            +
            end
         | 
| 139 | 
            +
            %w[certs meta].each {|dir|
         | 
| 140 | 
            +
              puts "WARNING: #{dir} directory is not used anymore. you can remove it" if File.exist? dir
         | 
| 141 | 
            +
            }
         | 
| 142 | 
            +
            if File.exist? 'openssl.ini'
         | 
| 143 | 
            +
              puts 'WARNING: openssl.ini file is not used anymore. you can remove it'
         | 
| 144 | 
            +
            end
         | 
| 97 145 |  | 
| 98 146 | 
             
            if options[:generate_ca]
         | 
| 99 | 
            -
               | 
| 100 | 
            -
               | 
| 101 | 
            -
               | 
| 147 | 
            +
              ca_pass = options[:no_password] ? nil : ask_password('ca')
         | 
| 148 | 
            +
              gen_key('ca', ca_pass)
         | 
| 149 | 
            +
              sign_key('ca', CN_CA, ca_pass)
         | 
| 150 | 
            +
              gen_crl(ca_pass)
         | 
| 102 151 | 
             
            end
         | 
| 103 152 | 
             
            if options[:generate_dh]
         | 
| 104 | 
            -
               | 
| 153 | 
            +
              File.open('dh.pem', 'w') do |f|
         | 
| 154 | 
            +
                print 'Generating dh.pem. This will take a while'
         | 
| 155 | 
            +
                f.write OpenSSL::PKey::DH.new(KEY_SIZE)
         | 
| 156 | 
            +
                puts '. Done'
         | 
| 157 | 
            +
              end
         | 
| 105 158 | 
             
            end
         | 
| 106 159 | 
             
            if options[:generate_static]
         | 
| 107 | 
            -
              exe "#{OPENVPN} --genkey --secret  | 
| 160 | 
            +
              exe "#{OPENVPN} --genkey --secret ta.key"
         | 
| 108 161 | 
             
            end
         | 
| 109 162 | 
             
            if options[:generate_server]
         | 
| 110 | 
            -
              gen_and_sign('server', options[:generate_server], options[:no_password])
         | 
| 163 | 
            +
              gen_and_sign('server', options[:generate_server], options[:no_password] ? nil : ask_password(options[:generate_server]))
         | 
| 111 164 | 
             
            end
         | 
| 112 165 | 
             
            if options[:generate_client]
         | 
| 113 | 
            -
              gen_and_sign('client', options[:generate_client], options[:no_password])
         | 
| 166 | 
            +
              gen_and_sign('client', options[:generate_client], options[:no_password] ? nil : ask_password(options[:generate_client]))
         | 
| 114 167 | 
             
            end
         | 
| 115 168 | 
             
            if options[:generate_zip]
         | 
| 116 169 | 
             
              ovpn_files = Dir['*.ovpn']
         | 
| @@ -118,29 +171,27 @@ if options[:generate_zip] | |
| 118 171 | 
             
              when 1
         | 
| 119 172 | 
             
                ovpn_file = ovpn_files.first
         | 
| 120 173 | 
             
              when 0
         | 
| 121 | 
            -
                abort  | 
| 174 | 
            +
                abort 'No .ovpn file in current directory, please add one'
         | 
| 122 175 | 
             
              else
         | 
| 123 | 
            -
                abort  | 
| 176 | 
            +
                abort 'More than one .ovpn files in current directory, aborting'
         | 
| 124 177 | 
             
              end
         | 
| 125 178 |  | 
| 126 | 
            -
              gen_and_sign('client', options[:generate_zip], options[:no_password])
         | 
| 179 | 
            +
              gen_and_sign('client', options[:generate_zip], options[:no_password] ? nil : ask_password(options[:generate_zip]))
         | 
| 127 180 |  | 
| 128 181 | 
             
              zip_file = File.join(File.expand_path(ZIP_DIR), "#{File.basename ovpn_file, '.ovpn'}.tblk.zip")
         | 
| 129 182 | 
             
              File.delete(zip_file) if File.exist?(zip_file)
         | 
| 130 183 | 
             
              File.umask umask
         | 
| 131 184 | 
             
              Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
         | 
| 132 185 | 
             
                zip.get_output_stream(ovpn_file) {|f|
         | 
| 133 | 
            -
                  File. | 
| 186 | 
            +
                  f.write File.read(ovpn_file)
         | 
| 134 187 | 
             
                  f.write "cert #{options[:generate_zip]}.crt\nkey #{options[:generate_zip]}.key\n"
         | 
| 135 188 | 
             
                }
         | 
| 136 | 
            -
                [ | 
| 189 | 
            +
                ['ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
         | 
| 137 190 | 
             
                  zip.add(i, i)
         | 
| 138 191 | 
             
                }
         | 
| 139 | 
            -
                 | 
| 192 | 
            +
                zip.add('ta.key', 'ta.key') if File.exist? 'ta.key'
         | 
| 140 193 | 
             
              end
         | 
| 141 194 | 
             
            end
         | 
| 142 195 | 
             
            if options[:revoke]
         | 
| 143 | 
            -
               | 
| 144 | 
            -
              gen_crl
         | 
| 145 | 
            -
              ['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
         | 
| 196 | 
            +
              revoke(options[:revoke])
         | 
| 146 197 | 
             
            end
         | 
    
        data/defaults/ovpn-key.yml
    CHANGED
    
    | @@ -1,8 +1,17 @@ | |
| 1 1 | 
             
            zip_dir:  '~'
         | 
| 2 2 | 
             
            openvpn:  openvpn
         | 
| 3 | 
            -
             | 
| 3 | 
            +
            encrypt:  AES128
         | 
| 4 | 
            +
            digest:   SHA256
         | 
| 4 5 | 
             
            key_size: 2048
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 6 | 
            +
            expire: # days
         | 
| 7 | 
            +
              ca:     3650
         | 
| 8 | 
            +
              crl:    3650
         | 
| 9 | 
            +
              server: 3650
         | 
| 10 | 
            +
              client: 3650
         | 
| 7 11 | 
             
            ca_name:  Certification Authority
         | 
| 8 | 
            -
             | 
| 12 | 
            +
            x509:
         | 
| 13 | 
            +
              C:  US
         | 
| 14 | 
            +
              ST: CA
         | 
| 15 | 
            +
              L:  San Francisco
         | 
| 16 | 
            +
              O:  Dva Debila
         | 
| 17 | 
            +
              OU: OpenVPN
         | 
    
        data/lib/functions.rb
    CHANGED
    
    | @@ -1,48 +1,170 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            def check_crt(filename)
         | 
| 4 | 
            +
              %w[key crt].each {|ext|
         | 
| 3 5 | 
             
                abort "#{filename}.#{ext} already exists, exiting" if File.exist? "#{filename}.#{ext}"
         | 
| 4 6 | 
             
              }
         | 
| 5 7 | 
             
            end
         | 
| 6 8 |  | 
| 7 | 
            -
            def check_client | 
| 8 | 
            -
              abort  | 
| 9 | 
            +
            def check_client(name)
         | 
| 10 | 
            +
              abort 'Error: client should have an alphanumeric name' unless name
         | 
| 9 11 | 
             
              check_crt(name)
         | 
| 10 12 | 
             
            end
         | 
| 11 13 |  | 
| 12 | 
            -
            def exe | 
| 13 | 
            -
              system(cmd)  | 
| 14 | 
            +
            def exe(cmd)
         | 
| 15 | 
            +
              system(cmd) || abort("error executing: #{cmd}")
         | 
| 14 16 | 
             
            end
         | 
| 15 17 |  | 
| 16 | 
            -
            def  | 
| 17 | 
            -
               | 
| 18 | 
            -
               | 
| 18 | 
            +
            def ask_password(name)
         | 
| 19 | 
            +
              password = ''
         | 
| 20 | 
            +
              loop do
         | 
| 21 | 
            +
                print "Enter password for #{name}.key: "
         | 
| 22 | 
            +
                password = $stdin.noecho(&:gets).chomp
         | 
| 23 | 
            +
                puts # trailing newline
         | 
| 24 | 
            +
                break unless password.empty?
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
              password
         | 
| 19 27 | 
             
            end
         | 
| 20 28 |  | 
| 21 | 
            -
            def  | 
| 22 | 
            -
               | 
| 23 | 
            -
                 | 
| 24 | 
            -
               | 
| 25 | 
            -
                 | 
| 29 | 
            +
            def unencrypt_ca_key
         | 
| 30 | 
            +
              begin
         | 
| 31 | 
            +
                OpenSSL::PKey::RSA.new File.read('ca.key'), ''
         | 
| 32 | 
            +
              rescue OpenSSL::PKey::RSAError
         | 
| 33 | 
            +
                # this means the file is encrypted
         | 
| 34 | 
            +
                OpenSSL::PKey::RSA.new File.read('ca.key'), ask_password('ca')
         | 
| 26 35 | 
             
              end
         | 
| 36 | 
            +
            rescue OpenSSL::PKey::RSAError
         | 
| 37 | 
            +
              retry
         | 
| 38 | 
            +
            end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            def gen_and_sign(type, certname, password)
         | 
| 41 | 
            +
              gen_key(certname, password)
         | 
| 42 | 
            +
              sign_key(type, certname, password)
         | 
| 27 43 | 
             
            end
         | 
| 28 44 |  | 
| 29 | 
            -
            def  | 
| 30 | 
            -
               | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
                exe "#{OPENSSL} req -new -key '#{certname}.key' -out '#{certname}.csr' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type}"
         | 
| 34 | 
            -
                exe "#{OPENSSL} ca -in '#{certname}.csr' -out '#{certname}.crt' -config #{SSL_CONF} -extensions ext.#{type} -batch"
         | 
| 35 | 
            -
                File.delete "#{certname}.csr"
         | 
| 45 | 
            +
            def gen_key(certname, password)
         | 
| 46 | 
            +
              key = OpenSSL::PKey::RSA.new(KEY_SIZE)
         | 
| 47 | 
            +
              File.open("#{certname}.key", 'w') do |f|
         | 
| 48 | 
            +
                f.write password ? key.to_pem(OpenSSL::Cipher.new(ENCRYPT), password) : key
         | 
| 36 49 | 
             
              end
         | 
| 37 50 | 
             
            end
         | 
| 38 51 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 52 | 
            +
            # type is one of: 'ca', 'server', 'client'
         | 
| 53 | 
            +
            def sign_key(type, cn, password)
         | 
| 54 | 
            +
              certname = type == 'ca' ? 'ca' : cn
         | 
| 55 | 
            +
              key = OpenSSL::PKey::RSA.new File.read("#{certname}.key"), password
         | 
| 56 | 
            +
              serial = new_serial
         | 
| 57 | 
            +
              cert = gen_cert(type, cn, key, serial)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              ca_key = type == 'ca' ? key : unencrypt_ca_key
         | 
| 60 | 
            +
              cert.sign ca_key, OpenSSL::Digest.new(DIGEST)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
              File.open(SERIAL_FILE, 'w') {|f| f.write serial }
         | 
| 63 | 
            +
              File.open("#{certname}.crt", 'w') {|f| f.write cert.to_pem }
         | 
| 64 | 
            +
            end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            def gen_cert(type, cn, key, serial)
         | 
| 67 | 
            +
              cert = basic_cert(type, cn)
         | 
| 68 | 
            +
              cert.public_key = key.public_key
         | 
| 69 | 
            +
              cert.serial = serial
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              customize_cert(type, cert)
         | 
| 72 | 
            +
            end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            # rubocop:disable Metrics/AbcSize
         | 
| 75 | 
            +
            def basic_cert(type, cn)
         | 
| 76 | 
            +
              # rubocop:enable Metrics/AbcSize
         | 
| 77 | 
            +
              subj = OpenSSL::X509::Name.new([['CN', cn]] + REQ.to_a)
         | 
| 78 | 
            +
              cert = OpenSSL::X509::Certificate.new
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              cert.version = 2
         | 
| 81 | 
            +
              cert.subject = subj
         | 
| 82 | 
            +
              cert.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a)
         | 
| 83 | 
            +
              cert.not_before = Time.now
         | 
| 84 | 
            +
              cert.not_after = Time.now + EXPIRE[type] * 86_400 # days to seconds
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              cert
         | 
| 87 | 
            +
            end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            # rubocop:disable Metrics/MethodLength
         | 
| 90 | 
            +
            # rubocop:disable Metrics/AbcSize
         | 
| 91 | 
            +
            def customize_cert(type, cert)
         | 
| 92 | 
            +
              # rubocop:enable Metrics/AbcSize
         | 
| 93 | 
            +
              # rubocop:enable Metrics/MethodLength
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              ef = OpenSSL::X509::ExtensionFactory.new nil, cert
         | 
| 96 | 
            +
              ef.issuer_certificate = cert
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash')
         | 
| 99 | 
            +
              cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid,issuer:always')
         | 
| 100 | 
            +
              cert.add_extension ef.create_extension('basicConstraints', type == 'ca' ? 'CA:true' : 'CA:false')
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              case type
         | 
| 103 | 
            +
              when 'ca'
         | 
| 104 | 
            +
                cert.add_extension ef.create_extension('keyUsage', 'cRLSign,keyCertSign')
         | 
| 105 | 
            +
              when 'server'
         | 
| 106 | 
            +
                cert.add_extension ef.create_extension('keyUsage', 'keyEncipherment,digitalSignature')
         | 
| 107 | 
            +
                cert.add_extension ef.create_extension('extendedKeyUsage', 'serverAuth')
         | 
| 108 | 
            +
              when 'client'
         | 
| 109 | 
            +
                cert.add_extension ef.create_extension('keyUsage', 'digitalSignature')
         | 
| 110 | 
            +
                cert.add_extension ef.create_extension('extendedKeyUsage', 'clientAuth')
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              cert
         | 
| 41 114 | 
             
            end
         | 
| 42 115 |  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 116 | 
            +
            # rubocop:disable Metrics/AbcSize
         | 
| 117 | 
            +
            # rubocop:disable Metrics/MethodLength
         | 
| 118 | 
            +
            def revoke(certname)
         | 
| 119 | 
            +
              # rubocop:enable Metrics/AbcSize
         | 
| 120 | 
            +
              # rubocop:enable Metrics/MethodLength
         | 
| 121 | 
            +
              crl = OpenSSL::X509::CRL.new(File.read(CRL_FILE))
         | 
| 122 | 
            +
              cert = OpenSSL::X509::Certificate.new(File.read("#{certname}.crt"))
         | 
| 123 | 
            +
              revoke = OpenSSL::X509::Revoked.new.tap {|rev|
         | 
| 124 | 
            +
                rev.serial = cert.serial
         | 
| 125 | 
            +
                rev.time = Time.now
         | 
| 126 | 
            +
              }
         | 
| 127 | 
            +
              crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds
         | 
| 128 | 
            +
              crl.add_revoked(revoke)
         | 
| 129 | 
            +
              begin
         | 
| 130 | 
            +
                update_crl(crl, ask_password('ca'))
         | 
| 131 | 
            +
              rescue OpenSSL::PKey::RSAError
         | 
| 132 | 
            +
                retry
         | 
| 47 133 | 
             
              end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
              %w[crt key].each {|ext| File.delete "#{certname}.#{ext}" }
         | 
| 136 | 
            +
            end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            def gen_crl(ca_pass)
         | 
| 139 | 
            +
              return if File.exist? CRL_FILE
         | 
| 140 | 
            +
             | 
| 141 | 
            +
              crl = OpenSSL::X509::CRL.new
         | 
| 142 | 
            +
              crl.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a)
         | 
| 143 | 
            +
              update_crl(crl, ca_pass)
         | 
| 144 | 
            +
            end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            # rubocop:disable Metrics/AbcSize
         | 
| 147 | 
            +
            def update_crl(crl, ca_pass)
         | 
| 148 | 
            +
              # rubocop:enable Metrics/AbcSize
         | 
| 149 | 
            +
              ca_key = OpenSSL::PKey::RSA.new File.read('ca.key'), ca_pass
         | 
| 150 | 
            +
              crl.last_update = Time.now
         | 
| 151 | 
            +
              crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds
         | 
| 152 | 
            +
              crl.version = crl.version + 1
         | 
| 153 | 
            +
              crl.sign(ca_key, OpenSSL::Digest.new(DIGEST))
         | 
| 154 | 
            +
              File.open(CRL_FILE, 'w') {|f| f.write crl.to_pem }
         | 
| 155 | 
            +
            end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            def new_serial
         | 
| 158 | 
            +
              begin
         | 
| 159 | 
            +
                File.read(SERIAL_FILE).to_i
         | 
| 160 | 
            +
              rescue Errno::ENOENT
         | 
| 161 | 
            +
                0
         | 
| 162 | 
            +
              end + 1
         | 
| 163 | 
            +
            end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            def create_dir(name)
         | 
| 166 | 
            +
              return if Dir.exist? name
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              Dir.mkdir name
         | 
| 169 | 
            +
              puts "Created directory: #{name}"
         | 
| 48 170 | 
             
            end
         | 
    
        data/lib/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ovpn-key
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.8.2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Vasily Korytov
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-03-08 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rubyzip
         | 
| @@ -16,17 +16,17 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: ' | 
| 19 | 
            +
                    version: '2.0'
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: ' | 
| 26 | 
            +
                    version: '2.0'
         | 
| 27 27 | 
             
            description: Generates and revokes certificates, also packs them to ZIP files with
         | 
| 28 28 | 
             
              OpenVPN configuration
         | 
| 29 | 
            -
            email:  | 
| 29 | 
            +
            email: v.korytov@outlook.com
         | 
| 30 30 | 
             
            executables:
         | 
| 31 31 | 
             
            - ovpn-key
         | 
| 32 32 | 
             
            extensions: []
         | 
| @@ -35,10 +35,6 @@ files: | |
| 35 35 | 
             
            - NOTICE
         | 
| 36 36 | 
             
            - README.md
         | 
| 37 37 | 
             
            - bin/ovpn-key
         | 
| 38 | 
            -
            - defaults/meta/index.txt
         | 
| 39 | 
            -
            - defaults/meta/index.txt.attr
         | 
| 40 | 
            -
            - defaults/meta/serial
         | 
| 41 | 
            -
            - defaults/openssl.ini
         | 
| 42 38 | 
             
            - defaults/ovpn-key.yml
         | 
| 43 39 | 
             
            - lib/functions.rb
         | 
| 44 40 | 
             
            - lib/version.rb
         | 
| @@ -46,7 +42,7 @@ homepage: https://github.com/chillum/ovpn-key | |
| 46 42 | 
             
            licenses:
         | 
| 47 43 | 
             
            - Apache-2.0
         | 
| 48 44 | 
             
            metadata: {}
         | 
| 49 | 
            -
            post_install_message: | 
| 45 | 
            +
            post_install_message:
         | 
| 50 46 | 
             
            rdoc_options: []
         | 
| 51 47 | 
             
            require_paths:
         | 
| 52 48 | 
             
            - lib
         | 
| @@ -54,16 +50,15 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 54 50 | 
             
              requirements:
         | 
| 55 51 | 
             
              - - ">="
         | 
| 56 52 | 
             
                - !ruby/object:Gem::Version
         | 
| 57 | 
            -
                  version: '2. | 
| 53 | 
            +
                  version: '2.4'
         | 
| 58 54 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 59 55 | 
             
              requirements:
         | 
| 60 56 | 
             
              - - ">="
         | 
| 61 57 | 
             
                - !ruby/object:Gem::Version
         | 
| 62 58 | 
             
                  version: '0'
         | 
| 63 59 | 
             
            requirements: []
         | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
            signing_key: 
         | 
| 60 | 
            +
            rubygems_version: 3.2.3
         | 
| 61 | 
            +
            signing_key:
         | 
| 67 62 | 
             
            specification_version: 4
         | 
| 68 63 | 
             
            summary: Key management utility for OpenVPN
         | 
| 69 64 | 
             
            test_files: []
         | 
    
        data/defaults/meta/index.txt
    DELETED
    
    | 
            File without changes
         | 
| @@ -1 +0,0 @@ | |
| 1 | 
            -
            unique_subject = yes
         | 
    
        data/defaults/meta/serial
    DELETED
    
    | @@ -1 +0,0 @@ | |
| 1 | 
            -
            01
         | 
    
        data/defaults/openssl.ini
    DELETED
    
    | @@ -1,48 +0,0 @@ | |
| 1 | 
            -
            [req]
         | 
| 2 | 
            -
            default_md = sha256
         | 
| 3 | 
            -
            distinguished_name = dn.ovpn
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            [dn.ovpn]
         | 
| 6 | 
            -
            CN          = Certificate name (required)
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            [ca]
         | 
| 9 | 
            -
            default_ca  = ca.ovpn
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            [ca.ovpn]
         | 
| 12 | 
            -
            default_md  = sha256
         | 
| 13 | 
            -
            private_key = ca.key
         | 
| 14 | 
            -
            certificate = ca.crt
         | 
| 15 | 
            -
            database    = meta/index.txt
         | 
| 16 | 
            -
            serial      = meta/serial
         | 
| 17 | 
            -
            crl         = crl.pem
         | 
| 18 | 
            -
            policy      = policy.ovpn
         | 
| 19 | 
            -
            # create this directory if changing this value
         | 
| 20 | 
            -
            new_certs_dir    = certs
         | 
| 21 | 
            -
            default_days     = 3650
         | 
| 22 | 
            -
            default_crl_days = 3650
         | 
| 23 | 
            -
            subjectKeyIdentifier   = hash
         | 
| 24 | 
            -
            authorityKeyIdentifier = keyid,issuer:always
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            [ext.ca]
         | 
| 27 | 
            -
            basicConstraints = CA:true
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            [ext.server]
         | 
| 30 | 
            -
            basicConstraints = CA:false
         | 
| 31 | 
            -
            nsCertType       = server
         | 
| 32 | 
            -
            extendedKeyUsage = serverAuth
         | 
| 33 | 
            -
            keyUsage         = digitalSignature, keyEncipherment
         | 
| 34 | 
            -
             | 
| 35 | 
            -
            [ext.client]
         | 
| 36 | 
            -
            basicConstraints = CA:false
         | 
| 37 | 
            -
            extendedKeyUsage = clientAuth
         | 
| 38 | 
            -
            keyUsage         = digitalSignature
         | 
| 39 | 
            -
             | 
| 40 | 
            -
            [policy.ovpn]
         | 
| 41 | 
            -
            commonName             = supplied
         | 
| 42 | 
            -
            countryName            = optional
         | 
| 43 | 
            -
            stateOrProvinceName    = optional
         | 
| 44 | 
            -
            localityName           = optional
         | 
| 45 | 
            -
            organizationName       = optional
         | 
| 46 | 
            -
            organizationalUnitName = optional
         | 
| 47 | 
            -
            name                   = optional
         | 
| 48 | 
            -
            emailAddress           = optional
         |