Class: Dotgpg::Dir

Inherits:
Object
  • Object
show all
Defined in:
lib/dotgpg/dir.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Dir

Open a Dotgpg::Dir

Parameters:

  • path (String)

    The location of the directory



34
35
36
# File 'lib/dotgpg/dir.rb', line 34

def initialize(path)
  @path = Pathname.new(File.absolute_path(path)).cleanpath
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



4
5
6
# File 'lib/dotgpg/dir.rb', line 4

def path
  @path
end

Class Method Details

.closest(path = ".", *others) ⇒ nil|[Dotgpg::Dir]

Find the Dotgpg::Dir that contains the given path.

If multiple are given only returns the directory if it contains all paths.

If no path is given, find the Dotgpg::Dir that contains the current working directory.

Parameters:

  • paths (*Array<String>)

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/dotgpg/dir.rb', line 16

def self.closest(path=".", *others)
  path = Pathname.new(File.absolute_path(path)).cleanpath

  result = path.ascend do |parent|
            maybe = Dotgpg::Dir.new(parent)
            break maybe if maybe.dotgpg?
          end

  if others.any? && closest(*others) != result
    nil
  else
    result
  end
end

Instance Method Details

#==(other) ⇒ Object



219
220
221
# File 'lib/dotgpg/dir.rb', line 219

def ==(other)
  Dotgpg::Dir === other && other.path == self.path
end

#add_key(key) ⇒ Object

Add a given key to the directory

Re-encrypts all files to add the new key as a recipient.

Parameters:

  • (GPGME::Key)


178
179
180
181
182
# File 'lib/dotgpg/dir.rb', line 178

def add_key(key)
  reencrypt all_encrypted_files do
    File.write key_path(key), key.export(armor: true).to_s
  end
end

#all_encrypted_files(dir = path) ⇒ Array<Pathname>

List every GPG-encrypted file in a directory recursively.

Assumes the files are armored (non-armored files are hard to detect and dotgpg itself always armors)

This is used to decide which files to re-encrypt when adding a user.

Parameters:

  • dir (Pathname) (defaults to: path)

Returns:

  • (Array<Pathname>)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/dotgpg/dir.rb', line 148

def all_encrypted_files(dir=path)
  results = []
  dir.each_child do |child|
    if child.directory?
      if !child.symlink? && child != dotgpg
        results += all_encrypted_files(child)
      end
    elsif child.readable?
      if child.read(1024) =~ /-----BEGIN PGP MESSAGE-----/
        results << child
      end
    end
  end

  results
end

#decrypt(path, output) ⇒ Boolean

Decrypt the contents of path and write to output.

The path should be absolute, and may point to outside this directory, though that is not recommended.

Parameters:

  • path (Pathname)

    The file to decrypt

  • output (IO)

    The IO to write to

Returns:

  • (Boolean)

    false if decryption failed for an understandable reason



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/dotgpg/dir.rb', line 55

def decrypt(path, output)
  # make sure all keys are imported before we try to decrypt - otherwise we
  # might fail with an invalid signature
  known_keys
  File.open(path) do |f|
    signature = false
    temp = GPGME::Crypto.new.decrypt f, passphrase_callback: Dotgpg.method(:passfunc) do |s|
      signature = s
    end

    unless ENV["DOTGPG_ALLOW_INJECTION_ATTACK"]
      raise InvalidSignature, "file was not signed" unless signature
      raise InvalidSignature, "signature was incorrect" unless signature.valid?
      raise InvalidSignature, "signed by a stranger" unless known_keys.include?(signature.key)
    end

    output.write temp.read
  end
  true
rescue GPGME::Error::NoData, GPGME::Error::DecryptFailed, SystemCallError => e
  Dotgpg.warn path, e
  false
end

#dotgpgPathname

The .gpg directory

Returns:

  • (Pathname)


208
209
210
# File 'lib/dotgpg/dir.rb', line 208

def dotgpg
  path + ".gpg"
end

#dotgpg?Boolean

Does the .gpg directory exist?

Returns:

  • (Boolean)


215
216
217
# File 'lib/dotgpg/dir.rb', line 215

def dotgpg?
  dotgpg.directory?
end

#encrypt(path, input) ⇒ Boolean

Encrypt the input and write it to the given path.

The path should be absolute, and may point to outside this directory, though that is not recommended.

Parameters:

  • path (Pathname)

    The desired destination

  • input (IO)

    The IO containing the plaintext

Returns:

  • (Boolean)

    false if encryption failed for an understandable reason



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/dotgpg/dir.rb', line 87

def encrypt(path, input)
  File.open(path, "w") do |f|
    GPGME::Crypto.new.encrypt input, output: f,
        recipients: known_keys,
        armor: true,
        always_trust: true,
        sign: true,
        passphrase_callback: Dotgpg.method(:passfunc),
        signers: known_keys.detect{ |key| GPGME::Key.find(:secret).include?(key) }
  end
  true
rescue SystemCallError => e
  Dotgpg.warn path, e
  false
end

#has_key?(key) ⇒ Boolean

Does this directory includea key for the given user yet?

Parameters:

  • (GPGME::Key)

Returns:

  • (Boolean)


169
170
171
# File 'lib/dotgpg/dir.rb', line 169

def has_key?(key)
  File.exist? key_path(key)
end

#key_path(key) ⇒ Pathname

The path at which a key should be stored

(i.e. .gpg/[email protected])

Parameters:

  • (GPGME::Key)

Returns:

  • (Pathname)


201
202
203
# File 'lib/dotgpg/dir.rb', line 201

def key_path(key)
  dotgpg + key.email
end

#known_keysArray<GPGME::Key>

Get the keys currently associated with this directory.

Returns:

  • (Array<GPGME::Key>)


41
42
43
44
45
# File 'lib/dotgpg/dir.rb', line 41

def known_keys
  dotgpg.each_child.map do |key_file|
    Dotgpg::Key.read key_file.open
  end
end

#reencrypt(files) {|the| ... } ⇒ Object

Re-encrypts a set of files with the currently known keys.

If a block is provided, it can be used to edit the files in their temporary un-encrypted state.

Parameters:

  • files (Array<Pathname>)

    the files to re-encrypt

Yield Parameters:

  • the (Hash<Pathname, Tempfile>)

    unencrypted files for each param



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/dotgpg/dir.rb', line 110

def reencrypt(files, &block)
  tempfiles = {}

  files.uniq.each do |f|
    temp = Tempfile.new([File.basename(f), ".sh"])
    tempfiles[f] = temp
    if File.exist? f
      decrypted =  decrypt f, temp
      tempfiles.delete f unless decrypted
    end
    temp.flush
    temp.close(false)
  end

  yield tempfiles if block_given?

  tempfiles.each_pair do |f, temp|
    temp.open
    temp.seek(0)
    encrypt f, temp
  end

  nil
ensure
  tempfiles.values.each do |temp|
    temp.close(true)
  end
end

#remove_key(key) ⇒ Object

Remove a given key from a directory

Re-encrypts all files so that the removed key no-longer has access.

Parameters:

  • (GPGME::Key)


189
190
191
192
193
# File 'lib/dotgpg/dir.rb', line 189

def remove_key(key)
  reencrypt all_encrypted_files do
    key_path(key).unlink
  end
end