ovpn-key 0.7.2 → 0.8
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 +14 -9
- data/bin/ovpn-key +110 -51
- data/defaults/ovpn-key.yml +14 -3
- data/lib/functions.rb +124 -28
- data/lib/version.rb +3 -1
- metadata +12 -16
- 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 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecf480dc290e372a3b081ebc12dceb82e2f0ca22ea508cbbe622e8e1ce80b58f
|
4
|
+
data.tar.gz: 585a28ba43b652afd0add8b912a8ff0590ad99ffcb96abcb9929c8ca488c03b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47783d3b02d6948cb19fc4434dff11a980c0fc9f22675a66d2cc6fc34423be7e7b2dc6b4e46f420eaa85755ef14ca8e787dddaa30eacd707837033138239753f
|
7
|
+
data.tar.gz: 405a4389cf9b26a8a8cf6e4e615a04dc2bcf80b6fe71ed7b22c3380c3e5d215e70bab5d3ef8ba78eab20e94e19e74d1dd3c58fe47e118760cae842fbfc1e7c40
|
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
|
@@ -12,9 +12,12 @@ It supports encrypting `.key` files with a passphrase (there is an option to dis
|
|
12
12
|
|
13
13
|
It can be used with a non-self signed CA, just place your `ca.key` and `ca.crt` in the keys directory and skip the `--ca` step.
|
14
14
|
|
15
|
-
It can be used to manage a non-OpenVPN CA, in that case `--zip`
|
15
|
+
It can be used to manage a non-OpenVPN CA, in that case `--zip` and `--static` steps will be useless, but all others will work.
|
16
16
|
|
17
|
-
|
17
|
+
OpenVPN static keys are supported partially, as they should be used for `tls-auth`/`tls-crypt` only.
|
18
|
+
Please note that they are not encrypted regardless of `--nopass` option.
|
19
|
+
|
20
|
+
For now this utility should be considered experimental and rather undocumented.
|
18
21
|
If you're brave, [let me know](https://github.com/chillum/ovpn-key/issues), where the problems are.
|
19
22
|
|
20
23
|
### Installation
|
@@ -25,13 +28,15 @@ If you're brave, [let me know](https://github.com/chillum/ovpn-key/issues), wher
|
|
25
28
|
### Usage
|
26
29
|
|
27
30
|
1. `ovpn-key --init`
|
28
|
-
2. edit `ovpn-key.yml`
|
29
|
-
3. `ovpn-key --ca --dh
|
30
|
-
4. `ovpn-key --
|
31
|
-
5. `ovpn-key --
|
32
|
-
6.
|
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
|
33
38
|
it should contain every setting except for `cert` and `key`
|
34
|
-
|
39
|
+
9. `ovpn-key --zip somebody-else [--nopass]`
|
35
40
|
|
36
41
|
### Configuration
|
37
42
|
|
data/bin/ovpn-key
CHANGED
@@ -1,76 +1,82 @@
|
|
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 = {}
|
13
18
|
OptionParser.new do |opts|
|
14
|
-
opts.banner = "Usage: #{File.basename $
|
15
|
-
opts.on(
|
16
|
-
options[:init] = v
|
19
|
+
opts.banner = "Usage: #{File.basename $PROGRAM_NAME} <options> [--nopass]"
|
20
|
+
opts.on('--init [directory]', 'Init a CA directory (defaults to current)') do |v|
|
21
|
+
options[:init] = v || '.'
|
17
22
|
end
|
18
|
-
opts.on(
|
23
|
+
opts.on('--ca', 'Generate a CA (ca.crt)') do |v|
|
19
24
|
check_crt('ca')
|
20
25
|
options[:generate_ca] = v
|
21
26
|
end
|
22
|
-
opts.on(
|
27
|
+
opts.on('--dh', 'Generate a DH keyfile (dh.pem)') do |v|
|
23
28
|
# it's safe to overwrite this file
|
24
29
|
options[:generate_dh] = v
|
25
30
|
end
|
26
|
-
opts.on(
|
27
|
-
options[:
|
31
|
+
opts.on('--static', 'Generate OpenVPN static key (ta.key)') do |v|
|
32
|
+
options[:generate_static] = v
|
33
|
+
check_crt('ta')
|
34
|
+
end
|
35
|
+
opts.on('--server [name]', "Generate a server key (defaults to 'server')") do |v|
|
36
|
+
options[:generate_server] = v || 'server'
|
28
37
|
check_crt(options[:generate_server])
|
29
38
|
end
|
30
|
-
opts.on(
|
39
|
+
opts.on('--client [name]', 'Generate a client key and sign it') do |v|
|
31
40
|
check_client(v)
|
32
41
|
options[:generate_client] = v
|
33
42
|
end
|
34
|
-
opts.on(
|
43
|
+
opts.on('--zip [name]', 'Ditto plus pack it to ZIP with OpenVPN config') do |v|
|
35
44
|
check_client(v)
|
36
45
|
options[:generate_zip] = v
|
37
46
|
end
|
38
|
-
opts.on("--revoke [name]
|
39
|
-
abort
|
47
|
+
opts.on("--revoke [name]', 'Revoke a certificate (using #{CRL_FILE}) and delete it") do |v|
|
48
|
+
abort 'Please specify what certificate to revoke' unless v
|
40
49
|
options[:revoke] = v
|
41
50
|
end
|
42
|
-
opts.on(
|
51
|
+
opts.on('--nopass', "Don't protect .key files with a password") do |v|
|
43
52
|
options[:no_password] = v
|
44
53
|
end
|
45
54
|
end.parse!
|
46
|
-
if ARGV.length
|
47
|
-
abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $
|
55
|
+
if ARGV.length.positive?
|
56
|
+
abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $PROGRAM_NAME} -h` for help"
|
48
57
|
end
|
49
|
-
unless options[:init] || options[:generate_ca] || options[:generate_dh] || options[:
|
50
|
-
|| options[:generate_client] || options[:generate_zip] || options[:revoke]
|
51
|
-
abort "See `#{File.basename $
|
58
|
+
unless options[:init] || options[:generate_ca] || options[:generate_dh] || options[:generate_static] \
|
59
|
+
|| options[:generate_server] || options[:generate_client] || options[:generate_zip] || options[:revoke]
|
60
|
+
abort "See `#{File.basename $PROGRAM_NAME} -h` for usage"
|
52
61
|
end
|
53
|
-
if options[:generate_client]
|
62
|
+
if options[:generate_client] && options[:generate_zip]
|
54
63
|
# I assume that user likely wants one of them and is confused with usage
|
55
|
-
abort
|
64
|
+
abort 'There can be only one: --client or --zip'
|
56
65
|
end
|
57
|
-
umask = File.umask
|
66
|
+
umask = File.umask 0o077
|
58
67
|
|
59
68
|
if options[:init]
|
60
69
|
unless options[:init] == '.'
|
61
70
|
create_dir options[:init]
|
62
71
|
Dir.chdir options[:init]
|
63
72
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
puts "Created file: #{file}"
|
69
|
-
end
|
70
|
-
}
|
73
|
+
unless File.exist? APP_CONF
|
74
|
+
FileUtils.copy_file(File.expand_path("defaults/#{APP_CONF}", "#{__dir__}/.."), "./#{APP_CONF}")
|
75
|
+
puts "Created file: #{APP_CONF}"
|
76
|
+
end
|
71
77
|
elsif !File.exist? APP_CONF
|
72
78
|
begin
|
73
|
-
rc = YAML.load_file(File.expand_path
|
79
|
+
rc = YAML.load_file(File.expand_path("~/.#{APP_CONF}"))
|
74
80
|
rescue Errno::ENOENT
|
75
81
|
# no configuration file in home directory is not an error
|
76
82
|
end
|
@@ -80,28 +86,82 @@ end
|
|
80
86
|
begin
|
81
87
|
settings = YAML.load_file(APP_CONF)
|
82
88
|
rescue Errno::ENOENT
|
83
|
-
abort "Run `#{File.basename $
|
89
|
+
abort "Run `#{File.basename $PROGRAM_NAME} --init` before generating certificates"
|
84
90
|
end
|
85
91
|
ZIP_DIR = settings['zip_dir'] || '~'
|
86
|
-
|
87
|
-
KEY_SIZE = settings['key_size'] || 2048
|
92
|
+
OPENVPN = settings['openvpn'] || 'openvpn'
|
88
93
|
ENCRYPT = settings['encrypt'] || 'aes128'
|
94
|
+
DIGEST = settings['digest'] || 'sha256'
|
95
|
+
KEY_SIZE = settings['key_size'] || 2048
|
89
96
|
CN_CA = settings['ca_name'] || 'Certification Authority'
|
90
|
-
|
97
|
+
|
98
|
+
unless settings['ca_days'].nil?
|
99
|
+
if settings['expire'].nil?
|
100
|
+
puts 'Migrating pre-0.8 configuration to new format: ca_days'
|
101
|
+
puts "WARNING: if you tweaked `default_days` or `default_days_crl` in #{SSL_CONF}, edit #{APP_CONF}"
|
102
|
+
File.open(APP_CONF, 'a') do |f|
|
103
|
+
f.write "# ca_days is not used anymore, you can remove it\nexpire:\n"
|
104
|
+
f.write " ca: #{settings['ca_days']}\n crl: 3650\n server: 3650\n client: 3650\n"
|
105
|
+
end
|
106
|
+
else
|
107
|
+
puts "WARNING: `ca_days` setting is deprecated, remove it from #{APP_CONF}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
settings['expire'] ||= {}
|
112
|
+
settings['expire']['ca'] ||= settings['ca_days'] || 3650
|
113
|
+
settings['expire']['crl'] ||= 3650
|
114
|
+
settings['expire']['server'] ||= 3650
|
115
|
+
settings['expire']['client'] ||= 3650
|
116
|
+
EXPIRE = settings['expire']
|
117
|
+
|
118
|
+
if settings['x509'].nil? && !settings['details'].nil?
|
119
|
+
puts 'Migrating pre-0.8 configuration to new format: details'
|
120
|
+
REQ = OpenSSL::X509::Name.parse(settings['details']).to_a
|
121
|
+
File.open(APP_CONF, 'a') do |f|
|
122
|
+
f.write "# details is not used anymore, you can remove it\nx509:\n"
|
123
|
+
REQ.map {|i, j| f.write " #{i}: #{j}\n" }
|
124
|
+
end
|
125
|
+
else
|
126
|
+
REQ = settings['x509']
|
127
|
+
puts "WARNING: `details` section is deprecated, remove it from #{APP_CONF}" unless settings['details'].nil?
|
128
|
+
end
|
129
|
+
if settings['openssl']
|
130
|
+
puts "WARNING: `openssl` setting is deprecated, remove it from #{APP_CONF}"
|
131
|
+
end
|
132
|
+
|
133
|
+
if !File.exist?(SERIAL_FILE) && File.exist?('meta/serial')
|
134
|
+
FileUtils.copy_file('meta/serial', SERIAL_FILE)
|
135
|
+
puts 'Copied meta/serial to serial'
|
136
|
+
end
|
137
|
+
%w[certs meta].each {|dir|
|
138
|
+
puts "WARNING: #{dir} directory is not used anymore. you can remove it" if File.exist? dir
|
139
|
+
}
|
140
|
+
if File.exist? 'openssl.ini'
|
141
|
+
puts 'WARNING: openssl.ini file is not used anymore. you can remove it'
|
142
|
+
end
|
91
143
|
|
92
144
|
if options[:generate_ca]
|
93
|
-
|
94
|
-
|
95
|
-
|
145
|
+
ca_pass = options[:no_password] ? nil : ask_password('ca')
|
146
|
+
gen_key('ca', ca_pass)
|
147
|
+
sign_key('ca', CN_CA, ca_pass)
|
148
|
+
gen_crl(ca_pass)
|
96
149
|
end
|
97
150
|
if options[:generate_dh]
|
98
|
-
|
151
|
+
File.open('dh.pem', 'w') do |f|
|
152
|
+
print 'Generating dh.pem. This will take a while'
|
153
|
+
f.write OpenSSL::PKey::DH.new(KEY_SIZE)
|
154
|
+
puts '. Done'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
if options[:generate_static]
|
158
|
+
exe "#{OPENVPN} --genkey --secret ta.key"
|
99
159
|
end
|
100
160
|
if options[:generate_server]
|
101
|
-
gen_and_sign('server', options[:generate_server], options[:no_password])
|
161
|
+
gen_and_sign('server', options[:generate_server], options[:no_password] ? nil : ask_password(options[:generate_server]))
|
102
162
|
end
|
103
163
|
if options[:generate_client]
|
104
|
-
gen_and_sign('client', options[:generate_client], options[:no_password])
|
164
|
+
gen_and_sign('client', options[:generate_client], options[:no_password] ? nil : ask_password(options[:generate_client]))
|
105
165
|
end
|
106
166
|
if options[:generate_zip]
|
107
167
|
ovpn_files = Dir['*.ovpn']
|
@@ -109,28 +169,27 @@ if options[:generate_zip]
|
|
109
169
|
when 1
|
110
170
|
ovpn_file = ovpn_files.first
|
111
171
|
when 0
|
112
|
-
abort
|
172
|
+
abort 'No .ovpn file in current directory, please add one'
|
113
173
|
else
|
114
|
-
abort
|
174
|
+
abort 'More than one .ovpn files in current directory, aborting'
|
115
175
|
end
|
116
176
|
|
117
|
-
gen_and_sign('client', options[:generate_zip], options[:no_password])
|
177
|
+
gen_and_sign('client', options[:generate_zip], options[:no_password] ? nil : ask_password(options[:generate_zip]))
|
118
178
|
|
119
179
|
zip_file = File.join(File.expand_path(ZIP_DIR), "#{File.basename ovpn_file, '.ovpn'}.tblk.zip")
|
120
180
|
File.delete(zip_file) if File.exist?(zip_file)
|
121
181
|
File.umask umask
|
122
182
|
Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
|
123
183
|
zip.get_output_stream(ovpn_file) {|f|
|
124
|
-
File.
|
184
|
+
f.write File.read(ovpn_file)
|
125
185
|
f.write "cert #{options[:generate_zip]}.crt\nkey #{options[:generate_zip]}.key\n"
|
126
186
|
}
|
127
|
-
[
|
187
|
+
['ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
|
128
188
|
zip.add(i, i)
|
129
189
|
}
|
190
|
+
zip.add('ta.key', 'ta.key') if File.exist? 'ta.key'
|
130
191
|
end
|
131
192
|
end
|
132
193
|
if options[:revoke]
|
133
|
-
|
134
|
-
gen_crl
|
135
|
-
['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
|
194
|
+
revoke(options[:revoke])
|
136
195
|
end
|
data/defaults/ovpn-key.yml
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
zip_dir: '~'
|
2
|
-
|
2
|
+
openvpn: openvpn
|
3
|
+
encrypt: AES128
|
4
|
+
digest: SHA256
|
3
5
|
key_size: 2048
|
4
|
-
|
6
|
+
expire: # days
|
7
|
+
ca: 3650
|
8
|
+
crl: 3650
|
9
|
+
server: 3650
|
10
|
+
client: 3650
|
5
11
|
ca_name: Certification Authority
|
6
|
-
|
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,144 @@
|
|
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
|
-
else
|
25
|
-
exe "#{OPENSSL} genrsa -#{ENCRYPT} -out '#{certname}.key' #{KEY_SIZE} -config #{SSL_CONF} -extensions ext.#{type}"
|
26
|
-
end
|
29
|
+
def gen_and_sign(type, certname, password)
|
30
|
+
gen_key(certname, password)
|
31
|
+
sign_key(type, certname, password)
|
27
32
|
end
|
28
33
|
|
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"
|
34
|
+
def gen_key(certname, password)
|
35
|
+
key = OpenSSL::PKey::RSA.new(KEY_SIZE)
|
36
|
+
File.open("#{certname}.key", 'w') do |f|
|
37
|
+
f.write password ? key.to_pem(OpenSSL::Cipher.new(ENCRYPT), password) : key
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
+
# type is one of: 'ca', 'server', 'client'
|
42
|
+
# rubocop:disable Naming/MethodParameterName
|
43
|
+
def sign_key(type, cn, password)
|
44
|
+
# rubocop:enable Naming/MethodParameterName
|
45
|
+
certname = type == 'ca' ? 'ca' : cn
|
46
|
+
key = OpenSSL::PKey::RSA.new File.read("#{certname}.key"), password
|
47
|
+
subj = OpenSSL::X509::Name.new([['CN', cn]] + REQ.to_a)
|
48
|
+
serial = begin
|
49
|
+
File.read(SERIAL_FILE).to_i
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
0
|
52
|
+
end + 1
|
53
|
+
|
54
|
+
cert = OpenSSL::X509::Certificate.new
|
55
|
+
cert.version = 2
|
56
|
+
cert.serial = serial
|
57
|
+
cert.not_before = Time.now
|
58
|
+
cert.not_after =
|
59
|
+
Time.now +
|
60
|
+
case type
|
61
|
+
when 'ca'
|
62
|
+
EXPIRE['ca']
|
63
|
+
when 'server'
|
64
|
+
EXPIRE['server']
|
65
|
+
when 'client'
|
66
|
+
EXPIRE['client']
|
67
|
+
# days to seconds
|
68
|
+
end * 86_400
|
69
|
+
cert.public_key = key.public_key
|
70
|
+
cert.subject = subj
|
71
|
+
cert.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a)
|
72
|
+
|
73
|
+
ef = OpenSSL::X509::ExtensionFactory.new nil, cert
|
74
|
+
ef.issuer_certificate = cert
|
75
|
+
|
76
|
+
cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash')
|
77
|
+
cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid,issuer:always')
|
78
|
+
cert.add_extension ef.create_extension('basicConstraints', type == 'ca' ? 'CA:true' : 'CA:false')
|
79
|
+
|
80
|
+
case type
|
81
|
+
when 'ca'
|
82
|
+
cert.add_extension ef.create_extension('keyUsage', 'cRLSign,keyCertSign')
|
83
|
+
cert.sign key, OpenSSL::Digest.new(DIGEST)
|
84
|
+
when 'server'
|
85
|
+
cert.add_extension ef.create_extension('keyUsage', 'keyEncipherment,digitalSignature')
|
86
|
+
cert.add_extension ef.create_extension('extendedKeyUsage', 'serverAuth')
|
87
|
+
when 'client'
|
88
|
+
cert.add_extension ef.create_extension('keyUsage', 'digitalSignature')
|
89
|
+
cert.add_extension ef.create_extension('extendedKeyUsage', 'clientAuth')
|
90
|
+
end
|
91
|
+
unless type == 'ca'
|
92
|
+
ca_key = begin
|
93
|
+
OpenSSL::PKey::RSA.new File.read('ca.key'), ask_password('ca')
|
94
|
+
rescue OpenSSL::PKey::RSAError
|
95
|
+
retry
|
96
|
+
end
|
97
|
+
cert.sign ca_key, OpenSSL::Digest.new(DIGEST)
|
98
|
+
end
|
99
|
+
|
100
|
+
File.open(SERIAL_FILE, 'w') {|f| f.write serial }
|
101
|
+
File.open("#{certname}.crt", 'w') {|f| f.write cert.to_pem }
|
41
102
|
end
|
42
103
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
104
|
+
def revoke(certname)
|
105
|
+
crl = OpenSSL::X509::CRL.new(File.read(CRL_FILE))
|
106
|
+
cert = OpenSSL::X509::Certificate.new(File.read("#{certname}.crt"))
|
107
|
+
revoke = OpenSSL::X509::Revoked.new.tap {|rev|
|
108
|
+
rev.serial = cert.serial
|
109
|
+
rev.time = Time.now
|
110
|
+
}
|
111
|
+
crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds
|
112
|
+
crl.add_revoked(revoke)
|
113
|
+
begin
|
114
|
+
update_crl(crl, ask_password('ca'))
|
115
|
+
rescue OpenSSL::PKey::RSAError
|
116
|
+
retry
|
47
117
|
end
|
118
|
+
|
119
|
+
%w[crt key].each {|ext| File.delete "#{certname}.#{ext}" }
|
120
|
+
end
|
121
|
+
|
122
|
+
def gen_crl(ca_pass)
|
123
|
+
return if File.exist? CRL_FILE
|
124
|
+
|
125
|
+
crl = OpenSSL::X509::CRL.new
|
126
|
+
crl.issuer = OpenSSL::X509::Name.new([['CN', CN_CA]] + REQ.to_a)
|
127
|
+
update_crl(crl, ca_pass)
|
128
|
+
end
|
129
|
+
|
130
|
+
def update_crl(crl, ca_pass)
|
131
|
+
ca_key = OpenSSL::PKey::RSA.new File.read('ca.key'), ca_pass
|
132
|
+
crl.last_update = Time.now
|
133
|
+
crl.next_update = Time.now + EXPIRE['crl'] * 86_400 # days to seconds
|
134
|
+
crl.version = crl.version + 1
|
135
|
+
crl.sign(ca_key, OpenSSL::Digest.new(DIGEST))
|
136
|
+
File.open(CRL_FILE, 'w') {|f| f.write crl.to_pem }
|
137
|
+
end
|
138
|
+
|
139
|
+
def create_dir(name)
|
140
|
+
return if Dir.exist? name
|
141
|
+
|
142
|
+
Dir.mkdir name
|
143
|
+
puts "Created directory: #{name}"
|
48
144
|
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'
|
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,16 +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: '
|
27
|
-
description:
|
28
|
-
|
26
|
+
version: '2.0'
|
27
|
+
description: Generates and revokes certificates, also packs them to ZIP files with
|
28
|
+
OpenVPN configuration
|
29
|
+
email: v.korytov@outlook.com
|
29
30
|
executables:
|
30
31
|
- ovpn-key
|
31
32
|
extensions: []
|
@@ -34,10 +35,6 @@ files:
|
|
34
35
|
- NOTICE
|
35
36
|
- README.md
|
36
37
|
- bin/ovpn-key
|
37
|
-
- defaults/meta/index.txt
|
38
|
-
- defaults/meta/index.txt.attr
|
39
|
-
- defaults/meta/serial
|
40
|
-
- defaults/openssl.ini
|
41
38
|
- defaults/ovpn-key.yml
|
42
39
|
- lib/functions.rb
|
43
40
|
- lib/version.rb
|
@@ -45,7 +42,7 @@ homepage: https://github.com/chillum/ovpn-key
|
|
45
42
|
licenses:
|
46
43
|
- Apache-2.0
|
47
44
|
metadata: {}
|
48
|
-
post_install_message:
|
45
|
+
post_install_message:
|
49
46
|
rdoc_options: []
|
50
47
|
require_paths:
|
51
48
|
- lib
|
@@ -53,16 +50,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
50
|
requirements:
|
54
51
|
- - ">="
|
55
52
|
- !ruby/object:Gem::Version
|
56
|
-
version: '2.
|
53
|
+
version: '2.4'
|
57
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
55
|
requirements:
|
59
56
|
- - ">="
|
60
57
|
- !ruby/object:Gem::Version
|
61
58
|
version: '0'
|
62
59
|
requirements: []
|
63
|
-
|
64
|
-
|
65
|
-
signing_key:
|
60
|
+
rubygems_version: 3.2.3
|
61
|
+
signing_key:
|
66
62
|
specification_version: 4
|
67
63
|
summary: Key management utility for OpenVPN
|
68
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,49 +0,0 @@
|
|
1
|
-
[req]
|
2
|
-
default_md = sha256
|
3
|
-
distinguished_name = dn.ovpn
|
4
|
-
days = 3650
|
5
|
-
|
6
|
-
[dn.ovpn]
|
7
|
-
CN = Certificate name (required)
|
8
|
-
|
9
|
-
[ca]
|
10
|
-
default_ca = ca.ovpn
|
11
|
-
|
12
|
-
[ca.ovpn]
|
13
|
-
default_md = sha256
|
14
|
-
private_key = ca.key
|
15
|
-
certificate = ca.crt
|
16
|
-
database = meta/index.txt
|
17
|
-
serial = meta/serial
|
18
|
-
crl = crl.pem
|
19
|
-
policy = policy.ovpn
|
20
|
-
# create this directory if changing this value
|
21
|
-
new_certs_dir = certs
|
22
|
-
default_days = 3650
|
23
|
-
default_crl_days = 3650
|
24
|
-
subjectKeyIdentifier = hash
|
25
|
-
authorityKeyIdentifier = keyid,issuer:always
|
26
|
-
|
27
|
-
[ext.ca]
|
28
|
-
basicConstraints = CA:true
|
29
|
-
|
30
|
-
[ext.server]
|
31
|
-
basicConstraints = CA:false
|
32
|
-
nsCertType = server
|
33
|
-
extendedKeyUsage = serverAuth
|
34
|
-
keyUsage = digitalSignature, keyEncipherment
|
35
|
-
|
36
|
-
[ext.client]
|
37
|
-
basicConstraints = CA:false
|
38
|
-
extendedKeyUsage = clientAuth
|
39
|
-
keyUsage = digitalSignature
|
40
|
-
|
41
|
-
[policy.ovpn]
|
42
|
-
commonName = supplied
|
43
|
-
countryName = optional
|
44
|
-
stateOrProvinceName = optional
|
45
|
-
localityName = optional
|
46
|
-
organizationName = optional
|
47
|
-
organizationalUnitName = optional
|
48
|
-
name = optional
|
49
|
-
emailAddress = optional
|