vpnmaker 0.0.0

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.
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+
3
+ require 'gibberish'
4
+ require 'rubyzip'
5
+
6
+ require 'fileutils'
7
+ require 'yaml'
8
+ require 'socket'
9
+
10
+ require 'ipaddr'
11
+ require 'ipaddr_extensions'
12
+ require 'haml'
13
+
14
+ require 'pry'
15
+
16
+ class String
17
+ def path(p)
18
+ File.join(File.dirname(__FILE__), p)
19
+ end
20
+ end
21
+
22
+ class HashBinding < Object
23
+ def self.from_hash(h)
24
+ hb = self.new
25
+ h.each do |k, v|
26
+ hb.instance_variable_set("@#{k}", v)
27
+ end
28
+ hb
29
+ end
30
+
31
+ def binding; super; end # normally private
32
+ end
33
+
34
+ module VPNMaker
35
+ autoload :ConfigGenerator, './vpnmaker/config_generator'
36
+ autoload :KeyDB, './vpnmaker/key_db'
37
+ autoload :KeyConfig, './vpnmaker/key_config'
38
+ autoload :KeyTracker, './vpnmaker/key_tracker'
39
+ autoload :Manager, './vpnmaker/manager'
40
+ autoload :KeyBuilder, './vpnmaker/key_builder'
41
+
42
+ def self.generate(*args)
43
+ KeyTracker.generate(args.first, args.last)
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module VPNMaker
2
+ class ConfigGenerator
3
+ def initialize(mgr)
4
+ @mgr = mgr
5
+ end
6
+
7
+ def client_conf(client)
8
+ {
9
+ :gen_host => Socket.gethostname,
10
+ :server => @mgr.config[:server],
11
+ :client => @mgr.config[:client]
12
+ }.merge(client)
13
+ end
14
+
15
+ def server_conf
16
+ {
17
+ :gen_host => Socket.gethostname
18
+ }.merge(@mgr.config[:server])
19
+ end
20
+
21
+ def server
22
+ haml_vars = server_conf.dup
23
+ haml_vars[:base_ip] = ((a = IPAddr.new haml_vars[:base_ip]); {:net => a.to_s, :mask => a.subnet_mask.to_s})
24
+ haml_vars[:bridgednets] = haml_vars[:bridgednets].map {|net| a = (IPAddr.new net); {:net => a.to_s, :mask => a.subnet_mask.to_s}}
25
+ haml_vars[:subnets] = haml_vars[:subnets].map {|net| a = (IPAddr.new net); {:net => a.to_s, :mask => a.subnet_mask.to_s}}
26
+ template = File.read(__FILE__.path('server.haml'))
27
+ Haml::Engine.new(template).render(Object.new, haml_vars)
28
+ end
29
+
30
+ def client(client)
31
+ haml_vars = client_conf(client).dup
32
+ template = File.read(__FILE__.path('client.haml'))
33
+ Haml::Engine.new(template).render(Object.new, haml_vars)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,141 @@
1
+ module VPNMaker
2
+ class KeyBuilder
3
+ def initialize(tracker, config)
4
+ @tmpdir = '/tmp/keybuilder'
5
+ clean_tmpdir
6
+ @tracker = tracker
7
+ @config = config
8
+ end
9
+
10
+ def clean_tmpdir
11
+ FileUtils.rm_rf(@tmpdir)
12
+ FileUtils.mkdir_p(@tmpdir)
13
+ end
14
+
15
+ def cnfpath; "/tmp/openssl-#{$$}.cnf"; end
16
+
17
+ def opensslvars
18
+ {
19
+ :key_size => 1024,
20
+ :key_dir => @tmpdir,
21
+ :key_country => @config[:key_properties][:country],
22
+ :key_province => @config[:key_properties][:province],
23
+ :key_city => @config[:key_properties][:city],
24
+ :key_org => @config[:key_properties][:organization],
25
+ :key_email => @config[:key_properties][:email],
26
+ :key_org => @config[:key_properties][:organization],
27
+ :key_ou => 'Organization Unit',
28
+ :key_cn => 'Common Name',
29
+ :key_name => 'Name'
30
+ }
31
+ end
32
+
33
+ def init
34
+ `touch #{@dir}/index.txt`
35
+ `echo 01 > #{@dir}/serial`
36
+ end
37
+
38
+ def opensslcnf(hash={})
39
+ c = cnfpath
40
+
41
+ File.open(cnfpath, 'w') do |f|
42
+ f.write(Haml::Engine.new(File.read __FILE__.path('openssl.haml')).render(Object.new, opensslvars.merge(hash)))
43
+ end
44
+
45
+ c
46
+ end
47
+
48
+ # Build Diffie-Hellman parameters for the server side of an SSL/TLS connection.
49
+ def build_dh_key(keysize=1024)
50
+ `openssl dhparam -out #{tmppath('dh.pem')} #{keysize}`
51
+ @tracker.set_dh(tmpfile('dh.pem'))
52
+ end
53
+
54
+ def ca
55
+ @tracker[:ca]
56
+ end
57
+
58
+ def gen_crl
59
+ `openssl ca -gencrl -crldays 3650 -keyfile #{tmppath('ca.key')} -cert #{tmppath('ca.crt')} -out #{tmppath('crl.pem')} -config #{opensslcnf}`
60
+ end
61
+
62
+ def build_ca
63
+ index = tmppath('index.txt')
64
+
65
+ FileUtils.touch(index)
66
+
67
+ `openssl req -batch -days 3650 -nodes -new -x509 -keyout #{@tmpdir}/ca.key -out #{@tmpdir}/ca.crt -config #{opensslcnf}`
68
+ gen_crl
69
+ @tracker.set_ca(tmpfile('ca.key'), tmpfile('ca.crt'), tmpfile('crl.pem'), tmpfile('index.txt'), "01\n")
70
+ end
71
+
72
+ def build_server_key
73
+ place_file('ca.crt')
74
+ place_file('ca.key')
75
+ place_file('index.txt')
76
+ place_file('serial')
77
+
78
+ `openssl req -batch -days 3650 -nodes -new -keyout #{tmppath('server.key')} -out #{tmppath('server.csr')} -extensions server -config #{opensslcnf}`
79
+ `openssl ca -batch -days 3650 -out #{tmppath('server.crt')} -in #{tmppath('server.csr')} -extensions server -config #{opensslcnf}`
80
+
81
+ @tracker.set_server_key(tmpfile('server.key'), tmpfile('server.crt'), tmpfile('index.txt'), tmpfile('serial'))
82
+ end
83
+
84
+ def build_ta_key
85
+ `openvpn --genkey --secret #{tmppath('ta.key')}`
86
+ @tracker.set_ta_key(tmpfile('ta.key'))
87
+ end
88
+
89
+ def place_file(name)
90
+ if data = @tracker.db.data(name)
91
+ File.open(File.join(@tmpdir, name), 'w') {|f| f.write(data)}
92
+ else
93
+ raise "No data for #{name}"
94
+ end
95
+ end
96
+
97
+ def tmppath(f, extn=nil); File.join(@tmpdir, extn ? "#{f}.#{extn}" : f); end
98
+ def tmpfile(*args); File.read(tmppath(*args)); end
99
+
100
+ def build_key(user, name, email, pass, delegate)
101
+ h = {:key_cn => user, :key_name => name, :key_email => email}
102
+ place_file('ca.crt')
103
+ place_file('ca.key')
104
+ place_file('index.txt')
105
+ place_file('serial')
106
+ if pass
107
+ pass_spec = "-passin 'pass:#{pass}' -passout 'pass:#{pass}'"
108
+ else
109
+ pass_spec = '-nodes'
110
+ end
111
+
112
+ `openssl req -batch -days 3650 -new -keyout #{tmppath(user, 'key')} -out #{tmppath(user, 'csr')} -config #{opensslcnf(h)} #{pass_spec}`
113
+ `openssl ca -batch -days 3650 -out #{tmppath(user, 'crt')} -in #{tmppath(user, 'csr')} -config #{opensslcnf(h)}`
114
+ # TODO: this still asks for the export password
115
+ `openssl pkcs12 -export -clcerts -in #{tmppath(user, 'crt')} -inkey #{tmppath(user, 'key')} -out #{tmppath(user, 'p12')} #{pass_spec}`
116
+ @tracker.send(delegate, user, name, email, tmpfile(user, 'key'), tmpfile(user, 'crt'), tmpfile(user, 'p12'), tmpfile('index.txt'), tmpfile('serial'))
117
+ end
118
+
119
+ def revoke_key(user, version)
120
+ h = {:key_cn => ""}
121
+ place_file('ca.crt')
122
+ place_file('ca.key')
123
+ place_file('crl.pem')
124
+ place_file('index.txt')
125
+ place_file('serial')
126
+
127
+ user_crt = tmppath(user, 'crt')
128
+ rev_crt = tmppath('rev-test.crt')
129
+ File.open(user_crt, 'w') {|f| f.write(@tracker.key(user, version, 'crt'))}
130
+ `openssl ca -revoke #{user_crt} -keyfile #{tmppath('ca.key')} -cert #{tmppath('ca.crt')} -config #{opensslcnf(h)}`
131
+ gen_crl
132
+
133
+ File.open(rev_crt, 'w') {|f| f.write(File.read(tmppath('ca.crt'))); f.write(File.read(tmppath('crl.pem')))}
134
+ if `openssl verify -CAfile #{rev_crt} -crl_check #{user_crt}` =~ /certificate revoked/
135
+ @tracker.user_key_revoked(user, version, tmpfile('crl.pem'), tmpfile('index.txt'))
136
+ else
137
+ raise "Revocation verification failed: openssl isn't recognizing it"
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,9 @@
1
+ module VPNMaker
2
+ class KeyConfig
3
+ def initialize(path)
4
+ @config = YAML.load_file(path)
5
+ end
6
+
7
+ def [](k); @config[k]; end
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ module VPNMaker
2
+ class KeyDB
3
+ def initialize(path)
4
+ @path = path
5
+ @db = File.exists?(path) ? YAML.load_file(path) : {}
6
+ @touched = false
7
+ end
8
+
9
+ def [](k); @db[k]; end
10
+
11
+ def []=(k, v)
12
+ @db[k] = v
13
+ @db[:modified] = Time.now
14
+ @touched = true
15
+ end
16
+
17
+ def touched!
18
+ @touched = true
19
+ @db[:modified] = Time.now
20
+ end
21
+
22
+
23
+ def datadir; self[:datadir]; end
24
+
25
+ def data_path(k)
26
+ File.join(File.dirname(@path), self.datadir, k)
27
+ end
28
+
29
+ def dump(k, v, overwrite=false)
30
+ p = data_path(k)
31
+ raise "#{k} already exists" if File.exists?(p) && !overwrite
32
+ File.open(p, 'w') {|f| f.write(v)}
33
+ @touched = true
34
+ end
35
+
36
+ def data(k)
37
+ File.exists?(data_path(k)) ? File.read(data_path(k)) : nil
38
+ end
39
+
40
+ def disk_version
41
+ File.exists?(@path) ? YAML.load_file(@path)[:version] : 0
42
+ end
43
+
44
+ def sync
45
+ if disk_version == @db[:version]
46
+ if @touched
47
+ FileUtils.mkdir_p(self.datadir)
48
+ @db[:version] += 1
49
+ File.open(@path, 'w') {|f| f.write(@db.to_yaml)}
50
+ true
51
+ else
52
+ false
53
+ end
54
+ else
55
+ raise "Disk version of #{@path} (#{disk_version}) != loaded version (#{@db[:version]}). " + \
56
+ "Try reloading and making your changes again."
57
+ end
58
+ end
59
+
60
+ def version; @db[:version]; end
61
+ end
62
+ end
@@ -0,0 +1,158 @@
1
+ module VPNMaker
2
+ class KeyTracker
3
+ attr_reader :builder
4
+ attr_reader :db
5
+ attr_reader :config
6
+
7
+ def self.generate(name, path=nil)
8
+ path ||= '/tmp'
9
+ dir = File.join(File.expand_path(path), name + '.vpn')
10
+
11
+ FileUtils.mkdir_p(dir)
12
+ datadir = "#{name}_data"
13
+ dbpath = File.join(dir, "#{name}.db.yaml")
14
+
15
+ d = KeyDB.new(dbpath)
16
+ d[:version] = 0
17
+ d[:modified] = Time.now
18
+ d[:users] = {}
19
+ d[:datadir] = datadir
20
+ d.sync
21
+ end
22
+
23
+ def assert_user(user)
24
+ raise "User doesn't exist: #{user}" unless @db[:users][user]
25
+ end
26
+
27
+ def ca; @db[:ca]; end
28
+
29
+ def set_ca(key, crt, crl, index, serial)
30
+ raise "CA already set" if @db[:ca]
31
+
32
+ @db[:ca] = {:modified => Time.now}
33
+ @db.dump('ca.key', key)
34
+ @db.dump('ca.crt', crt)
35
+ @db.dump('crl.pem', crl)
36
+ @db.dump('index.txt', index)
37
+ @db.dump('serial', serial)
38
+ @db.touched!
39
+ @db.sync
40
+ end
41
+
42
+ def set_server_key(key, crt, index, serial)
43
+ raise "Server key already set" if @db[:server]
44
+
45
+ @db[:server] = {:modified => Time.now}
46
+ @db.dump('server.key', key)
47
+ @db.dump('server.crt', crt)
48
+ @db.dump('index.txt', index, true)
49
+ @db.dump('serial', serial, true)
50
+ @db.touched!
51
+ @db.sync
52
+ end
53
+
54
+ def set_ta_key(ta)
55
+ raise "TA key already set" if @db[:ta]
56
+
57
+ @db[:ta] = {:modified => Time.now}
58
+ @db.dump('ta.key', ta)
59
+ @db.touched!
60
+ @db.sync
61
+ end
62
+
63
+ def set_dh(dh)
64
+ raise "DH key already set" if @db[:dh]
65
+
66
+ @db[:dh] = {:modified => Time.now}
67
+ @db.dump('dh.pem', dh)
68
+ @db.touched!
69
+ @db.sync
70
+ end
71
+
72
+ def add_key(user, key, crt, p12, ver)
73
+ @db.dump("#{user}-#{ver}.key", key)
74
+ @db.dump("#{user}-#{ver}.crt", crt)
75
+ @db.dump("#{user}-#{ver}.p12", p12)
76
+ end
77
+
78
+ def key(user, ver, type)
79
+ @db.data("#{user}-#{ver}.#{type}")
80
+ end
81
+
82
+ def add_user(user, name, email, key, crt, p12, index, serial)
83
+ raise "User must be a non-empty string" unless user.is_a?(String) && user.size > 0
84
+ raise "User already exists: #{user}" if @db[:users][user]
85
+
86
+ @db[:users][user] = {
87
+ :user => user,
88
+ :name => name,
89
+ :email => email,
90
+ :active_key => 0,
91
+ :revoked => [],
92
+ :modified => Time.now
93
+ }
94
+ @db.dump('serial', serial, true)
95
+ @db.dump('index.txt', index, true)
96
+ add_key(user, key, crt, p12, 0)
97
+ @db.touched!
98
+ @db.sync
99
+ end
100
+
101
+ def add_user_key(user, name, email, key, crt, p12, index, serial)
102
+ assert_user(user)
103
+
104
+ u = @db[:users][user]
105
+ u[:modified] = Time.now
106
+ u[:active_key] += 1
107
+ add_key(user, key, crt, p12, u[:active_key])
108
+
109
+ @db.dump('serial', serial, true)
110
+ @db.dump('index.txt', index, true)
111
+
112
+ @db.touched!
113
+ @db.sync
114
+ end
115
+
116
+ def user_key_revoked(user, version, crl, index)
117
+ assert_user(user)
118
+
119
+ raise "Verison must be an int" unless version.kind_of?(Integer)
120
+ u = @db[:users][user]
121
+ u[:revoked] << version
122
+ u[:modified] = Time.now
123
+ @db.dump('index.txt', index, true)
124
+ @db.dump('crl.pem', crl, true)
125
+ @db.touched!
126
+ @db.sync
127
+ end
128
+
129
+ def revoked?(user, version)
130
+ assert_user(user)
131
+
132
+ @db[:users][user][:revoked].include?(version)
133
+ end
134
+
135
+ def active_key_version(user)
136
+ assert_user(user)
137
+
138
+ @db[:users][user][:active_key]
139
+ end
140
+
141
+ def user(user)
142
+ assert_user(user)
143
+ @db[:users][user]
144
+ end
145
+
146
+ def users; @db[:users]; end
147
+
148
+ def initialize(name, dir)
149
+ @db = KeyDB.new(File.join(dir, name + '.db.yaml'))
150
+ @config = KeyConfig.new(File.join(dir, name + '.config.yaml'))
151
+ @builder = KeyBuilder.new(self, @config)
152
+ end
153
+ end
154
+
155
+ def self.generate(name, path)
156
+ KeyTracker.generate(name, path)
157
+ end
158
+ end