Giter Site home page Giter Site logo

python-gnupg's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-gnupg's Issues

gnupg._parsers.ListPackets can't parse GOOD_PASSPHRASE status messages

>>> e = '-----BEGIN PGP MESSAGE-----\n\nhQEMAwdd9Q8Gx/nRAQf+KUJ3KT+Dw4jopTcv3MAJY3ZupId3+sUlm7HYLK1VGAiA\n720ccR6bxaaO6ZuvV/w9irhQNke7ekIn7Q87oS+iXDu27xuPCb/vwKRrBjGw0HuE\n/i4TaX610bsOtlaY2le1gW+YDGhNDATZ5PNQtUR5hjlIIJJf4c4FJ9biLjkI7hNh\nNX1QsB6GdT5MEd9jHFNrLY4ckNsF3/+1qBrkWzkNHRIYGT3ddNFcZ9jR+TJaMlQA\nmtBwDUStMgD2JWwWkib1lNaNBD/PJ4WQJTudEAqY7jn8HCqy27TV2UQn2Q+qHa4U\nKvFv5llAmblTVBUYUtL4fuzy/45t5osSwkl4K4Z93NJBAXN3KAkA8Bi2R/4Edbss\nVlBELWvI+ELho8UUSJLegusuEyw+VuxPwEwBXA+bJtD1maBNWpofR7/rbXkXbYB8\nNGs=\n=l0h+\n-----END PGP MESSAGE-----\n'
>>> gpg.list_packets(e)
Exception in thread Thread-30:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "gnupg/_meta.py", line 626, in _read_response
    line = _util._deprefix(line, 'gpg: ')
  File "gnupg/_parsers.py", line 1555, in _handle_status
ValueError: Unknown status message: u'GOOD_PASSPHRASE'

PY3: Significantly slower than gpg

Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz

$ time gpg --passphrase speedtest --compress-algo none --cipher-algo AES256 --output /dev/null --symmetric ./ramdisk/rnd-1G.bin
real 0m18.791s, user 0m18.470s, sys 0m0.316s

That puts it at about 425.8 Mb/s on my machine as compared to 129.9 Mb/s for gnupg. I fully read the file into memory and passed bytearray to the encrypt routine. I only measured the encrypt.

$ python3 ./tgpp.py
gnupg version: 1.4.0
[True] tottime 64.02s, encrypt time 63.95s 129.686477 Mb/s

Here's the test code:

import time
import os
import sys
import gnupg

print ('gnupg version:', gnupg.__version__)

infile = os.path.expanduser ('~/.tmp/rnd-1M.bin')
with open (infile, 'rb') as f:
    fsz = os.path.getsize (infile)
    data = bytearray (fsz)
    f.readinto (data)
    kwargs = dict (passphrase='speedtest',
                   symmetric=True,
                   cipher_algo='AES256',
                   armor=False,
                   encrypt=False,
                   compress_algo='Uncompressed')

    t0 = time.perf_counter()
    gpg = gnupg.GPG (binary='/usr/bin/gpg')
    t1 = time.perf_counter()
    r = gpg.encrypt (data, None, **kwargs)
    t2 = time.perf_counter()

print ('[{}] tottime {:.2f}s, encrypt time {:.2f}s'.format (r.ok, t2-t0, t2-t1), end=' ')
print ('{:2f} Mb/s'.format (fsz*8 / 1e6 / (t2-t1)))

When I added cProfile'ing code around encrypt, the two big time users were encrypt and _thread.lock.

         1010 function calls in 63.545 seconds

   Ordered by: cumulative time
   List reduced from 131 to 40 due to restriction <40>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   63.545   63.545 /usr/local/lib/python3.4/dist-packages/gnupg/gnupg.py:871(encrypt)
        1   16.353   16.353   62.879   62.879 /usr/local/lib/python3.4/dist-packages/gnupg/_meta.py:799(_encrypt)
        1    0.000    0.000   46.525   46.525 /usr/local/lib/python3.4/dist-packages/gnupg/_meta.py:713(_handle_io)
        1    0.000    0.000   46.467   46.467 /usr/local/lib/python3.4/dist-packages/gnupg/_meta.py:681(_collect_output)
       18   46.467    2.582   46.467    2.582 {method 'acquire' of '_thread.lock' objects}
        3    0.000    0.000   46.466   15.489 /usr/lib/python3.4/threading.py:1028(join)

PY3: Cannot encrypt to a file

import gnupg
import os

print ('gnupg version:', gnupg.__version__)

infile = os.path.expanduser ('~/usr/purernd.bin')
with open (infile, 'rb') as f:
    data = f.read()

kwargs = dict (passphrase='speedtest',
               symmetric=True,
               cipher_algo='AES256',
               armor=False,
               encrypt=False,
               output='/tmp/purernd.enc.gnupg')

gpg = gnupg.GPG (binary='/usr/bin/gpg')
r = gpg.encrypt (data, None, **kwargs)
print (r.ok)
gnupg version: 1.4.0
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-c5d667873aa8> in <module>()
     17 gpg = gnupg.GPG (binary='/usr/bin/gpg')
     18 t1 = time.perf_counter()
---> 19 r = gpg.encrypt (data, None, **kwargs)
     20 t2 = time.perf_counter()
     21 

/mnt/hgfs/work/branches/jayne/virts/jayne/lib/python3.4/site-packages/gnupg/gnupg.py in encrypt(self, data, *recipients, **kwargs)
    954         """
    955         stream = _make_binary_stream(data, self._encoding)
--> 956         result = self._encrypt(stream, recipients, **kwargs)
    957         stream.close()
    958         return result

/mnt/hgfs/work/branches/jayne/virts/jayne/lib/python3.4/site-packages/gnupg/_meta.py in _encrypt(self, data, recipients, default_key, passphrase, armor, encrypt, symmetric, always_trust, output, throw_keyids, hidden_recipients, cipher_algo, digest_algo, compress_algo)
    991             log.info("Writing encrypted output to file: %s" % output_filename)
    992             with open(output_filename, 'w+') as fh:
--> 993                 fh.write(result.data)
    994                 fh.flush()
    995                 log.info("Encrypted output written successfully.")

TypeError: must be str, not bytes

PY3: signing of StringIO fails

the following test fails with Python 3.4.
It hangs forever and if you abort the process you get:

Traceback (most recent call last):
File ".../3.4/lib/python3.4/threading.py", line 921, in _bootstrap_inner
self.run()
File ".../3.4/lib/python3.4/threading.py", line 869, in run
self._target(_self._args, *_self._kwargs)
File ".../python-gnupg/gnupg/_util.py", line 162, in _copy_data
outstream.write(data)
TypeError: 'str' does not support the buffer interface

def test_signature_stringio(self):
    """Test that signing a message file works."""
    key = self.generate_key("Leonard Adleman", "rsa.com")
    message_file = os.path.join(_files, 'cypherpunk_manifesto')
    msg = io.StringIO(open(message_file,'rt').read())
    sig = self.gpg.sign(msg, default_key=key.fingerprint,
                        passphrase='leonardadleman')
    self.assertTrue(sig, "I thought I typed my password correctly...")

gnugp.GPG() init

Why dose it need to check whether 'gpg' is a symbolic link? and must not be a symbolic link?
image

location: _find_binary() function in _util.py

Inconsistency in the library when importing keys

Hi,

While importing a PGP public key via import_keys(), the fingerprints() method on the result
returns not only the fingerprints of the imported keys but all the keys present in the keyring. It should only return the imported keys fingerprint.

Thank you.

Documentation:

fingerprints = []¶

A list of strings containing the fingerprints of the GnuPG keyIDs imported.

tar.gz missing requirements.txt

The tar.gz that is distributed in pypi is missing the requirements.txt.
The fix is simply adding the file to Manifest.in
This will be needed for proper debianization.

10690 ◯ : pip install gnupg-1.2.6.tar.gz                                                                                                                                                                           
Unpacking ./gnupg-1.2.6.tar.gz
  Running setup.py egg_info for package from file:///tmp/foo-bar/gnupg-1.2.6.tar.gz
    [Errno 2] No such file or directory: '/tmp/pip-7w6G9S-build/requirements.txt'

Installing collected packages: gnupg
  Running setup.py install for gnupg
    [Errno 2] No such file or directory: '/tmp/pip-7w6G9S-build/requirements.txt'
    got version from file gnupg/_version.py {'version': '1.2.6', 'full': 'ebd93db8c24b906379bbb9792f8d8c24fc0b9545'}
    changing mode of build/scripts-2.7/versioneer.py from 644 to 755
    UPDATING build/lib.linux-x86_64-2.7/gnupg/_version.py

    changing mode of /tmp/foo-bar/bin/versioneer.py to 755

patch on its way.

MISSING_PASSPHRASE not handled

when the passphrase is missing, gnupg returns MISSING_PASSPHRASE.
this message should be handled like BAD_PASSPHRASE

File ".../gnupg-1.4.0-py2.7.egg/gnupg/_parsers.py", line 950, in _handle_status
raise ValueError("Unknown status message: %r" % key)
ValueError: Unknown status message: u'MISSING_PASSPHRASE'

"Too many open files" in tests

With OSX 10.9.4, Python 2.7.8 running the testsuite results in the following error for the last quarter of tests:

ERROR: test_parsers_is_hex_valid (gnupg.test.test_gnupg.GPGTestCase)
Test that valid hexidecimal passes the parsers._is_hex() check
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<path>/python-gnupg/gnupg/test/test_gnupg.py", line 171, in setUp
    self.gpg = gnupg.GPG(binary='gpg', homedir=hd)
  File "<path>/python-gnupg/gnupg/gnupg.py", line 155, in __init__
    proc = self._open_subprocess(["--list-config", "--with-colons"])
  File "<path>/python-gnupg/gnupg/_meta.py", line 521, in _open_subprocess
    env={'LANGUAGE': 'en'})
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1215, in _execute_child
    errpipe_read, errpipe_write = self.pipe_cloexec()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1167, in pipe_cloexec
    r, w = os.pipe()
OSError: [Errno 24] Too many open files

Running the test suites separately does not cause those errors.
This indicates that some files are not properly closed and the file limit is reached.

For example, in line 155 above the pipes are not explicitly closed, while for most other calls of _open_subprocess() they are closed by _collect_output().
Closing the file handles after line 155, however, did not fix it.

gnupg.GPG(verbose=True) does not work as expected

The verbose option in the GPG constructor is not documented, but it defaults to False, implying that True is a valid option. In fact, the value of verbose is passed directly to gpg --debug-level, so it should be one of the values described in the man page for this option. The code in _make_args attempts to determine if the given value is valid, but fails in the case where verbose=True because Booleans are a subtype of plain integers and True evaluates to 1 in the presence of comparison operators. _make_args then passes True as the value for --debug-level, which causes every subsequent call to gpg to fail with the error

gpg: unknown configuration item `True'

The best way to resolve this IMHO is to document the expected values for verbose, and decide on a way to handle verbose=True. There are two options:

  1. Ignore verbose=True, require one of the specific valid options for --debug-level
  2. Pick a valid option to alias verbose=True to. The correct choice here is debatable (should it be the maximum debug-level, or the minimum? etc.)

gpg or gpg2?

gen_key_input() generates only gpg2-compatible output, as gpg-1.x --batch --gen-key command does not support "Key-Type: default".
https://github.com/isislovecruft/python-gnupg/blob/master/gnupg/gnupg.py#L704

Should this be made backwards compatible, i.e. supply a proper Key-Type instead of "default", or should python-gnupg explicitly only support gpg2? (are there any other issues with doing that?)

Either way, on all my Debian derived systems, '/usr/bin/gpg' is still 1.x even if gnupg2 is installed, and that's currently the default binary for python-gnupg

If python-gnupg is meant to be mainly gpg2-compliant, how does one deal with the agent? (or will it not affect batch commands at all?)
Maybe I'm better off asking this on a gnupg mailing list .)

Symmetric encryption with compress_algo='none' fails

Following short test:

#!/usr/bin/env python

import os
import gnupg

binary = '/usr/bin/gpg2'
home = os.path.expanduser('~/.gnupg')
g = gnupg.GPG(binary=binary, homedir=home)

pIn =  '/home/me/testfile.pdf'
pOut = '/home/me/testfile.enc'

with open(pIn, 'rb') as fIn:
    b = g.encrypt(fIn.read(), passphrase='secret', armor=False, encrypt=False, symmetric=True, always_trust=True, cipher_algo='AES256', compress_algo='Uncompressed')
    with open(pOut, 'wb') as fOut:
        fOut.write(b.data)

If compress_algo is set to 'Uncompressed' or None everything works as expected. But if compress_algo is set to 'none', I only get an error message during an error message:

$ ./test.py 
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.4/site-packages/gnupg/_util.py", line 143, in _copy_data
    outstream.write(data)
BrokenPipeError: [Errno 32] Broken pipe

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.4/threading.py", line 921, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.4/threading.py", line 869, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib/python3.4/site-packages/gnupg/_util.py", line 152, in _copy_data
    if 'Broken pipe' in ioe.message:
AttributeError: 'BrokenPipeError' object has no attribute 'message'

I'm not sure how to handle this. Should 'none' be redirected to None or 'Uncompressed' or is this the users fault?

And besides that: If the error message is fixed like that:

in gnupg/_util.py, line 152
- if 'Broken pipe' in ioe.message:
+ if 'Broken pipe' in ioe:

Would this break python2 compatibility? (Btw: Do we still need python2 compatibility?)

PY3: Cannot encrypt from a file

I haven't figured out how to use the encrypt interface to stream from a file. I've tried passing the filename and the handle from open, both with no avail. The docs suggest one should be possible (":param str data: The file or bytestream to encrypt.").

import gnupg
import os

print ('gnupg version:', gnupg.__version__)

infile = os.path.expanduser ('~/usr/tmp/ts.txt')
with open (infile, 'rb') as f:
    kwargs = dict (passphrase='speedtest',
                   symmetric=True,
                   cipher_algo='AES256',
                   armor=False,
                   encrypt=False,
                   output='/tmp/purernd.enc.gnupg')

    gpg = gnupg.GPG (binary='/usr/bin/gpg')
    r = gpg.encrypt (f, None, **kwargs)
    print (r.ok)
gnupg version: 1.4.0
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-5db62b4adb3f> in <module>()
     14 
     15     gpg = gnupg.GPG (binary='/usr/bin/gpg')
---> 16     r = gpg.encrypt (f, None, **kwargs)
     17     print (r.ok)
     18 

/mnt/hgfs/work/branches/jayne/virts/jayne/lib/python3.4/site-packages/gnupg/gnupg.py in encrypt(self, data, *recipients, **kwargs)
    953         .. seealso:: :meth:`._encrypt`
    954         """
--> 955         stream = _make_binary_stream(data, self._encoding)
    956         result = self._encrypt(stream, recipients, **kwargs)
    957         stream.close()

/mnt/hgfs/work/branches/jayne/virts/jayne/lib/python3.4/site-packages/gnupg/_util.py in _make_binary_stream(s, encoding)
    396                 s = s.encode(encoding)
    397         from io import BytesIO
--> 398         rv = BytesIO(s)
    399     except ImportError:
    400         rv = StringIO(s)

TypeError: '_io.BufferedReader' does not support the buffer interface

Encrypting to a file results in an OK status code, but does not work

>>> x = gpg.encrypt('testing','D3366755', output='/home/legind/testing1234')
>>> x.ok
True
>>> open('/home/legind/testing1234')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: '/home/legind/testing1234'

gnupg.GPG() init ought to check ownership and permissions

after a slight oversight on my part and getting frustrated because gpg "did nothing at all", i realized file permissions were off.

here's a patch to do some moderate sanity checking on class init.

--- /usr/lib/python3.4/site-packages/gnupg.py.orig      2014-07-31 04:06:15.264387409 -0400
+++ /usr/lib/python3.4/site-packages/gnupg.py   2014-07-31 04:27:58.258839683 -0400
@@ -661,6 +661,7 @@
             self.encoding = 'utf-8'
         if gnupghome and not os.path.isdir(self.gnupghome):
             os.makedirs(self.gnupghome,0x1C0)
+        self._has_safe_gnupghome()
         p = self._open_subprocess(["--version"])
         result = self.result_map['verify'](self) # any result will do for this
         self._collect_output(p, result, stdin=p.stdin)
@@ -673,6 +674,30 @@
         else:
             dot = '.'.encode('utf-8')
             self.version = tuple([int(s) for s in m.groups()[0].split(dot)])
+
+    def _has_safe_gnupghome(self):
+        """
+        Test ownership and mode bits on ~/.gnupg/ and files that gnupg will complain
+        about if they are too lax or don't match my current UID
+        """
+
+        my_uid = os.geteuid()
+
+        # ~/.gnupg should be 0o40700, that is, owner=rwx, group=, other=
+        if not os.stat(self.gnupghome).st_mode == 0o40700:
+            raise ValueError("Permissions on {} should be 0o40700".format(self.gnupghome))
+        if not os.stat(self.gnupghome).st_uid == my_uid:
+            raise ValueError("Ownership on {} doesn't match my uid".format(self.gnupghome))
+
+        for _ in ('gpg.conf','pubring.gpg','secring.gpg','trustdb.gpg'):
+            # file should be 0o100600, that is, owner=rw, group=, other=
+            _f = os.path.join(self.gnupghome, _)
+            if os.path.exists(_f):
+                if not os.stat(_f).st_mode == 0o100600:
+                    raise ValueError("Permissions on {} should be 0o100600".format(_f))
+                if not os.stat(_f).st_uid == my_uid:
+                    raise ValueError("Ownership on {} doesn't match my uid".format(_f))
+

     def make_args(self, args, passphrase):
         """

gen_key doesn't appear to be generating keys

micah@spock:~/tmp$ python
Python 2.7.3 (default, Jan  2 2013, 13:56:14) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import gnupg
GnuPG logging disabled...
>>> gpg = gnupg.GPG(homedir='/home/micah/tmp/homedir')
>>> input = gpg.gen_key_input()
>>> input
'Key-Type: default\nKey-Length: 4096\nSubkey-Type: default\nName-Email: micah@spock\nExpire-Date: 2014-07-19\nName-Real: Autogenerated Key\n%commit\n'
>>> key = gpg.gen_key(input)
>>> key.fingerprint
>>> dir(key)
['__bool__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_gpg', '_handle_status', 'data', 'fingerprint', 'keyring', 'primary_created', 'secring', 'status', 'stderr', 'subkey_created', 'type']
>>> key.status
'key not created'
>>> 
micah@spock:~/tmp$ gpg --homedir /home/micah/tmp/homedir --list-keys
gpg: /home/micah/tmp/homedir/trustdb.gpg: trustdb created
micah@spock:~/tmp$ 

gnupg.GPG(verbose=True) passes invalid option to gpg-1.4.16 on OS X 10.9.2

I was getting:

Traceback (most recent call last):
  File "test.py", line 63, in <module>
    gpg = gnupg.GPG(homedir=tempdir, verbose=True, use_agent=False, binary=keybase.keybase.gpg())
  File "/Users/ian/.virtualenvs/keybase/lib/python2.7/site-packages/gnupg/gnupg.py", line 174, in __init__
    self._create_trustdb()
  File "/Users/ian/.virtualenvs/keybase/lib/python2.7/site-packages/gnupg/gnupg.py", line 178, in _create_trustdb
    if self.is_gpg2():
  File "/Users/ian/.virtualenvs/keybase/lib/python2.7/site-packages/gnupg/gnupg.py", line 210, in is_gpg2
    return _util._is_gpg2(self.binary_version)
  File "/Users/ian/.virtualenvs/keybase/lib/python2.7/site-packages/gnupg/_util.py", line 367, in _is_gpg2
    (major, minor, micro) = _match_version_string(version)
  File "/Users/ian/.virtualenvs/keybase/lib/python2.7/site-packages/gnupg/_util.py", line 441, in _match_version_string
    major, minor, micro = int(g[0]), int(g[2]), int(g[4])
TypeError: int() argument must be a string or a number, not 'NoneType'

When I was trying to create a gnupg.GPG() instance using:

gpg = gnupg.GPG(verbose=True)

After some digging it turns out that gnupg was call gpg with the options --debug-all --debug-level basic which caused the output from the GnuPG 1.4.16 version on my OS X 10.9.2 machine to be:

gpg: unknown configuration item `basic'
random usage: poolsize=600 mixed=0 polls=0/0 added=0/0
              outmix=0 getlvl1=0/0 getlvl2=0/0
secmem usage: 0/0 bytes in 0/0 blocks of pool 0/32768

Which was resulting in the TypeError being thrown trying to parse the version of GnuPG in use on my system. The real invalid option is the --debug-level basic part of that string. If you drop that, you get a properly behaved GnuPG command line call and the output you expect as it initializes the keystore and returns the configuration details about the installed version.

Can't encrypt stream objects (StringIO, BytesIO, etc.)

POC

$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
>>> import gnupg
>>> gpg = gnupg.GPG()
>>> plaintext = "No justice, no peace"
>>> type(plaintext)
<type 'str'>
>>> out = gpg.encrypt(plaintext, symmetric=True, passphrase="no racist police", encrypt=False)
>>> out.ok
True
>>> from StringIO import StringIO
>>> plaintext = StringIO("No justice, no peace")
>>> out = gpg.encrypt(plaintext, symmetric=True, passphrase="no racist police", encrypt=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/gnupg/gnupg.py", line 955, in encrypt
    stream = _make_binary_stream(data, self._encoding)
  File "/usr/local/lib/python2.7/dist-packages/gnupg/_util.py", line 396, in _make_binary_stream
    s = s.encode(encoding)
AttributeError: StringIO instance has no attribute 'encode'

There are numerous references to files and file-like objects in the docstrings of these functions, which leads me to believe this is supposed to work. It did work when using encrypt_file in 1.2.5, but it was broken when encrypt_file was changed to be encrypt in 295d98f.

A closer look at the site of the traceback shows:

  1. If the input to encrypt is a byte string (definition of that depends on Python 2.x or 3.x), it is wrapped in either a BytesIO or a StringIO and returned.
  2. Otherwise, it is first encoded (default is utf-8), then converted into a stream object and returned.

Therefore, it seems that if data is already a stream object, we should simply return it rather than trying (and failing) to encode it - which doesn't really make sense for a stream object anyway.

The most obvious fix would be something like:

            if type(s) not in (str, StringIO, BytesIO): # etc.
                s = s.encode(encoding)

Does that seem reasonable, or am I missing something?

UnicodeDecodeError

I am getting a UnicodeDecodeError when decrypting a text that was encrypted using the library. However, the text is decypted properly despite the error.

Details:
OS: Mac OS Mavericks 10.9.2
Python: 2.7.6
gnupg: 1.2.5

Sample error from my Terminal:

from gnupg import GPG
g=GPG(homedir='MY GPG HOME DIR')
c=g.encrypt('hola', 'KEY ID')
p=g.decrypt(str(c), passphrase='MYPASSWORD')
Exception in thread Thread-8:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 810, in __bootstrap_inner
self.run()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 763, in run
self.__target(_self.__args, *_self.__kwargs)
File "/Library/Python/2.7/site-packages/gnupg/_meta.py", line 532, in _read_response
line = stream.readline()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py", line 530, in readline
data = self.read(readsize, firstline=True)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py", line 477, in read
newchars, decodedbytes = self.decode(data, self.errors)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xed in position 0: invalid continuation byte
print(p)
hola

As the last line shows, decryption worked, but it still threw the error.

Thanks!

Change project name/package

Am I the only one who is very confused by the name/package of this project? Is it Ok that the name "python-gnupg" no longer identifies which module we are referring to?

Not only the name but also the Python package is not unique, which may have been fine if it were a drop-in replacement but it's not.

When typing the command: pip install gnupg or pip install python-gnupg I don't have any idea which module it's actually going to install.

Support PINENTRY_LAUNCHED

Since gpg 2.1, the gpg utility now emits a new status message "PINENTRY_LAUNCHED". python-gnupg raises an exception during signing/decryption because it cannot handle this status code.

I suggest that the list of ignored status messages in Verify.handle_status is extended to include PINENTRY_LAUNCHED.

See also: the same issue in vinay.sajip's python-gnupg

Handle 'SIG_SUBPACKET' status in gnupg._parsers.Verify

While debugging Issue #76, I produced the following error:

In [1]: import gnupg

In [2]: gpg = gnupg.GPG(homedir='TICKET-82-homedir', verbose="basic")

In [3]: vfy = gpg.verify(open('README.sig').read())
Exception in thread Thread-14:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "gnupg/_meta.py", line 584, in _read_response
    result._handle_status(keyword, value)
  File "gnupg/_parsers.py", line 1294, in _handle_status
    raise ValueError("Unknown status message: %r" % key)
ValueError: Unknown status message: u'SIG_SUBPACKET'

list_sigs always return an empty list

The 'data' returned by list_sigs in the result there is some valid result, but the parser used there (list) don't seems to parse it correctly and always return an empty list.

The documentation there is confusing, as the example in the documentation don't use list_sigs and claims to return a dictionary, but returns a list.

I'm looking at the code trying to fix it.

gpg.encrypt should be able to take a list of recipients

In the docs, encrypt can take a list of recipients. This results in an error when actually used:

>>> x = gpg.encrypt('testing',['D3366755'])
>>> x.ok
False
>>> x.stderr
u'gpg: WARNING: "--no-use-agent" is an obsolete option - it has no effect\ngpg: Sorry, no terminal at all requested - can\'t get input\n'

sphinx: assertion error: losing "ids" attribute

with sphinx 1.2b1 I'm finding the following error, which is due to https://bitbucket.org/birkenfeld/sphinx/issue/1160.

Although it's fixed upstream, I'm submitting a pullreq with a workaround since it will avoid the correct documentation build for the debian package.

looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] index                                                                                                                                                                                                    
Exception occurred:
  File "/usr/local/lib/python2.7/dist-packages/docutils/nodes.py", line 715, in replace_self
    'Losing "%s" attribute: %s' % (att, self[att])
AssertionError: Losing "ids" attribute: ['id1']

Proposal: Add a function to handle modification of a key's trust level

In order to verify file signatures the ability to specify the trust level of a key needs to be specified.

Currently when calling import_keys there is no way to specify the level of the trust. Furthermore there is no equivalent of edit-key to modify the trust level. The result is the inability to perform verification of a file using a known GPG ID and corresponding public key.

This issue is to propose the addition of the feature and coordinate how it is implemented.

Currently there is a stale pull request implementing this feature here:

https://bitbucket.org/PeacefullyDisturbed/python-gnupg-trust-key

The primary questions to answer (initially) are whether the modification of a key's trust level should be handled as a standalone function or through an implementation of a batch process.

It should be noted that this is only for verification of known, trusted key material which is being used to validate that content has not been modified by a bad actor.

Installation through setup.py or easy_install causes the version to be unknown

I am bumping into this problem often, what I've seen is that when you install python-gnupg module through easy_install (as setup.py does), it falls into the gnupg._version.versions_from_parentdir function with the IN_LONG_VERSION_PY = True

That means it tries to match the parentdir_prefix with "python-gnupg-" when in reality the parent directory (tested on debian and mac) is named in the following way: "gnupg-1.3.2-py2.7.egg". As it can't match the prefix with the actual directory name, it gets the default version "unknown".

I managed to get it working by changing the parentdir_prefix pattern to "gnupg-", but I am cautious about the other uses this might have.

I can make a pull request about this if you want or think is a good idea.

relax psutil pinning

Currently, pinning on psutil is strict about == 1.2.1
This is troublesome for distribution with debian/ubuntu. The current version table is:

 python-psutil | 0.5.1-1            | wheezy
 python-psutil | 2.1.1-1            | jessie
 python-psutil | 2.1.1-1            | sid
 python-psutil | 0.6.1-2ubuntu1     | saucy/universe
 python-psutil | 1.2.1-1ubuntu2     | trusty
 python-psutil | 2.1.1-1            | utopic

We could relax the pinning to just >= 1.2.1
Patch on its way

os.getresuid() not supported on OSX

It seems os.getresuid() is currently not supported on OSX:

http://bugs.python.org/msg91837:

getresgid function: needs implementing[1]
[1] Not POSIX but available on Linux, HP/UX, FreeBSD, OpenBSD,
DragonFlyBSD.  Not available on Solaris, AIX, NetBSD, OSX, cygwin.

It is used on the project to:

  • name a file in which to optionally save a passphrase
  • discover if a gpg-agent process for the current euid is running

it would be cool if this could be circumvented, for example dropping agent support on platforms which do not support os.getresuid() so that the rest of the features could be used.

Add function to search keyservers

gnupg.GPG.search_keys should be an alias to gpg --search-keys so users can search keyservers.

gpg --search-keys is an interactive command that returns a list of search results followed by an interface for deciding what to do with each result. If run with --batch, the search results are printed to stdout and the command exits because it cannot accept input. We can parse this output (--search-keys accepts --with-colons, nice) and return a dict of search results to the caller. Each entry in the dict would contain the keyid, metadata about the key, and a tuple of uids. The caller could then use gnupg.GPG.recv_keys to request specific keys from the keyserver.

I have a patch nearly completed. The only sticking point is the keyids returned by the keyservers are inconsistent. Ideally it would return fingerprints in order to avoid ambiguities with duplicated key ids. Unfortunately, they do not - different key servers return either the short key ID or the fingerprint, even though the spec strongly recommends they return the fingerprint whenever possible. This does not appear to be affected by any of GPG's command line options.

Since the goal of this project is to provide a Python API to GPG, I believe we should simply accept this and return whatever keyids (short, long, or fingerprint) the keyservers choose to give us, even though this is not ideal. I will upload a patch in the next few days that implements this feature.

tarballs differ

Hello!

It seems as if the code that is on github differs from the code that is on pypi. If you pull down the latest tarball from pypi and then do a recursive diff of the two unpacked sources, you will find there is a lot of differences. I noticed this because I was trying to figure out where the embedded jquery.js was coming from, because I dont think that should be shipped in the code, and I found that the pypi source includes the entire _build directory, which I don't think it should include.

$ diff -ur gnupg-1.3.1/ python-gnupg-1.3.1/                                                                                                                                                                                                                                                                                   ∞
Only in gnupg-1.3.1/docs: _build            
Only in gnupg-1.3.1/docs: change-license-emails.txt~
Only in gnupg-1.3.1/docs: NOTES-isec-audit.org
Only in gnupg-1.3.1/docs: NOTES-python-gnupg-development.org
Only in gnupg-1.3.1/docs: pip-install.log
Only in gnupg-1.3.1/docs: smartcard-idea.txt
Only in gnupg-1.3.1/docs/_static: pgp-subkeys.html
Only in gnupg-1.3.1/docs: the-internals-of-a-gpgpgp-key
Only in gnupg-1.3.1/docs: to-monkeysphere-list
Only in gnupg-1.3.1/docs: upstream-python-gnupg-POC-exploit.py
Only in python-gnupg-1.3.1/: examples
Only in python-gnupg-1.3.1/: .gitattributes
Only in python-gnupg-1.3.1/: .gitignore
Only in python-gnupg-1.3.1/gnupg: test
diff -ur gnupg-1.3.1/gnupg/_version.py python-gnupg-1.3.1/gnupg/_version.py
--- gnupg-1.3.1/gnupg/_version.py   2014-08-02 00:20:43.000000000 -0400
+++ python-gnupg-1.3.1/gnupg/_version.py    2014-08-02 00:18:27.000000000 -0400
@@ -1,11 +1,197 @@

-# This file was generated by 'versioneer.py' (0.7+) from
-# revision-control system data, or from the parent directory name of an
-# unpacked source archive. Distribution tarballs contain a pre-generated copy
-# of this file.
-
-version_version = '1.3.1'
-version_full = '87928205a87afc66779b9760df039642eed291b2'
-def get_versions(default={}, verbose=False):
-    return {'version': version_version, 'full': version_full}
+IN_LONG_VERSION_PY = True
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (build by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.7+ (https://github.com/warner/python-versioneer)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "$Format:%d$"
+git_full = "$Format:%H$"
+
+
+import subprocess
+import sys
+
+def run_command(args, cwd=None, verbose=False):
+    try:
+        # remember shell=False, so use git.cmd on windows, not just git
+        p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+    except EnvironmentError:
+        e = sys.exc_info()[1]
+        if verbose:
+            print("unable to run %s" % args[0])
+            print(e)
+        return None
+    stdout = p.communicate()[0].strip()
+    if sys.version >= '3':
+        stdout = stdout.decode()
+    if p.returncode != 0:
+        if verbose:
+            print("unable to run %s (error)" % args[0])
+        return None
+    return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+    # the code embedded in _version.py can just fetch the value of these
+    # variables. When used from setup.py, we don't want to import
+    # _version.py, so we do it with a regexp instead. This function is not
+    # used from _version.py.
+    variables = {}
+    try:
+        for line in open(versionfile_source,"r").readlines():
+            if line.strip().startswith("git_refnames ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["refnames"] = mo.group(1)
+            if line.strip().startswith("git_full ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["full"] = mo.group(1)
+    except EnvironmentError:
+        pass
+    return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+    refnames = variables["refnames"].strip()
+    if refnames.startswith("$Format"):
+        if verbose:
+            print("variables are unexpanded, not using")
+        return {} # unexpanded, so not in an unpacked git-archive tarball
+    refs = set([r.strip() for r in refnames.strip("()").split(",")])
+    for ref in list(refs):
+        if not re.search(r'\d', ref):
+            if verbose:
+                print("discarding '%s', no digits" % ref)
+            refs.discard(ref)
+            # Assume all version tags have a digit. git's %d expansion
+            # behaves like git log --decorate=short and strips out the
+            # refs/heads/ and refs/tags/ prefixes that would let us
+            # distinguish between branches and tags. By ignoring refnames
+            # without digits, we filter out many common branch names like
+            # "release" and "stabilization", as well as "HEAD" and "master".
+    if verbose:
+        print("remaining refs: %s" % ",".join(sorted(refs)))
+    for ref in sorted(refs):
+        # sorting will prefer e.g. "2.0" over "2.0rc1"
+        if ref.startswith(tag_prefix):
+            r = ref[len(tag_prefix):]
+            if verbose:
+                print("picking %s" % r)
+            return { "version": r,
+                     "full": variables["full"].strip() }
+    # no suitable tags, so we use the full revision id
+    if verbose:
+        print("no suitable tags, using full revision id")
+    return { "version": variables["full"].strip(),
+             "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+    # this runs 'git' from the root of the source tree. That either means
+    # someone ran a setup.py command (and this code is in versioneer.py, so
+    # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+    # the source tree), or someone ran a project-specific entry point (and
+    # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+    # containing directory is somewhere deeper in the source tree). This only
+    # gets called if the git-archive 'subst' variables were *not* expanded,
+    # and _version.py hasn't already been rewritten with a short version
+    # string, meaning we're inside a checked out source tree.
+
+    try:
+        here = os.path.abspath(__file__)
+    except NameError:
+        # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+        return {} # not always correct
+
+    # versionfile_source is the relative path from the top of the source tree
+    # (where the .git directory might live) to this file. Invert this to find
+    # the root from __file__.
+    root = here
+    if IN_LONG_VERSION_PY:
+        for i in range(len(versionfile_source.split("/"))):
+            root = os.path.dirname(root)
+    else:
+        root = os.path.dirname(here)
+    if not os.path.exists(os.path.join(root, ".git")):
+        if verbose:
+            print("no .git in %s" % root)
+        return {}
+
+    GIT = "git"
+    if sys.platform == "win32":
+        GIT = "git.cmd"
+    stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+                         cwd=root)
+    if stdout is None:
+        return {}
+    if not stdout.startswith(tag_prefix):
+        if verbose:
+            print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
+        return {}
+    tag = stdout[len(tag_prefix):]
+    stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+    if stdout is None:
+        return {}
+    full = stdout.strip()
+    if tag.endswith("-dirty"):
+        full += "-dirty"
+    return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+    if IN_LONG_VERSION_PY:
+        # We're running from _version.py. If it's from a source tree
+        # (execute-in-place), we can work upwards to find the root of the
+        # tree, and then check the parent directory for a version string. If
+        # it's in an installed application, there's no hope.
+        try:
+            here = os.path.abspath(__file__)
+        except NameError:
+            # py2exe/bbfreeze/non-CPython don't have __file__
+            return {} # without __file__, we have no hope
+        # versionfile_source is the relative path from the top of the source
+        # tree to _version.py. Invert this to find the root from __file__.
+        root = here
+        for i in range(len(versionfile_source.split("/"))):
+            root = os.path.dirname(root)
+    else:
+        # we're running from versioneer.py, which means we're running from
+        # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+        here = os.path.abspath(sys.argv[0])
+        root = os.path.dirname(here)
+
+    # Source tarballs conventionally unpack into a directory that includes
+    # both the project name and a version string.
+    dirname = os.path.basename(root)
+    if not dirname.startswith(parentdir_prefix):
+        if verbose:
+            print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
+                  (root, dirname, parentdir_prefix))
+        return None
+    return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+tag_prefix = "python-gnupg-"
+parentdir_prefix = "python-gnupg-"
+versionfile_source = "src/_version.py"
+
+def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
+    variables = { "refnames": git_refnames, "full": git_full }
+    ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
+    if not ver:
+        ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+    if not ver:
+        ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
+                                      verbose)
+    if not ver:
+        ver = default
+    return ver

Only in python-gnupg-1.3.1/: Makefile
Only in python-gnupg-1.3.1/: MANIFEST.in
Only in python-gnupg-1.3.1/: patches
Only in gnupg-1.3.1/: PKG-INFO
Only in python-gnupg-1.3.1/: README.md
Only in python-gnupg-1.3.1/: scripts
Only in python-gnupg-1.3.1/: TODO
Only in python-gnupg-1.3.1/: .travis.yml

py3k unittest failures

most of these are due to trivialities, such as Exception objects no longer having a message attribute, and bytestrings being returned and then compared to string literals. should be easy to fix.

======================================================================
ERROR: test_make_args (__main__.GPGTestCase)
Test argument line construction.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 200, in _is_allowed
    assert hyphenated in allowed
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 290, in _check_option
    flag = _is_allowed(arg)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 204, in _is_allowed
    raise ProtectedOption("Option '%s' not supported." % dropped)
gnupg._parsers.ProtectedOption: Option '--bicycle' not supported.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 334, in test_make_args
    args = self.gpg._make_args(not_allowed[2:], False)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_meta.py", line 441, in _make_args
    [cmd.append(opt) for opt in iter(_sanitise_list(self.options))]
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_meta.py", line 441, in <listcomp>
    [cmd.append(opt) for opt in iter(_sanitise_list(self.options))]
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 441, in _sanitise_list
    safe_arg = _sanitise(arg)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 423, in _sanitise
    checked = _check_groups(option_groups)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 395, in _check_groups
    safe = _check_option(a, v)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 293, in _check_option
    log.warn("_check_option(): %s" % error.message)
AttributeError: 'ProtectedOption' object has no attribute 'message'

======================================================================
ERROR: test_make_args_drop_protected_options (__main__.GPGTestCase)
Test that unsupported gpg options are dropped.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 200, in _is_allowed
    assert hyphenated in allowed
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 290, in _check_option
    flag = _is_allowed(arg)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 204, in _is_allowed
    raise ProtectedOption("Option '%s' not supported." % dropped)
gnupg._parsers.ProtectedOption: Option '--tyrannosaurus-rex' not supported.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 321, in test_make_args_drop_protected_options
    cmd = self.gpg._make_args(None, False)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_meta.py", line 441, in _make_args
    [cmd.append(opt) for opt in iter(_sanitise_list(self.options))]
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_meta.py", line 441, in <listcomp>
    [cmd.append(opt) for opt in iter(_sanitise_list(self.options))]
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 441, in _sanitise_list
    safe_arg = _sanitise(arg)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 423, in _sanitise
    checked = _check_groups(option_groups)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 395, in _check_groups
    safe = _check_option(a, v)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_parsers.py", line 293, in _check_option
    log.warn("_check_option(): %s" % error.message)
AttributeError: 'ProtectedOption' object has no attribute 'message'

======================================================================
ERROR: test_copy_data_bytesio (__main__.GPGTestCase)
Test that _copy_data() is able to duplicate byte streams.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 356, in test_copy_data_bytesio
    instream = io.BytesIO(message)
TypeError: 'str' does not support the buffer interface

======================================================================
ERROR: test_parsers_fix_unsafe (__main__.GPGTestCase)
Test that unsafe inputs are quoted out and then ignored.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_util.py", line 274, in _is_file
    assert os.lstat(input).st_size > 0, "not a file: %s" % input
FileNotFoundError: [Errno 2] No such file or directory: '\'"&coproc /bin/sh"\''

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 223, in test_parsers_fix_unsafe
    has_shell = self.gpg.verify_file(test_file, fixed)
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/gnupg.py", line 245, in verify_file
    if not _util._is_file(sig_file):
  File "/home/isis/.virtualenvs/py3kgnupg/lib/python3.3/site-packages/gnupg-1.2.1_15_g08f066d_dirty-py3.3.egg/gnupg/_util.py", line 276, in _is_file
    log.error(err.message, exc_info=1)
AttributeError: 'FileNotFoundError' object has no attribute 'message'

======================================================================
ERROR: test_signature_verification_detached_binary (__main__.GPGTestCase)
Test that detached signature verification in binary mode fails.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 720, in test_signature_verification_detached_binary
    bs.write(sig.data)
TypeError: must be str, not bytes

======================================================================
ERROR: test_signature_verification_detached (__main__.GPGTestCase)
Test that verification of a detached signature of a file works.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 697, in test_signature_verification_detached
    sigfile.write(sig.data)
TypeError: must be str, not bytes

======================================================================
ERROR: test_file_encryption_and_decryption (__main__.GPGTestCase)
Test that encryption/decryption to/from file works.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 965, in test_file_encryption_and_decryption
    data = data.encode(self.gpg._encoding)
AttributeError: 'bytes' object has no attribute 'encode'

======================================================================
FAIL: test_encryption_decryption_multi_recipient (__main__.GPGTestCase)
Test decryption of an encrypted string for multiple users
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 919, in test_encryption_decryption_multi_recipient
    self.assertEquals(message, str(dec_alice.data))
AssertionError: '\nIn 2010 Riggio and Sicari presented a practical application of homomorphic\ne [truncated]... != "b''"
+ b''-
- In 2010 Riggio and Sicari presented a practical application of homomorphic
- encryption to a hybrid wireless sensor/mesh network. The system enables
- transparent multi-hop wireless backhauls that are able to perform statistical
- analysis of different kinds of data (temperature, humidity, etc.)  coming from
- a WSN while ensuring both end-to-end encryption and hop-by-hop
- authentication.

======================================================================
FAIL: test_symmetric_encryption_and_decryption (__main__.GPGTestCase)
Test symmetric encryption and decryption
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./gnupg/test/test_gnupg.py", line 941, in test_symmetric_encryption_and_decryption
    self.assertEqual(decrypted, msg)
AssertionError: 'b"If you have something that you don\'t want anyone to\\nknow, maybe you should [truncated]... != "If you have something that you don't want anyone to\nknow, maybe you shouldn't  [truncated]...
- b"If you have something that you don't want anyone to\nknow, maybe you shouldn't be doing it in the first place.\n-- Eric Schmidt, CEO of Google"
? --                                                   ^^                                                         ^^                              -
+ If you have something that you don't want anyone to
know, maybe you shouldn't be doing it in the first place.
-- Eric Schmidt, CEO of Google
?                                                    ^                                                         ^


----------------------------------------------------------------------
Ran 51 tests in 26.577s

FAILED (failures=2, errors=7)
make: *** [test] Error 1
(py3kgnupg)∃!isisⒶwintermute:(develop *>)~/code/riseup/python-gnupg ∴

Make it easier to use --hidden-recipient and --throw-keyids options

The ability to use the --hidden-recipient, --hidden-encrypt-to, and --throw-keyids GnuPG flags was added in #30 in python-gnupg-1.3.2. You can use them by doing, for example:

>>> import gnupg
>>> keyid = 'a3adb67a2cdb8b35'
>>> gpg = gnupg.GPG(homedir='./hidden-recipient-test')
>>> gpg.options = ['--hidden-recipient %s' % keyid]
>>> key = gpg.recv_keys('http://pgp.mit.edu', keyid)
>>> fingerprint = key.fingerprints[0]
>>> message = 'the secret words are squeamish ossifrage'
>>> encrypted = gpg.encrypt(message, fingerprint)

But… that's not really easy or intuitive. To make it so, we'd probably want to patch gnupg._meta.GPGBase._encrypt() to take some parameter like hidden_recipients=list and/or hide_all_recipients=bool. Or however seems like the most straightforward way to add this.

Handle KEYREVOKED in Verify

This was pointed out in PR#38, and it's actually a bug in python-gnupg<=1.3.1 because it results in a ValueError.

setting verbose when instantiating GPG class causes failure

This is on Fedora 20 which ships gnupg version 1.4.8

$ gpg --version
gpg (GnuPG) 1.4.18
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: ~/.gnupg
Supported algorithms:
Pubkey: RSA, RSA-E, RSA-S, ELG-E, DSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

In the following example you can see that when attempting to set verbose="basic" that it causes the output to be mangled, as per the "can opened signed data" line.

>>> gpg = gnupg.GPG(homedir='/tmp/coreosVTMr_5', verbose="basic")
>>> vfy = gpg.verify_file('/tmp/coreosVTMr_5/coreos_developer_container.bin.bz2.DIGESTS', sig_file='/tmp/coreosVTMr_5/coreos_developer_container.bin.bz2.DIGESTS.sig')
>>> vfy.stderr
u"gpg: can't open signed data `--debug-all'\ngpg: can't hash datafile: file open error\n"
>>> gpg = gnupg.GPG(homedir='/tmp/coreosVTMr_5')
>>> vfy = gpg.verify_file('/tmp/coreosVTMr_5/coreos_developer_container.bin.bz2.DIGESTS', sig_file='/tmp/coreosVTMr_5/coreos_developer_container.bin.bz2.DIGESTS.sig')
>>> vfy.stderr
u'gpg: Signature made Thu Oct 16 17:32:43 2014 PDT using RSA key ID E5676EFC\n[GNUPG:] KEYEXPIRED 1410042310\n[GNUPG:] SIGEXPIRED deprecated-use-keyexpired-instead\n[GNUPG:] KEYEXPIRED 1410042310\n[GNUPG:] SIGEXPIRED deprecated-use-keyexpired-instead\n[GNUPG:] BADSIG A5A96635E5676EFC CoreOS Buildbot (Offical Builds) <[email protected]>\ngpg: BAD signature from "CoreOS Buildbot (Offical Builds) <[email protected]>"\n'

Don't pass --use-agent or --no-use-agent to gpg2

From man gpg2:

--use-agent

--no-use-agent
       This is dummy option. gpg2 always requires the agent.

The gnupg.GPG constructor calls _make_args, which defaults to setting --no-use-agent if use_agent is not specified as a keyword argument to the constructor. When python-gnupg uses gpg2, this causes gpg2 to print a warning message every time it is called:

vagrant@development:~$ gpg2 --no-use-agent
gpg: WARNING: "--no-use-agent" is an obsolete option - it has no effect
gpg: Go ahead and type your message ...

This message cannot be suppressed even by changing the --debug-level to none. When used in an automated fashion (such as in SecureDrop), this creates a lot of log noise.

Since neither argument has an effect with gpg2, they should not be set when python-gnupg is used with gpg2.

Handle NOTATION_* statuses in Verify parser

While testing my patch for #83, I realised we'll need to also handle NOTATION_* statuses in gnupg._parsers.Verify.

From doc/DETAILS (in the GnuPG repository):

*** NOTATION_ 
    There are actually two related status codes to convey notation  
    data:      

    - NOTATION_NAME <name>  
    - NOTATION_DATA <string>  

    <name> and <string> are %XX escaped; the data may be split among  
    several NOTATION_DATA lines.   

The traceback which I received was:

(gpg)∃!isisⒶwintermute:(fix/83-SIG_SUBPACKET *$=)~/code/riseup/python-gnupg ∴ ipython
WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv.

In [1]: import gnupg

In [2]: import logging

In [3]: gnupg._util.log.addHandler(logging.StreamHandler())

In [4]: gnupg._util.log.setLevel(5)

In [5]: gpg = gnupg.GPG(homedir='TICKET-82-homedir')

In [6]: vfy = gpg.verify(open('README.sig').read())
verify_file(): Handling embedded signature
Got arg string: --verify
Got groups: {'--verify': ''}
Appending option: --verify
Sending command to GnuPG process:
['/usr/bin/gpg', '--no-options', '--no-emit-version', '--no-tty', '--status-fd', '2', '--homedir', 'TICKET-82-homedir', '--no-default-keyring', '--keyring', 'TICKET-82-homedir/pubring.gpg', '--secret-keyring', 'TICKET-82-homedir/secring.gpg', '--no-use-agent', '--verify']
<Thread(Thread-1, initial daemon)>, <_io.BytesIO object at 0x7f5ced7f8ef0>, <open file '<fdopen>', mode 'wb' at 0x7f5ced7e9f60>
Sending chunk 1024 bytes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

# python-gnupg #

Fork of [python-gnupg-0.3.2](https://code.google.com/p/python-gnupg/), patched
to fix a potential vulnerability which could result in remote code execution,
do to unsanitised inputs being passed to ```subprocess.Popen([...], shell=True)```.

### Installation ###

#### From [PyPI](https://pypi.python.org) ####
It's simple. Just do:

[sudo] pip install gnupg


#### From this git repository ####
To install this package from this git repository, do:

git clone https://github.com/isislovecruft/python-gnupg.git
cd python-gnupg
make install
make test


Optionally to build the documentation after installation, do:

make docs


To get started using python-gnupg's API, see the [online documentation](https://python-gnupg.readthedocs.org/en/latest/),
and import the module like so:

import gnupg

The primary interface class you'll likely want to interact with is
[gnupg.GPG](https://pyt
stderr reader: <Thread%28Thread-2, initial daemon%29>
Sending chunk 2048 bytes:
hon-gnupg.readthedocs.org/en/latest/gnupg.html#gpg):

>>> gpg = gnupg.GPG(gpgbinary='/usr/bin/gpg',
...     gpghome='./keys',
...     pubring='pubring.gpg',
...     secring='secring.gpg')
>>> batch_key_input = gpg.gen_key_input()
>>> print batch_key_input
Key-Type: RSA
Name-Email: isis@wintermute
Name-Comment: Generated by gnupg.py
Key-Length: 4096
Name-Real: Autogenerated Key
%pubring /home/isis/code/python-gnupg/keys/pubring.gpg
%secring /home/isis/code/python-gnupg/keys/secring.gpg
%commit

>>> key = gpg.gen_key(batch_key_input)
>>> print key.fingerprint
245D8FA30F543B742053949F553C0E154F2E7A98

Bug Reports & Feature Requests

Currently, the bugtracker is
here on Github. This
may change in the future, but for now please feel free to use it to make
bugreports and feature requests.

Public comments and discussions are also welcome on the bugtracker, or as
tweets.

Patc
Sending chunk 3072 bytes:
hes are greatly appreciated, and if unsuitable for merging I will make
improvement suggestions based on code review until the patch is acceptable.
-----BEGIN PGP SIGNATURE-----

iQMhBAEBCgELBQJUa83nBYMB4TOAVhSAAAAAACUAKGlzaXMrc2lnbnN1YmtleUBw
YXR0ZXJuc2ludGhldm9pZC5uZXRDMkYwMzNBNjFEMDAxQjU1MjE4NTVBOUUxOEMx
NkVDNUY5RjFENjczSxSAAAAAABoAKGlzaXNAcGF0dGVybnNpbnRoZXZvaWQubmV0
MEE2QTU4QTE0QjU5NDZBQkRFMThFMjA3QTNBREI2N0EyQ0RCOEIzNS4aaHR0cHM6
Ly9ibG9nLnBhdHRlcm5zaW50aGV2b2lkLm5ldC9wb2xpY3kudHh0LJhodHRwczov
L2Jsb2cucGF0dGVybnNpbnRoZXZvaWQubmV0L2lzaXMudHh0AAoJEBjBbsX58dZz
hQIP/0fdtbBPNR1Xvx4wHYyy3yNNH1U+CDEhHvZZ3IUsUJEKSeYniVjmjrkT5gIb
IbgD0JE0e6O62LZBRZzbJVD355aKZT4RIglz4gvl4qduaPPOLgh30GbdkdoWXtvN
R5Fbxr52gQiKX9XVrjogmFSUPITqOLss4wBTRfHcEEfNNlJrhzOSRWaHlvXg0XAP
tuMEfihBfFmaAxp/HNcQsyossF2TTeWTT4VbtFxDpa3taiHmq8fpGbM9tlQ2N7qN
2GAVRG170e5Md6nyXb/+UnQt7ng31dOzefFKhVcprO7SYa1NOk/5AZ3VNTo9YfUg
V3er5twOqB0y7gBfWB32nUA7xE/8aobr+LXdW7GV1XSL3en/2MIO2F8TX9CY/K5V
rCGvRn3ZvCcwgK4P9x2lcX/hAMfFrCD6Biw3dgZGePh+FQvM1e4hmY+p76aAKLQn
Sending chunk 3351 bytes:

3J8oVZ/yMy/9NAtRagpqSXib6o5/b51yRC1Lea8jysPQ0IsIwP26gS9UIu8FoOsZ
nNBOIbyVhU0WHniIlVXZipnMFIIpcN2c0jkpPrRKmVc97MS60t8BBls13rIWZKCq
Nf1F0fC/Boy/jBaTib0POjNI705icKvETYbrXi9HJB9FK9Z80LIBx5WXv1cmAWNa
Kv3QTCV5h2t2XEUEjAGvZDDbe1DrH+feo7gdx+BkxsBnz8Ic
=AJhV
-----END PGP SIGNATURE-----

Closed outstream: 3351 bytes sent.
stdout reader: <Thread(Thread-3, initial daemon)>
Reading data from stream "<open file '', mode 'rb' at 0x7f5ced7e9e40>"...
unsafe permissions on homedir `TICKET-82-homedir'
KEYEXPIRED 1410123618
SIGEXPIRED deprecated-use-keyexpired-instead
SIG_ID v8coz4o7rpnH+NbYEjL3r3oDyn8 2014-11-18 1416351207
KEYEXPIRED 1410123618
SIGEXPIRED deprecated-use-keyexpired-instead
GOODSIG 18C16EC5F9F1D673 Isis! [email protected]
POLICY_URL https://blog.patternsinthevoid.net/policy.txt
SIG_SUBPACKET 24 3 43 https://blog.patternsinthevoid.net/isis.txt
NOTATION_NAME [email protected]
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
self.run()
File "/usr/lib/python2.7/threading.py", line 763, in run
self.__target(_self.__args, *_self.__kwargs)
File "gnupg/_meta.py", line 584, in _read_response
result._handle_status(keyword, value)
File "gnupg/_parsers.py", line 1340, in _handle_status
raise ValueError("Unknown status message: %r" % key)
ValueError: Unknown status message: u'NOTATION_NAME'

Finishing reading from stream "<open file '', mode 'rb' at 0x7f5ced7e9e40>"...
Read 0 bytes total

`exceptions` module not present in Python 3

I'm not clear whether this module is supposed to run under Python 3 (setup.py suggests so, and I can pip3 install it), but I attempted to use it under 3.4 and cannot import gnupg:

$ python
Python 3.4.0 (default, Mar 25 2014, 20:30:43) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import gnupg
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/matt/tmp/python-gnupg/gnupg/__init__.py", line 23, in <module>
    from . import gnupg
  File "/Users/matt/tmp/python-gnupg/gnupg/gnupg.py", line 34, in <module>
    import exceptions
ImportError: No module named 'exceptions'

It is my understanding exceptions is no longer present in Python 3.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.