Class: Redwood::CryptoManager
- Includes:
- Singleton
- Defined in:
- lib/sup/crypto.rb
Defined Under Namespace
Classes: Error
Constant Summary collapse
- OUTGOING_MESSAGE_OPERATIONS =
OrderedHash.new( [:sign, "Sign"], [:sign_and_encrypt, "Sign and encrypt"], [:encrypt, "Encrypt only"] )
Instance Method Summary collapse
-
#decrypt(payload, armor = false) ⇒ Object
returns decrypted_message, status, desc, lines.
- #encrypt(from, to, payload, sign = false) ⇒ Object
- #have_crypto? ⇒ Boolean
-
#initialize ⇒ CryptoManager
constructor
A new instance of CryptoManager.
- #sign(from, to, payload) ⇒ Object
- #sign_and_encrypt(from, to, payload) ⇒ Object
- #verified_ok?(output, rc) ⇒ Boolean
-
#verify(payload, signature, detached = true) ⇒ Object
both RubyMail::Message objects.
Methods included from Singleton
Constructor Details
#initialize ⇒ CryptoManager
Returns a new instance of CryptoManager.
25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/sup/crypto.rb', line 25 def initialize @mutex = Mutex.new bin = `which gpg`.chomp @cmd = case bin when /\S/ debug "crypto: detected gpg binary in #{bin}" "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent" else debug "crypto: no gpg binary detected" nil end end |
Instance Method Details
#decrypt(payload, armor = false) ⇒ Object
returns decrypted_message, status, desc, lines
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/sup/crypto.rb', line 142 def decrypt payload, armor=false # a RubyMail::Message object return unknown_status(cant_find_binary) unless @cmd payload_fn = Tempfile.new(["redwood.payload", ".asc"]) payload_fn.write payload.to_s payload_fn.close output_fn = Tempfile.new "redwood.output" output_fn.close = run_gpg "--output #{output_fn.path} --skip-verify --yes --decrypt #{payload_fn.path}", :interactive => true unless $?.success? info "Error while running gpg: #{}" return Chunk::CryptoNotice.new(:invalid, "This message could not be decrypted", .split("\n")) end output = IO.read output_fn.path 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 # It appears that some clients use Windows new lines - CRLF - but RMail # splits the body and header on "\n\n". So to allow the parse below to # succeed, we will convert the newlines to what RMail expects output = output.gsub(/\r\n/, "\n") # 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] end |
#encrypt(from, to, payload, sign = false) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/sup/crypto.rb', line 64 def encrypt from, to, payload, sign=false payload_fn = Tempfile.new "redwood.payload" payload_fn.write format_payload(payload) payload_fn.close encrypted_fn = Tempfile.new "redwood.encrypted"; encrypted_fn.close recipient_opts = (to + [ from ] ).map { |r| "--recipient '<#{r}>'" }.join(" ") sign_opts = "" sign_opts = "--sign --digest-algo sha256 " + gen_sign_user_opts(from) if sign = run_gpg "--output #{encrypted_fn.path} --yes --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}", :interactive => true unless $?.success? info "Error while running gpg: #{}" raise Error, "GPG command failed. See log for details." end 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 = IO.read(encrypted_fn.path) 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 |
#have_crypto? ⇒ Boolean
39 |
# File 'lib/sup/crypto.rb', line 39 def have_crypto?; !@cmd.nil? end |
#sign(from, to, payload) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/sup/crypto.rb', line 41 def sign from, to, payload payload_fn = Tempfile.new "redwood.payload" payload_fn.write format_payload(payload) payload_fn.close sig_fn = Tempfile.new "redwood.signature"; sig_fn.close sign_user_opts = gen_sign_user_opts from = run_gpg "--output #{sig_fn.path} --yes --armor --detach-sign --textmode --digest-algo sha256 #{sign_user_opts} #{payload_fn.path}", :interactive => true unless $?.success? info "Error while running gpg: #{}" raise Error, "GPG command failed. See log for details." end envelope = RMail::Message.new envelope.header["Content-Type"] = 'multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha256' envelope.add_part payload signature = RMail::Message. IO.read(sig_fn.path), "application/pgp-signature", nil, "signature.asc" envelope.add_part signature envelope end |
#sign_and_encrypt(from, to, payload) ⇒ Object
98 99 100 |
# File 'lib/sup/crypto.rb', line 98 def sign_and_encrypt from, to, payload encrypt from, to, payload, true end |
#verified_ok?(output, rc) ⇒ Boolean
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/sup/crypto.rb', line 102 def verified_ok? output, rc output_lines = output.split(/\n/) if output =~ /^gpg: (.* signature from .*$)/ if rc == 0 Chunk::CryptoNotice.new :valid, $1, output_lines else Chunk::CryptoNotice.new :invalid, $1, output_lines end elsif output_lines.length == 0 && rc == 0 # the message wasn't signed Chunk::CryptoNotice.new :valid, "Encrypted message wasn't signed", output_lines else unknown_status output_lines end end |
#verify(payload, signature, detached = true) ⇒ Object
both RubyMail::Message objects
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/sup/crypto.rb', line 119 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 |