From 9f48584248b8d14ae9a60027b823afcc10151312 Mon Sep 17 00:00:00 2001 From: wmorgan Date: Thu, 27 Sep 2007 00:04:19 +0000 Subject: [PATCH] prelim gpg decryption support git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@594 5c8cc53c-5e98-4d25-b20a-d8db53a31250 --- lib/sup/crypto.rb | 111 +++++++++++++++++++++++++++--- lib/sup/message.rb | 70 ++++++++++--------- lib/sup/modes/thread-view-mode.rb | 25 +++---- 3 files changed, 153 insertions(+), 53 deletions(-) diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb index f3aefaf..93fd4d7 100644 --- a/lib/sup/crypto.rb +++ b/lib/sup/crypto.rb @@ -1,5 +1,25 @@ 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 @@ -7,13 +27,21 @@ class CryptoManager @mutex = Mutex.new self.class.i_am_the_instance self - @cmd = `which gpg`.chomp - @cmd = `which pgp`.chomp unless @cmd =~ /\S/ - @cmd = nil unless @cmd =~ /\S/ + bin = `which gpg`.chomp + bin = `which pgp`.chomp unless bin =~ /\S/ + + @cmd = + case bin + when /\S/ + "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent" + else + nil + end end + # returns a cryptosignature def verify payload, signature # both RubyMail::Message objects - return unknown unless @cmd + 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/, "") @@ -23,24 +51,87 @@ class CryptoManager signature_fn.write signature.decode signature_fn.close - cmd = "#{@cmd} --quiet --batch --no-verbose --verify --logger-fd 1 #{signature_fn.path} #{payload_fn.path} 2> /dev/null" + cmd = "#{@cmd} --verify #{signature_fn.path} #{payload_fn.path} 2> /dev/null" #Redwood::log "gpg: running: #{cmd}" gpg_output = `#{cmd}` #Redwood::log "got output: #{gpg_output.inspect}" - lines = gpg_output.split(/\n/) + output_lines = gpg_output.split(/\n/) if gpg_output =~ /^gpg: (.* signature from .*$)/ - $? == 0 ? [:valid, $1, lines] : [:invalid, $1, lines] + if $? == 0 + CryptoSignature.new :valid, $1, output_lines + else + CryptoSignature.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 + 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}" + + if $? == 0 # successful decryption + decrypted_payload, sig_lines = + if gpg_output =~ /\A(.*?)((^gpg: .*$)+)\Z/m + [$1, $2] + else + [gpg_output, nil] + end + + sig = + if sig_lines # encrypted & signed + if sig_lines =~ /^gpg: (Good signature from .*$)/ + CryptoSignature.new :valid, $1, sig_lines.split("\n") + else + CryptoSignature.new :invalid, $1, sig_lines.split("\n") + end + end + + notice = CryptoDecryptedNotice.new :valid, "This message has been decrypted for display." + [RMail::Parser.read(decrypted_payload), sig, notice] else - unknown lines + notice = CryptoDecryptedNotice.new :invalid, "This message could not be decrypted", gpg_output.split("\n") + [nil, nil, notice] end end private - def unknown lines=[] - [:unknown, "Unable to determine validity of cryptographic signature", lines] + def unknown_status lines=[] + CryptoSignature.new :unknown, "Unable to determine validity of cryptographic signature", lines + end + + def cant_find_binary + ["Can't find gpg or pgp binary in path"] end end end + + +## to check: +## failed decryption +## decription but failed signature +## no gpg found +## multiple private keys diff --git a/lib/sup/message.rb b/lib/sup/message.rb index ef431ed..7583b7a 100644 --- a/lib/sup/message.rb +++ b/lib/sup/message.rb @@ -112,34 +112,6 @@ EOS end end - class CryptoSignature - attr_reader :lines, :description - - def initialize payload, signature - @payload = payload - @signature = signature - @status = nil - @description = nil - @lines = [] - end - - def status - verify - @status - end - - def description - verify - @description - end - -private - - def verify - @status, @description, @lines = CryptoManager.verify(@payload, @signature) unless @status - end - end - QUOTE_PATTERN = /^\s{0,4}[>|\}]/ BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/ QUOTE_START_PATTERN = /(^\s*Excerpts from)|(^\s*In message )|(^\s*In article )|(^\s*Quoting )|((wrote|writes|said|says)\s*:\s*$)/ @@ -390,16 +362,52 @@ private return end - [CryptoSignature.new(payload, signature), message_to_chunks(payload)].flatten + [CryptoManager.verify(payload, signature), message_to_chunks(payload)] + end + + def multipart_encrypted_to_chunks m + Redwood::log ">> multipart ENCRYPTED: #{m.header['Content-Type']}: #{m.body.size}" + if m.body.size != 2 + Redwood::log "warning: multipart/encrypted with #{m.body.size} parts (expecting 2)" + return + end + + control, payload = m.body + if control.multipart? + Redwood::log "warning: multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}" + return + end + + if payload.header.content_type != "application/octet-stream" + Redwood::log "warning: multipart/encrypted with payload content type #{payload.header.content_type}" + return + end + + if control.header.content_type != "application/pgp-encrypted" + Redwood::log "warning: multipart/encrypted with control content type #{signature.header.content_type}" + return + end + + decryptedm, sig, notice = CryptoManager.decrypt payload + children = message_to_chunks(decryptedm) if decryptedm + [notice, sig, children].flatten.compact end - + def message_to_chunks m, sibling_types=[] if m.multipart? - chunks = multipart_signed_to_chunks(m) if m.header.content_type == "multipart/signed" + chunks = + case m.header.content_type + when "multipart/signed" + multipart_signed_to_chunks m + when "multipart/encrypted" + multipart_encrypted_to_chunks m + end + unless chunks sibling_types = m.body.map { |p| p.header.content_type } chunks = m.body.map { |p| message_to_chunks p, sibling_types }.flatten.compact end + chunks else filename = diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread-view-mode.rb index 897850c..af9a791 100644 --- a/lib/sup/modes/thread-view-mode.rb +++ b/lib/sup/modes/thread-view-mode.rb @@ -179,8 +179,8 @@ class ThreadViewMode < LineCursorMode l = @layout[chunk] l.state = (l.state != :closed ? :closed : :open) cursor_down if l.state == :closed - when Message::Quote, Message::Signature, Message::CryptoSignature - return if chunk.lines.length == 1 + when Message::Quote, Message::Signature, CryptoSignature, CryptoDecryptedNotice + return if chunk.lines.length <= 1 toggle_chunk_expansion chunk when Message::Attachment if chunk.inlineable? @@ -435,22 +435,22 @@ private rest = [] unless m.to.empty? m.to.each_with_index { |p, i| @person_lines[start + rest.length + from.length + i] = p } - rest += format_person_list " To: ", m.to + rest += format_person_list " To: ", m.to end unless m.cc.empty? m.cc.each_with_index { |p, i| @person_lines[start + rest.length + from.length + i] = p } - rest += format_person_list " Cc: ", m.cc + rest += format_person_list " Cc: ", m.cc end unless m.bcc.empty? m.bcc.each_with_index { |p, i| @person_lines[start + rest.length + from.length + i] = p } - rest += format_person_list " Bcc: ", m.bcc + rest += format_person_list " Bcc: ", m.bcc end rest += [ - " Date: #{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})", - " Subject: #{m.subj}", - (parent ? " In reply to: #{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}" : nil), - m.labels.empty? ? nil : " Labels: #{m.labels.join(', ')}", + " Date: #{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})", + " Subject: #{m.subj}", + (parent ? " In reply to: #{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}" : nil), + m.labels.empty? ? nil : " Labels: #{m.labels.join(', ')}", ].compact from + rest.map { |l| [[color, prefix + " " + l]] } @@ -510,18 +510,19 @@ private when :open [[[:sig_patina_color, "#{prefix}- (#{chunk.lines.length}-line signature)"]]] + chunk.lines.map { |line| [[:sig_color, "#{prefix}#{line}"]] } end - when Message::CryptoSignature + when CryptoSignature, CryptoDecryptedNotice color = case chunk.status when :valid: :cryptosig_valid_color when :invalid: :cryptosig_invalid_color else :cryptosig_unknown_color end + widget = chunk.lines.empty? ? "x" : (state == :closed ? "+" : "-") case state when :closed - [[[color, "#{prefix}+ #{chunk.description}"]]] + [[[color, "#{prefix}#{widget} #{chunk.description}"]]] when :open - [[[color, "#{prefix}- #{chunk.description}"]]] + + [[[color, "#{prefix}#{widget} #{chunk.description}"]]] + chunk.lines.map { |line| [[color, "#{prefix}#{line}"]] } end else -- 2.45.2