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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fde0a6063ccfbd4a7f51ee4d0749ce791999dda0c4d4b5254d21c4de821a1d7
4
- data.tar.gz: 54d1af5174381b59453545900ecd56d90d8aef8f240c4bd5738393e03374cbeb
3
+ metadata.gz: ecf480dc290e372a3b081ebc12dceb82e2f0ca22ea508cbbe622e8e1ce80b58f
4
+ data.tar.gz: 585a28ba43b652afd0add8b912a8ff0590ad99ffcb96abcb9929c8ca488c03b1
5
5
  SHA512:
6
- metadata.gz: f979223351319b4b440c27ded450507a8d13f5a3092fdd7af26150b92cb81ea52911bb729589b0bb50f9c5fe6f22e84bed1f9f88964a88fe3958a6fa1d1e46ef
7
- data.tar.gz: e5b87efe66c151a90fa3abf209c848c08615f22a6df090c07a3337bb4002fdcb903c5dd652234966b672b1994a13e6f4246af219722c017aa3d4ac8fbfcfc66f
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
@@ -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` step will be useless, but all others will work.
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
- For now it should be considered experimental and rather undocumented.
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` and `openssl.ini`
29
- 3. `ovpn-key --ca --dh --server --nopass`
30
- 4. `ovpn-key --client somebody`
31
- 5. `ovpn-key --revoke somebody`
32
- 6. 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
33
38
  it should contain every setting except for `cert` and `key`
34
- 7. `ovpn-key --zip somebody-else`
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
- 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 = {}
13
18
  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 : "."
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("--ca", "Generate a CA (ca.crt)") do |v|
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("--dh", "Generate a DH keyfile (dh.pem)") do |v|
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("--server [name]", "Generate a server key (defaults to 'server')") do |v|
27
- options[:generate_server] = v ? v : "server"
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("--client [name]", "Generate a client key and sign it") do |v|
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("--zip [name]", "Ditto plus pack it to ZIP with OpenVPN config") do |v|
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]", "Revoke a certificate (using crl.pem) and delete it") do |v|
39
- abort "Please specify what certificate to revoke" unless v
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("--nopass", "Don't protect .key files with a password") do |v|
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 > 0
47
- abort "Error: invalid args: #{ARGV.join ' '}\nSee `#{File.basename $0} -h` for help"
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[:generate_server] \
50
- || options[:generate_client] || options[:generate_zip] || options[:revoke]
51
- abort "See `#{File.basename $0} -h` for usage"
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] and options[:generate_zip]
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 "There can be only one: --client or --zip"
64
+ abort 'There can be only one: --client or --zip'
56
65
  end
57
- umask = File.umask 0077
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
- ['certs', 'meta'].each {|dir| create_dir dir}
65
- ['meta/index.txt', 'meta/index.txt.attr', 'meta/serial', SSL_CONF, APP_CONF].each {|file|
66
- unless File.exist? file
67
- FileUtils.copy_file(File.expand_path("defaults/#{file}", "#{__dir__}/.."), "./#{file}")
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 "~/.#{APP_CONF}")
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 $0} --init` before generating certificates"
89
+ abort "Run `#{File.basename $PROGRAM_NAME} --init` before generating certificates"
84
90
  end
85
91
  ZIP_DIR = settings['zip_dir'] || '~'
86
- OPENSSL = settings['openssl'] || 'openssl'
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
- 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
91
143
 
92
144
  if options[:generate_ca]
93
- gen_key('ca', 'ca', options[:no_password])
94
- sign_key('ca', 'ca', CN_CA)
95
- 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)
96
149
  end
97
150
  if options[:generate_dh]
98
- 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
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 "No .ovpn file in current directory, please add one"
172
+ abort 'No .ovpn file in current directory, please add one'
113
173
  else
114
- abort "More than one .ovpn files in current directory, aborting"
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.open(ovpn_file).each {|line| f.write line}
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
- [ 'ca.crt', "#{options[:generate_zip]}.crt", "#{options[:generate_zip]}.key"].each {|i|
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
- exe "#{OPENSSL} ca -revoke '#{options[:revoke]}.crt' -config #{SSL_CONF}"
134
- gen_crl
135
- ['crt', 'key'].each {|ext| File.delete "#{options[:revoke]}.#{ext}"}
194
+ revoke(options[:revoke])
136
195
  end
@@ -1,6 +1,17 @@
1
1
  zip_dir: '~'
2
- openssl: openssl
2
+ openvpn: openvpn
3
+ encrypt: AES128
4
+ digest: SHA256
3
5
  key_size: 2048
4
- encrypt: aes128
6
+ expire: # days
7
+ ca: 3650
8
+ crl: 3650
9
+ server: 3650
10
+ client: 3650
5
11
  ca_name: Certification Authority
6
- 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,144 @@
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(type, 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 type, certname, no_password
22
- if no_password
23
- exe "#{OPENSSL} genrsa -out '#{certname}.key' #{KEY_SIZE} -config #{SSL_CONF} -extensions ext.#{type}"
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 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}"
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"
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
- def gen_crl
40
- exe "#{OPENSSL} ca -gencrl -out crl.pem -config #{SSL_CONF}"
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 create_dir name
44
- unless Dir.exist? name
45
- Dir.mkdir name
46
- puts "Created directory: #{name}"
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
@@ -1 +1,3 @@
1
- ::Version = '0.7.2'
1
+ # frozen_string_literal: true
2
+
3
+ ::VERSION = '0.8'
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.2
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: 2018-09-25 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,16 +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'
27
- description: ''
28
- email: vasily.korytov@icloud.com
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.0'
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
- rubyforge_project:
64
- rubygems_version: 2.7.6
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: []
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