ovpn-key 0.7.4 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 315b12aaa3f38b10e0c6d190f56eb6715efeb8b702c6427eb10ee51c6fd56246
4
- data.tar.gz: 14586944d2f1b9b388198e6e05c8bc2347fe706c7aa065539e61bbc0588c3bd5
3
+ metadata.gz: af60a802bc319338bb17b74b6add7c6cd78d7bdc7a96021ad105a0928a59d491
4
+ data.tar.gz: 3b9aef64a304187f18a963ebf703a360f5790c9705af15864372796abdb353c8
5
5
  SHA512:
6
- metadata.gz: bde20521e7bcce945590fb9d04cba55e71b64ffab36c950341dd94f0b36cbd366d5e8437fa7fb2716ea4f1724331a829be36336c558d7b5dc5d076d9463278fb
7
- data.tar.gz: 15197a504f944248d85e865e0262c1c4283c2c56a9d683f22a9411e2185570c4f05b260278b43323d6b6676851619ec619040be21b7777d079b6801923959c3d
6
+ metadata.gz: bf1d15c25d12102fa3ac5429e40fae565e6ebeb2bc4fe32edea884eca9e9ffbe58bb5a15ce979a7d5403ef0ce67ceca28e38d7da5ea54f0890d1938e4ef6015a
7
+ data.tar.gz: 5855147ac84b0a8dc29b02b65d730a39caa46ad755ed69f92b845770d115478956045b99f40159abdb221ec2e5799abca1abd75c698a236612cfe4c10cf78421
data/NOTICE CHANGED
@@ -1,6 +1,6 @@
1
1
  ovpn-key: https://github.com/chillum/ovpn-key
2
2
 
3
- Copyright 2018 Vasily Korytov
3
+ Copyright 2018-2021 Vasily Korytov
4
4
 
5
5
  Licensed under the Apache License, Version 2.0 (the "License");
6
6
  you may not use this software except in compliance with the License.
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 `openssl` to:
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` and `openssl.ini`
32
- 3. `ovpn-key --ca --dh --server --nopass`
33
- 4. `ovpn-key --client somebody [--nopass]`
34
- 5. `ovpn-key --revoke somebody`
35
- 6. `ovpn-key --static` (generates `ta.key`)
36
- 7. add a file with `.ovpn` extension to the directory
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
- 8. `ovpn-key --zip somebody-else [--nopass]`
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
- require 'optparse'
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.rb'
7
- require_relative '../lib/functions.rb'
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
- opts.banner = "Usage: #{File.basename $0} <options> [--nopass]"
15
- opts.on("--init [directory]", "Init a CA directory (defaults to current)") do |v|
16
- options[:init] = v ? v : "."
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("--ca", "Generate a CA (ca.crt)") do |v|
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("--dh", "Generate a DH keyfile (dh.pem)") do |v|
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("--static [name]", "Generate OpenVPN static key (defaults to 'ta')") do |v|
27
- options[:generate_static] = v ? v : "ta"
28
- check_crt(options[:generate_static])
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("--server [name]", "Generate a server key (defaults to 'server')") do |v|
31
- options[:generate_server] = v ? v : "server"
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("--client [name]", "Generate a client key and sign it") do |v|
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("--zip [name]", "Ditto plus pack it to ZIP with OpenVPN config") do |v|
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("--revoke [name]", "Revoke a certificate (using crl.pem) and delete it") do |v|
43
- abort "Please specify what certificate to revoke" unless v
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("--nopass", "Don't protect .key files with a password") do |v|
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 > 0
51
- abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $0} -h` for help"
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 $0} -h` for usage"
62
+ abort "See `#{File.basename $PROGRAM_NAME} -h` for usage"
56
63
  end
57
- if options[:generate_client] and options[:generate_zip]
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 "There can be only one: --client or --zip"
66
+ abort 'There can be only one: --client or --zip'
60
67
  end
61
- umask = File.umask 0077
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
- ['certs', 'meta'].each {|dir| create_dir dir}
69
- ['meta/index.txt', 'meta/index.txt.attr', 'meta/serial', SSL_CONF, APP_CONF].each {|file|
70
- unless File.exist? file
71
- FileUtils.copy_file(File.expand_path("defaults/#{file}", "#{__dir__}/.."), "./#{file}")
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 "~/.#{APP_CONF}")
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 $0} --init` before generating certificates"
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
- CA_DAYS = settings['ca_days'] || 3650
96
+ DIGEST = settings['digest'] || 'sha256'
97
+ KEY_SIZE = settings['key_size'] || 2048
95
98
  CN_CA = settings['ca_name'] || 'Certification Authority'
96
- REQ = settings['details']
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
- gen_key('ca', options[:no_password])
100
- sign_key('ca', 'ca', CN_CA)
101
- gen_crl
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
- exe "#{OPENSSL} dhparam -out dh.pem #{KEY_SIZE}"
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 '#{options[:generate_static]}.key'"
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 "No .ovpn file in current directory, please add one"
174
+ abort 'No .ovpn file in current directory, please add one'
122
175
  else
123
- abort "More than one .ovpn files in current directory, aborting"
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.open(ovpn_file).each {|line| f.write line}
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
- [ 'ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
189
+ ['ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
137
190
  zip.add(i, i)
138
191
  }
139
- # TODO: include TLS key if specified in config with tls-auth/tls-crypt
192
+ zip.add('ta.key', 'ta.key') if File.exist? 'ta.key'
140
193
  end
141
194
  end
142
195
  if options[:revoke]
143
- exe "#{OPENSSL} ca -revoke '#{options[:revoke]}.crt' -config #{SSL_CONF}"
144
- gen_crl
145
- ['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
196
+ revoke(options[:revoke])
146
197
  end
@@ -1,8 +1,17 @@
1
1
  zip_dir: '~'
2
2
  openvpn: openvpn
3
- openssl: openssl
3
+ encrypt: AES128
4
+ digest: SHA256
4
5
  key_size: 2048
5
- encrypt: aes128
6
- ca_days: 3650
6
+ expire: # days
7
+ ca: 3650
8
+ crl: 3650
9
+ server: 3650
10
+ client: 3650
7
11
  ca_name: Certification Authority
8
- details: /C=US/ST=CA/L=San Francisco/O=Dva Debila/OU=OpenVPN
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
- def check_crt filename
2
- ['key', 'crt'].each {|ext|
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 name
8
- abort "Error: client should have an alphanumeric name" unless name
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 cmd
13
- system(cmd) or abort "error executing: #{cmd}"
14
+ def exe(cmd)
15
+ system(cmd) || abort("error executing: #{cmd}")
14
16
  end
15
17
 
16
- def gen_and_sign type, certname, no_password
17
- gen_key(certname, no_password)
18
- sign_key(type, certname, certname)
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 gen_key certname, no_password
22
- if no_password
23
- exe "#{OPENSSL} genrsa -out '#{certname}.key' #{KEY_SIZE}"
24
- else
25
- exe "#{OPENSSL} genrsa -#{ENCRYPT} -out '#{certname}.key' #{KEY_SIZE}"
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 sign_key type, certname, cn
30
- if certname == 'ca'
31
- exe "#{OPENSSL} req -new -x509 -key '#{certname}.key' -out '#{certname}.crt' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type} -days #{CA_DAYS}"
32
- else
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
- def gen_crl
40
- exe "#{OPENSSL} ca -gencrl -out crl.pem -config #{SSL_CONF}"
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
- def create_dir name
44
- unless Dir.exist? name
45
- Dir.mkdir name
46
- puts "Created directory: #{name}"
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
@@ -1 +1,3 @@
1
- ::Version = '0.7.4'
1
+ # frozen_string_literal: true
2
+
3
+ ::VERSION = '0.8.2'
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.7.4
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: 2018-12-18 00:00:00.000000000 Z
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: '1.2'
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: '1.2'
26
+ version: '2.0'
27
27
  description: Generates and revokes certificates, also packs them to ZIP files with
28
28
  OpenVPN configuration
29
- email: vasily.korytov@icloud.com
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.0'
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
- rubyforge_project:
65
- rubygems_version: 2.7.7
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: []
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