6 class Error < StandardError; end
8 OUTGOING_MESSAGE_OPERATIONS = OrderedHash.new(
10 [:sign_and_encrypt, "Sign and encrypt"],
11 [:encrypt, "Encrypt only"]
16 self.class.i_am_the_instance self
18 bin = `which gpg`.chomp
23 Redwood::log "crypto: detected gpg binary in #{bin}"
24 "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
26 Redwood::log "crypto: no gpg binary detected"
31 def have_crypto?; !@cmd.nil? end
33 def sign from, to, payload
34 payload_fn = Tempfile.new "redwood.payload"
35 payload_fn.write format_payload(payload)
38 output = run_gpg "--output - --armor --detach-sign --textmode --local-user '#{from}' #{payload_fn.path}"
40 raise Error, (output || "gpg command failed: #{cmd}") unless $?.success?
42 envelope = RMail::Message.new
43 envelope.header["Content-Type"] = 'multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha1'
45 envelope.add_part payload
46 signature = RMail::Message.make_attachment output, "application/pgp-signature", nil, "signature.asc"
47 envelope.add_part signature
51 def encrypt from, to, payload, sign=false
52 payload_fn = Tempfile.new "redwood.payload"
53 payload_fn.write format_payload(payload)
56 recipient_opts = to.map { |r| "--recipient '<#{r}>'" }.join(" ")
57 sign_opts = sign ? "--sign --local-user '#{from}'" : ""
58 gpg_output = run_gpg "--output - --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}"
59 raise Error, (gpg_output || "gpg command failed: #{cmd}") unless $?.success?
61 encrypted_payload = RMail::Message.new
62 encrypted_payload.header["Content-Type"] = "application/octet-stream"
63 encrypted_payload.header["Content-Disposition"] = 'inline; filename="msg.asc"'
64 encrypted_payload.body = gpg_output
66 control = RMail::Message.new
67 control.header["Content-Type"] = "application/pgp-encrypted"
68 control.header["Content-Disposition"] = "attachment"
69 control.body = "Version: 1\n"
71 envelope = RMail::Message.new
72 envelope.header["Content-Type"] = 'multipart/encrypted; protocol="application/pgp-encrypted"'
74 envelope.add_part control
75 envelope.add_part encrypted_payload
79 def sign_and_encrypt from, to, payload
80 encrypt from, to, payload, true
83 def verify payload, signature # both RubyMail::Message objects
84 return unknown_status(cant_find_binary) unless @cmd
86 payload_fn = Tempfile.new "redwood.payload"
87 payload_fn.write format_payload(payload)
90 signature_fn = Tempfile.new "redwood.signature"
91 signature_fn.write signature.decode
94 output = run_gpg "--verify #{signature_fn.path} #{payload_fn.path}"
95 output_lines = output.split(/\n/)
97 if output =~ /^gpg: (.* signature from .*$)/
99 Chunk::CryptoNotice.new :valid, $1, output_lines
101 Chunk::CryptoNotice.new :invalid, $1, output_lines
104 unknown_status output_lines
108 ## returns decrypted_message, status, desc, lines
109 def decrypt payload # a RubyMail::Message object
110 return unknown_status(cant_find_binary) unless @cmd
112 payload_fn = Tempfile.new "redwood.payload"
113 payload_fn.write payload.to_s
116 output = run_gpg "--decrypt #{payload_fn.path}"
119 decrypted_payload, sig_lines =
120 if output =~ /\A(.*?)((^gpg: .*$)+)\Z/m
127 if sig_lines # encrypted & signed
128 if sig_lines =~ /^gpg: (Good signature from .*$)/
129 Chunk::CryptoNotice.new :valid, $1, sig_lines.split("\n")
131 Chunk::CryptoNotice.new :invalid, $1, sig_lines.split("\n")
135 notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
136 [RMail::Parser.read(decrypted_payload), sig, notice]
138 notice = Chunk::CryptoNotice.new :invalid, "This message could not be decrypted", output.split("\n")
145 def unknown_status lines=[]
146 Chunk::CryptoNotice.new :unknown, "Unable to determine validity of cryptographic signature", lines
150 ["Can't find gpg binary in path."]
153 ## here's where we munge rmail output into the format that signed/encrypted
154 ## PGP/GPG messages should be
155 def format_payload payload
156 payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "")
160 cmd = "#{@cmd} #{args} 2> /dev/null"
161 #Redwood::log "crypto: running: #{cmd}"
163 #Redwood::log "crypto: output: #{output.inspect}" unless $?.success?