require 'open3' module IOStreams module Pgp class Writer # Sign all encrypted files with this users key. # Default: Do not sign encyrpted files. def self.default_signer=(default_signer) @default_signer = default_signer end # Passphrase to use to open the private key when signing the file. # Default: None. def self.default_signer_passphrase=(default_signer_passphrase) @default_signer_passphrase = default_signer_passphrase end # Write to a PGP / GPG file or stream, encrypting the contents as it is written # # file_name_or_io: [String|IO] # Name of file to write to. # Or, the IO stream to write the encrypted contents to. # # recipient: [String] # Email of user for which to encypt the file. # # signer: [String] # Name of user with which to sign the encypted file. # Default: default_signer or do not sign. # # signer_passphrase: [String] # Passphrase to use to open the private key when signing the file. # Default: default_signer_passphrase # # binary: [true|false] # Whether to write binary data. # Default: true # # compression: [:none|:zip|:zlib|:bzip2] # Note: Standard PGP only supports :zip. # :zlib is better than zip. # :bzip2 is best, but uses a lot of memory and is much slower. # Default: :zip # # compress_level: [Integer] # Compression level # Default: 6 def self.open(file_name_or_io, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6) compress_level = 0 if compression == :none if IOStreams.writer_stream?(file_name_or_io) raise(NotImplementedError, 'Can only PGP Encrypt directly to a file name. Output to streams are not yet supported.') else # Write to stdin, with encrypted contents being written to the file command = "#{IOStreams::Pgp.executable} --batch --no-tty --yes --encrypt" command << " --sign --local-user \"#{signer}\"" if signer if signer_passphrase command << " --pinentry-mode loopback" if IOStreams::Pgp.pgp_version.to_f >= 2.1 command << " --passphrase \"#{signer_passphrase}\"" end command << " -z #{compress_level}" if compress_level != 6 command << " --compress-algo #{compression}" unless compression == :none command << " --recipient \"#{recipient}\" -o \"#{file_name_or_io}\"" IOStreams::Pgp.logger.debug { "IOStreams::Pgp::Writer.open: #{command}" } if IOStreams::Pgp.logger Open3.popen2e(command) do |stdin, out, waith_thr| begin stdin.binmode if binary yield(stdin) stdin.close rescue Errno::EPIPE # Ignore broken pipe because gpg terminates early due to an error ::File.delete(file_name_or_io) if ::File.exist?(file_name_or_io) raise(Pgp::Failure, "GPG Failed writing to encrypted file: #{file_name_or_io}: #{out.read.chomp}") end unless waith_thr.value.success? ::File.delete(file_name_or_io) if ::File.exist?(file_name_or_io) raise(Pgp::Failure, "GPG Failed to create encrypted file: #{file_name_or_io}: #{out.read.chomp}") end end end end private @default_signer_passphrase = nil @default_signer = nil def self.default_signer_passphrase @default_signer_passphrase end def self.default_signer @default_signer end end end end