commit 4c36843665a06c9aff70119d905fa8e5ec4945a0
parent 654c906e92378c9a9f9c4ade442282bf5f5ec45b
Author: Rich Lane <rlane@club.cc.cmu.edu>
Date: Thu, 13 May 2010 19:30:16 -0700
Merge branch 'inline-gpg'
Diffstat:
3 files changed, 110 insertions(+), 45 deletions(-)
diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb
@@ -97,22 +97,11 @@ EOS
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 format_payload(payload)
- payload_fn.close
-
- signature_fn = Tempfile.new "redwood.signature"
- signature_fn.write signature.decode
- signature_fn.close
-
- output = run_gpg "--verify #{signature_fn.path} #{payload_fn.path}"
+ def verified_ok? output, rc
output_lines = output.split(/\n/)
if output =~ /^gpg: (.* signature from .*$)/
- if $? == 0
+ if rc == 0
Chunk::CryptoNotice.new :valid, $1, output_lines
else
Chunk::CryptoNotice.new :invalid, $1, output_lines
@@ -122,8 +111,30 @@ EOS
end
end
+ def verify payload, signature, detached=true # both RubyMail::Message objects
+ return unknown_status(cant_find_binary) unless @cmd
+
+ if detached
+ payload_fn = Tempfile.new "redwood.payload"
+ payload_fn.write format_payload(payload)
+ payload_fn.close
+ end
+
+ signature_fn = Tempfile.new "redwood.signature"
+ signature_fn.write signature.decode
+ signature_fn.close
+
+ if detached
+ output = run_gpg "--verify #{signature_fn.path} #{payload_fn.path}"
+ else
+ output = run_gpg "--verify #{signature_fn.path}"
+ end
+
+ self.verified_ok? output, $?
+ end
+
## returns decrypted_message, status, desc, lines
- def decrypt payload # a RubyMail::Message object
+ def decrypt payload, armor=false # a RubyMail::Message object
return unknown_status(cant_find_binary) unless @cmd
payload_fn = Tempfile.new "redwood.payload"
@@ -133,7 +144,7 @@ EOS
output_fn = Tempfile.new "redwood.output"
output_fn.close
- message = run_gpg "--output #{output_fn.path} --yes --decrypt #{payload_fn.path}", :interactive => true
+ message = run_gpg "--output #{output_fn.path} --skip-verify --yes --decrypt #{payload_fn.path}", :interactive => true
unless $?.success?
info "Error while running gpg: #{message}"
@@ -143,34 +154,39 @@ EOS
output = IO.read output_fn.path
output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
- ## there's probably a better way to do this, but we're using the output to
- ## look for a valid signature being present.
-
- sig = case message
- when /^gpg: (Good signature from .*$)/i
- Chunk::CryptoNotice.new :valid, $1, message.split("\n")
- when /^gpg: (Bad signature from .*$)/i
- Chunk::CryptoNotice.new :invalid, $1, message.split("\n")
- end
-
- # This is gross. This decrypted payload could very well be a multipart
- # element itself, as opposed to a simple payload. For example, a
- # multipart/signed element, like those generated by Mutt when encrypting
- # and signing a message (instead of just clearsigning the body).
- # Supposedly, decrypted_payload being a multipart element ought to work
- # out nicely because Message::multipart_encrypted_to_chunks() runs the
- # decrypted message through message_to_chunks() again to get any
- # children. However, it does not work as intended because these inner
- # payloads need not carry a MIME-Version header, yet they are fed to
- # RMail as a top-level message, for which the MIME-Version header is
- # required. This causes for the part not to be detected as multipart,
- # hence being shown as an attachment. If we detect this is happening,
- # we force the decrypted payload to be interpreted as MIME.
- msg = RMail::Parser.read output
- if msg.header.content_type =~ %r{^multipart/} && !msg.multipart?
- output = "MIME-Version: 1.0\n" + output
- output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+ ## check for a valid signature in an extra run because gpg aborts if the
+ ## signature cannot be verified (but it is still able to decrypt)
+ sigoutput = run_gpg "#{payload_fn.path}"
+ sig = self.verified_ok? sigoutput, $?
+
+ if armor
+ msg = RMail::Message.new
+ # Look for Charset, they are put before the base64 crypted part
+ charsets = payload.body.split("\n").grep(/^Charset:/)
+ if !charsets.empty? and charsets[0] =~ /^Charset: (.+)$/
+ output = Iconv.easy_decode($encoding, $1, output)
+ end
+ msg.body = output
+ else
+ # This is gross. This decrypted payload could very well be a multipart
+ # element itself, as opposed to a simple payload. For example, a
+ # multipart/signed element, like those generated by Mutt when encrypting
+ # and signing a message (instead of just clearsigning the body).
+ # Supposedly, decrypted_payload being a multipart element ought to work
+ # out nicely because Message::multipart_encrypted_to_chunks() runs the
+ # decrypted message through message_to_chunks() again to get any
+ # children. However, it does not work as intended because these inner
+ # payloads need not carry a MIME-Version header, yet they are fed to
+ # RMail as a top-level message, for which the MIME-Version header is
+ # required. This causes for the part not to be detected as multipart,
+ # hence being shown as an attachment. If we detect this is happening,
+ # we force the decrypted payload to be interpreted as MIME.
msg = RMail::Parser.read output
+ if msg.header.content_type =~ %r{^multipart/} && !msg.multipart?
+ output = "MIME-Version: 1.0\n" + output
+ output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+ msg = RMail::Parser.read output
+ end
end
notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
[notice, sig, msg]
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -26,7 +26,13 @@ class Message
QUOTE_PATTERN = /^\s{0,4}[>|\}]/
BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/
- SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)|(^\s*--~--~-)|(^\s*--\+\+\*\*==)/
+ SIG_PATTERN = /(^(- )*-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)|(^\s*--~--~-)|(^\s*--\+\+\*\*==)/
+
+ GPG_SIGNED_START = "-----BEGIN PGP SIGNED MESSAGE-----"
+ GPG_SIGNED_END = "-----END PGP SIGNED MESSAGE-----"
+ GPG_START = "-----BEGIN PGP MESSAGE-----"
+ GPG_END = "-----END PGP MESSAGE-----"
+ GPG_SIG_END = "-----BEGIN PGP SIGNATURE-----"
MAX_SIG_DISTANCE = 15 # lines from the end
DEFAULT_SUBJECT = ""
@@ -522,8 +528,46 @@ private
## if there's no charset, use the current encoding as the charset.
## this ensures that the body is normalized to avoid non-displayable
## characters
- body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode) if m.body
- text_to_chunks((body || "").normalize_whitespace.split("\n"), encrypted)
+ if m.body
+ body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode)
+ else
+ body = ""
+ end
+
+ ## Check for inline-PGP
+ chunks = inline_gpg_to_chunks body.split("\n")
+ return chunks if chunks
+
+ text_to_chunks(body.normalize_whitespace.split("\n"), encrypted)
+ end
+ end
+ end
+
+ ## looks for gpg signed (but not encrypted) inline messages inside the
+ ## message body (there is no extra header for inline GPG) or for encrypted
+ ## (and possible signed) inline GPG messages
+ def inline_gpg_to_chunks lines
+ gpg = lines.between(GPG_SIGNED_START, GPG_SIGNED_END)
+ if !gpg.empty?
+ msg = RMail::Message.new
+ msg.body = gpg.join("\n")
+
+ sig = lines.between(GPG_SIGNED_START, GPG_SIG_END)
+ payload = RMail::Message.new
+ payload.body = sig[1, sig.size-2].join("\n")
+ return [CryptoManager.verify(nil, msg, false), message_to_chunks(payload)].flatten.compact
+ end
+
+ gpg = lines.between(GPG_START, GPG_END)
+ if !gpg.empty?
+ msg = RMail::Message.new
+ msg.body = gpg.join("\n")
+ notice, sig, decryptedm = CryptoManager.decrypt msg, true
+ if decryptedm # managed to decrypt
+ children = message_to_chunks(decryptedm, true)
+ return [notice, sig].compact + children
+ else
+ return [notice]
end
end
end
diff --git a/lib/sup/util.rb b/lib/sup/util.rb
@@ -460,6 +460,11 @@ module Enumerable
def max_of
map { |e| yield e }.max
end
+
+ ## returns all the entries which are equal to startline up to endline
+ def between startline, endline
+ select { |l| true if l == startline .. l == endline }
+ end
end
unless Object.const_defined? :Enumerator