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



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

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)


175
176
177
178
179
# File 'lib/dotgpg/dir.rb', line 175

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>)


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

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
# File 'lib/dotgpg/dir.rb', line 55

def decrypt(path, output)
  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)


205
206
207
# File 'lib/dotgpg/dir.rb', line 205

def dotgpg
  path + ".gpg"
end

#dotgpg?Boolean

Does the .gpg directory exist?

Returns:

  • (Boolean)


212
213
214
# File 'lib/dotgpg/dir.rb', line 212

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



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/dotgpg/dir.rb', line 84

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)


166
167
168
# File 'lib/dotgpg/dir.rb', line 166

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)


198
199
200
# File 'lib/dotgpg/dir.rb', line 198

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



107
108
109
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
# File 'lib/dotgpg/dir.rb', line 107

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)


186
187
188
189
190
# File 'lib/dotgpg/dir.rb', line 186

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