Revision 960c259e
Added by Marc Dequènes almost 16 years ago
- ID 960c259e23a88e40cbcfd210fbc8cc052eb7bb5a
TODO | ||
---|---|---|
- handle incoming encrypted messages from user to receive sensitive data, and to sign replies -> postman would need a GPG key too
|
||
- sign mail replies
|
||
- ban keys from unknow users flooding -> counter, reseted when key added in DB
|
||
- protect against replay (foo resending eavesdropped mail) -> store message IDs, but how to limit to a reasonnable timeframe ?
|
||
- protect against intercepted mail with falsified headers (From/Reply-To/... could be tampered to get replies, reply tampered too, and then resent to avoid being detected)
|
||
- check "protocol" field in "Content-Type" for received signed/encrypted mails
|
lib/cyborghood/mail.rb | ||
---|---|---|
end
|
||
|
||
def parse
|
||
return parse_signed() if is_pgp_signed?
|
||
return parse_encrypted() if is_pgp_encrypted?
|
||
{:ok => false, :msg => "mail not RFC3156 compliant"}.to_ostruct
|
||
end
|
||
|
||
def parse_signed
|
||
order = {:ok => false, :msg => "mail not formatted correctly"}
|
||
|
||
sig_check = verify_pgp_signature()
|
||
... | ... | |
order.to_ostruct
|
||
end
|
||
|
||
def parse_encrypted
|
||
order = {:ok => false, :msg => "mail not formatted correctly"}
|
||
|
||
catch :notforme do
|
||
begin
|
||
# block is not passed to delegate (limitation ?)
|
||
clear_message = @mail.decrypt do |uid_hint, passphrase_info, prev_was_bad|
|
||
logger.info "Mail crypted for #{uid_hint}"
|
||
|
||
# check if requesting passphrase for the expected key
|
||
key_id = passphrase_info.split(" ")[1]
|
||
throw :notforme if key_id != @config.mail.key_id[-(key_id.size)..-1]
|
||
|
||
# sending key
|
||
@config.mail.key_passphrase
|
||
end
|
||
|
||
# create a fake mail and chain parsing operations
|
||
clear_mail = self.class.new(clear_message)
|
||
return clear_mail.parse
|
||
rescue GPGME::Error, NotImplementedError => e
|
||
raise CyberError.new(:unrecoverable, "protocol/mail", e.message)
|
||
end
|
||
end
|
||
|
||
order.to_ostruct
|
||
end
|
||
|
||
def create_reply
|
||
tmail_reply = @mail.create_reply
|
||
tmail_reply.from_addrs = TMail::Address.parse(@config.mail.from_address || self.to.first)
|
lib/tmail_extra.rb | ||
---|---|---|
module TMail
|
||
class Mail
|
||
def is_pgp_signed?
|
||
content_type == "multipart/signed" and parts.size == 2 and parts[1].content_type == "application/pgp-signature"
|
||
content_type == "multipart/signed" and parts.size == 2 and
|
||
parts[1].content_type == "application/pgp-signature"
|
||
end
|
||
|
||
def pgp_signature
|
||
... | ... | |
sig = pgp_signature()
|
||
|
||
sig_check = nil
|
||
GPGME::verify(sig, content) do |signature|
|
||
GPGME.verify(sig, content) do |signature|
|
||
sig_check = signature
|
||
end
|
||
|
||
sig_check
|
||
end
|
||
|
||
def is_pgp_encrypted?
|
||
content_type == "multipart/encrypted" and parts.size == 2 and
|
||
parts[0].content_type == "application/pgp-encrypted" and
|
||
parts[1].content_type == "application/octet-stream"
|
||
end
|
||
|
||
def pgp_crypt_info
|
||
return nil unless is_pgp_encrypted?
|
||
a = parts[0].body.split("\n").collect{|l| l.chomp.split(": ") if l =~ /: / }.compact.flatten
|
||
Hash[*a]
|
||
end
|
||
|
||
def pgp_encrypted_part
|
||
return nil unless is_pgp_encrypted?
|
||
parts[1].body
|
||
end
|
||
|
||
def decrypt(&passphrase_callback)
|
||
return nil unless is_pgp_encrypted?
|
||
protocol_version = pgp_crypt_info()["Version"].to_i
|
||
raise NotImplementedError, "pgp-encrypted protocol version #{protocol_version} is not implemented" unless protocol_version == 1
|
||
|
||
encrypted_data = pgp_encrypted_part()
|
||
passphrase_callback_wrapper = Proc.new do |hook, uid_hint, passphrase_info, prev_was_bad, fd|
|
||
# sending key
|
||
io = IO.for_fd(fd, 'w')
|
||
io.puts hook.call(uid_hint, passphrase_info, prev_was_bad)
|
||
io.flush
|
||
end
|
||
GPGME.decrypt(encrypted_data, {:passphrase_callback => passphrase_callback_wrapper, :passphrase_callback_value => passphrase_callback, :textmode => true})
|
||
end
|
||
|
||
protected
|
||
|
||
def raw
|
postman | ||
---|---|---|
mail = Mail.new(msg.content)
|
||
logger.info "Received mail with ID '#{mail.message_id}': #{mail.from_addrs} -> #{mail.to_addrs} (#{mail.subject})"
|
||
|
||
# ignore mails not signed
|
||
unless mail.is_pgp_signed?
|
||
logger.info "Mail not signed or not RFC3156 compliant, ignoring..."
|
||
# ignore mails not signed or encrypted
|
||
unless mail.is_pgp_signed? or mail.is_pgp_encrypted?
|
||
logger.info "Mail not signed/encrypted or not RFC3156 compliant, ignoring..."
|
||
msg.delete
|
||
next
|
||
end
|
||
|
||
logger.debug "Signed content detected"
|
||
logger.debug "RFC3156 content detected"
|
||
begin
|
||
order = mail.parse
|
||
rescue CyberError => e
|
||
... | ... | |
msg.delete
|
||
next
|
||
elsif not order.ok
|
||
logger.info "Sending reply for rejected message (#{order.msg})"
|
||
mail_reply = mail.create_reply
|
||
mail_reply.quoted_printable_body = "A message (ID: #{mail.message_id}) apparently from you was rejected for the following reason:\n #{order.msg}"
|
||
mail_reply.deliver
|
||
... | ... | |
next
|
||
end
|
||
|
||
logger.info "Message accepted, processing orders..."
|
||
result_list = CommandParser.run(order)
|
||
|
||
# create reply
|
Also available in: Unified diff
[evol] handle incoming encrypted messages