ovpn-key 0.7.7 → 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 +2 -2
- data/bin/ovpn-key +72 -25
- data/defaults/ovpn-key.yml +13 -4
- data/lib/functions.rb +112 -18
- data/lib/version.rb +1 -1
- metadata +1 -5
- data/defaults/meta/index.txt +0 -0
- data/defaults/meta/index.txt.attr +0 -1
- data/defaults/meta/serial +0 -1
- data/defaults/openssl.ini +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 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
|
@@ -28,7 +28,7 @@ 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`
|
31
|
+
2. edit `ovpn-key.yml`
|
32
32
|
3. `ovpn-key --ca --dh`
|
33
33
|
4. `ovpn-key --server --nopass`
|
34
34
|
5. `ovpn-key --client somebody [--nopass]`
|
data/bin/ovpn-key
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
1
|
+
#! /usr/bin/env ruby -w
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'optparse'
|
5
4
|
require 'fileutils'
|
5
|
+
require 'io/console'
|
6
|
+
require 'openssl'
|
7
|
+
require 'optparse'
|
6
8
|
require 'yaml'
|
7
9
|
require 'zip'
|
8
10
|
require_relative '../lib/version'
|
9
11
|
require_relative '../lib/functions'
|
10
12
|
|
11
|
-
SSL_CONF = 'openssl.ini'
|
12
13
|
APP_CONF = 'ovpn-key.yml'
|
14
|
+
CRL_FILE = 'crl.pem'
|
15
|
+
SERIAL_FILE = 'serial'
|
13
16
|
|
14
17
|
options = {}
|
15
18
|
OptionParser.new do |opts|
|
@@ -41,7 +44,7 @@ OptionParser.new do |opts|
|
|
41
44
|
check_client(v)
|
42
45
|
options[:generate_zip] = v
|
43
46
|
end
|
44
|
-
opts.on(
|
47
|
+
opts.on("--revoke [name]', 'Revoke a certificate (using #{CRL_FILE}) and delete it") do |v|
|
45
48
|
abort 'Please specify what certificate to revoke' unless v
|
46
49
|
options[:revoke] = v
|
47
50
|
end
|
@@ -67,13 +70,10 @@ if options[:init]
|
|
67
70
|
create_dir options[:init]
|
68
71
|
Dir.chdir options[:init]
|
69
72
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
puts "Created file: #{file}"
|
75
|
-
end
|
76
|
-
}
|
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
|
77
77
|
elsif !File.exist? APP_CONF
|
78
78
|
begin
|
79
79
|
rc = YAML.load_file(File.expand_path("~/.#{APP_CONF}"))
|
@@ -90,29 +90,78 @@ rescue Errno::ENOENT
|
|
90
90
|
end
|
91
91
|
ZIP_DIR = settings['zip_dir'] || '~'
|
92
92
|
OPENVPN = settings['openvpn'] || 'openvpn'
|
93
|
-
OPENSSL = settings['openssl'] || 'openssl'
|
94
93
|
ENCRYPT = settings['encrypt'] || 'aes128'
|
94
|
+
DIGEST = settings['digest'] || 'sha256'
|
95
95
|
KEY_SIZE = settings['key_size'] || 2048
|
96
|
-
CA_DAYS = settings['ca_days'] || 3650
|
97
96
|
CN_CA = settings['ca_name'] || 'Certification Authority'
|
98
|
-
|
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
|
99
143
|
|
100
144
|
if options[:generate_ca]
|
101
|
-
|
102
|
-
|
103
|
-
|
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)
|
104
149
|
end
|
105
150
|
if options[:generate_dh]
|
106
|
-
|
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
|
107
156
|
end
|
108
157
|
if options[:generate_static]
|
109
158
|
exe "#{OPENVPN} --genkey --secret ta.key"
|
110
159
|
end
|
111
160
|
if options[:generate_server]
|
112
|
-
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]))
|
113
162
|
end
|
114
163
|
if options[:generate_client]
|
115
|
-
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]))
|
116
165
|
end
|
117
166
|
if options[:generate_zip]
|
118
167
|
ovpn_files = Dir['*.ovpn']
|
@@ -125,14 +174,14 @@ if options[:generate_zip]
|
|
125
174
|
abort 'More than one .ovpn files in current directory, aborting'
|
126
175
|
end
|
127
176
|
|
128
|
-
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]))
|
129
178
|
|
130
179
|
zip_file = File.join(File.expand_path(ZIP_DIR), "#{File.basename ovpn_file, '.ovpn'}.tblk.zip")
|
131
180
|
File.delete(zip_file) if File.exist?(zip_file)
|
132
181
|
File.umask umask
|
133
182
|
Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
|
134
183
|
zip.get_output_stream(ovpn_file) {|f|
|
135
|
-
File.
|
184
|
+
f.write File.read(ovpn_file)
|
136
185
|
f.write "cert #{options[:generate_zip]}.crt\nkey #{options[:generate_zip]}.key\n"
|
137
186
|
}
|
138
187
|
['ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
|
@@ -142,7 +191,5 @@ if options[:generate_zip]
|
|
142
191
|
end
|
143
192
|
end
|
144
193
|
if options[:revoke]
|
145
|
-
|
146
|
-
gen_crl
|
147
|
-
%w[crt key].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
|
194
|
+
revoke(options[:revoke])
|
148
195
|
end
|
data/defaults/ovpn-key.yml
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
zip_dir: '~'
|
2
2
|
openvpn: openvpn
|
3
|
-
|
4
|
-
|
3
|
+
encrypt: AES128
|
4
|
+
digest: SHA256
|
5
5
|
key_size: 2048
|
6
|
-
|
6
|
+
expire: # days
|
7
|
+
ca: 3650
|
8
|
+
crl: 3650
|
9
|
+
server: 3650
|
10
|
+
client: 3650
|
7
11
|
ca_name: Certification Authority
|
8
|
-
|
12
|
+
x509:
|
13
|
+
C: US
|
14
|
+
ST: CA
|
15
|
+
L: San Francisco
|
16
|
+
O: Dva Debila
|
17
|
+
OU: OpenVPN
|
data/lib/functions.rb
CHANGED
@@ -12,34 +12,128 @@ def check_client(name)
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def exe(cmd)
|
15
|
-
system(cmd)
|
15
|
+
system(cmd) || abort("error executing: #{cmd}")
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
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
|
27
|
+
end
|
28
|
+
|
29
|
+
def gen_and_sign(type, certname, password)
|
30
|
+
gen_key(certname, password)
|
31
|
+
sign_key(type, certname, password)
|
21
32
|
end
|
22
33
|
|
23
|
-
def gen_key(certname,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
exe "#{OPENSSL} genrsa -#{ENCRYPT} -out '#{certname}.key' #{KEY_SIZE}"
|
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
|
28
38
|
end
|
29
39
|
end
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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')
|
38
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 }
|
102
|
+
end
|
103
|
+
|
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
|
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)
|
39
128
|
end
|
40
129
|
|
41
|
-
def
|
42
|
-
|
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 }
|
43
137
|
end
|
44
138
|
|
45
139
|
def create_dir(name)
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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
|
@@ -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
|
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
|
-
00
|
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
|