2005-11-08 14:46:09 +01:00
|
|
|
## common/GnuPG.py
|
2005-04-12 13:46:20 +02:00
|
|
|
##
|
2005-12-09 18:15:30 +01:00
|
|
|
## Contributors for this file:
|
2005-04-12 13:46:20 +02:00
|
|
|
## - Yann Le Boulanger <asterix@lagaule.org>
|
2006-09-28 13:31:25 +02:00
|
|
|
## - Nikos Kouremenos <kourem@gmail.com>
|
2005-04-12 13:46:20 +02:00
|
|
|
##
|
2005-12-10 00:30:28 +01:00
|
|
|
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
|
|
|
## Vincent Hanquez <tab@snarc.org>
|
|
|
|
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
|
|
|
## Vincent Hanquez <tab@snarc.org>
|
2006-09-28 13:31:25 +02:00
|
|
|
## Nikos Kouremenos <kourem@gmail.com>
|
2005-12-10 00:30:28 +01:00
|
|
|
## Dimitur Kirov <dkirov@gmail.com>
|
|
|
|
## Travis Shirk <travis@pobox.com>
|
|
|
|
## Norman Rasmussen <norman@rasmussen.co.za>
|
2005-04-12 13:46:20 +02:00
|
|
|
##
|
|
|
|
## This program is free software; you can redistribute it and/or modify
|
|
|
|
## it under the terms of the GNU General Public License as published
|
|
|
|
## by the Free Software Foundation; version 2 only.
|
|
|
|
##
|
|
|
|
## This program is distributed in the hope that it will be useful,
|
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
## GNU General Public License for more details.
|
|
|
|
##
|
|
|
|
|
2006-12-13 13:29:24 +01:00
|
|
|
import os
|
2005-08-30 23:10:14 +02:00
|
|
|
from os import tmpfile
|
2005-10-09 12:25:02 +02:00
|
|
|
from common import helpers
|
2005-04-12 13:46:20 +02:00
|
|
|
|
2005-07-20 22:19:27 +02:00
|
|
|
USE_GPG = True
|
2005-04-12 13:46:20 +02:00
|
|
|
|
|
|
|
try:
|
2005-06-26 14:41:18 +02:00
|
|
|
import GnuPGInterface # Debian package doesn't distribute 'our' file
|
|
|
|
except ImportError:
|
|
|
|
try:
|
|
|
|
from common import GnuPGInterface # use 'our' file
|
|
|
|
except ImportError:
|
2005-11-12 15:17:27 +01:00
|
|
|
USE_GPG = False # user can't do OpenGPG only if he or she removed the file!
|
2005-06-03 19:35:10 +02:00
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
else:
|
2006-12-13 13:29:24 +01:00
|
|
|
status = os.system('gpg -h >/dev/null 2>&1')
|
|
|
|
if status != 0:
|
|
|
|
USE_GPG = False
|
|
|
|
|
2005-06-03 19:35:10 +02:00
|
|
|
class GnuPG(GnuPGInterface.GnuPG):
|
2007-01-06 21:19:47 +01:00
|
|
|
def __init__(self, use_agent = False):
|
2005-04-17 19:08:28 +02:00
|
|
|
GnuPGInterface.GnuPG.__init__(self)
|
2007-01-06 21:19:47 +01:00
|
|
|
self.use_agent = use_agent
|
2005-04-17 19:08:28 +02:00
|
|
|
self._setup_my_options()
|
|
|
|
|
|
|
|
def _setup_my_options(self):
|
|
|
|
self.options.armor = 1
|
|
|
|
self.options.meta_interactive = 0
|
|
|
|
self.options.extra_args.append('--no-secmem-warning')
|
|
|
|
# Nolith's patch - prevent crashs on non fully-trusted keys
|
|
|
|
self.options.extra_args.append('--always-trust')
|
2007-01-06 21:19:47 +01:00
|
|
|
if self.use_agent:
|
|
|
|
self.options.extra_args.append('--use-agent')
|
2005-04-17 19:08:28 +02:00
|
|
|
|
|
|
|
def _read_response(self, child_stdout):
|
|
|
|
# Internal method: reads all the output from GPG, taking notice
|
|
|
|
# only of lines that begin with the magic [GNUPG:] prefix.
|
|
|
|
# (See doc/DETAILS in the GPG distribution for info on GPG's
|
|
|
|
# output when --status-fd is specified.)
|
|
|
|
#
|
|
|
|
# Returns a dictionary, mapping GPG's keywords to the arguments
|
|
|
|
# for that keyword.
|
|
|
|
|
|
|
|
resp = {}
|
|
|
|
while 1:
|
2005-10-09 12:43:14 +02:00
|
|
|
line = helpers.temp_failure_retry(child_stdout.readline)
|
2005-09-16 02:14:18 +02:00
|
|
|
if line == "": break
|
2005-04-17 19:08:28 +02:00
|
|
|
line = line.rstrip()
|
|
|
|
if line[0:9] == '[GNUPG:] ':
|
2005-09-16 02:14:18 +02:00
|
|
|
# Chop off the prefix
|
2005-04-17 19:08:28 +02:00
|
|
|
line = line[9:]
|
|
|
|
L = line.split(None, 1)
|
2005-09-16 02:14:18 +02:00
|
|
|
keyword = L[0]
|
|
|
|
if len(L) > 1:
|
|
|
|
resp[ keyword ] = L[1]
|
|
|
|
else:
|
|
|
|
resp[ keyword ] = ""
|
2005-04-17 19:08:28 +02:00
|
|
|
return resp
|
|
|
|
|
|
|
|
def encrypt(self, str, recipients):
|
|
|
|
if not USE_GPG:
|
2006-12-14 11:56:14 +01:00
|
|
|
return str, 'GnuPG not usable'
|
2005-04-17 19:08:28 +02:00
|
|
|
self.options.recipients = recipients # a list!
|
|
|
|
|
2006-12-06 17:19:47 +01:00
|
|
|
proc = self.run(['--encrypt'], create_fhs=['stdin', 'stdout',
|
|
|
|
'stderr'])
|
2005-04-17 19:08:28 +02:00
|
|
|
proc.handles['stdin'].write(str)
|
|
|
|
proc.handles['stdin'].close()
|
|
|
|
|
|
|
|
output = proc.handles['stdout'].read()
|
|
|
|
proc.handles['stdout'].close()
|
|
|
|
|
2006-12-06 17:19:47 +01:00
|
|
|
error = proc.handles['stderr'].read()
|
|
|
|
proc.handles['stderr'].close()
|
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
try: proc.wait()
|
|
|
|
except IOError: pass
|
2006-12-06 17:19:47 +01:00
|
|
|
return self._stripHeaderFooter(output), error
|
2005-04-12 13:46:20 +02:00
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
def decrypt(self, str, keyID):
|
|
|
|
if not USE_GPG:
|
|
|
|
return str
|
2005-07-22 19:26:05 +02:00
|
|
|
proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout'])
|
2005-04-17 19:08:28 +02:00
|
|
|
enc = self._addHeaderFooter(str, 'MESSAGE')
|
|
|
|
proc.handles['stdin'].write(enc)
|
|
|
|
proc.handles['stdin'].close()
|
2005-04-12 13:46:20 +02:00
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
output = proc.handles['stdout'].read()
|
|
|
|
proc.handles['stdout'].close()
|
|
|
|
|
|
|
|
try: proc.wait()
|
|
|
|
except IOError: pass
|
|
|
|
return output
|
|
|
|
|
2005-09-16 02:14:18 +02:00
|
|
|
def sign(self, str, keyID):
|
2005-04-17 19:08:28 +02:00
|
|
|
if not USE_GPG:
|
2005-09-16 02:14:18 +02:00
|
|
|
return str
|
|
|
|
proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr'])
|
|
|
|
proc.handles['stdin'].write(str)
|
2005-11-08 14:43:23 +01:00
|
|
|
try:
|
|
|
|
proc.handles['stdin'].close()
|
|
|
|
except IOError:
|
|
|
|
pass
|
2005-04-17 19:08:28 +02:00
|
|
|
|
|
|
|
output = proc.handles['stdout'].read()
|
2005-11-08 14:43:23 +01:00
|
|
|
try:
|
|
|
|
proc.handles['stdout'].close()
|
|
|
|
proc.handles['stderr'].close()
|
|
|
|
except IOError:
|
|
|
|
pass
|
2005-04-17 19:08:28 +02:00
|
|
|
|
2005-09-16 02:14:18 +02:00
|
|
|
stat = proc.handles['status']
|
|
|
|
resp = self._read_response(stat)
|
2005-11-08 14:43:23 +01:00
|
|
|
try:
|
|
|
|
proc.handles['status'].close()
|
|
|
|
except IOError:
|
|
|
|
pass
|
2005-09-16 02:14:18 +02:00
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
try: proc.wait()
|
|
|
|
except IOError: pass
|
2005-09-16 02:14:18 +02:00
|
|
|
if resp.has_key('GOOD_PASSPHRASE') or resp.has_key('SIG_CREATED'):
|
|
|
|
return self._stripHeaderFooter(output)
|
2005-07-22 19:28:45 +02:00
|
|
|
return 'BAD_PASSPHRASE'
|
2005-04-17 19:08:28 +02:00
|
|
|
|
|
|
|
def verify(self, str, sign):
|
|
|
|
if not USE_GPG:
|
|
|
|
return str
|
2006-07-24 18:40:11 +02:00
|
|
|
if str == None:
|
2005-04-17 19:08:28 +02:00
|
|
|
return ''
|
2005-08-30 23:10:14 +02:00
|
|
|
f = tmpfile()
|
2005-07-22 19:27:03 +02:00
|
|
|
fd = f.fileno()
|
|
|
|
f.write(str)
|
|
|
|
f.seek(0)
|
2005-04-17 19:08:28 +02:00
|
|
|
|
|
|
|
proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr'])
|
|
|
|
|
2005-07-22 19:27:03 +02:00
|
|
|
f.close()
|
2005-04-17 19:08:28 +02:00
|
|
|
sign = self._addHeaderFooter(sign, 'SIGNATURE')
|
|
|
|
proc.handles['stdin'].write(sign)
|
|
|
|
proc.handles['stdin'].close()
|
|
|
|
proc.handles['stderr'].close()
|
|
|
|
|
|
|
|
stat = proc.handles['status']
|
|
|
|
resp = self._read_response(stat)
|
|
|
|
proc.handles['status'].close()
|
|
|
|
|
|
|
|
try: proc.wait()
|
|
|
|
except IOError: pass
|
|
|
|
|
|
|
|
keyid = ''
|
|
|
|
if resp.has_key('GOODSIG'):
|
|
|
|
keyid = resp['GOODSIG'].split()[0]
|
|
|
|
return keyid
|
|
|
|
|
2005-05-29 23:34:01 +02:00
|
|
|
def get_keys(self, secret = False):
|
2005-04-17 19:08:28 +02:00
|
|
|
if not USE_GPG:
|
2005-07-22 19:28:45 +02:00
|
|
|
return {}
|
2005-05-29 23:34:01 +02:00
|
|
|
if secret:
|
|
|
|
opt = '--list-secret-keys'
|
|
|
|
else:
|
|
|
|
opt = '--list-keys'
|
2005-11-18 14:12:16 +01:00
|
|
|
proc = self.run(['--with-colons', opt],
|
2005-04-17 19:08:28 +02:00
|
|
|
create_fhs=['stdout'])
|
|
|
|
output = proc.handles['stdout'].read()
|
|
|
|
proc.handles['stdout'].close()
|
|
|
|
|
|
|
|
keys = {}
|
|
|
|
lines = output.split('\n')
|
|
|
|
for line in lines:
|
|
|
|
sline = line.split(':')
|
2005-05-29 23:34:01 +02:00
|
|
|
if (sline[0] == 'sec' and secret) or \
|
|
|
|
(sline[0] == 'pub' and not secret):
|
2005-09-23 20:49:51 +02:00
|
|
|
# make it unicode instance
|
2005-11-18 13:22:33 +01:00
|
|
|
keys[sline[4][8:]] = helpers.decode_string(sline[9])
|
2005-04-17 19:08:28 +02:00
|
|
|
return keys
|
|
|
|
try: proc.wait()
|
|
|
|
except IOError: pass
|
|
|
|
|
2005-05-29 23:34:01 +02:00
|
|
|
def get_secret_keys(self):
|
|
|
|
return self.get_keys(True)
|
|
|
|
|
2005-04-17 19:08:28 +02:00
|
|
|
def _stripHeaderFooter(self, data):
|
|
|
|
"""Remove header and footer from data"""
|
2005-04-26 23:33:01 +02:00
|
|
|
if not data: return ''
|
2005-04-17 19:08:28 +02:00
|
|
|
lines = data.split('\n')
|
|
|
|
while lines[0] != '':
|
|
|
|
lines.remove(lines[0])
|
|
|
|
while lines[0] == '':
|
|
|
|
lines.remove(lines[0])
|
|
|
|
i = 0
|
|
|
|
for line in lines:
|
|
|
|
if line:
|
|
|
|
if line[0] == '-': break
|
|
|
|
i = i+1
|
|
|
|
line = '\n'.join(lines[0:i])
|
|
|
|
return line
|
|
|
|
|
|
|
|
def _addHeaderFooter(self, data, type):
|
|
|
|
"""Add header and footer from data"""
|
|
|
|
out = "-----BEGIN PGP %s-----\n" % type
|
|
|
|
out = out + "Version: PGP\n"
|
|
|
|
out = out + "\n"
|
|
|
|
out = out + data + "\n"
|
|
|
|
out = out + "-----END PGP %s-----\n" % type
|
|
|
|
return out
|