ovpn-key 0.7.3 → 0.8.1
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 +104 -54
- data/defaults/ovpn-key.yml +13 -3
- data/lib/functions.rb +124 -28
- 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 -49
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 314eca1d302d14375dbba738bbd66ffcc29bd00d5b77e45705a415e9aadbd5aa
|
|
4
|
+
data.tar.gz: '042730556841b7e05cf523bf57d9a57b7b987d12e15e1308b110fafbf5ab3dac'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82736489c4feba97e504de91c939c3cdb074a988effa3972f07a7e2e674cbdac81af8a1ad7ae89f17c2fa35ed8afa69dbfaaf7bb504baac4ea971ed04aabd824
|
|
7
|
+
data.tar.gz: 7f8d4f05ec4d4a64c4231e4d575a69c7d6bc03180b6538ed2542486792692ff24145ab058644b6bdce50570ed627a8177b38a8837743c7ac4faa031cfbd89099
|
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.
|
|
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
|
|
36
38
|
it should contain every setting except for `cert` and `key`
|
|
37
|
-
|
|
38
|
-
8. `ovpn-key --static` (generates `ta.key`)
|
|
39
|
+
9. `ovpn-key --zip somebody-else [--nopass]`
|
|
39
40
|
|
|
40
41
|
### Configuration
|
|
41
42
|
|
data/bin/ovpn-key
CHANGED
|
@@ -1,80 +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[:generate_static] = v
|
|
28
|
-
check_crt(
|
|
31
|
+
opts.on('--static', 'Generate OpenVPN static key (ta.key)') do |v|
|
|
32
|
+
options[:generate_static] = v
|
|
33
|
+
check_crt('ta')
|
|
29
34
|
end
|
|
30
|
-
opts.on(
|
|
31
|
-
options[:generate_server] = v
|
|
35
|
+
opts.on('--server [name]', "Generate a server key (defaults to 'server')") do |v|
|
|
36
|
+
options[:generate_server] = v || 'server'
|
|
32
37
|
check_crt(options[:generate_server])
|
|
33
38
|
end
|
|
34
|
-
opts.on(
|
|
39
|
+
opts.on('--client [name]', 'Generate a client key and sign it') do |v|
|
|
35
40
|
check_client(v)
|
|
36
41
|
options[:generate_client] = v
|
|
37
42
|
end
|
|
38
|
-
opts.on(
|
|
43
|
+
opts.on('--zip [name]', 'Ditto plus pack it to ZIP with OpenVPN config') do |v|
|
|
39
44
|
check_client(v)
|
|
40
45
|
options[:generate_zip] = v
|
|
41
46
|
end
|
|
42
|
-
opts.on(
|
|
43
|
-
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
|
|
44
49
|
options[:revoke] = v
|
|
45
50
|
end
|
|
46
|
-
opts.on(
|
|
51
|
+
opts.on('--nopass', "Don't protect .key files with a password") do |v|
|
|
47
52
|
options[:no_password] = v
|
|
48
53
|
end
|
|
49
54
|
end.parse!
|
|
50
|
-
if ARGV.length
|
|
51
|
-
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"
|
|
52
57
|
end
|
|
53
58
|
unless options[:init] || options[:generate_ca] || options[:generate_dh] || options[:generate_static] \
|
|
54
59
|
|| options[:generate_server] || options[:generate_client] || options[:generate_zip] || options[:revoke]
|
|
55
|
-
abort "See `#{File.basename $
|
|
60
|
+
abort "See `#{File.basename $PROGRAM_NAME} -h` for usage"
|
|
56
61
|
end
|
|
57
|
-
if options[:generate_client]
|
|
62
|
+
if options[:generate_client] && options[:generate_zip]
|
|
58
63
|
# I assume that user likely wants one of them and is confused with usage
|
|
59
|
-
abort
|
|
64
|
+
abort 'There can be only one: --client or --zip'
|
|
60
65
|
end
|
|
61
|
-
umask = File.umask
|
|
66
|
+
umask = File.umask 0o077
|
|
62
67
|
|
|
63
68
|
if options[:init]
|
|
64
69
|
unless options[:init] == '.'
|
|
65
70
|
create_dir options[:init]
|
|
66
71
|
Dir.chdir options[:init]
|
|
67
72
|
end
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
puts "Created file: #{file}"
|
|
73
|
-
end
|
|
74
|
-
}
|
|
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
|
|
75
77
|
elsif !File.exist? APP_CONF
|
|
76
78
|
begin
|
|
77
|
-
rc = YAML.load_file(File.expand_path
|
|
79
|
+
rc = YAML.load_file(File.expand_path("~/.#{APP_CONF}"))
|
|
78
80
|
rescue Errno::ENOENT
|
|
79
81
|
# no configuration file in home directory is not an error
|
|
80
82
|
end
|
|
@@ -84,32 +86,82 @@ end
|
|
|
84
86
|
begin
|
|
85
87
|
settings = YAML.load_file(APP_CONF)
|
|
86
88
|
rescue Errno::ENOENT
|
|
87
|
-
abort "Run `#{File.basename $
|
|
89
|
+
abort "Run `#{File.basename $PROGRAM_NAME} --init` before generating certificates"
|
|
88
90
|
end
|
|
89
91
|
ZIP_DIR = settings['zip_dir'] || '~'
|
|
90
92
|
OPENVPN = settings['openvpn'] || 'openvpn'
|
|
91
|
-
OPENSSL = settings['openssl'] || 'openssl'
|
|
92
|
-
KEY_SIZE = settings['key_size'] || 2048
|
|
93
93
|
ENCRYPT = settings['encrypt'] || 'aes128'
|
|
94
|
+
DIGEST = settings['digest'] || 'sha256'
|
|
95
|
+
KEY_SIZE = settings['key_size'] || 2048
|
|
94
96
|
CN_CA = settings['ca_name'] || 'Certification Authority'
|
|
95
|
-
|
|
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
|
|
96
143
|
|
|
97
144
|
if options[:generate_ca]
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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)
|
|
101
149
|
end
|
|
102
150
|
if options[:generate_dh]
|
|
103
|
-
|
|
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
|
|
104
156
|
end
|
|
105
157
|
if options[:generate_static]
|
|
106
|
-
exe "#{OPENVPN} --genkey --secret
|
|
158
|
+
exe "#{OPENVPN} --genkey --secret ta.key"
|
|
107
159
|
end
|
|
108
160
|
if options[:generate_server]
|
|
109
|
-
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]))
|
|
110
162
|
end
|
|
111
163
|
if options[:generate_client]
|
|
112
|
-
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]))
|
|
113
165
|
end
|
|
114
166
|
if options[:generate_zip]
|
|
115
167
|
ovpn_files = Dir['*.ovpn']
|
|
@@ -117,29 +169,27 @@ if options[:generate_zip]
|
|
|
117
169
|
when 1
|
|
118
170
|
ovpn_file = ovpn_files.first
|
|
119
171
|
when 0
|
|
120
|
-
abort
|
|
172
|
+
abort 'No .ovpn file in current directory, please add one'
|
|
121
173
|
else
|
|
122
|
-
abort
|
|
174
|
+
abort 'More than one .ovpn files in current directory, aborting'
|
|
123
175
|
end
|
|
124
176
|
|
|
125
|
-
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]))
|
|
126
178
|
|
|
127
179
|
zip_file = File.join(File.expand_path(ZIP_DIR), "#{File.basename ovpn_file, '.ovpn'}.tblk.zip")
|
|
128
180
|
File.delete(zip_file) if File.exist?(zip_file)
|
|
129
181
|
File.umask umask
|
|
130
182
|
Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
|
|
131
183
|
zip.get_output_stream(ovpn_file) {|f|
|
|
132
|
-
File.
|
|
184
|
+
f.write File.read(ovpn_file)
|
|
133
185
|
f.write "cert #{options[:generate_zip]}.crt\nkey #{options[:generate_zip]}.key\n"
|
|
134
186
|
}
|
|
135
|
-
[
|
|
187
|
+
['ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
|
|
136
188
|
zip.add(i, i)
|
|
137
189
|
}
|
|
138
|
-
|
|
190
|
+
zip.add('ta.key', 'ta.key') if File.exist? 'ta.key'
|
|
139
191
|
end
|
|
140
192
|
end
|
|
141
193
|
if options[:revoke]
|
|
142
|
-
|
|
143
|
-
gen_crl
|
|
144
|
-
['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
|
|
194
|
+
revoke(options[:revoke])
|
|
145
195
|
end
|
data/defaults/ovpn-key.yml
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
zip_dir: '~'
|
|
2
2
|
openvpn: openvpn
|
|
3
|
-
|
|
3
|
+
encrypt: AES128
|
|
4
|
+
digest: SHA256
|
|
4
5
|
key_size: 2048
|
|
5
|
-
|
|
6
|
+
expire: # days
|
|
7
|
+
ca: 3650
|
|
8
|
+
crl: 3650
|
|
9
|
+
server: 3650
|
|
10
|
+
client: 3650
|
|
6
11
|
ca_name: Certification Authority
|
|
7
|
-
|
|
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}"
|
|
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.1
|
|
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,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
|