class Dotgpg::Dir

Attributes

path[R]

Public Class Methods

closest(path=".", *others) click to toggle source

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.

@param [*Array<String>] paths @return {nil|}

# 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
new(path) click to toggle source

Open a Dotgpg::Dir

@param [String] path The location of the directory

# File lib/dotgpg/dir.rb, line 34
def initialize(path)
  @path = Pathname.new(File.absolute_path(path)).cleanpath
end

Public Instance Methods

==(other) click to toggle source
# File lib/dotgpg/dir.rb, line 216
def ==(other)
  Dotgpg::Dir === other && other.path == self.path
end
add_key(key) click to toggle source

Add a given key to the directory

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

@param [GPGME::Key]

# 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) click to toggle source

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.

@param [Pathname] dir @return [Array<Pathname>]

# 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) click to toggle source

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.

@param [Pathname] path The file to decrypt @param [IO] output The IO to write to @return [Boolean] false if decryption failed for an understandable reason

# 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
dotgpg() click to toggle source

The .gpg directory

@return [Pathname]

# File lib/dotgpg/dir.rb, line 205
def dotgpg
  path + ".gpg"
end
dotgpg?() click to toggle source

Does the .gpg directory exist?

@return [Boolean]

# File lib/dotgpg/dir.rb, line 212
def dotgpg?
  dotgpg.directory?
end
encrypt(path, input) click to toggle source

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.

@param [Pathname] path The desired destination @param [IO] input The IO containing the plaintext @return [Boolean] false if encryption failed for an understandable reason

# 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) click to toggle source

Does this directory includea key for the given user yet?

@param [GPGME::Key] @return [Boolean]

# File lib/dotgpg/dir.rb, line 166
def has_key?(key)
  File.exist? key_path(key)
end
key_path(key) click to toggle source

The path at which a key should be stored

(i.e. .gpg/me@cirw.in)

@param [GPGME::Key] @return [Pathname]

# File lib/dotgpg/dir.rb, line 198
def key_path(key)
  dotgpg + key.email
end
known_keys() click to toggle source

Get the keys currently associated with this directory.

@return [Array<GPGME::Key>]

# 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) { |tempfiles| ... } click to toggle source

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.

@param [Array<Pathname>] files the files to re-encrypt @yieldparam [Hash<Pathname, Tempfile>] the unencrypted files for each param

# 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) click to toggle source

Remove a given key from a directory

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

@param [GPGME::Key]

# File lib/dotgpg/dir.rb, line 186
def remove_key(key)
  reencrypt all_encrypted_files do
    key_path(key).unlink
  end
end