X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fcrypto.rb;h=9e16132304ebad107b8cab43c0706de82b6e1a78;hb=b5c1bcbd4c25618574f94e683773647906f023e0;hp=5aca1a4d15653b9e76c2dbd20e8d9105b6ddb93f;hpb=867647a846b558c715a4cce14022425a4fda8dde;p=sup diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb index 5aca1a4..9e16132 100644 --- a/lib/sup/crypto.rb +++ b/lib/sup/crypto.rb @@ -1,118 +1,141 @@ module Redwood -class CryptoSignature - attr_reader :lines, :status, :description - - def initialize status, description, lines - @status = status - @description = description - @lines = lines - end -end - -class CryptoDecryptedNotice - attr_reader :lines, :status, :description - - def initialize status, description, lines=[] - @status = status - @description = description - @lines = lines - end -end - class CryptoManager include Singleton + class Error < StandardError; end + + OUTGOING_MESSAGE_OPERATIONS = OrderedHash.new( + [:sign, "Sign"], + [:sign_and_encrypt, "Sign and encrypt"], + [:encrypt, "Encrypt only"] + ) + def initialize @mutex = Mutex.new self.class.i_am_the_instance self bin = `which gpg`.chomp - bin = `which pgp`.chomp unless bin =~ /\S/ @cmd = case bin when /\S/ + Redwood::log "crypto: detected gpg binary in #{bin}" "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent" else + Redwood::log "crypto: no gpg binary detected" nil end end - # returns a cryptosignature + def have_crypto?; !@cmd.nil? end + + def sign from, to, payload + payload_fn = Tempfile.new "redwood.payload" + payload_fn.write format_payload(payload) + payload_fn.close + + output = run_gpg "--output - --armor --detach-sign --textmode --local-user '#{from}' #{payload_fn.path}" + + raise Error, (output || "gpg command failed: #{cmd}") unless $?.success? + + envelope = RMail::Message.new + envelope.header["Content-Type"] = 'multipart/signed; protocol="application/pgp-signature"; micalg=pgp-sha1' + + envelope.add_part payload + signature = RMail::Message.make_attachment output, "application/pgp-signature", nil, "signature.asc" + envelope.add_part signature + envelope + end + + def encrypt from, to, payload, sign=false + payload_fn = Tempfile.new "redwood.payload" + payload_fn.write format_payload(payload) + payload_fn.close + + recipient_opts = to.map { |r| "--recipient '#{r}'" }.join(" ") + sign_opts = sign ? "--sign --local-user '#{from}'" : "" + gpg_output = run_gpg "--output - --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}" + raise Error, (gpg_output || "gpg command failed: #{cmd}") unless $?.success? + + encrypted_payload = RMail::Message.new + encrypted_payload.header["Content-Type"] = "application/octet-stream" + encrypted_payload.header["Content-Disposition"] = 'inline; filename="msg.asc"' + encrypted_payload.body = gpg_output + + control = RMail::Message.new + control.header["Content-Type"] = "application/pgp-encrypted" + control.header["Content-Disposition"] = "attachment" + control.body = "Version: 1\n" + + envelope = RMail::Message.new + envelope.header["Content-Type"] = 'multipart/encrypted; protocol="application/pgp-encrypted"' + + envelope.add_part control + envelope.add_part encrypted_payload + envelope + end + + def sign_and_encrypt from, to, payload + encrypt from, to, payload, true + end + def verify payload, signature # both RubyMail::Message objects return unknown_status(cant_find_binary) unless @cmd payload_fn = Tempfile.new "redwood.payload" - payload_fn.write payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "") + payload_fn.write format_payload(payload) payload_fn.close signature_fn = Tempfile.new "redwood.signature" signature_fn.write signature.decode signature_fn.close - cmd = "#{@cmd} --verify #{signature_fn.path} #{payload_fn.path} 2> /dev/null" + output = run_gpg "--verify #{signature_fn.path} #{payload_fn.path}" + output_lines = output.split(/\n/) - #Redwood::log "gpg: running: #{cmd}" - gpg_output = `#{cmd}` - #Redwood::log "got output: #{gpg_output.inspect}" - output_lines = gpg_output.split(/\n/) - - if gpg_output =~ /^gpg: (.* signature from .*$)/ + if output =~ /^gpg: (.* signature from .*$)/ if $? == 0 - CryptoSignature.new :valid, $1, output_lines + Chunk::CryptoNotice.new :valid, $1, output_lines else - CryptoSignature.new :invalid, $1, output_lines + Chunk::CryptoNotice.new :invalid, $1, output_lines end else unknown_status output_lines end end - # returns decrypted_message, status, desc, lines - def decrypt payload # RubyMail::Message objects + ## returns decrypted_message, status, desc, lines + def decrypt payload # a RubyMail::Message object return unknown_status(cant_find_binary) unless @cmd -# cmd = "#{@cmd} --decrypt 2> /dev/null" - -# Redwood::log "gpg: running: #{cmd}" - -# gpg_output = -# IO.popen(cmd, "a+") do |f| -# f.puts payload.to_s -# f.gets -# end - payload_fn = Tempfile.new "redwood.payload" payload_fn.write payload.to_s payload_fn.close - cmd = "#{@cmd} --decrypt #{payload_fn.path} 2> /dev/null" - Redwood::log "gpg: running: #{cmd}" - gpg_output = `#{cmd}` - Redwood::log "got output: #{gpg_output.inspect}" + output = run_gpg "--decrypt #{payload_fn.path}" - if $? == 0 # successful decryption + if $?.success? decrypted_payload, sig_lines = - if gpg_output =~ /\A(.*?)((^gpg: .*$)+)\Z/m + if output =~ /\A(.*?)((^gpg: .*$)+)\Z/m [$1, $2] else - [gpg_output, nil] + [output, nil] end sig = if sig_lines # encrypted & signed if sig_lines =~ /^gpg: (Good signature from .*$)/ - CryptoSignature.new :valid, $1, sig_lines.split("\n") + Chunk::CryptoNotice.new :valid, $1, sig_lines.split("\n") else - CryptoSignature.new :invalid, $1, sig_lines.split("\n") + Chunk::CryptoNotice.new :invalid, $1, sig_lines.split("\n") end end - notice = CryptoDecryptedNotice.new :valid, "This message has been decrypted for display" + notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display" [RMail::Parser.read(decrypted_payload), sig, notice] else - notice = CryptoDecryptedNotice.new :invalid, "This message could not be decrypted", gpg_output.split("\n") + notice = Chunk::CryptoNotice.new :invalid, "This message could not be decrypted", output.split("\n") [nil, nil, notice] end end @@ -120,18 +143,23 @@ class CryptoManager private def unknown_status lines=[] - CryptoSignature.new :unknown, "Unable to determine validity of cryptographic signature", lines + Chunk::CryptoNotice.new :unknown, "Unable to determine validity of cryptographic signature", lines end def cant_find_binary - ["Can't find gpg or pgp binary in path"] + ["Can't find gpg binary in path."] end -end -end + def format_payload payload + payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "") + end -## to check: -## failed decryption -## decription but failed signature -## no gpg found -## multiple private keys + def run_gpg args + cmd = "#{@cmd} #{args} 2> /dev/null" + #Redwood::log "crypto: running: #{cmd}" + output = `#{cmd}` + #Redwood::log "crypto: output: #{output.inspect}" unless $?.success? + output + end +end +end