class Dotgpg::Dir
Attributes
Public Class Methods
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
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
# File lib/dotgpg/dir.rb, line 219 def ==(other) Dotgpg::Dir === other && other.path == self.path end
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 178 def add_key(key) reencrypt all_encrypted_files do File.write key_path(key), key.export(armor: true).to_s end end
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 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 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) # 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
The .gpg directory
@return [Pathname]
# File lib/dotgpg/dir.rb, line 208 def dotgpg path + ".gpg" end
Does the .gpg directory exist?
@return [Boolean]
# File lib/dotgpg/dir.rb, line 215 def dotgpg? dotgpg.directory? end
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 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
Does this directory includea key for the given user yet?
@param [GPGME::Key] @return [Boolean]
# File lib/dotgpg/dir.rb, line 169 def has_key?(key) File.exist? key_path(key) end
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 201 def key_path(key) dotgpg + key.email end
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
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 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 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 189 def remove_key(key) reencrypt all_encrypted_files do key_path(key).unlink end end