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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13681f4cf8c6abc0badce3feceacf4ce489daab4ff9ea7b177bdca7c3fe983ed
4
- data.tar.gz: 621ad232db5032b90b1b631f7765dc15aca7b8131465bb2658408c13a7c1d8b0
3
+ metadata.gz: ecf480dc290e372a3b081ebc12dceb82e2f0ca22ea508cbbe622e8e1ce80b58f
4
+ data.tar.gz: 585a28ba43b652afd0add8b912a8ff0590ad99ffcb96abcb9929c8ca488c03b1
5
5
  SHA512:
6
- metadata.gz: cc2d031bd9f8a595fa1efd862c2e5e371643928d34c7d7398db179466b655e48d412dd5494cfc6d705e614114846acbcce717b1117026df685bac5a4eb6e65b7
7
- data.tar.gz: d31fd3d8936ab9bbd94daed1db9f2334925e073dfb9319d6d96aabfa1db566bd2f340144dd0190ee27ee2652d64ef70d59e6c0a6a1c63af6392dfdba0191073e
6
+ metadata.gz: 47783d3b02d6948cb19fc4434dff11a980c0fc9f22675a66d2cc6fc34423be7e7b2dc6b4e46f420eaa85755ef14ca8e787dddaa30eacd707837033138239753f
7
+ data.tar.gz: 405a4389cf9b26a8a8cf6e4e615a04dc2bcf80b6fe71ed7b22c3380c3e5d215e70bab5d3ef8ba78eab20e94e19e74d1dd3c58fe47e118760cae842fbfc1e7c40
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,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` and `openssl.ini`
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('--revoke [name]', 'Revoke a certificate (using crl.pem) and delete it') do |v|
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
- %w[certs meta].each {|dir| create_dir dir}
71
- ['meta/index.txt', 'meta/index.txt.attr', 'meta/serial', SSL_CONF, APP_CONF].each {|file|
72
- unless File.exist? file
73
- FileUtils.copy_file(File.expand_path("defaults/#{file}", "#{__dir__}/.."), "./#{file}")
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
- REQ = settings['details']
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
- gen_key('ca', options[:no_password])
102
- sign_key('ca', 'ca', CN_CA)
103
- gen_crl
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
- exe "#{OPENSSL} dhparam -out dh.pem #{KEY_SIZE}"
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.open(ovpn_file).each {|line| f.write line}
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
- exe "#{OPENSSL} ca -revoke '#{options[:revoke]}.crt' -config #{SSL_CONF}"
146
- gen_crl
147
- %w[crt key].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
194
+ revoke(options[:revoke])
148
195
  end
@@ -1,8 +1,17 @@
1
1
  zip_dir: '~'
2
2
  openvpn: openvpn
3
- openssl: openssl
4
- encrypt: aes128
3
+ encrypt: AES128
4
+ digest: SHA256
5
5
  key_size: 2048
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
@@ -12,34 +12,128 @@ def check_client(name)
12
12
  end
13
13
 
14
14
  def exe(cmd)
15
- system(cmd) or abort "error executing: #{cmd}"
15
+ system(cmd) || abort("error executing: #{cmd}")
16
16
  end
17
17
 
18
- def gen_and_sign(type, certname, no_password)
19
- gen_key(certname, no_password)
20
- 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
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, no_password)
24
- if no_password
25
- exe "#{OPENSSL} genrsa -out '#{certname}.key' #{KEY_SIZE}"
26
- else
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
- def sign_key(type, certname, cn)
32
- if certname == 'ca'
33
- exe "#{OPENSSL} req -new -x509 -key '#{certname}.key' -out '#{certname}.crt' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type} -days #{CA_DAYS}"
34
- else
35
- exe "#{OPENSSL} req -new -key '#{certname}.key' -out '#{certname}.csr' -config #{SSL_CONF} -subj '/CN=#{cn}#{REQ}' -extensions ext.#{type}"
36
- exe "#{OPENSSL} ca -in '#{certname}.csr' -out '#{certname}.crt' -config #{SSL_CONF} -extensions ext.#{type} -batch"
37
- File.delete "#{certname}.csr"
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 gen_crl
42
- exe "#{OPENSSL} ca -gencrl -out crl.pem -config #{SSL_CONF}"
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
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ::VERSION = '0.7.7'
3
+ ::VERSION = '0.8'
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.7.7
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
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