lib/io_streams/pgp.rb in iostreams-0.12.0 vs lib/io_streams/pgp.rb in iostreams-0.12.1

- old
+ new

@@ -24,11 +24,11 @@ # # # Decrypt the file sent to `receiver@example.org` using its private key # # Recipient must also have the senders public key to verify the signature # IOStreams::Pgp::Reader.open('secure.gpg', passphrase: 'receiver_passphrase') do |stream| # while !stream.eof? - # ap stream.read(10) + # p stream.read(10) # puts # end # end # # Example 2: @@ -50,11 +50,11 @@ # # # Decrypt the file sent to `receiver@example.org` using its private key # # Recipient must also have the senders public key to verify the signature # IOStreams.reader('secure.gpg') do |stream| # while data = stream.read(10) - # ap data + # p data # end # end # # FAQ: # - If you get not trusted errors @@ -75,20 +75,34 @@ # Input file: test.log 3.6GB # :none: size: 3.6GB write: 52s read: 45s # :zip: size: 411MB write: 75s read: 31s # :zlib: size: 241MB write: 66s read: 23s ( 756KB Memory ) # :bzip2: size: 129MB write: 430s read: 130s ( 5MB Memory ) + # + # Notes: + # - Tested against gnupg v1.4.21 and v2.0.30 + # - Does not work yet with gnupg v2.1. Pull Requests welcome. module Pgp autoload :Reader, 'io_streams/pgp/reader' autoload :Writer, 'io_streams/pgp/writer' class Failure < StandardError end class UnsupportedVersion < Failure end + def self.executable + @executable + end + + def self.executable=(executable) + @executable = executable + end + + @executable = 'gpg' + # Generate a new ultimate trusted local public and private key. # # Returns [String] the key id for the generated key. # Raises an exception if it fails to generate the key. # @@ -119,11 +133,11 @@ params << "Name-Comment: #{comment}\n" if comment params << "Name-Email: #{email}\n" if email params << "Expire-Date: #{expire_date}\n" if expire_date params << "Passphrase: #{passphrase}\n" if passphrase params << '%commit' - out, err, status = Open3.capture3('gpg --batch --gen-key', binmode: true, stdin_data: params) + out, err, status = Open3.capture3("#{executable} --batch --gen-key", binmode: true, stdin_data: params) logger.debug { "IOStreams::Pgp.generate_key output:\n#{out}#{err}" } if logger if status.success? if match = err.match(/gpg: key ([0-9A-F]+)\s+/) return match[1] end @@ -147,12 +161,12 @@ # Whether to delete the private key # Default: false def self.delete_keys(email:, public: true, private: false) version_check cmd = "for i in `gpg --with-colons --fingerprint #{email} | grep \"^fpr\" | cut -d: -f10`; do\n" - cmd << "gpg --batch --delete-secret-keys \"$i\" ;\n" if private - cmd << "gpg --batch --delete-keys \"$i\" ;\n" if public + cmd << "#{executable} --batch --delete-secret-keys \"$i\" ;\n" if private + cmd << "#{executable} --batch --delete-keys \"$i\" ;\n" if public cmd << 'done' out, err, status = Open3.capture3(cmd, binmode: true) logger.debug { "IOStreams::Pgp.delete_keys output:\n#{err}#{out}" } if logger @@ -182,20 +196,24 @@ # email: [String] # Returns [] if no keys were found. def self.list_keys(email: nil, key_id: nil, private: false) version_check cmd = private ? '--list-secret-keys' : '--list-keys' - out, err, status = Open3.capture3("gpg #{cmd} #{email || key_id}", binmode: true) + out, err, status = Open3.capture3("#{executable} #{cmd} #{email || key_id}", binmode: true) logger.debug { "IOStreams::Pgp.list_keys output:\n#{err}#{out}" } if logger if status.success? && out.length > 0 - # Sample output + # v2.0.30 output: # pub 4096R/3A5456F5 2017-06-07 # uid [ unknown] Joe Bloggs <j@bloggs.net> # sub 4096R/2C9B240B 2017-06-07 + # v1.4 output: + # sec 2048R/27D2E7FA 2016-10-05 + # uid Receiver <receiver@example.org> + # ssb 2048R/893749EA 2016-10-05 parse_list_output(out) else - return [] if err =~ /(key not found|No (public|secret) key)/i + return [] if err =~ /(key not found|No (public|secret) key|key not available)/i raise(Pgp::Failure, "GPG Failed calling gpg to list keys for #{email || key_id}: #{err}#{out}") end end # Extract information from the supplied key. @@ -210,11 +228,11 @@ # date: [String] # name: [String] # email: [String] def self.key_info(key:) version_check - out, err, status = Open3.capture3('gpg', binmode: true, stdin_data: key) + out, err, status = Open3.capture3(executable, binmode: true, stdin_data: key) logger.debug { "IOStreams::Pgp.key_info output:\n#{err}#{out}" } if logger if status.success? && out.length > 0 # Sample Output: # # pub 4096R/3A5456F5 2017-06-07 @@ -237,13 +255,13 @@ # private: [true|false] # Whether to export the private key # Default: false def self.export(email:, ascii: true, private: false) version_check - armor = ascii ? ' --armor' : nil + armor = ascii ? '--armor' : nil cmd = private ? '--export-secret-keys' : '--export' - out, err, status = Open3.capture3("gpg#{armor} #{cmd} #{email}", binmode: true) + out, err, status = Open3.capture3("#{executable} #{armor} #{cmd} #{email}", binmode: true) logger.debug { "IOStreams::Pgp.export output:\n#{err}" } if logger if status.success? && out.length > 0 out else raise(Pgp::Failure, "GPG Failed reading key: #{email}: #{err}") @@ -265,11 +283,11 @@ # Notes: # * Importing a new key for the same email address does not remove the prior key if any. # * Invalidated keys must be removed manually. def self.import(key:) version_check - out, err, status = Open3.capture3('gpg --import', binmode: true, stdin_data: key) + out, err, status = Open3.capture3("#{executable} --import", binmode: true, stdin_data: key) logger.debug { "IOStreams::Pgp.import output:\n#{err}#{out}" } if logger if status.success? && err.length > 0 # Sample output # # gpg: key C16500E3: secret key imported\n" @@ -314,11 +332,11 @@ version_check fingerprint = fingerprint(email: email) return unless fingerprint trust = "#{fingerprint}:#{level + 1}:\n" - out, err, status = Open3.capture3('gpg --import-ownertrust', stdin_data: trust) + out, err, status = Open3.capture3("#{executable} --import-ownertrust", stdin_data: trust) logger.debug { "IOStreams::Pgp.set_trust output:\n#{err}#{out}" } if logger if status.success? err else raise(Pgp::Failure, "GPG Failed trusting key: #{err} #{out}") @@ -326,11 +344,11 @@ end # DEPRECATED - Use key_ids instead of fingerprints def self.fingerprint(email:) version_check - Open3.popen2e("gpg --list-keys --fingerprint --with-colons #{email}") do |stdin, out, waith_thr| + Open3.popen2e("#{executable} --list-keys --fingerprint --with-colons #{email}") do |stdin, out, waith_thr| output = out.read.chomp if waith_thr.value.success? output.each_line do |line| if match = line.match(/\Afpr.*::([^\:]*):\Z/) return match[1] @@ -349,11 +367,11 @@ end # Returns [String] the version of pgp currently installed def self.pgp_version @pgp_version ||= begin - out, err, status = Open3.capture3("gpg --version") + out, err, status = Open3.capture3("#{executable} --version") logger.debug { "IOStreams::Pgp.version output:\n#{err}#{out}" } if logger if status.success? # Sample output # gpg (GnuPG) 2.0.30 # libgcrypt 1.7.6 @@ -393,30 +411,36 @@ def self.parse_list_output(out) results = [] hash = {} out.each_line do |line| - if match = line.match(/(pub|sec)\s+(\d+)(.*)\/(\w+)\s+(\S+)/) + if match = line.match(/(pub|sec)\s+(\d+)(.*)\/(\w+)\s+(\d+-\d+-\d+)(\s+(.+)<(.+)>)?/) + # Matches: pub 2048R/C7F9D9CB 2016-10-26 + # Or: pub 2048R/C7F9D9CB 2016-10-26 Receiver <receiver@example.org> hash = { private: match[1] == 'sec', key_length: match[2].to_s.to_i, key_type: match[3], key_id: match[4], date: (Date.parse(match[5].to_s) rescue match[5]) } - elsif match = line.match(/uid\s+(.+)<(.+)>/) - name = match[1].strip - hash[:email] = match[2].strip - if match = name.match(/(\[(.+)\])?(.+)/) - trust = match[2].to_s.strip - hash[:trust] = trust unless trust.empty? - hash[:name] = match[3].to_s.strip - else - hash[:name] = name + # Prior to gpg v2.0.30 + if match[7] + hash[:name] = match[7].strip + hash[:email] = match[8].strip + results << hash + hash = {} end + elsif match = line.match(/uid\s+(\[(.+)\]\s+)?(.+)<(.+)>/) + # Matches: uid [ unknown] Joe Bloggs <j@bloggs.net> + # Or: uid Joe Bloggs <j@bloggs.net> + hash[:email] = match[4].strip + hash[:name] = match[3].to_s.strip + hash[:trust] = match[2].to_s.strip if match[1] results << hash hash = {} end + end results end end