]> git.cworth.org Git - sup/commitdiff
prelim gpg decryption support
authorwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Thu, 27 Sep 2007 00:04:19 +0000 (00:04 +0000)
committerwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Thu, 27 Sep 2007 00:04:19 +0000 (00:04 +0000)
git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@594 5c8cc53c-5e98-4d25-b20a-d8db53a31250

lib/sup/crypto.rb
lib/sup/message.rb
lib/sup/modes/thread-view-mode.rb

index f3aefaf2d659856d7c0f9e5a9e99fb16ffeb1f4d..93fd4d7f8bc43de63fb40a0dd8507f655b9831e6 100644 (file)
@@ -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
index ef431ed5b9bf2d291f0cb17d5224599b93cef36a..7583b7a5d5760f03a7945c5b8abf45e928332fdc 100644 (file)
@@ -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 =
index 897850cfe57d9a2c1b3eef437ad4a964116e4f1e..af9a791b7a47e0916a4f42d0b10bca71e7d5fd13 100644 (file)
@@ -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