Sunday, January 06, 2008

GPG Encryption for TextMate

Here is a wxPython based encryption script for TextMate, it presents a dialog box for selection of the key. Set default_gpg_user to the user you normally use for encryption.


#!/usr/bin/env python
import os, sys, tempfile, traceback
from subprocess import *
import wx

default_gpg_user = "brianlane"

exit_discard = 200
exit_show_tooltip = 206

def GetRecipient():
keys = GetGpgKeys()
key_list = []
user_idx = 0
i=0
for k in keys:
if k[0] == "uid":
s = " %s %s" % (k[9],k[5])
else:
s = "%s %s %s" % (k[9],k[5],k[4])
key_list += [s]
if k[9].find(default_gpg_user) > -1:
user_idx = i
i += 1

dialog = wx.SingleChoiceDialog(None, "Select a GPG Key", "Encrypt Text with GPG", key_list)
dialog.SetSelection(user_idx)
dialog.SetSize((400,200))
key_info = None
if dialog.ShowModal() == wx.ID_OK:
key_info = keys[dialog.GetSelection()]
dialog.Destroy()

return key_info


def GpgEncryptFile( gpg_file, gpg_recipient ):
cmd = 'gpg -qeaR "%s" --batch --output - %s' % (gpg_recipient, gpg_file)
p = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE )
output = ""
for line in p.stdout:
output += line
stderr = ""
for line in p.stderr:
stderr += line

return output, stderr


def rmfile( file_path ):
if os.path.exists(file_path):
os.unlink(file_path)

def GetGpgKeys():
cmd = "gpg --list-keys --with-colons"
p = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE )
key_list = []
for k in p.stdout:
key_args = k.strip().split(':')
if key_args[0] in ['pub']:
key_list += [key_args]
return key_list


app = wx.PySimpleApp()

key_info = GetRecipient()
if not key_info:
exit(exit_discard)

# Get the input for gpg
gpg_input = sys.stdin.read()

# Write it to a secure file
(out_fp, out_path) = tempfile.mkstemp()
os.write(out_fp,gpg_input)
os.close(out_fp)

try:
gpg_armour, gpg_error = GpgEncryptFile(out_path, key_info[4])
if not gpg_armour:
rmfile(out_path)
sys.stdout.write(gpg_error)
exit(exit_show_tooltip)
sys.stdout.write(gpg_armour)
except:
rmfile(out_path)
traceback.print_exc(file=sys.stdout)
exit(exit_show_tooltip)

rmfile(out_path)

GPG Decryption for TextMate

Its been a while since my last post. I've switched desktops to OSX (a dual-core Mac mini), settled on wxPython as a good GUI development environment and switched to using TextMate as my main text editor.

TextMate has a really nice feature called Bundles, these are scripts that you can use to filter the text in a document or selection. Inspired by this blog posting I set out to add GPG encryption/decryption to TextMate. After fighting with cut&paste problems (for some reason the code wouldn't run when cut from the website and pasted into the Bundle Editor. His code works fine, but has one serious drawback -- it exposes the passphrase on the command line when decrypting the GPG message.

After whacking around with bash a bit I finally just wrote a python script to do it safely. I use wxPython for the passphrase prompt dialog
and standard python modules for everything else (I am using Python 2.5).

(Sorry! Blogspot seems to mangle the formatting!)


#!/usr/bin/env python
''' TextMate Bundle for Decrypting GPG messages
Copyright 2008 by coderpunk@gmail.com
All Rights Reserved
GPL V2.0
'''
#!/usr/bin/env python
import os, sys, tempfile, traceback
from subprocess import *
import wx

exit_discard = 200
exit_show_tooltip = 206

def GetPassword():
passphrase = None
dialog = wx.TextEntryDialog( None,
"GPG Passphrase",
"Encrypt Text with GPG",
"",
style=wx.OK|wx.CANCEL|wx.PASSWORD)
if dialog.ShowModal() == wx.ID_OK:
passphrase = dialog.GetValue()
dialog.Destroy()
return passphrase


def GpgDecryptFile( gpg_file, passphrase ):
cmd = "gpg -qd --batch --passphrase-fd 0 %s" % (gpg_file)
p = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE )
(stdout, stderr) = p.communicate( passphrase )
return stdout

def rmfile( file_path ):
if os.path.exists(file_path):
os.unlink(file_path)

app = wx.PySimpleApp()
passphrase = GetPassword()
if not passphrase:
exit(exit_discard)

# Get the input for gpg
gpg_input = sys.stdin.read()

# Write it to a secure file
(out_fp, out_path) = tempfile.mkstemp()
os.write(out_fp,gpg_input)
os.close(out_fp)

try:
gpg_output = GpgDecryptFile( out_path, passphrase )
if not gpg_output:
rmfile(out_path)
exit(exit_show_tooltip)
sys.stdout.write(gpg_output)
except:
rmfile(out_path)
traceback.print_exc(file=sys.stdout)
exit(exit_show_tooltip)

rmfile(out_path)