Commit 22e89de0 authored by sand's avatar sand

py3 and python-gnupg compatibility fixes

parent d8d6af1d
# Package to send PGP-encrypted emails to users using ephemeral key
# storage (so, no keyserver interaction, no keyring management).
from __future__ import absolute_import
import contextlib
import gnupg
import os
import re
import shutil
import tempfile
from cStringIO import StringIO
from email import encoders
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from pgp_mime_lib import rfc3156
import gnupg
from ._compat import StringIO
from . import rfc3156
# Digest algorithm to use in signatures.
......@@ -33,20 +34,20 @@ class GPG(object):
public keyring which is deleted afterwards.
def __init__(self, key_id, public_keyring=None, secret_keyring=None,
def __init__(self, key_id, public_keyring=None, secret_key_dir=None,
if not public_keyring:
public_keyring = os.path.join(_default_gpghome(), 'pubring.gpg')
if not secret_keyring:
secret_keyring = os.path.join(_default_gpghome(), 'secring.gpg')
for f in (public_keyring, secret_keyring):
if not secret_key_dir:
secret_key_dir = os.path.join(_default_gpghome(), 'private-keys-v1.d')
for f in (public_keyring, secret_key_dir):
if not os.path.exists(f):
raise Error('no such file or directory: %s' % f)
self.public_keyring = public_keyring
self.secret_keyring = secret_keyring
self.secret_key_dir = secret_key_dir
self.secret_key_id = key_id
self.passphrase = passphrase
......@@ -67,7 +68,8 @@ class GPG(object):
if not public_keyring:
public_keyring = os.path.join(tmpdir, 'pubring.gpg')
shutil.copy(self.public_keyring, public_keyring)
os.chmod(public_keyring, 0600)
os.chmod(public_keyring, 0o600)
# Create a gpg.conf with some options. Not all are used
# and for some of them there are equivalent command-line
# options available through the gnupg module.
......@@ -77,14 +79,18 @@ personal-digest-preferences SHA512 SHA384 SHA256 SHA224
cert-digest-algo SHA256
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
default-key %s\n
''' % self.secret_key_id)
''' % self.secret_key_id) # noqa
# symlink the private key directory
os.symlink(self.secret_key_dir, os.path.join(tmpdir, 'private-keys-v1.d'))
# Create the gnupg.GPG object to talk to gnupg.
gpg = gnupg.GPG(binary=_default_gpgbinary(),
gpg = gnupg.GPG(gpgbinary=_default_gpgbinary(),
# XXX this below might be wrong.
gpg.encoding = 'utf-8'
yield gpg
......@@ -96,15 +102,14 @@ default-key %s\n
# public key, but we can't do it with the gnupg module.
fp = _import_one_key(gpg, public_key)
s = gpg.encrypt(msg_str, fp, passphrase=self.passphrase,
throw_keyids=True, always_trust=True)
always_trust=True, extra_args=["--throw-keyids"])
if not s:
raise Error('gpg --encrypt failed (no result)')
return str(s)
def _sign(self, gpg, msg_str):
"""Sign msg_str and return the detached signature."""
s = gpg.sign(msg_str, digest_algo=DIGEST_ALGO,
clearsign=False, detach=True, binary=False,
s = gpg.sign(msg_str, clearsign=False, detach=True, binary=False,
if not s:
raise Error('gpg --sign failed (no result)')
......@@ -133,7 +138,8 @@ default-key %s\n
def _sign_message(self, gpg, msg):
"""Build a multipart/signed MIME message."""
container = rfc3156.MultipartSigned('application/pgp-signature', 'pgp-' + DIGEST_ALGO.lower())
container = rfc3156.MultipartSigned('application/pgp-signature',
'pgp-' + DIGEST_ALGO.lower())
# _openpgp_mangle_for_signature modifies msg as a side effect.
sig_msg = rfc3156.PGPSignature(
......@@ -177,7 +183,7 @@ default-key %s\n
with self._sane_gpg(self.public_keyring) as gpg:
return self._sign_message(gpg, msg)
def _openpgp_mangle_for_signature(msg):
"""Return a message suitable for signing.
......@@ -233,10 +239,10 @@ def parse_public_key(public_key):
with _temp_dir() as tmpdir:
# Create empty pubrings.
for p in 'pubring.gpg', 'secring.gpg':
with open(os.path.join(tmpdir, p), 'w') as fd:
with open(os.path.join(tmpdir, p), 'w'):
gpg = gnupg.GPG(binary=_default_gpgbinary(),
gpg = gnupg.GPG(gpgbinary=_default_gpgbinary(),
gpg.encoding = 'utf-8'
return _import_one_key(gpg, public_key)
......@@ -251,10 +257,13 @@ def _import_one_key(gpg, public_key):
# one new key. We also want to report meaningful error
# messages, so we are just going to bail out on the first bad
# status returned by gpg.
if import_result.count != 1:
raise Error('more than one public key found in input')
fp = None
for res in import_result.results:
if res['status'].strip() != 'Entirely new key':
raise Error('no valid keys found in input: %s' % res['status'])
if "Entirely new key" not in res["text"]:
raise Error('no valid keys found in input: %r' % res['text'])
if not res['fingerprint']:
if fp:
import sys
import base64
PY2 = sys.version_info[0] == 2
if not PY2:
from io import StringIO
string_types = (str,)
from cStringIO import StringIO
string_types = (str, unicode)
def base64_encodestring(data):
if PY2:
return base64.encodestring(data)
return base64.encodebytes(data)
......@@ -20,9 +20,7 @@
Implements RFC 3156: MIME Security with OpenPGP.
import base64
from StringIO import StringIO
from __future__ import absolute_import
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
......@@ -34,6 +32,8 @@ from email.generator import (
from ._compat import StringIO, string_types, base64_encodestring
# A generator that solves
......@@ -66,7 +66,7 @@ class RFC3156CompliantGenerator(Generator):
subparts = msg.get_payload()
if subparts is None:
subparts = []
elif isinstance(subparts, basestring):
elif isinstance(subparts, string_types):
# e.g. a non-strict parse of a message with no starting boundary.
......@@ -120,18 +120,18 @@ class RFC3156CompliantGenerator(Generator):
# solution, but a bit modified.
def _bencode(s):
def _bencode(data):
Encode C{s} in base64.
Encode C{data} in base64.
:param s: The string to be encoded.
:type s: str
:param data: The string to be encoded.
:type data: bytes
# We can't quite use base64.encodestring() since it tacks on a "courtesy
# newline". Blech!
if not s:
return s
value = base64.encodestring(s)
if not data:
return data
value = base64_encodestring(data)
return value[:-1]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment