leeloo 0.0.16 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,40 +1,207 @@
1
- require 'fileutils'
2
1
  require 'gpgme'
2
+ require 'fileutils'
3
3
  require 'git'
4
+ require 'base64'
4
5
 
5
6
  module Leeloo
7
+
8
+ class KeystoreFactory
9
+ def self.create keystore
10
+ keystore_created = nil
11
+ case keystore["cypher"]
12
+ when "gpg"
13
+ keystore_created = GpgPrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
14
+ else
15
+ keystore_created = PrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
16
+ end
17
+
18
+ case keystore["vc"]
19
+ when "git"
20
+ GitKeystoreDecorator.new keystore_created
21
+ else
22
+ keystore_created
23
+ end
24
+ end
25
+ end
26
+
6
27
  class Keystore
7
28
 
8
- def self.secret_key_empty?
9
- GPGME::Key.find(:secret, nil, ).empty?
29
+ attr_reader :name
30
+
31
+ def initialize name
32
+ @name = name
33
+ end
34
+
35
+ def secrets
36
+ # returns the secrets list
10
37
  end
11
38
 
12
- def self.add_keystore name, path
13
- FileUtils.mkdir_p path
14
- FileUtils.mkdir_p "#{path}/secrets/"
15
- FileUtils.mkdir_p "#{path}/keys/"
39
+ def secret_of path
40
+ # returns a secret object
41
+ end
42
+
43
+ def secret_from_name name
44
+ # returns a secret object
45
+ end
46
+
47
+ def sync
48
+ # synchronizes the keystore
49
+ end
50
+
51
+ def init
52
+ # initialize the keystore
53
+ end
54
+
55
+ def footprint name
56
+ # footprint a given secret path
57
+ end
16
58
 
17
- GPGME::Key.find(:public, nil, ).each do |key|
18
- key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+"))
59
+ def secret_from_footprint footprint
60
+ # returns a secret object
61
+ end
62
+
63
+ def == keystore
64
+ self.name == keystore.name
65
+ end
66
+ end
67
+
68
+ class PrivateLocalFileSystemKeystore < Keystore
69
+
70
+ attr_reader :path
71
+
72
+ def initialize name, path
73
+ super name
74
+ @path = path
75
+ FileUtils.mkdir_p "#{@path}/secrets"
76
+ end
77
+
78
+ def secrets
79
+ find_secrets "#{@path}/secrets"
80
+ end
81
+
82
+ def find_secrets path
83
+ elements = []
84
+ Dir.glob("#{path}/**") do |element|
85
+ elements << secret_of(element) unless Dir.exist? element
86
+ elements << find_secrets(element) if Dir.exist? element
19
87
  end
88
+ return elements.flatten
89
+ end
90
+
91
+ def == keystore
92
+ self.name == keystore.name && self.path == keystore.path
93
+ end
20
94
 
21
- g = Git.init path
22
- g.add
23
- g.commit "keystore #{path} added"
95
+ def secret_of path
96
+ name = path.gsub("#{@path}/secrets/", "")
97
+ LocalFileSystemSecret.new path, name
24
98
  end
25
99
 
26
- def self.add_remote path, remote
27
- g = Git.open path
28
- g.add_remote 'origin', remote
100
+ def secret_from_name name
101
+ secret_of "#{path}/secrets/#{name}"
29
102
  end
30
103
 
31
- def self.sync_keystore path
32
- g = Git.open path
33
- unless g.remotes.empty?
34
- g.pull
35
- g.push
104
+ def footprint_of name
105
+ secret = secret_from_name name
106
+ { "footprint" => secret.footprint, "keystore" => self.name, "secret" => secret.name }
107
+ end
108
+
109
+ def secret_from_footprint footprint
110
+ secret = secret_from_name footprint["secret"]
111
+ unless secret.footprint == footprint["footprint"]
112
+ raise "footprint is not valid"
36
113
  end
37
- return !g.remotes.empty?
114
+ secret
115
+ end
116
+
117
+ end
118
+
119
+ class GpgPrivateLocalFileSystemKeystore < PrivateLocalFileSystemKeystore
120
+
121
+ def initialize name, path
122
+ super name, path
123
+ FileUtils.mkdir_p "#{@path}/keys"
124
+
125
+ @recipients = []
126
+ Dir.glob("#{path}/keys/*") { |key| @recipients << File.basename(key) }
127
+ @recipients.each { |key| GPGME::Key.import(File.open("#{path}/keys/#{key}")) }
128
+ end
129
+
130
+ def init
131
+ super
132
+ GPGME::Key.find(:public, nil, ).each { |key| key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+")) }
133
+ end
134
+
135
+ def secret_of path
136
+ name = path.gsub("#{@path}/secrets/", "").gsub(".gpg", "")
137
+ GpgLocalFileSystemSecret.new path, name, @recipients
138
+ end
139
+
140
+ def secret_from_name name
141
+ secret_of "#{path}/secrets/#{name}.gpg"
142
+ end
143
+
144
+ def footprint_of name
145
+ footprint = super name
146
+ footprint["sign"] = Base64.strict_encode64 GPGME::Crypto.new.sign(footprint["footprint"]).to_s
147
+ footprint
148
+ end
149
+
150
+ def secret_from_footprint footprint
151
+ data = GPGME::Crypto.new.verify(Base64.strict_decode64 footprint["sign"]) { |signature| signature.valid? }
152
+ if data.read == footprint["footprint"]
153
+ super footprint
154
+ else
155
+ raise "signature is not valid"
156
+ end
157
+ end
158
+ end
159
+
160
+ class GitKeystoreDecorator < Keystore
161
+ def initialize keystore
162
+ @keystore = keystore
163
+ Git.init @keystore.path
164
+ @git = Git.open keystore.path
165
+ end
166
+
167
+ def secret_of element
168
+ GitSecretDecorator.new(@git, element)
169
+ end
170
+
171
+ def secret_from_name element
172
+ secret_of @keystore.secret_from_name(element)
173
+ end
174
+
175
+ def secrets
176
+ @keystore.secrets
177
+ end
178
+
179
+ def name
180
+ @keystore.name
181
+ end
182
+
183
+ def path
184
+ @keystore.path
185
+ end
186
+
187
+ def footprint_of element
188
+ @keystore.footprint_of element
189
+ end
190
+
191
+ def secret_from_footprint footprint
192
+ @keystore.secret_from_footprint footprint
193
+ end
194
+
195
+ def sync
196
+ @git.pull
197
+ @keystore.sync
198
+ @git.push
199
+ end
200
+
201
+ def init
202
+ @keystore.init
203
+ @git.add
204
+ @git.commit "keystore #{@keystore.name} added"
38
205
  end
39
206
 
40
207
  end
@@ -0,0 +1,149 @@
1
+ require 'clipboard'
2
+ require 'tty-table'
3
+ require 'tty-tree'
4
+ require 'json'
5
+ require 'base64'
6
+ require 'socket'
7
+
8
+ module Leeloo
9
+
10
+ class Output
11
+
12
+ def render_preferences preferences
13
+ end
14
+
15
+ def render_secrets secrets
16
+ end
17
+
18
+ def render_secret secret
19
+ end
20
+
21
+ def render_text text
22
+ end
23
+
24
+ def render_footprint footprint
25
+ end
26
+
27
+ def render_share footprint
28
+ end
29
+ end
30
+
31
+ class Ascii < Output
32
+
33
+ def render_preferences preferences
34
+ preferences.keystores.each { |keystore| puts keystore.name }
35
+ end
36
+
37
+ def render_secrets secrets
38
+ secrets.sort_by(&:name).each {|secret| puts secret.name}
39
+ end
40
+
41
+ def render_secret secret
42
+ begin
43
+ puts secret.read
44
+ rescue => exception
45
+ puts "#{secret.name} doesn't exist"
46
+ end
47
+ end
48
+
49
+ def render_text text
50
+ puts text
51
+ end
52
+
53
+ def render_footprint footprint
54
+ puts "token:"
55
+ puts Base64.strict_encode64 footprint.to_json
56
+ end
57
+ end
58
+
59
+ class Terminal < Ascii
60
+
61
+ def render_preferences preferences
62
+ rows = []
63
+ default_keystore = preferences.default
64
+ preferences.keystores.each do |keystore|
65
+ is_default = '*' if keystore.name == default_keystore
66
+ rows << [keystore.name, keystore.path, is_default ]
67
+ end
68
+ puts TTY::Table.new(header: ['Name', 'Path', 'Default'], rows: rows).render(:ascii)
69
+ end
70
+
71
+ def render_secrets secrets
72
+ hash = {:secrets => []}
73
+ secrets.sort_by(&:name).each { |secret| sort(hash[:secrets], secret.name) }
74
+ puts TTY::Tree.new(hash).render
75
+ end
76
+
77
+ def sort array, element
78
+ if element
79
+ e = element.split("/", 2)
80
+ if e.length > 1
81
+ found = false
82
+ array.each do |a|
83
+ if a.is_a? Hash
84
+ if a[e.first]
85
+ found = true
86
+ sort(a[e.first], e.last)
87
+ break
88
+ end
89
+ end
90
+ end
91
+
92
+ unless found
93
+ array << { e.first => sort([], e.last) }
94
+ end
95
+ else
96
+ array << e.last
97
+ end
98
+ end
99
+ array
100
+ end
101
+ end
102
+
103
+ class ClipboardOutputDecorator < Output
104
+
105
+ def initialize output
106
+ @output = output
107
+ end
108
+
109
+ def render_preferences preferences
110
+ @output.render_preferences preferences
111
+ end
112
+
113
+ def render_secrets secrets
114
+ @output.render_secrets secrets
115
+ end
116
+
117
+ def render_text text
118
+ @output.render_text text
119
+ end
120
+
121
+ def render_secret secret
122
+
123
+ Signal.trap("INT") do
124
+ Clipboard.clear
125
+ abort "cleared"
126
+ end
127
+
128
+ Clipboard.copy secret.read
129
+ wait = Thread.new do
130
+ puts "cleaning in 30s"
131
+ 30.times {
132
+ print "."
133
+ sleep 1
134
+ }
135
+ end
136
+ wait.join
137
+ Clipboard.clear
138
+ end
139
+
140
+ def render_footprint footprint
141
+ @output.render_footprint footprint
142
+ end
143
+
144
+ def render_share footprint
145
+ @output.render_share footprint
146
+ end
147
+ end
148
+
149
+ end
@@ -0,0 +1,100 @@
1
+ require 'yaml'
2
+
3
+ module Leeloo
4
+ class Preferences
5
+
6
+ attr_reader :default
7
+
8
+ def initialize
9
+ @keystores = []
10
+ @default = nil
11
+ end
12
+
13
+ def load
14
+ # this method loads all preferences
15
+ self
16
+ end
17
+
18
+ def set_default_keystore name
19
+ @default = name
20
+ end
21
+
22
+ def keystore name=nil
23
+ keystores.find { |k| k.name == (name||@default) }
24
+ end
25
+
26
+ def keystores
27
+ @keystores.map { |k| KeystoreFactory::create k }
28
+ end
29
+
30
+ def add_keystore keystore
31
+ unless @keystores.include? keystore
32
+ @keystores << keystore
33
+ end
34
+ end
35
+
36
+ def remove_keystore name
37
+ abort "you can not remove default keystore" if name == @default
38
+ keystore = @keystores.find { |k| k["name"] == name }
39
+ if keystore != nil
40
+ @keystores.delete keystore
41
+ end
42
+ end
43
+ end
44
+
45
+ class PrivateLocalFileSystemPreferences < Preferences
46
+
47
+ DEFAULT_PATH = "#{Dir.home}/.leeloo"
48
+
49
+ def load(path=DEFAULT_PATH)
50
+ @path = path
51
+
52
+ if File.exist? "#{path}/keystores"
53
+ @keystores = YAML.load_file "#{path}/keystores"
54
+ end
55
+
56
+ if File.exist? "#{path}/config"
57
+ config = YAML.load_file "#{path}/config"
58
+ set_default_keystore config["keystore"]
59
+ else
60
+ default_keystore = {
61
+ 'name' => "private",
62
+ 'path' => "#{path}/private",
63
+ 'cypher' => "gpg",
64
+ 'vc' => "git"
65
+ }
66
+ add_keystore default_keystore
67
+ set_default_keystore "private"
68
+ keystore_of("private").init
69
+ end
70
+
71
+ self
72
+ end
73
+
74
+ def keystore_of name
75
+ keystore = @keystores.find { |keystore| keystore["name"] == name }
76
+ KeystoreFactory::create keystore
77
+ end
78
+
79
+ def set_default_keystore name
80
+ super name
81
+ config = {
82
+ "keystore" => name
83
+ }
84
+ File.write("#{@path}/config", config.to_yaml)
85
+ end
86
+
87
+ def add_keystore keystore
88
+ super keystore
89
+ FileUtils.mkdir_p keystore["path"]
90
+ File.write("#{@path}/keystores", @keystores.to_yaml)
91
+ end
92
+
93
+ def remove_keystore name
94
+ super name
95
+ File.write("#{@path}/keystores", @keystores.to_yaml)
96
+ end
97
+
98
+ end
99
+
100
+ end