isislovecruft / python-gnupg Goto Github PK
View Code? Open in Web Editor NEWA modified version of python-gnupg, including security patches, extensive documentation, and extra features.
License: Other
A modified version of python-gnupg, including security patches, extensive documentation, and extra features.
License: Other
>>> 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'
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)
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
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...")
Please implement the --hidden-recipient / -R flag
thank you
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.
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.
Peter Malmgren on Twitter reported that some uWSGI servers don't export environment variables to worker threads, causing breakage because gnupg/_util.py
expects a $HOME
env variable. We should set to to /tmp/something...
if we don't have a $HOME
.
@micahflee from the EFF reported through email that running the unittests fails on the command "pip list"
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'
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.
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:
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 .)
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?)
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
>>> 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'
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):
"""
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$
_meta.py makes the following call, which triggers AttributeError: 'Process' object has no attribute 'uids'
identity = psutil.Process(os.getpid()).uids
psutil docs state that this is a Unix-only property
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.
see this comment from PR#3.
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:
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.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?
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!
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.
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.
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'
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.
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'
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']
It's most obvious here:
https://python-gnupg.readthedocs.org/en/latest/gnupg.html#gnupg._meta.GPGBase._sign_file
It's defined with:
_sign_file(file, default_key=None, passphrase=None, clearsign=True, detach=False, binary=False)
but the list of parameters has:
keyid (str) – The key to sign with.
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.
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.
When running:
g = gnupg.GPG(...)
x = g.sign('abc\ndef\n, passphrase='')
print x.data
The signed thing will be only "def\n", "abc" will be skipped in the signature. When passphrase=None it's fine.
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
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:
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.
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.
Hi,
I suggest a feature to sign a public key :
gpg2 --sign-key name
http://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html#OpenPGP-Key-Management
Best regards,
Stephane
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
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 ∴
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.
in https://github.com/isislovecruft/python-gnupg/blob/master/gnupg/_meta.py#L364
hd = _parsers._fix_unsafe(directory)
converts the relative path "foo bar" to "'foo bar'" and creates a subdirectory with extra quotes.
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
.
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'
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.
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
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
It is annoying, slow, and prone to random failures. Also, bad practice. It should be refactored to not touch the network (as much as is possible).
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.