dotenv-gpg 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +33 -0
- data/README.md +132 -0
- data/Rakefile +7 -0
- data/bin/dotgpg +5 -0
- data/dotenv-gpg.gemspec +22 -0
- data/lib/dotgpg.rb +96 -0
- data/lib/dotgpg/cli.rb +194 -0
- data/lib/dotgpg/dir.rb +220 -0
- data/lib/dotgpg/key.rb +105 -0
- data/lib/dotgpg/template/README.md +20 -0
- data/spec/cli_spec.rb +255 -0
- data/spec/dir_spec.rb +214 -0
- data/spec/fixture/add1.key +30 -0
- data/spec/fixture/add2.key +30 -0
- data/spec/fixture/add3.key +30 -0
- data/spec/fixture/basic/.gpg/removed1@example.com +30 -0
- data/spec/fixture/basic/.gpg/removed2@example.com +30 -0
- data/spec/fixture/basic/.gpg/removed3@example.com +0 -0
- data/spec/fixture/basic/.gpg/test2@example.com +31 -0
- data/spec/fixture/basic/.gpg/test3@example.com +31 -0
- data/spec/fixture/basic/.gpg/test@example.com +31 -0
- data/spec/fixture/basic/README.md +28 -0
- data/spec/fixture/basic/a +47 -0
- data/spec/fixture/basic/b/c +47 -0
- data/spec/fixture/gnupghome/pubring.gpg +0 -0
- data/spec/fixture/gnupghome/pubring.gpg~ +0 -0
- data/spec/fixture/gnupghome/random_seed +0 -0
- data/spec/fixture/gnupghome/secring.gpg +0 -0
- data/spec/fixture/gnupghome/trustdb.gpg +0 -0
- data/spec/fixture/secret1.key +31 -0
- data/spec/helper.rb +19 -0
- data/spec/helper/assertions.rb +54 -0
- metadata +136 -0
data/lib/dotgpg/dir.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
class Dotgpg
|
2
|
+
class Dir
|
3
|
+
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
# Find the Dotgpg::Dir that contains the given path.
|
7
|
+
#
|
8
|
+
# If multiple are given only returns the directory if it contains all
|
9
|
+
# paths.
|
10
|
+
#
|
11
|
+
# If no path is given, find the Dotgpg::Dir that contains the current
|
12
|
+
# working directory.
|
13
|
+
#
|
14
|
+
# @param [*Array<String>] paths
|
15
|
+
# @return {nil|[Dotgpg::Dir]}
|
16
|
+
def self.closest(path=".", *others)
|
17
|
+
path = Pathname.new(File.absolute_path(path)).cleanpath
|
18
|
+
|
19
|
+
result = path.ascend do |parent|
|
20
|
+
maybe = Dotgpg::Dir.new(parent)
|
21
|
+
break maybe if maybe.dotgpg?
|
22
|
+
end
|
23
|
+
|
24
|
+
if others.any? && closest(*others) != result
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Open a Dotgpg::Dir
|
32
|
+
#
|
33
|
+
# @param [String] path The location of the directory
|
34
|
+
def initialize(path)
|
35
|
+
@path = Pathname.new(File.absolute_path(path)).cleanpath
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the keys currently associated with this directory.
|
39
|
+
#
|
40
|
+
# @return [Array<GPGME::Key>]
|
41
|
+
def known_keys
|
42
|
+
dotgpg.each_child.map do |key_file|
|
43
|
+
Dotgpg::Key.read key_file.open
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Decrypt the contents of path and write to output.
|
48
|
+
#
|
49
|
+
# The path should be absolute, and may point to outside
|
50
|
+
# this directory, though that is not recommended.
|
51
|
+
#
|
52
|
+
# @param [Pathname] path The file to decrypt
|
53
|
+
# @param [IO] output The IO to write to
|
54
|
+
# @return [Boolean] false if decryption failed for an understandable reason
|
55
|
+
def decrypt(path, output)
|
56
|
+
File.open(path) do |f|
|
57
|
+
signature = false
|
58
|
+
temp = GPGME::Crypto.new.decrypt f, passphrase_callback: Dotgpg.method(:passfunc) do |s|
|
59
|
+
signature = s
|
60
|
+
end
|
61
|
+
|
62
|
+
unless ENV["DOTGPG_ALLOW_INJECTION_ATTACK"]
|
63
|
+
raise InvalidSignature, "file was not signed" unless signature
|
64
|
+
raise InvalidSignature, "signature was incorrect" unless signature.valid?
|
65
|
+
raise InvalidSignature, "signed by a stranger" unless known_keys.include?(signature.key)
|
66
|
+
end
|
67
|
+
|
68
|
+
output.write temp.read
|
69
|
+
end
|
70
|
+
true
|
71
|
+
rescue GPGME::Error::NoData, GPGME::Error::DecryptFailed, SystemCallError => e
|
72
|
+
Dotgpg.warn path, e
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Encrypt the input and write it to the given path.
|
77
|
+
#
|
78
|
+
# The path should be absolute, and may point to outside
|
79
|
+
# this directory, though that is not recommended.
|
80
|
+
#
|
81
|
+
# @param [Pathname] path The desired destination
|
82
|
+
# @param [IO] input The IO containing the plaintext
|
83
|
+
# @return [Boolean] false if encryption failed for an understandable reason
|
84
|
+
def encrypt(path, input)
|
85
|
+
File.open(path, "w") do |f|
|
86
|
+
GPGME::Crypto.new.encrypt input, output: f,
|
87
|
+
recipients: known_keys,
|
88
|
+
armor: true,
|
89
|
+
always_trust: true,
|
90
|
+
sign: true,
|
91
|
+
passphrase_callback: Dotgpg.method(:passfunc),
|
92
|
+
signers: known_keys.detect{ |key| GPGME::Key.find(:secret).include?(key) }
|
93
|
+
end
|
94
|
+
true
|
95
|
+
rescue SystemCallError => e
|
96
|
+
Dotgpg.warn path, e
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
# Re-encrypts a set of files with the currently known keys.
|
101
|
+
#
|
102
|
+
# If a block is provided, it can be used to edit the files in
|
103
|
+
# their temporary un-encrypted state.
|
104
|
+
#
|
105
|
+
# @param [Array<Pathname>] files the files to re-encrypt
|
106
|
+
# @yieldparam [Hash<Pathname, Tempfile>] the unencrypted files for each param
|
107
|
+
def reencrypt(files, &block)
|
108
|
+
tempfiles = {}
|
109
|
+
|
110
|
+
files.uniq.each do |f|
|
111
|
+
temp = Tempfile.new([File.basename(f), ".sh"])
|
112
|
+
tempfiles[f] = temp
|
113
|
+
if File.exist? f
|
114
|
+
decrypted = decrypt f, temp
|
115
|
+
tempfiles.delete f unless decrypted
|
116
|
+
end
|
117
|
+
temp.flush
|
118
|
+
temp.close(false)
|
119
|
+
end
|
120
|
+
|
121
|
+
yield tempfiles if block_given?
|
122
|
+
|
123
|
+
tempfiles.each_pair do |f, temp|
|
124
|
+
temp.open
|
125
|
+
temp.seek(0)
|
126
|
+
encrypt f, temp
|
127
|
+
end
|
128
|
+
|
129
|
+
nil
|
130
|
+
ensure
|
131
|
+
tempfiles.values.each do |temp|
|
132
|
+
temp.close(true)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# List every GPG-encrypted file in a directory recursively.
|
137
|
+
#
|
138
|
+
# Assumes the files are armored (non-armored files are hard to detect and
|
139
|
+
# dotgpg itself always armors)
|
140
|
+
#
|
141
|
+
# This is used to decide which files to re-encrypt when adding a user.
|
142
|
+
#
|
143
|
+
# @param [Pathname] dir
|
144
|
+
# @return [Array<Pathname>]
|
145
|
+
def all_encrypted_files(dir=path)
|
146
|
+
results = []
|
147
|
+
dir.each_child do |child|
|
148
|
+
if child.directory?
|
149
|
+
if !child.symlink? && child != dotgpg
|
150
|
+
results += all_encrypted_files(child)
|
151
|
+
end
|
152
|
+
elsif child.readable?
|
153
|
+
if child.read(1024) =~ /-----BEGIN PGP MESSAGE-----/
|
154
|
+
results << child
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
results
|
160
|
+
end
|
161
|
+
|
162
|
+
# Does this directory includea key for the given user yet?
|
163
|
+
#
|
164
|
+
# @param [GPGME::Key]
|
165
|
+
# @return [Boolean]
|
166
|
+
def has_key?(key)
|
167
|
+
File.exist? key_path(key)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Add a given key to the directory
|
171
|
+
#
|
172
|
+
# Re-encrypts all files to add the new key as a recipient.
|
173
|
+
#
|
174
|
+
# @param [GPGME::Key]
|
175
|
+
def add_key(key)
|
176
|
+
reencrypt all_encrypted_files do
|
177
|
+
File.write key_path(key), key.export(armor: true).to_s
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Remove a given key from a directory
|
182
|
+
#
|
183
|
+
# Re-encrypts all files so that the removed key no-longer has access.
|
184
|
+
#
|
185
|
+
# @param [GPGME::Key]
|
186
|
+
def remove_key(key)
|
187
|
+
reencrypt all_encrypted_files do
|
188
|
+
key_path(key).unlink
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# The path at which a key should be stored
|
193
|
+
#
|
194
|
+
# (i.e. .gpg/me@cirw.in)
|
195
|
+
#
|
196
|
+
# @param [GPGME::Key]
|
197
|
+
# @return [Pathname]
|
198
|
+
def key_path(key)
|
199
|
+
dotgpg + key.email
|
200
|
+
end
|
201
|
+
|
202
|
+
# The .gpg directory
|
203
|
+
#
|
204
|
+
# @return [Pathname]
|
205
|
+
def dotgpg
|
206
|
+
path + ".gpg"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Does the .gpg directory exist?
|
210
|
+
#
|
211
|
+
# @return [Boolean]
|
212
|
+
def dotgpg?
|
213
|
+
dotgpg.directory?
|
214
|
+
end
|
215
|
+
|
216
|
+
def ==(other)
|
217
|
+
Dotgpg::Dir === other && other.path == self.path
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
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/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
|