dotgpg 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dotgpg/key.rb ADDED
@@ -0,0 +1,105 @@
1
+ class Dotgpg
2
+ class Key
3
+
4
+ def self.read(file)
5
+ GPGME::Key.import(file).imports.map do |import|
6
+ GPGME::Key.find(:public, import.fingerprint)
7
+ end.flatten.first
8
+ end
9
+
10
+ def self.secret_key(email=nil, force_new=nil)
11
+ new.secret_key(email, force_new)
12
+ end
13
+
14
+ def secret_key(email=nil, force_new=nil)
15
+ existing = existing_key(email)
16
+ if existing && !force_new
17
+ existing
18
+ else
19
+ create_new_key email
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def existing_key(email=nil)
26
+ existing_private_keys.detect do |k|
27
+ email.nil? || k.email == email
28
+ end
29
+ end
30
+
31
+ def create_new_key(email=nil)
32
+ name = guess_name
33
+ email ||= guess_email
34
+
35
+ if email
36
+ puts "Creating a new GPG key: #{name} <#{email}>"
37
+ passphrase = get_passphrase
38
+ else
39
+ puts "Creating a new GPG key for #{name}"
40
+ email = get_email
41
+ passphrase = get_passphrase
42
+ end
43
+
44
+ puts "Generating large prime numbers, please wait..."
45
+ ctx = GPGME::Ctx.new
46
+ ctx.genkey(<<EOF, nil, nil)
47
+ <GnupgKeyParms format="internal">
48
+ Key-Type: RSA
49
+ Key-Length: 2048
50
+ Subkey-Type: RSA
51
+ Subkey-Length: 2048
52
+ Name-Real: #{name}
53
+ Name-Comment: dotgpg
54
+ Name-Email: #{email}
55
+ Expire-Date: 0
56
+ Passphrase: #{passphrase}
57
+ </GnupgKeyParms>
58
+ EOF
59
+
60
+ # return the most recently created key (race!)
61
+ GPGME::Key.find(:secret).sort_by{ |key|
62
+ key.primary_subkey.timestamp
63
+ }.last
64
+ end
65
+
66
+ def guess_name
67
+ name = `git config user.name 2>/dev/null`.strip
68
+ name = `whoami`.strip if name == ""
69
+ name
70
+ end
71
+
72
+ def guess_email
73
+ email = `git config user.email 2>/dev/null`.strip
74
+ email if email != ""
75
+ end
76
+
77
+ def get_email
78
+ email = ""
79
+ until email =~ /@/
80
+ email = Dotgpg.read_input "Email address: "
81
+ end
82
+ email
83
+ end
84
+
85
+ def get_passphrase
86
+ passphrase = confirmation = nil
87
+ until passphrase && passphrase == confirmation
88
+ times = 0
89
+ until passphrase && passphrase.length >= 10
90
+ times += 1
91
+ $stderr.puts "Passphrases should be secure! (>=10 chars)" if times >= 2
92
+ passphrase = Dotgpg.read_passphrase("Passphrase: ")
93
+ end
94
+ until confirmation && confirmation.length >= 10
95
+ confirmation = Dotgpg.read_passphrase("Passphrase confirmation: ")
96
+ end
97
+ end
98
+ passphrase
99
+ end
100
+
101
+ def existing_private_keys
102
+ GPGME::Key.find(:secret)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,20 @@
1
+ This directory contains GPG-encrypted files managed by `dotgpg`.
2
+
3
+ Getting started
4
+ ---------------
5
+
6
+ To read files in this directory, send the output of running `dotgpg key` to someone
7
+ who has access already. They will be able to run `dotgpg add` on your behalf.
8
+
9
+ Usage
10
+ -----
11
+
12
+ You can edit any file with `dotgpg edit FILE`, and read any file with `dotgpg cat FILE`.
13
+
14
+ The edit command looks at the value of `$EDITOR`, the internet will have a tutorial on
15
+ how to set this up with your favourite editor.
16
+
17
+ Details
18
+ -------
19
+
20
+ For more information, please see `dotgpg --help`, or the [README](https://github.com/ConradIrwin/dotgpg).
data/lib/dotgpg.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'pathname'
2
+ require 'fileutils'
3
+ require 'tempfile'
4
+ require 'shellwords'
5
+
6
+ require 'gpgme'
7
+ require 'thor'
8
+
9
+ require "dotgpg/key.rb"
10
+ require "dotgpg/dir.rb"
11
+ require "dotgpg/cli.rb"
12
+
13
+ class Dotgpg
14
+
15
+ class Failure < RuntimeError; end
16
+ class InvalidSignature < RuntimeError; end
17
+
18
+ # This method copied directly from Pry and is
19
+ # Copyright (c) 2013 John Mair (banisterfiend)
20
+ # https://github.com/pry/pry/blob/master/LICENSE
21
+ def self.editor
22
+ configured = ENV["VISUAL"] || ENV["EDITOR"] || guess_editor
23
+ case configured
24
+ when /^mate/, /^subl/
25
+ configured << " -w"
26
+ when /^[gm]vim/
27
+ configured << " --nofork"
28
+ when /^jedit/
29
+ configured << " -wait"
30
+ end
31
+
32
+ configured
33
+ end
34
+
35
+ def self.guess_editor
36
+ %w(subl sublime-text sensible-editor editor mate nano vim vi open).detect do |editor|
37
+ system("which #{editor} > /dev/null 2>&1")
38
+ end
39
+ end
40
+
41
+ def self.read_input(prompt)
42
+ $stderr.print prompt
43
+ $stderr.flush
44
+ $stdin.readline.strip
45
+ end
46
+
47
+ def self.read_passphrase(prompt)
48
+ `stty -echo`
49
+ read_input prompt
50
+ ensure
51
+ $stderr.print "\n"
52
+ `stty echo`
53
+ end
54
+
55
+ def self.interactive=(bool)
56
+ @interactive = bool
57
+ if interactive?
58
+ # get rid of stack trace on <ctrl-c>
59
+ trap(:INT){ exit 2 }
60
+ else
61
+ trap(:INT, "DEFAULT")
62
+ end
63
+ end
64
+
65
+ def self.interactive?
66
+ !!@interactive
67
+ end
68
+
69
+ # TODO: it'd be nice not to store the passphrase in
70
+ # plaintext in RAM.
71
+ def self.passphrase=(passphrase)
72
+ @passphrase = passphrase
73
+ end
74
+
75
+ def self.warn(context, error)
76
+ if interactive?
77
+ $stderr.puts "#{context}: #{error.message}"
78
+ else
79
+ puts "raising warning"
80
+ raise error
81
+ end
82
+ end
83
+
84
+ def self.passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd)
85
+ if interactive? && (!@passphrase || prev_was_bad != 0)
86
+ uid_hint = $1 if uid_hint =~ /<(.*)>/
87
+ @passphrase = read_passphrase "GPG passphrase for #{uid_hint}: "
88
+ elsif !@passphrase
89
+ raise "You must set Dotgpg.password or Dotgpg.interactive"
90
+ end
91
+
92
+ io = IO.for_fd(fd, 'w')
93
+ io.puts(@passphrase)
94
+ io.flush
95
+ end
96
+ end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,255 @@
1
+ require "./spec/helper"
2
+
3
+ describe Dotgpg::Cli do
4
+ before do
5
+ @dotgpg = Dotgpg::Cli.new
6
+ end
7
+
8
+ describe "init" do
9
+ it "should default to the current directory" do
10
+ $fixture.join("create-0").mkdir
11
+ Dir.chdir $fixture.join("create-0") do
12
+ @dotgpg.invoke(:init, [])
13
+ assert $fixture.join("create-0", ".gpg", "test@example.com").exist?
14
+ end
15
+ end
16
+
17
+ it "should create a .gpg directory" do
18
+ refute $fixture.join("create-1", ".gpg").exist?
19
+ @dotgpg.invoke(:init, [($fixture + "create-1").to_s])
20
+ assert $fixture.join("create-1", ".gpg").exist?
21
+ end
22
+
23
+ it "should add the user's secret key to the .gpg directory" do
24
+ @dotgpg.invoke(:init, [($fixture + "create-2").to_s])
25
+ assert_equal $fixture.join("create-2", ".gpg", "test@example.com").read, GPGME::Key.find(:secret).first.export(armor: true).to_s
26
+ end
27
+
28
+ it "should add a README to the directory" do
29
+ @dotgpg.invoke(:init, [($fixture + "create-3").to_s])
30
+ assert_equal $fixture.join("create-3", ".gpg", "test@example.com").read, GPGME::Key.find(:secret).first.export(armor: true).to_s
31
+ end
32
+
33
+ it "should fail if the .gpg directory already exists" do
34
+ FileUtils.mkdir_p $fixture + "create-4" + ".gpg"
35
+ assert_fails(/\.gpg already exists/) do
36
+ @dotgpg.invoke(:init, [($fixture + "create-4").to_s])
37
+ end
38
+ end
39
+
40
+ it "can succeed if the directory itself already exists" do
41
+ FileUtils.mkdir_p $fixture + "create-5"
42
+ @dotgpg.invoke(:init, [($fixture + "create-5").to_s])
43
+ assert $fixture.join("create-5", ".gpg").exist?
44
+ end
45
+ end
46
+
47
+ describe "key" do
48
+ it "should output the secret key" do
49
+ assert_outputs GPGME::Key.find(:secret).first.export(armor: true).to_s do
50
+ @dotgpg.invoke(:key)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "add" do
56
+ before do
57
+ @path = $fixture + rand.to_s.gsub(".", "")
58
+ @path.mkdir
59
+ Dir.chdir @path do
60
+ Dotgpg::Cli.new.invoke(:init, [])
61
+ end
62
+ end
63
+
64
+ it "should add the specified key" do
65
+ key_path = ($fixture + "add1.key").to_s
66
+ Dir.chdir @path do
67
+ @dotgpg.invoke(:add, [key_path])
68
+ end
69
+ assert Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read(key_path))
70
+ end
71
+
72
+ it "should abort if the current working directory is not dotgpg" do
73
+ key_path = ($fixture + "add1.key").to_s
74
+ assert_fails(/not in a dotgpg directory/) do
75
+ @dotgpg.invoke(:add, [key_path])
76
+ end
77
+ end
78
+
79
+ it "should abort if the key cannot be read" do
80
+ key_path = ($fixture + "no-add1.key").to_s
81
+ Dir.chdir @path do
82
+ assert_fails(/no-add1.key: not a valid GPG key/) do
83
+ @dotgpg.invoke(:add, [key_path])
84
+ end
85
+ end
86
+ end
87
+
88
+ it "should abort if the key already exists" do
89
+ key_path = ($fixture + "add1.key").to_s
90
+ Dir.chdir @path do
91
+ Dotgpg::Cli.new.invoke(:add, [key_path])
92
+
93
+ assert_fails(/add1@example.com: already exists/) do
94
+ @dotgpg.invoke(:add, [key_path])
95
+ end
96
+ end
97
+ end
98
+
99
+ it "should do nothing if the key exists and --force is specified" do
100
+ key_path = ($fixture + "add1.key").to_s
101
+ Dir.chdir @path do
102
+ Dotgpg::Cli.new.invoke(:add, [key_path])
103
+
104
+ @dotgpg.invoke(:add, [key_path], force: true)
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "rm" do
110
+ before do
111
+ @path = $fixture + rand.to_s.gsub(".", "")
112
+ @path.mkdir
113
+ Dir.chdir @path do
114
+ Dotgpg::Cli.new.invoke(:init, [])
115
+ Dotgpg::Cli.new.invoke(:add, [($fixture + "add1.key").to_s])
116
+ end
117
+ end
118
+
119
+ it "should remove the specified key" do
120
+ Dir.chdir @path do
121
+ @dotgpg.invoke :rm, [".gpg/add1@example.com"]
122
+ end
123
+ refute Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read($fixture + "add1.key"))
124
+ end
125
+
126
+ it "should find the key by email" do
127
+ Dir.chdir @path do
128
+ @dotgpg.invoke :rm, ["add1@example.com"]
129
+ end
130
+ refute Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read($fixture + "add1.key"))
131
+ end
132
+
133
+ it "should abort if the key doesn't exist" do
134
+ Dir.chdir @path do
135
+ assert_fails(/add2@example.com: not a valid GPG key/) do
136
+ @dotgpg.invoke :rm, ["add2@example.com"]
137
+ end
138
+ end
139
+ end
140
+
141
+ it "should abort if the key is the user's secret key" do
142
+ Dir.chdir @path do
143
+ assert_fails(/test@example.com: refusing to remove your own secret key/) do
144
+ @dotgpg.invoke :rm, ["test@example.com"]
145
+ end
146
+ end
147
+ end
148
+
149
+ it "should do nothing if they key doesn't exist and --force is specified" do
150
+ Dir.chdir @path do
151
+ @dotgpg.invoke :rm, ["add2@example.com"], force: true
152
+ end
153
+ end
154
+
155
+ it "should remove a secret key if --force is given" do
156
+ key = Dotgpg::Key.read(File.read(@path + ".gpg" + "test@example.com"))
157
+ Dir.chdir @path do
158
+ @dotgpg.invoke :rm, ["test@example.com"], force: true
159
+ end
160
+
161
+ refute Dotgpg::Dir.new(@path).has_key? key
162
+ end
163
+ end
164
+
165
+ describe "cat" do
166
+ before do
167
+ Dotgpg.passphrase = 'test'
168
+
169
+ @path = $fixture + rand.to_s.gsub(".", "")
170
+ Dotgpg::Cli.new.invoke(:init, [@path.to_s])
171
+ Dotgpg::Dir.new(@path).encrypt @path + "a", "Test\n"
172
+ end
173
+
174
+ it "should cat an existing encrypted file" do
175
+ assert_outputs "Test\n" do
176
+ @dotgpg.invoke :cat, [(@path + "a").to_s]
177
+ end
178
+ end
179
+
180
+ it "should warn if a file doesn't exist" do
181
+ assert_warns "#{@path + "b"}: No such file or directory" do
182
+ @dotgpg.invoke :cat, [(@path + "b").to_s]
183
+ end
184
+ end
185
+
186
+ it "should cat the existing files if a mixture is specified" do
187
+ assert_outputs "Test\n" do
188
+ assert_warns "#{@path + "b"}: No such file or directory" do
189
+ @dotgpg.invoke :cat, [(@path + "b").to_s, (@path + "a").to_s]
190
+ end
191
+ end
192
+ end
193
+
194
+ it "should fail if the file is not in a .gpg directory" do
195
+ assert_fails "not in a dotgpg directory" do
196
+ @dotgpg.invoke :cat, ["/tmp/b"]
197
+ end
198
+ end
199
+
200
+ it "should fail if the passphrase is wrong" do
201
+ Dotgpg.passphrase = 'wrong'
202
+ assert_fails "Bad passphrase" do
203
+ @dotgpg.invoke :cat, [(@path + "a").to_s]
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "edit" do
209
+ before do
210
+ Dotgpg.passphrase = 'test'
211
+
212
+ @path = $fixture + rand.to_s.gsub(".", "")
213
+ Dotgpg::Cli.new.invoke(:init, [@path.to_s])
214
+ Dotgpg::Dir.new(@path).encrypt @path + "a", "Bad test\n"
215
+
216
+ end
217
+
218
+ it "should let you edit an existing file" do
219
+ ENV['EDITOR'] = "sed -i '' s/Bad/Good/"
220
+ path = (@path + "a").to_s
221
+ @dotgpg.invoke(:edit, [path])
222
+ assert_outputs "Good test\n" do
223
+ Dotgpg::Dir.new(@path).decrypt path, $stdout
224
+ end
225
+ end
226
+
227
+ it "should open a non-existing file as blank" do
228
+ ENV['EDITOR'] = "ruby -e 'File.write(ARGV[0], %(Good test\n)) if File.read(ARGV[0]) == %()'"
229
+ path = (@path + "b").to_s
230
+ @dotgpg.invoke(:edit, [path])
231
+ assert_outputs "Good test\n" do
232
+ Dotgpg::Dir.new(@path).decrypt path, $stdout
233
+ end
234
+ end
235
+
236
+ it "should warn if a file cannot be decrypted" do
237
+ File.write(@path + "d", "not encrypted...")
238
+ path = (@path + "d").to_s
239
+ assert_warns "#{@path + "d"}: No data" do
240
+ @dotgpg.invoke(:edit, [path])
241
+ end
242
+ end
243
+
244
+ it "should fail if invoking the editor doesn't work" do
245
+ ENV['EDITOR'] = 'not-an-editor'
246
+ assert_fails "Problem with editor. Not saving changes" do
247
+ @dotgpg.invoke :edit, [(@path + "a").to_s]
248
+ end
249
+ end
250
+
251
+ it "should edit the existing files if a mixture is specified" do
252
+
253
+ end
254
+ end
255
+ end
data/spec/dir_spec.rb ADDED
@@ -0,0 +1,214 @@
1
+ require "./spec/helper"
2
+
3
+ describe Dotgpg::Dir do
4
+ before do
5
+ @dir = Dotgpg::Dir.new($basic)
6
+ end
7
+
8
+ describe ".closest" do
9
+ it "should find the current directory" do
10
+ Dir.chdir $basic do
11
+ assert_equal @dir, Dotgpg::Dir.closest(".")
12
+ end
13
+ end
14
+
15
+ it "should find a specified directory" do
16
+ assert_equal @dir, Dotgpg::Dir.closest($basic)
17
+ end
18
+
19
+ it "should find the directory containing the given file" do
20
+ assert_equal @dir, Dotgpg::Dir.closest($basic + "a")
21
+ end
22
+
23
+ it "should find the ancestor of the directory containing the given file" do
24
+ assert_equal @dir, Dotgpg::Dir.closest($basic + "b" + "c")
25
+ end
26
+
27
+ it "should not find a directory that does not exist" do
28
+ assert_nil Dotgpg::Dir.closest("/tmp")
29
+ end
30
+
31
+ it "should not find a directory that only some of the files are in" do
32
+ assert_equal nil, Dotgpg::Dir.closest($basic, "/tmp")
33
+ end
34
+
35
+ it "should find a directory that all of the files are in" do
36
+ assert_equal @dir, Dotgpg::Dir.closest($basic, $basic + "a", $basic + "b" + "c")
37
+ end
38
+ end
39
+
40
+ describe "dotgpg?" do
41
+ it 'should be true in a directory managed by dotgpg' do
42
+ assert_equal true, @dir.dotgpg?
43
+ end
44
+
45
+ it 'should not be true in a random directory' do
46
+ assert_equal false, Dotgpg::Dir.new(".").dotgpg?
47
+ end
48
+
49
+ it "should not be true in a directory that doesn't exist" do
50
+ assert_equal false, Dotgpg::Dir.new(rand.to_s).dotgpg?
51
+ end
52
+ end
53
+
54
+ describe "known_keys" do
55
+ before do
56
+ @keys = @dir.known_keys
57
+ end
58
+
59
+ it "should return private keys in the truststore" do
60
+ assert_includes @keys, GPGME::Key.find(:secret, "test@example.com").first
61
+ end
62
+
63
+ it "should return public keys not yet in the truststore" do
64
+ assert_includes @keys.map(&:email), "test2@example.com"
65
+ end
66
+
67
+ it "should return public keys in the truststore" do
68
+ assert_includes @keys.map(&:email), "test2@example.com"
69
+ end
70
+ end
71
+
72
+ describe "all_encrypted_files" do
73
+ before do
74
+ @files = @dir.all_encrypted_files
75
+ end
76
+
77
+ it "should find files in the top-level" do
78
+ assert_includes @files, $basic + "a"
79
+ end
80
+
81
+ it "should find files in sub-directories" do
82
+ assert_includes @files, $basic + "b" + "c"
83
+ end
84
+
85
+ it "should not find unencrypted files" do
86
+ readme = $basic + "README.md"
87
+ assert readme.exist?
88
+ refute_includes @files, $basic + "README.md"
89
+ end
90
+
91
+ it "should not find files through symlinks" do
92
+ duplicate_a = $basic + "c" + "basic" + "a"
93
+ assert duplicate_a.exist?
94
+ refute_includes @files, duplicate_a
95
+ end
96
+ end
97
+
98
+ describe "decrypt" do
99
+ before do
100
+ Dotgpg.passphrase = "test"
101
+ end
102
+
103
+ it "should be able to decrypt files for which the secret is known" do
104
+ s = StringIO.new
105
+ @dir.decrypt $basic + "a", s
106
+ s.rewind
107
+ assert_equal "Test\n", s.read
108
+ end
109
+
110
+ it "should warn if the file cannot be read" do
111
+ assert_warns "#{$basic + "404"}: No such file or directory" do
112
+ @dir.decrypt $basic + "404", StringIO.new
113
+ end
114
+ end
115
+
116
+ it "should warn if the file cannot be decrypted" do
117
+ assert_warns "#{$basic + "README.md"}: No data" do
118
+ @dir.decrypt $basic + "README.md", StringIO.new
119
+ end
120
+ end
121
+
122
+ it "should raise on bad passphrase" do
123
+ Dotgpg.passphrase = 'wrong'
124
+ assert_raises GPGME::Error::BadPassphrase do
125
+ assert_warns nil do
126
+ @dir.decrypt $basic + "a", StringIO.new
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "encrypt" do
133
+ before do
134
+ Dotgpg.passphrase = 'test'
135
+ end
136
+
137
+ after do
138
+ FileUtils.rm_f $basic + "test-armor"
139
+ FileUtils.rm_f $basic + "test-recipients"
140
+ end
141
+
142
+ it "should armor files" do
143
+ @dir.encrypt $basic + "test-armor", 'test'
144
+ assert_match(/-----BEGIN PGP MESSAGE-----/, File.read($basic + "test-armor"))
145
+ end
146
+
147
+ it "should encrypt files to all recipients" do
148
+ @dir.encrypt $basic + "test-recipients", 'test'
149
+
150
+ ["D1B8548C844F4881", "54907534D1B5A86B", "8490321363F14C03"].each do |keyid|
151
+ assert_contains_keyid keyid, File.read($basic + "test-recipients")
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "add_key" do
157
+ before do
158
+ Dotgpg.passphrase = 'test'
159
+ end
160
+
161
+ it "should create the file in the .gpg directory" do
162
+ add1 = Dotgpg::Key.read $fixture + "add1.key"
163
+ refute $basic.join(".gpg", "add1@example.com").exist?
164
+ @dir.add_key add1
165
+ assert $basic.join(".gpg", "add1@example.com").exist?
166
+ end
167
+
168
+ it "should add the key as a recipient on all the files" do
169
+ add2 = Dotgpg::Key.read $fixture + "add2.key"
170
+ refute_contains_keyid add2.subkeys.last.keyid, File.read($basic + "a")
171
+ @dir.add_key add2
172
+ assert_contains_keyid add2.subkeys.last.keyid, File.read($basic + "a")
173
+ end
174
+
175
+ it "should not add the key if re-encryption fails" do
176
+ Dotgpg.passphrase = 'wrong'
177
+ add3 = Dotgpg::Key.read $fixture + "add3.key"
178
+ assert_raises GPGME::Error::BadPassphrase do
179
+ @dir.add_key add3
180
+ end
181
+ refute $basic.join(".gpg", "add3@example.com").exist?
182
+ refute_contains_keyid add3.subkeys.last.keyid, File.read($basic + "a")
183
+ end
184
+ end
185
+
186
+ describe "remove_key" do
187
+ before do
188
+ Dotgpg.passphrase = 'test'
189
+ end
190
+
191
+ it "should remove the file from the .gpg directory" do
192
+ removed1 = Dotgpg::Key.read $basic.join(".gpg", "removed1@example.com")
193
+ assert $basic.join(".gpg", "removed1@example.com").exist?
194
+ @dir.remove_key removed1
195
+ refute $basic.join(".gpg", "removed1@example.com").exist?
196
+ end
197
+
198
+ it "should remove the key as a recipient from all the files" do
199
+ removed2 = Dotgpg::Key.read $basic.join(".gpg", "removed2@example.com")
200
+ assert_contains_keyid removed2.subkeys.last.keyid, File.read($basic + "a")
201
+ @dir.remove_key removed2
202
+ refute_contains_keyid removed2.subkeys.last.keyid, File.read($basic + "a")
203
+ end
204
+
205
+ it "should not remove the key if re-encryption fails" do
206
+ Dotgpg.passphrase = 'wrong'
207
+ removed3 = Dotgpg::Key.read $basic.join(".gpg", "removed3@example.com")
208
+ assert_raises GPGME::Error::BadPassphrase do
209
+ @dir.add_key removed3
210
+ end
211
+ assert $basic.join(".gpg", "removed3@example.com").exist?
212
+ end
213
+ end
214
+ end