require 'tempfile' require 'shellwords' module Rgpg module GpgHelper def self.generate_key_pair(key_base_name, recipient, real_name) public_key_file_name = "#{key_base_name}.pub" private_key_file_name = "#{key_base_name}.sec" script = generate_key_script(public_key_file_name, private_key_file_name, recipient, real_name) script_file = Tempfile.new('gpg-script') begin script_file.write(script) script_file.close run_gpg_no_capture( '--batch', '--gen-key', script_file.path ) ensure script_file.close script_file.unlink end end def self.encrypt_file(public_key_file_name, input_file_name, output_file_name) raise ArgumentError.new("Public key file \"#{public_key_file_name}\" does not exist") unless File.exist?(public_key_file_name) raise ArgumentError.new("Input file \"#{input_file_name}\" does not exist") unless File.exist?(input_file_name) recipient = get_recipient(public_key_file_name) with_temporary_encrypt_keyring(public_key_file_name) do |keyring_file_name| run_gpg_capture( '--keyring', keyring_file_name, '--output', output_file_name, '--encrypt', '--recipient', recipient, '--yes', '--trust-model', 'always', '--no-tty', input_file_name ) end end def self.decrypt_file(public_key_file_name, private_key_file_name, input_file_name, output_file_name, passphrase=nil) raise ArgumentError.new("Public key file \"#{public_key_file_name}\" does not exist") unless File.exist?(public_key_file_name) raise ArgumentError.new("Private key file \"#{private_key_file_name}\" does not exist") unless File.exist?(private_key_file_name) raise ArgumentError.new("Input file \"#{input_file_name}\" does not exist") unless File.exist?(input_file_name) recipient = get_recipient(private_key_file_name) with_temporary_decrypt_keyrings(public_key_file_name, private_key_file_name) do |keyring_file_name, secret_keyring_file_name| args = '--keyring', keyring_file_name, '--secret-keyring', secret_keyring_file_name, '--output', output_file_name, '--decrypt', '--yes', '--trust-model', 'always', '--no-tty', input_file_name args.unshift '--passphrase', passphrase unless passphrase.nil? run_gpg_capture(*args) end end private def self.with_temp_home_dir Dir.mktmpdir('.rgpg-tmp-', ENV['HOME']) do |home_dir| yield home_dir end end def self.build_safe_command_line(home_dir, *args) fragments = [ 'gpg', '--homedir', home_dir, '--no-default-keyring' ] + args fragments.collect { |fragment| Shellwords.escape(fragment) }.join(' ') end def self.run_gpg_no_capture(*args) with_temp_home_dir do |home_dir| command_line = build_safe_command_line(home_dir, *args) result = system(command_line) raise RuntimeError.new('gpg failed') unless result end end def self.run_gpg_capture(*args) with_temp_home_dir do |home_dir| command_line = build_safe_command_line(home_dir, *args) output_file = Tempfile.new('gpg-output') begin output_file.close result = system("#{command_line} > #{Shellwords.escape(output_file.path)} 2>&1") output = nil File.open(output_file.path) do |f| output = f.read end raise RuntimeError.new("gpg failed: #{output}") unless result output.lines.collect(&:chomp) ensure output_file.unlink end end end def self.generate_key_script(public_key_file_name, private_key_file_name, recipient, real_name) <<-EOS %echo Generating a standard key Key-Type: DSA Key-Length: 1024 Subkey-Type: ELG-E Subkey-Length: 1024 Name-Real: #{real_name} Name-Comment: Key automatically generated by rgpg Name-Email: #{recipient} Expire-Date: 0 %pubring #{public_key_file_name} %secring #{private_key_file_name} # Do a commit here, so that we can later print "done" :-) %commit %echo done EOS end def self.get_recipient(key_file_name) lines = run_gpg_capture(key_file_name) result = lines.detect { |line| line =~ /^(pub|sec)\s+\d+(D|R)\/([0-9a-fA-F]{8}).+<(.+)>/ } raise RuntimeError.new('Invalid output') unless result key_id = $2 recipient = $3 end def self.with_temporary_encrypt_keyring(public_key_file_name) with_temporary_keyring_file do |keyring_file_name| run_gpg_capture( '--keyring', keyring_file_name, '--import', public_key_file_name ) yield keyring_file_name end end def self.with_temporary_decrypt_keyrings(public_key_file_name, private_key_file_name) with_temporary_keyring_file do |keyring_file_name| with_temporary_keyring_file do |secret_keyring_file_name| run_gpg_capture( '--keyring', keyring_file_name, '--secret-keyring', secret_keyring_file_name, '--import', private_key_file_name ) yield keyring_file_name, secret_keyring_file_name end end end def self.with_temporary_keyring_file keyring_file = Tempfile.new('gpg-key-ring') begin keyring_file_name = keyring_file.path keyring_file.close keyring_file.unlink yield keyring_file_name ensure File.unlink(keyring_file_name) if File.exist?(keyring_file_name) backup_keyring_file_name = "#{keyring_file_name}~" File.unlink(backup_keyring_file_name) if File.exist?(backup_keyring_file_name) end end end end