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 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