lib/io_streams/pgp.rb in iostreams-1.1.0 vs lib/io_streams/pgp.rb in iostreams-1.1.1
- old
+ new
@@ -1,6 +1,6 @@
-require 'open3'
+require "open3"
module IOStreams
# Read/Write PGP/GPG file or stream.
#
# Example Setup:
#
@@ -80,12 +80,12 @@
#
# 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'
+ autoload :Reader, "io_streams/pgp/reader"
+ autoload :Writer, "io_streams/pgp/writer"
class Failure < StandardError
end
class UnsupportedVersion < Failure
@@ -97,11 +97,11 @@
def self.executable=(executable)
@executable = executable
end
- @executable = 'gpg'
+ @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.
@@ -120,30 +120,30 @@
# Highly Recommended.
# To generate a good passphrase:
# `SecureRandom.urlsafe_base64(128)`
#
# See `man gpg` for the remaining options
- def self.generate_key(name:, email:, comment: nil, passphrase:, key_type: 'RSA', key_length: 4096, subkey_type: 'RSA', subkey_length: key_length, expire_date: nil)
+ def self.generate_key(name:, email:, comment: nil, passphrase:, key_type: "RSA", key_length: 4096, subkey_type: "RSA", subkey_length: key_length, expire_date: nil)
version_check
- params = ''
+ params = ""
params << "Key-Type: #{key_type}\n" if key_type
params << "Key-Length: #{key_length}\n" if key_length
params << "Subkey-Type: #{subkey_type}\n" if subkey_type
params << "Subkey-Length: #{subkey_length}\n" if subkey_length
params << "Name-Real: #{name}\n" if name
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'
- command = "#{executable} --batch --gen-key --no-tty --quiet"
+ params << "%commit"
+ command = "#{executable} --batch --gen-key --no-tty"
out, err, status = Open3.capture3(command, binmode: true, stdin_data: params)
- logger.debug { "IOStreams::Pgp.generate_key: #{command}\n#{err}#{out}" } if logger
+ logger&.debug { "IOStreams::Pgp.generate_key: #{command}\n#{params}\n#{err}#{out}" }
if status.success?
if match = err.match(/gpg: key ([0-9A-F]+)\s+/)
- return match[1]
+ match[1]
end
else
raise(Pgp::Failure, "GPG Failed to generate key: #{err}#{out}")
end
end
@@ -170,16 +170,21 @@
status = send(method_name, email: email, private: false) if public
status
end
# Returns [true|false] whether their is a key for the supplied email or key_id
- def self.has_key?(email: nil, key_id: nil, private: false)
- raise(ArgumentError, 'Either :email, or :key_id must be supplied') if email.nil? && key_id.nil?
+ def self.key?(email: nil, key_id: nil, private: false)
+ raise(ArgumentError, "Either :email, or :key_id must be supplied") if email.nil? && key_id.nil?
!list_keys(email: email, key_id: key_id, private: private).empty?
end
+ # Deprecated
+ def self.has_key?(**args)
+ key(**args)
+ end
+
# Returns [Array<Hash>] the list of keys.
# Each Hash consists of:
# key_length: [Integer]
# key_type: [String]
# key_id: [String]
@@ -187,19 +192,20 @@
# name: [String]
# 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'
+ cmd = private ? "--list-secret-keys" : "--list-keys"
command = "#{executable} #{cmd} #{email || key_id}"
out, err, status = Open3.capture3(command, binmode: true)
- logger.debug { "IOStreams::Pgp.list_keys: #{command}\n#{err}#{out}" } if logger
- if status.success? && out.length > 0
+ logger&.debug { "IOStreams::Pgp.list_keys: #{command}\n#{err}#{out}" }
+ if status.success? && out.length.positive?
parse_list_output(out)
else
return [] if err =~ /(not found|No (public|secret) key|key not available)/i
+
raise(Pgp::Failure, "GPG Failed calling '#{executable}' to list keys for #{email || key_id}: #{err}#{out}")
end
end
# Extract information from the supplied key.
@@ -217,12 +223,12 @@
def self.key_info(key:)
version_check
command = executable.to_s
out, err, status = Open3.capture3(command, binmode: true, stdin_data: key)
- logger.debug { "IOStreams::Pgp.key_info: #{command}\n#{err}#{out}" } if logger
- if status.success? && out.length > 0
+ logger&.debug { "IOStreams::Pgp.key_info: #{command}\n#{err}#{out}" }
+ if status.success? && out.length.positive?
# Sample Output:
#
# pub 4096R/3A5456F5 2017-06-07
# uid Joe Bloggs <j@bloggs.net>
# sub 4096R/2C9B240B 2017-06-07
@@ -241,19 +247,19 @@
# Default: true
def self.export(email:, ascii: true, private: false, passphrase: nil)
version_check
command = "#{executable} "
- command << '--pinentry-mode loopback ' if pgp_version.to_f >= 2.1
- command << '--armor ' if ascii
+ command << "--pinentry-mode loopback " if pgp_version.to_f >= 2.1
+ command << "--armor " if ascii
command << "--no-tty --batch --passphrase"
command << (passphrase ? " #{passphrase} " : "-fd 0 ")
command << (private ? "--export-secret-keys #{email}" : "--export #{email}")
out, err, status = Open3.capture3(command, binmode: true)
- logger.debug { "IOStreams::Pgp.export: #{command}\n#{err}" } if logger
- if status.success? && out.length > 0
+ logger&.debug { "IOStreams::Pgp.export: #{command}\n#{err}" }
+ if status.success? && out.length.positive?
out
else
raise(Pgp::Failure, "GPG Failed reading key: #{email}: #{err}")
end
end
@@ -317,11 +323,11 @@
# Returns [String] email for the supplied after importing and trusting the key
#
# Notes:
# - If the same email address has multiple keys then only the first is currently trusted.
def self.import_and_trust(key:)
- raise(ArgumentError, "Key cannot be empty") if key.nil? || (key == '')
+ raise(ArgumentError, "Key cannot be empty") if key.nil? || (key == "")
email = key_info(key: key).first.fetch(:email)
raise(ArgumentError, "Recipient email cannot be extracted from supplied key") unless email
import(key: key)
@@ -441,24 +447,32 @@
hash = {}
out.each_line do |line|
if match = line.match(/(pub|sec)\s+(\D+)(\d+)\s+(\d+-\d+-\d+)\s+(.*)/)
# v2.2: pub rsa1024 2017-10-24 [SCEA]
hash = {
- private: match[1] == 'sec',
+ private: match[1] == "sec",
key_length: match[3].to_s.to_i,
key_type: match[2],
- date: (Date.parse(match[4].to_s) rescue match[4])
+ date: (begin
+ Date.parse(match[4].to_s)
+ rescue StandardError
+ match[4]
+ end)
}
elsif match = line.match(%r{(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',
+ 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])
+ date: (begin
+ Date.parse(match[5].to_s)
+ rescue StandardError
+ match[5]
+ end)
}
# Prior to gpg v2.0.30
if match[7]
hash[:name] = match[7].strip
hash[:email] = match[8].strip
@@ -481,11 +495,11 @@
end
results
end
def self.delete_public_or_private_keys(email:, private: false)
- keys = private ? 'secret-keys' : 'keys'
+ keys = private ? "secret-keys" : "keys"
list = list_keys(email: email, private: private)
return false if list.empty?
list.each do |key_info|
@@ -497,33 +511,29 @@
logger&.debug { "IOStreams::Pgp.delete_keys: #{command}\n#{err}#{out}" }
unless status.success?
raise(Pgp::Failure, "GPG Failed calling #{executable} to delete #{keys} for #{email}: #{err}: #{out}")
end
- if out.include?('error')
- raise(Pgp::Failure, "GPG Failed to delete #{keys} for #{email} #{err.strip}:#{out}")
- end
+ raise(Pgp::Failure, "GPG Failed to delete #{keys} for #{email} #{err.strip}:#{out}") if out.include?("error")
end
true
end
def self.delete_public_or_private_keys_v1(email:, private: false)
- keys = private ? 'secret-keys' : 'keys'
+ keys = private ? "secret-keys" : "keys"
command = "for i in `#{executable} --list-#{keys} --with-colons --fingerprint #{email} | grep \"^fpr\" | cut -d: -f10`; do\n"
command << "#{executable} --batch --no-tty --yes --delete-#{keys} \"$i\" ;\n"
- command << 'done'
+ command << "done"
out, err, status = Open3.capture3(command, binmode: true)
logger&.debug { "IOStreams::Pgp.delete_keys: #{command}\n#{err}: #{out}" }
return false if err =~ /(not found|no public key)/i
unless status.success?
raise(Pgp::Failure, "GPG Failed calling #{executable} to delete #{keys} for #{email}: #{err}: #{out}")
end
- if out.include?('error')
- raise(Pgp::Failure, "GPG Failed to delete #{keys} for #{email} #{err.strip}: #{out}")
- end
+ raise(Pgp::Failure, "GPG Failed to delete #{keys} for #{email} #{err.strip}: #{out}") if out.include?("error")
true
end
end
end