Project

General

Profile

Download (2.75 KB) Statistics
| Branch: | Tag: | Revision:
2891e0c2 Marc Dequenes
# attempt to check PGP signature in a RFC3156-compliant way
module TMail
class Mail
def is_pgp_signed?
960c259e Marc Dequenes
content_type == "multipart/signed" and parts.size == 2 and
parts[1].content_type == "application/pgp-signature"
2891e0c2 Marc Dequenes
end

def pgp_signature
return nil unless is_pgp_signed?
parts[1].decoded
end

def pgp_signed_part
return nil unless is_pgp_signed?
parts[0]
end

def verify_pgp_signature
return nil unless is_pgp_signed?

09b32d70 Marc Dequenes
content = parts[0].to_rfc3156
2891e0c2 Marc Dequenes
sig = pgp_signature()

09b32d70 Marc Dequenes
sigs_check = nil
960c259e Marc Dequenes
GPGME.verify(sig, content) do |signature|
09b32d70 Marc Dequenes
sigs_check ||= []
sigs_check << signature
2891e0c2 Marc Dequenes
end

09b32d70 Marc Dequenes
sigs_check
2891e0c2 Marc Dequenes
end

960c259e Marc Dequenes
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

09b32d70 Marc Dequenes
def pgp_decrypt(&passphrase_callback)
960c259e Marc Dequenes
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()
09b32d70 Marc Dequenes
GPGME.decrypt(encrypted_data, {:passphrase_callback => method(:gpg_passphrase_callback_wrapper),
:passphrase_callback_value => passphrase_callback, :textmode => true})
end

def pgp_crypt(fingerprint)
GPGME.encrypt([gpg_key(fingerprint)], self.to_s, {:armor => true, :always_trust => true})
end

def pgp_sign(signers_id, &passphrase_callback)
signers = signers_id.collect{|key_id| gpg_key(key_id) }
GPGME.sign(self.to_rfc3156, {:signers => signers, :passphrase_callback => method(:gpg_passphrase_callback_wrapper),
:passphrase_callback_value => passphrase_callback, :armor => true})
end

def to_rfc3156
# using RAW part, without any decoding
# remove last EOL due to MIME protocol and properly convert all EOL to CRLF
raw.chomp.gsub(/\r?\n/, "\r\n")
960c259e Marc Dequenes
end

2891e0c2 Marc Dequenes
protected

09b32d70 Marc Dequenes
def gpg_key(fingerprint)
gpg = GPGME::Ctx.new
gpg.get_key(fingerprint)
end

def gpg_passphrase_callback_wrapper(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

2891e0c2 Marc Dequenes
def raw
@port.read_all
end
end
end