Merge branch 'master' of github.com:isislovecruft/python-gnupg

fix/44-verbose-arg
Les Aker 2014-06-14 19:39:07 -04:00
commit ff96904233
23 changed files with 860 additions and 1144 deletions

6
.gitignore vendored
View File

@ -83,3 +83,9 @@ gpg
# setuptools/distribute files:
PKG-INFO
MANIFEST
# sphinx default build
docs/_build
# ignore keys which have been generated example scripts:
8192-bit-key/*

View File

@ -2,6 +2,11 @@ SHELL=/bin/sh
TESTDIR=./gnupg/test
TESTHANDLE=$(TESTDIR)/test_gnupg.py
FILES=$(SHELL find ./gnupg/ -name "*.py" -printf "%p,")
PKG_NAME=python-gnupg
DOC_DIR=docs
DOC_BUILD_DIR:=$(DOC_DIR)/_build
DOC_HTML_DIR:=$(DOC_BUILD_DIR)/html
DOC_BUILD_ZIP:=$(PKG_NAME)-docs.zip
.PHONY=all
all: uninstall install test
@ -73,11 +78,15 @@ py3k-uninstall: uninstall
reinstall: uninstall install
py3k-reinstall: py3k-uninstall py3k-install
cleandocs:
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" \
-o docs gnupg/ tests/
docs-clean:
-rm -rf $(DOC_BUILD_DIR)
docs:
cd docs && \
make clean && \
make html
docs-completely-new:
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" -o $(DOC_DIR) gnupg/ tests/
docs-html:
cd $(DOC_DIR) && make clean && make html
docs-zipfile: docs-html
cd $(DOC_HTML_DIR) && { find . -name '*' | zip -@ -v ../$(DOC_BUILD_ZIP) ;};
@echo "Built documentation in $(DOC_BUILD_DIR)/$(DOC_BUILD_ZIP)"

View File

@ -2,7 +2,7 @@
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXOPTS = -E -n
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build

View File

@ -25,6 +25,16 @@ div.header-wrapper {
border-bottom: 3px solid #2e3436;
}
div.headertitle a {
font-family: "Georgia", "Times New Roman", serif;
font-size: 2em;
color: rgb(252, 175, 62);
font-weight: normal;
}
h1 {
color: #204a87;
}
/* Default body styles */
a {

View File

@ -1,232 +0,0 @@
/// XXX: make it cross browser
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
*/
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
"group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {}
}
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
}
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
}
/**
* small function to check if an array contains
* a given item.
*/
jQuery.contains = function(arr, item) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == item)
return true;
}
return false;
}
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this)
});
}
}
return this.each(function() {
highlight(this);
});
}
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initModIndex();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can savely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlight');
});
}, 10);
$('<li class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></li>')
.appendTo($('.sidebar .this-page-menu'));
}
},
/**
* init the modindex toggle buttons
*/
initModIndex : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
console.log($('tr.cg-' + idnum).toggle());
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('.sidebar .this-page-menu li.highlight-link').fadeOut(300);
$('span.highlight').removeClass('highlight');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});

386
docs/_static/haiku.css vendored
View File

@ -1,386 +0,0 @@
/* custom stuff I put in FIXME where is it "supposed" to go? */
div.admonition-todo
{
border: 1px solid red;
background-color: #Fdd;
}
div.admonition-todo p.admonition-title
{
margin: 0;
color: red;
text-transform: lowercase;
}
p.admonition-title
{
font-size: 120%;
font-weight: bold;
}
dl.class>dt, dl.interface>dt, dl.function>dt, dl.staticmethod>dt
{
font-size: 150%;
background-color:#ddd;
}
dl.method>dt
{
background-color: #eee;
border-bottom: 2px solid #ddd;
}
dl.method:hover
{
background-color:#ffd;
}
/** end custom */
html {
margin: 0px;
padding: 0px;
background: #FFF url(bg-page.png) top left repeat-x;
}
body {
line-height: 1.5;
margin: auto;
padding: 0px;
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
min-width: 59em;
max-width: 70em;
color: #333333;
}
div.footer {
padding: 8px;
font-size: 11px;
text-align: center;
letter-spacing: 0.5px;
}
/* link colors and text decoration */
a:link {
font-weight: bold;
text-decoration: none;
color: #dc3c01;
}
a:visited {
font-weight: bold;
text-decoration: none;
color: #892601;
}
a:hover, a:active {
text-decoration: underline;
color: #ff4500;
}
/* Some headers act as anchors, don't give them a hover effect */
h1 a:hover, a:active {
text-decoration: none;
color: #0c3762;
}
h2 a:hover, a:active {
text-decoration: none;
color: #0c3762;
}
h3 a:hover, a:active {
text-decoration: none;
color: #0c3762;
}
h4 a:hover, a:active {
text-decoration: none;
color: #0c3762;
}
a.headerlink {
color: #a7ce38;
padding-left: 5px;
}
a.headerlink:hover {
color: #a7ce38;
}
/* basic text elements */
div.content {
margin-top: 20px;
margin-left: 40px;
margin-right: 40px;
margin-bottom: 50px;
font-size: 0.9em;
}
/* heading and navigation */
div.header {
position: relative;
left: 0px;
top: 0px;
height: 85px;
/* background: #eeeeee; */
padding: 0 40px;
}
div.header h1 {
font-size: 1.6em;
font-weight: normal;
letter-spacing: 1px;
color: #0c3762;
border: 0;
margin: 0;
padding-top: 15px;
}
div.header h1 a {
font-weight: normal;
color: #0c3762;
}
div.header h2 {
font-size: 1.3em;
font-weight: normal;
letter-spacing: 1px;
text-transform: uppercase;
color: #aaa;
border: 0;
margin-top: -3px;
padding: 0;
}
div.header img.rightlogo {
float: right;
}
div.title {
font-size: 1.3em;
font-weight: bold;
color: #0c3762;
border-bottom: dotted thin #e0e0e0;
margin-bottom: 25px;
}
div.topnav {
/* background: #e0e0e0; */
}
div.topnav p {
margin-top: 0;
margin-left: 40px;
margin-right: 40px;
margin-bottom: 0px;
text-align: right;
font-size: 0.8em;
}
div.bottomnav {
background: #eeeeee;
}
div.bottomnav p {
margin-right: 40px;
text-align: right;
font-size: 0.8em;
}
a.uplink {
font-weight: normal;
}
/* contents box */
table.index {
margin: 0px 0px 30px 30px;
padding: 1px;
border-width: 1px;
border-style: dotted;
border-color: #e0e0e0;
}
table.index tr.heading {
background-color: #e0e0e0;
text-align: center;
font-weight: bold;
font-size: 1.1em;
}
table.index tr.index {
background-color: #eeeeee;
}
table.index td {
padding: 5px 20px;
}
table.index a:link, table.index a:visited {
font-weight: normal;
text-decoration: none;
color: #dc3c01;
}
table.index a:hover, table.index a:active {
text-decoration: underline;
color: #ff4500;
}
/* Haiku User Guide styles and layout */
/* Rounded corner boxes */
/* Common declarations */
div.admonition {
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border-style: dotted;
border-width: thin;
border-color: #dcdcdc;
padding: 10px 15px 10px 15px;
margin-bottom: 15px;
margin-top: 15px;
}
div.note {
padding: 10px 15px 10px 80px;
background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;
min-height: 42px;
}
div.warning {
padding: 10px 15px 10px 80px;
background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;
min-height: 42px;
}
div.seealso {
background: #e4ffde;
}
/* More layout and styles */
h1 {
font-size: 1.3em;
font-weight: bold;
color: #0c3762;
border-bottom: dotted thin #e0e0e0;
margin-top: 30px;
}
h2 {
font-size: 1.2em;
font-weight: normal;
color: #0c3762;
border-bottom: dotted thin #e0e0e0;
margin-top: 30px;
}
h3 {
font-size: 1.1em;
font-weight: normal;
color: #0c3762;
margin-top: 30px;
}
h4 {
font-size: 1.0em;
font-weight: normal;
color: #0c3762;
margin-top: 30px;
}
p {
text-align: justify;
}
p.last {
margin-bottom: 0;
}
ol {
padding-left: 20px;
}
ul {
padding-left: 5px;
margin-top: 3px;
}
li {
line-height: 1.3;
}
div.content ul > li {
-moz-background-clip:border;
-moz-background-inline-policy:continuous;
-moz-background-origin:padding;
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
list-style-image: none;
list-style-type: none;
padding: 0 0 0 1.666em;
margin-bottom: 3px;
}
td {
vertical-align: top;
}
tt {
background-color: #e2e2e2;
font-size: 1.0em;
font-family: monospace;
}
pre {
border-color: #0c3762;
border-style: dotted;
border-width: thin;
margin: 0 0 12px 0;
padding: 0.8em;
background-color: #f0f0f0;
}
hr {
border-top: 1px solid #ccc;
border-bottom: 0;
border-right: 0;
border-left: 0;
margin-bottom: 10px;
margin-top: 20px;
}
/* printer only pretty stuff */
@media print {
.noprint {
display: none;
}
/* for acronyms we want their definitions inlined at print time */
acronym[title]:after {
font-size: small;
content: " (" attr(title) ")";
font-style: italic;
}
/* and not have mozilla dotted underline */
acronym {
border: none;
}
div.topnav, div.bottomnav, div.header, table.index {
display: none;
}
div.content {
margin: 0px;
padding: 0px;
}
html {
background: #FFF;
}
}
.viewcode-back {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
margin: -1px -12px;
padding: 0 12px;
}

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,7 @@
# serve to show the default.
import sys, os
import psutil
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@ -28,11 +29,16 @@ autoclass_content = 'both'
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.0'
needs_sphinx = '1.1'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx.ext.doctest',
'sphinxcontrib.fulltoc',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_static']
@ -41,7 +47,7 @@ templates_path = ['_static']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@ -82,7 +88,7 @@ add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
add_module_names = False
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
@ -94,6 +100,8 @@ pygments_style = 'monokai'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
# -- Options for HTML output ---------------------------------------------------

View File

@ -17,6 +17,7 @@ do:
:private-members:
:show-inheritance:
.. _meta:
meta module
-----------
@ -28,10 +29,13 @@ doing some serious hacking.
.. automodule:: gnupg._meta
:members:
:undoc-members:
:private-members:
:special-members:
:exclude-members: _agent_proc, __module__, __dict__, _decode_errors, init,
__weakref__, _result_map, __metaclass__
:show-inheritance:
.. _parsers:
parsers module
--------------
@ -39,10 +43,11 @@ parsers module
These are classes for parsing both user inputs and status file descriptor
flags from GnuPG's output. The latter are used in order to determine what our
GnuPG process is doing and retrieve information about its operations, which
are stored in corresponding classes in :attr:`gnupg.GPG._result_dict`. Some
status flags aren't handled yet -- infomation on *all* of the flags (well, at
least the documented ones…) can be found in the docs/DETAILS file in GnuPG's
source_, which has been included here_ as well.
are stored in corresponding classes in
:attr:`~gnupg._meta.GPGBase._result_map`. Some status flags aren't handled yet
-- information on *all* of the flags (well, at least the documented ones…) can
be found in the :file:`docs/DETAILS` file in GnuPG's source_, which has been
included here_ as well.
.. automodule:: gnupg._parsers
@ -52,6 +57,8 @@ source_, which has been included here_ as well.
:show-inheritance:
.. _util:
util module
-----------
@ -75,51 +82,50 @@ by Steve Traugott, which in turn is a modification of the pycrypto GnuPG
interface written by A.M. Kuchling.
This version is patched to sanitize untrusted inputs, due to the necessity of
executing :class:`subprocess.Popen([...], shell=True)` in order to communicate
with GnuPG. Several speed improvements were also made based on code profiling,
and the API has been cleaned up to support an easier, more Pythonic,
interaction.
executing ``subprocess.Popen([...], shell=True)`` in order to communicate with
GnuPG. Several speed improvements were also made based on code profiling, and
the API has been cleaned up to support an easier, more Pythonic, interaction.
Previous Authors' Documentation
-------------------------------
Steve Traugott's documentation:
| Portions of this module are derived from A.M. Kuchling's well-designed
| GPG.py, using Richard Jones' updated version 1.3, which can be found in
| the pycrypto CVS repository on Sourceforge:
|
| http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
|
| This module is *not* forward-compatible with amk's; some of the old
| interface has changed. For instance, since I've added decrypt
| functionality, I elected to initialize with a 'gpghome' argument instead
| of 'keyring', so that gpg can find both the public and secret keyrings.
| I've also altered some of the returned objects in order for the caller to
| not have to know as much about the internals of the result classes.
|
| While the rest of ISconf is released under the GPL, I am releasing this
| single file under the same terms that A.M. Kuchling used for pycrypto.
|
| Steve Traugott, stevegt@terraluna.org
| Thu Jun 23 21:27:20 PDT 2005
|
| Portions of this module are derived from A.M. Kuchling's well-designed
| GPG.py, using Richard Jones' updated version 1.3, which can be found in
| the pycrypto CVS repository on Sourceforge:
|
| http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
|
| This module is *not* forward-compatible with amk's; some of the old
| interface has changed. For instance, since I've added decrypt
| functionality, I elected to initialize with a 'gpghome' argument instead
| of 'keyring', so that gpg can find both the public and secret keyrings.
| I've also altered some of the returned objects in order for the caller to
| not have to know as much about the internals of the result classes.
|
| While the rest of ISconf is released under the GPL, I am releasing this
| single file under the same terms that A.M. Kuchling used for pycrypto.
|
| Steve Traugott, stevegt@terraluna.org
| Thu Jun 23 21:27:20 PDT 2005
Vinay Sajip's documentation:
| This version of the module has been modified from Steve Traugott's version
| (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
| Vinay Sajip to make use of the subprocess module (Steve's version uses
| os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid
| confusion with the previous versions.
|
| A unittest harness (test_gnupg.py) has also been added.
|
| Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
|
| This version of the module has been modified from Steve Traugott's version
| (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
| Vinay Sajip to make use of the subprocess module (Steve's version uses
| os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid
| confusion with the previous versions.
|
| A unittest harness (test_gnupg.py) has also been added.
|
| Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
.. _GnuPG: http://gnupg.org
.. _python-gnupg: https://code.google.com/p/python-gnupg/
.. _source: http://http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=shortlog;h=refs/heads/master
.. _here: ./DETAILS.html
.. _here: ./_static/DETAILS.html

View File

@ -21,7 +21,7 @@ Contents:
Source, license, & bug reports
==============================
The source code which was used to generate this documentation is accessible by
clicking the little [source]_ links next to the docs. Current source code can
clicking the little `source` links next to the docs. Current source code can
be found in this github repository_. The **master** branch always reflects the
latest release, all releases are tagged with signed, annotated git tags, and
the **develop** branch represents the state of the next release.

View File

@ -0,0 +1,213 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Create a new 8192-bit GnuPG keypair.
:authors: Isis <isis@patternsinthevoid.net> 0xa3adb67a2cdb8b35
:license: MIT license
:copyright: (c) 2013 Isis Agora Lovecruft
"""
from __future__ import print_function
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import gnupg
import sys
from gnupg import _logger
# Set up logging:
log = _logger.create_logger(9)
log.setLevel(9)
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Settings
#
# You probably want to edit the following variables. Ones which are currently
# set to strings are necessary; the ones which are set to `None` are optional.
# The directory to use as the homedir for GnuPG (it will contain the
# secring.gpg and pubring.gpg, etc.)
NEWKEY_DIR = './8192-bit-key'
# The name you go by, as it should appear in the primary keyid, i.e. "Evey
# Hammond":
NAME = 'Someone'
# The comment which goes in parantheses after the name and before the email
# address on the key's primary uid. Leave as None to not have one.
NAME_COMMENT = None
# The email address for the primary UID (You *should* actually be able to put
# whatever you want here, like a domain or something, because the GnuPG
# `--allow-freeform-uid` option will be used. I've not actually tested this
# though.)
NAME_EMAIL = 'someone@example.com'
# Expiration date for the new key. To use the default expiration of one year,
# set to None.
#EXPIRE_DATE = '1999-09-19'
EXPIRE_DATE = None
# GnuPG-1.4.x allows the automated creation of passphraseless keys. If using
# GnuPG-1.4.x, and you don't specify the passphrase, you can of course set it
# later with `$ gpg --edit-key` and then at the prompt typing `password`. If
# using a GnuPG from the 2.x series, you *must* specify a password here
# (though you can still change it afterward).
PASSPHRASE = None
# Type of key, i.e. 'RSA' or 'DSA' or something else. I've only tested
# 8192-bit keys with RSA.
KEY_TYPE = 'RSA'
# Uses for the key. Can be things like 'cert,sign' or 'cert' or 'cert,auth'.
KEY_USAGE = 'cert'
# Key bitlength. You likely want 8192, if you're using this script.
#
# It *is* possible to create 16834-bit keys, though it requires modifying and
# recompiling GnuPG. Doing this is a bit janky due to internal GnuPG buffers
# in several parts of the codebase being limited to 8192-bits, the key cannot
# be handled by *most* keyservers (there appears to be only one public
# keyserver which supports 16384-bit keys being uploaded to it), and the
# 16834-bit key will likely require the modified GnuPG to work with it (even
# then some operations, such as removal of the primary secret key, but not the
# primary public key, from the keychain will be badly broken).
KEY_LENGTH = 8192
# Type of subkey. None to skip subkey generation. You can add keys later
# through `$ gpg --edit-key`. For compatibility with people who aren't doing
# crazy things with their keys, you maybe probably want to use `--edit-key` to
# create some nice, normal, "overly-paranoid" 4096-bit keys.
SUBKEY_TYPE = 'RSA'
# Same as KEY_USAGE.
#SUBKEY_USAGE = None
SUBKEY_USAGE = 'sign'
# Same as KEY_LENGTH.
#SUBKEY_LENGTH = None
SUBKEY_LENGTH = 4096
# The default keyserver for the key, which is embedded into the key, telling
# other people's GnuPGs to fetch (and send updates) to this URL:
KEYSERVER = None
# Set the cipher, hash, and compression preference values for this key. This
# expects the same type of string as the sub-command setpref in the
# --edit-key menu. The default preferences are given in
# ``gnupg.GPG.default_preference_list``.
PREFERENCES = None
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
gpg = gnupg.GPG(homedir=NEWKEY_DIR)
allparams = {'name_real': NAME,
'name_comment': NAME_COMMENT,
'name_email': NAME_EMAIL,
'expire_date': EXPIRE_DATE,
'passphrase': PASSPHRASE,
'key_type': KEY_TYPE,
'key_usage': KEY_USAGE,
'key_length': KEY_LENGTH,
'subkey_type': SUBKEY_TYPE,
'subkey_usage': SUBKEY_USAGE,
'subkey_length': SUBKEY_LENGTH,
'keyserver': KEYSERVER,
'preferences': PREFERENCES}
def createBatchfile(keyparams=allparams):
"""Create the batchfile for our new key.
:params dict keyparams: A dictionary of arguments for creating the key. It
should probably be ``allparams``.
:rtype: str
:returns: A string containing the entire GnuPG batchfile.
"""
useparams = {}
for key, value in keyparams.items():
if value:
useparams.update({key: value})
batchfile = gpg.gen_key_input(separate_keyring=True,
save_batchfile=True,
**useparams)
log.info("Generated GnuPG batch file:\n%s" % batchfile)
return batchfile
def createKey(batchfile):
"""Create a new keypair from a **batchfile**.
Writes the new keys into keyrings named after ``NAME_EMAIL`` inside the
``NEWKEY_DIR``.
:params str batchfile: A GnuPG batchfile. See :func:`createBatchfile`.
"""
key = gpg.gen_key(batchfile)
fingerprint = key.fingerprint
if not fingerprint:
log.error("Key creation seems to have failed: %s" % key.status)
return None, None
return key, fingerprint
def displayNewKey(key):
"""Use ``gnupg.GPG.list_keys()`` to display details of the new key."""
if key.keyring:
gpg.keyring = key.keyring
if key.secring:
gpg.secring = key.secring
# Using '--fingerprint' twice will display subkey fingerprints too:
gpg.options = ['--fingerprint', '--fingerprint']
keylist = gpg.list_keys(secret=True)
# `result` is a `gnupg._parsers.ListKeys`, which is list-like, so iterate
# over all the keys and display their info:
for gpgkey in keylist:
for k, v in gpgkey:
log.info("%s: %s" % (k.capitalize(), v))
return keylist
def exportNewKey(fingerprint):
"""Export the new keys into .asc files.
:param str fingerprint: A full key fingerprint.
"""
log.info("Exporting key: %s" % fingerprint)
keyfn = os.path.join(gpg.homedir,
fingerprint + '-8192-bit-key') + os.path.extsep
pubkey = gpg.export_keys(fingerprint)
seckey = gpg.export_keys(fingerprint, secret=True)
subkey = gpg.export_keys(fingerprint, secret=True, subkeys=True)
with open(keyfn + 'pub' + os.path.extsep + 'asc', 'w') as fh:
fh.write(pubkey)
with open(keyfn + 'sec' + os.path.extsep + 'asc', 'w') as fh:
fh.write(seckey)
with open(keyfn + 'sub' + os.path.extsep + 'asc', 'w') as fh:
fh.write(subkey)
if __name__ == '__main__':
if (NAME == 'Someone') or (NAME_EMAIL == 'someone@example.com'):
log.info("Please edit the settings variables within this script.")
log.info("Exiting...")
exit(1)
else:
try:
batchfile = createBatchfile()
key, fingerprint = createKey(batchfile)
log.info("New key with fingerprint %r created" % fingerprint)
displayNewKey(key)
exportNewKey(fingerprint)
except Exception as error:
log.error(error)

View File

@ -8,31 +8,31 @@
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
from __future__ import absolute_import
from . import gnupg
from . import copyleft
from . import _ansistrm
from . import _logger
from . import _meta
from . import _parsers
from . import _util
from .gnupg import GPG
from . import gnupg
from . import copyleft
from . import _ansistrm
from . import _logger
from . import _meta
from . import _parsers
from . import _util
from .gnupg import GPG
from ._version import get_versions
__version__ = get_versions()['version']
__authors__ = copyleft.authors
__license__ = copyleft.full_text
__version__ = get_versions()['version']
__authors__ = copyleft.authors
__license__ = copyleft.full_text
__copyleft__ = copyleft.copyright
## do not set __package__ = "gnupg", else we will end up with

View File

@ -17,10 +17,7 @@
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
'''log.py
----------
Logging module for python-gnupg.
'''
'''Logging module for python-gnupg.'''
from __future__ import absolute_import
from __future__ import print_function
@ -51,17 +48,23 @@ def status(self, message, *args, **kwargs):
def create_logger(level=logging.NOTSET):
"""Create a logger for python-gnupg at a specific message level.
:type level: int or str
:param level: A string or an integer for the lowest level to log.
Available levels:
int str description
0 NOTSET Disable all logging.
9 GNUPG Log GnuPG's internal status messages.
10 DEBUG Log module level debuging messages.
20 INFO Normal user-level messages.
30 WARN Warning messages.
40 ERROR Error messages and tracebacks.
50 CRITICAL Unhandled exceptions and tracebacks.
:type level: :obj:`int` or :obj:`str`
:param level: A string or an integer for the lowest level to include in
logs.
**Available levels:**
==== ======== ========================================
int str description
==== ======== ========================================
0 NOTSET Disable all logging.
9 GNUPG Log GnuPG's internal status messages.
10 DEBUG Log module level debuging messages.
20 INFO Normal user-level messages.
30 WARN Warning messages.
40 ERROR Error messages and tracebacks.
50 CRITICAL Unhandled exceptions and tracebacks.
==== ======== ========================================
"""
_test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test')
_now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
@ -75,21 +78,19 @@ def create_logger(level=logging.NOTSET):
if level > logging.NOTSET:
logging.basicConfig(level=level, filename=_fn,
filemode="a", format=_fmt)
logging.captureWarnings(True)
logging.logThreads = True
if hasattr(logging,'captureWarnings'):
logging.captureWarnings(True)
colouriser = _ansistrm.ColorizingStreamHandler
colouriser.level_map[9] = (None, 'blue', False)
colouriser.level_map[10] = (None, 'cyan', False)
handler = colouriser(stream=sys.stderr)
handler = colouriser(sys.stderr)
handler.setLevel(level)
formatr = logging.Formatter(_fmt)
handler.setFormatter(formatr)
print("Starting the logger...")
else:
handler = NullHandler()
print("GnuPG logging disabled...")
log = logging.getLogger('gnupg')
log.addHandler(handler)

View File

@ -7,20 +7,18 @@
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
'''meta.py
----------
Meta and base classes for hiding internal functions, and controlling attribute
creation and handling.
'''Meta and base classes for hiding internal functions, and controlling
attribute creation and handling.
'''
from __future__ import absolute_import
@ -52,6 +50,10 @@ class GPGMeta(type):
Detects running gpg-agent processes and the presence of a pinentry
program, and disables pinentry so that python-gnupg can write the
passphrase to the controlled GnuPG process without killing the agent.
:attr _agent_proc: If a :program:`gpg-agent` process is currently running
for the effective userid, then **_agent_proc** will be
set to a ``psutil.Process`` for that process.
"""
def __new__(cls, name, bases, attrs):
@ -69,13 +71,13 @@ class GPGMeta(type):
If there is a matching gpg-agent process, set a :class:`psutil.Process`
instance containing the gpg-agent process' information to
:attr:`GPG._agent_proc`.
``cls._agent_proc``.
:returns: True if there exists a gpg-agent process running under the
same effective user ID as that of this program. Otherwise,
returns None.
"""
identity = os.getresuid()
identity = psutil.Process(os.getpid()).uids
for proc in psutil.process_iter():
if (proc.name == "gpg-agent") and proc.is_running:
log.debug("Found gpg-agent process with pid %d" % proc.pid)
@ -87,8 +89,13 @@ class GPGMeta(type):
class GPGBase(object):
"""Base class for property storage and to control process initialisation."""
"""Base class for storing properties and controlling process initialisation.
:const _result_map: A *dict* containing classes from
:mod:`~gnupg._parsers`, used for parsing results
obtained from GnuPG commands.
:const _decode_errors: How to handle encoding errors.
"""
__metaclass__ = GPGMeta
_decode_errors = 'strict'
_result_map = { 'crypt': _parsers.Crypt,
@ -103,7 +110,28 @@ class GPGBase(object):
def __init__(self, binary=None, home=None, keyring=None, secring=None,
use_agent=False, default_preference_list=None,
verbose=False, options=None):
"""Create a ``GPGBase``.
This class is used to set up properties for controlling the behaviour
of configuring various options for GnuPG, such as setting GnuPG's
**homedir** , and the paths to its **binary** and **keyring** .
:const binary: (:obj:`str`) The full path to the GnuPG binary.
:ivar homedir: (:class:`~gnupg._util.InheritableProperty`) The full
path to the current setting for the GnuPG
``--homedir``.
:ivar _generated_keys: (:class:`~gnupg._util.InheritableProperty`)
Controls setting the directory for storing any
keys which are generated with
:meth:`~gnupg.GPG.gen_key`.
:ivar str keyring: The filename in **homedir** to use as the keyring
file for public keys.
:ivar str secring: The filename in **homedir** to use as the keyring
file for secret keys.
"""
self.binary = _util._find_binary(binary)
self.homedir = home if home else _util._conf
pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg'
@ -125,7 +153,7 @@ class GPGBase(object):
self._filesystemencoding = encodings.normalize_encoding(
sys.getfilesystemencoding().lower())
self._keyserver = 'hkp://subkeys.pgp.net'
self._keyserver = 'hkp://wwwkeys.pgp.net'
self.__generated_keys = os.path.join(self.homedir, 'generated-keys')
try:
@ -136,8 +164,8 @@ class GPGBase(object):
if self.options is not None:
assert isinstance(self.options, str), "options not string"
except (AssertionError, AttributeError) as ae:
log.error("GPGBase.__init__(): %s" % ae.message)
raise RuntimeError(ae.message)
log.error("GPGBase.__init__(): %s" % str(ae))
raise RuntimeError(str(ae))
else:
if verbose is True:
# The caller wants logging, but we need a valid --debug-level
@ -154,15 +182,14 @@ class GPGBase(object):
self.__remove_path__('pinentry')
def __remove_path__(self, prog=None, at_exit=True):
"""Remove a the directories containing a program from the system's
``$PATH``. If :attr:`GPG.binary` is in a directory being removed, it
is symlinked to './gpg'
"""Remove the directories containing a program from the system's
``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it
is linked to :file:'./gpg' in the current directory.
:param str prog: The program to remove from ``$PATH``.
:param bool at_exit: Add the program back into the ``$PATH`` when the
Python interpreter exits, and delete any symlinks
to :attr:`GPG.binary` which were created.
to ``GPGBase.binary`` which were created.
"""
#: A list of ``$PATH`` entries which were removed to disable pinentry.
self._removed_path_entries = []
@ -173,7 +200,7 @@ class GPGBase(object):
try:
program = _util._which(prog)[0]
except (OSError, IOError, IndexError) as err:
log.err(err.message)
log.err(str(err))
log.err("Cannot find program '%s', not changing PATH." % prog)
return
@ -222,7 +249,7 @@ class GPGBase(object):
@staticmethod
def update_path(environment, path):
"""Add paths to the string at os.environ['PATH'].
"""Add paths to the string at ``os.environ['PATH']``.
:param str environment: The environment mapping to update.
:param list path: A list of strings to update the PATH with.
@ -270,7 +297,7 @@ class GPGBase(object):
Note that "original state" does not mean the default preference
list for whichever version of GnuPG is being used. It means the
default preference list defined by :attr:`GPGBase._preferences`.
default preference list defined by :attr:`GPGBase._prefs`.
Using BZIP2 is avoided due to not interacting well with some versions
of GnuPG>=2.0.0.
@ -293,14 +320,14 @@ class GPGBase(object):
should contain the desired keyserver protocol
which is supported by the keyserver, for example,
``'hkps://keys.mayfirst.org'``. The default
keyserver is ``'hkp://subkeys.pgp.net'``.
keyserver is ``'hkp://wwwkeys.pgp.net'``.
"""
self._keyserver = location
@keyserver.deleter
def keyserver(self):
"""Reset the keyserver to the default setting."""
self._keyserver = 'hkp://subkeys.pgp.net'
self._keyserver = 'hkp://wwwkeys.pgp.net'
def _homedir_getter(self):
"""Get the directory currently being used as GnuPG's homedir.
@ -321,11 +348,11 @@ class GPGBase(object):
created. Lastly, the ``direcory`` will be checked that the EUID has
read and write permissions for it.
:param str homedir: A relative or absolute path to the directory to use
for storing/accessing GnuPG's files, including
:param str directory: A relative or absolute path to the directory to
use for storing/accessing GnuPG's files, including
keyrings and the trustdb.
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
use.
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
directory to use.
"""
if not directory:
log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'"
@ -346,8 +373,8 @@ class GPGBase(object):
except AssertionError as ae:
msg = ("Unable to set '%s' as GnuPG homedir" % directory)
log.debug("GPGBase.homedir.setter(): %s" % msg)
log.debug(ae.message)
raise RuntimeError(ae.message)
log.debug(str(ae))
raise RuntimeError(str(ae))
else:
log.info("Setting homedir to '%s'" % hd)
self._homedir = hd
@ -365,17 +392,18 @@ class GPGBase(object):
def _generated_keys_setter(self, directory):
"""Set the directory for storing generated keys.
If unspecified, use $GNUPGHOME/generated-keys. If specified, ensure
that the ``directory`` does not contain various shell escape
characters. If ``directory`` is not found, it will be automatically
created. Lastly, the ``direcory`` will be checked that the EUID has
read and write permissions for it.
If unspecified, use
:meth:`~gnupg._meta.GPGBase.homedir`/generated-keys. If specified,
ensure that the ``directory`` does not contain various shell escape
characters. If ``directory`` isn't found, it will be automatically
created. Lastly, the ``directory`` will be checked to ensure that the
current EUID has read and write permissions for it.
:param str directory: A relative or absolute path to the directory to
use for storing/accessing GnuPG's files, including keyrings and
the trustdb.
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
use.
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
directory to use.
"""
if not directory:
directory = os.path.join(self.homedir, 'generated-keys')
@ -397,8 +425,8 @@ class GPGBase(object):
except AssertionError as ae:
msg = ("Unable to set '%s' as generated keys dir" % directory)
log.debug("GPGBase._generated_keys_setter(): %s" % msg)
log.debug(ae.message)
raise RuntimeError(ae.message)
log.debug(str(ae))
raise RuntimeError(str(ae))
else:
log.info("Setting homedir to '%s'" % hd)
self.__generated_keys = hd
@ -407,10 +435,11 @@ class GPGBase(object):
_generated_keys_setter)
def _make_args(self, args, passphrase=False):
"""Make a list of command line elements for GPG. The value of ``args``
will be appended only if it passes the checks in
:func:`parsers._sanitise`. The ``passphrase`` argument needs to be True
if a passphrase will be sent to GPG, else False.
"""Make a list of command line elements for GPG.
The value of ``args`` will be appended only if it passes the checks in
:func:`gnupg._parsers._sanitise`. The ``passphrase`` argument needs to
be True if a passphrase will be sent to GnuPG, else False.
:param list args: A list of strings of options and flags to pass to
``GPG.binary``. This is input safe, meaning that
@ -489,12 +518,13 @@ class GPGBase(object):
Calls methods on the response object for each valid token found, with
the arg being the remainder of the status line.
:param stream: A byte-stream, file handle, or :class:`subprocess.PIPE`
to parse the for status codes from the GnuPG process.
:param stream: A byte-stream, file handle, or a
:data:`subprocess.PIPE` for parsing the status codes
from the GnuPG process.
:param result: The result parser class from :mod:`_parsers` with which
to call ``handle_status`` and parse the output of
``stream``.
:param result: The result parser class from :mod:`~gnupg._parsers`
the ``handle_status()`` method of that class will be
called in order to parse the output of ``stream``.
"""
lines = []
while True:
@ -535,8 +565,8 @@ class GPGBase(object):
and stored as ``result.data``.
:param stream: An open file-like object to read() from.
:param result: An instance of one of the result parsing classes from
:attr:`GPGBase._result_mapping`.
:param result: An instance of one of the :ref:`result parsing classes
<parsers>` from :const:`~gnupg._meta.GPGBase._result_map`.
"""
chunks = []
log.debug("Reading data from stream %r..." % stream.__repr__())
@ -604,13 +634,13 @@ class GPGBase(object):
:param str keyids: A space-delimited string containing the keyids to
request.
:param str keyserver: The keyserver to request the ``keyids`` from;
defaults to :property:`gnupg.GPG.keyserver`.
defaults to `gnupg.GPG.keyserver`.
"""
if not keyserver:
keyserver = self.keyserver
args = ['--keyserver {}'.format(keyserver),
'--recv-keys {}'.format(keyids)]
args = ['--keyserver {0}'.format(keyserver),
'--recv-keys {0}'.format(keyids)]
log.info('Requesting keys from %s: %s' % (keyserver, keyids))
result = self._result_map['import'](self)
@ -631,9 +661,10 @@ class GPGBase(object):
:param bool detach: If True, create a detached signature.
:param bool binary: If True, do not ascii armour the output.
:param str digest_algo: The hash digest to use. Again, to see which
hashes your GnuPG is capable of using, do:
``$ gpg --with-colons --list-config digestname``.
The default, if unspecified, is ``'SHA512'``.
hashes your GnuPG is capable of using, do:
``$ gpg --with-colons --list-config
digestname``. The default, if unspecified, is
``'SHA512'``.
"""
log.debug("_sign_file():")
if binary:
@ -665,7 +696,7 @@ class GPGBase(object):
_util._write_passphrase(proc.stdin, passphrase, self._encoding)
writer = _util._threaded_copy_data(file, proc.stdin)
except IOError as ioe:
log.exception("Error writing message: %s" % ioe.message)
log.exception("Error writing message: %s" % str(ioe))
writer = None
self._collect_output(proc, result, writer, proc.stdin)
return result
@ -681,46 +712,55 @@ class GPGBase(object):
cipher_algo='AES256',
digest_algo='SHA512',
compress_algo='ZLIB'):
"""Encrypt the message read from the file-like object ``data``.
"""Encrypt the message read from the file-like object **data**.
:param str data: The file or bytestream to encrypt.
:param str recipients: The recipients to encrypt to. Recipients must
be specified keyID/fingerprint. Care should be taken in Python2.x
to make sure that the given fingerprint is in fact a string and
not a unicode object.
be specified keyID/fingerprint.
.. warning:: Care should be taken in Python2 to make sure that the
given fingerprints for **recipients** are in fact strings
and not unicode objects.
:param str default_key: The keyID/fingerprint of the key to use for
signing. If given, ``data`` will be encrypted and signed.
signing. If given, **data** will be encrypted
*and* signed.
:param str passphrase: If given, and ``default_key`` is also given,
use this passphrase to unlock the secret portion of the
``default_key`` to sign the encrypted ``data``. Otherwise, if
``default_key`` is not given, but ``symmetric=True``, then use
this passphrase as the passphrase for symmetric
encryption. Signing and symmetric encryption should *not* be
combined when sending the ``data`` to other recipients, else the
passphrase to the secret key would be shared with them.
:param str passphrase: If given, and **default_key** is also given,
use this passphrase to unlock the secret
portion of the **default_key** to sign the
encrypted **data**. Otherwise, if
**default_key** is not given, but **symmetric**
is ``True``, then use this passphrase as the
passphrase for symmetric encryption. Signing
and symmetric encryption should *not* be
combined when sending the **data** to other
recipients, else the passphrase to the secret
key would be shared with them.
:param bool armor: If True, ascii armor the output; otherwise, the
output will be in binary format. (Default: True)
output will be in binary format. (Default: True)
:param bool encrypt: If True, encrypt the ``data`` using the
``recipients`` public keys. (Default: True)
:param bool encrypt: If True, encrypt the **data** using the
**recipients** public keys. (Default: True)
:param bool symmetric: If True, encrypt the ``data`` to ``recipients``
using a symmetric key. See the ``passphrase`` parameter. Symmetric
encryption and public key encryption can be used simultaneously,
and will result in a ciphertext which is decryptable with either
the symmetric ``passphrase`` or one of the corresponding private
keys.
:param bool symmetric: If True, encrypt the **data** to **recipients**
using a symmetric key. See the **passphrase**
parameter. Symmetric encryption and public key
encryption can be used simultaneously, and will
result in a ciphertext which is decryptable
with either the symmetric **passphrase** or one
of the corresponding private keys.
:param bool always_trust: If True, ignore trust warnings on recipient
keys. If False, display trust warnings. (default: True)
:param bool always_trust: If True, ignore trust warnings on
**recipients** keys. If False, display trust
warnings. (default: True)
:param str output: The output file to write to. If not specified, the
encrypted output is returned, and thus should be stored as an
object in Python. For example:
encrypted output is returned, and thus should be
stored as an object in Python. For example:
>>> import shutil
>>> import gnupg
@ -742,17 +782,20 @@ class GPGBase(object):
'The crow flies at midnight.'
:param str cipher_algo: The cipher algorithm to use. To see available
algorithms with your version of GnuPG, do:
``$ gpg --with-colons --list-config ciphername``.
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
algorithms with your version of GnuPG, do:
:command:`$ gpg --with-colons --list-config
ciphername`. The default **cipher_algo**, if
unspecified, is ``'AES256'``.
:param str digest_algo: The hash digest to use. Again, to see which
hashes your GnuPG is capable of using, do:
``$ gpg --with-colons --list-config digestname``.
The default, if unspecified, is ``'SHA512'``.
hashes your GnuPG is capable of using, do:
:command:`$ gpg --with-colons --list-config
digestname`. The default, if unspecified, is
``'SHA512'``.
:param str compress_algo: The compression algorithm to use. Can be one
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or
``'Uncompressed'``.
"""
args = []

View File

@ -7,25 +7,28 @@
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
'''parsers.py
-------------
Classes for parsing GnuPG status messages and sanitising commandline options.
'''Classes for parsing GnuPG status messages and sanitising commandline
options.
'''
from __future__ import absolute_import
from __future__ import print_function
import collections
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
import re
from . import _util
@ -50,8 +53,8 @@ def _check_keyserver(location):
:param str location: A string containing the default keyserver. This
should contain the desired keyserver protocol which
is supported by the keyserver, for example, the
default is ``'hkp://subkeys.pgp.net'``.
:rtype: str or None
default is ``'hkp://wwwkeys .pgp.net'``.
:rtype: :obj:`str` or :obj:`None`
:returns: A string specifying the protocol and keyserver hostname, if the
checks passed. If not, returns None.
"""
@ -74,10 +77,11 @@ def _check_keyserver(location):
def _check_preferences(prefs, pref_type=None):
"""Check cipher, digest, and compression preference settings.
MD5 is not allowed. This is not 1994.[0] SHA1 is allowed grudgingly.[1]
MD5 is not allowed. This is `not 1994`__. SHA1 is allowed_ grudgingly_.
[0]: http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
[1]: http://eprint.iacr.org/2008/469.pdf
__ http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
.. _allowed: http://eprint.iacr.org/2008/469.pdf
.. _grudgingly: https://www.schneier.com/blog/archives/2012/10/when_will_we_se.html
"""
if prefs is None: return
@ -115,8 +119,8 @@ def _check_preferences(prefs, pref_type=None):
return allowed
def _fix_unsafe(shell_input):
"""Find characters used to escape from a string into a shell, and wrap them
in quotes if they exist. Regex pilfered from python-3.x shlex module.
"""Find characters used to escape from a string into a shell, and wrap them in
quotes if they exist. Regex pilfered from Python3 :mod:`shlex` module.
:param str shell_input: The input intended for the GnuPG process.
"""
@ -161,11 +165,15 @@ def _is_allowed(input):
class and its name will need to be added to this
set.
:rtype: Exception or str
:raise: :exc:UsageError if ``_allowed`` is not a subset of ``_possible``.
ProtectedOption if ``input`` is not in the set ``_allowed``.
:return: The original parameter ``input``, unmodified and unsanitized,
if no errors occur.
:raises: :exc:`UsageError` if **input** is not a subset of the hard-coded
set of all GnuPG options in :func:`_get_all_gnupg_options`.
:exc:`ProtectedOption` if **input** is not in the set of allowed
options.
:rtype: str
:return: The original **input** parameter, unmodified and unsanitized, if
no errors occur.
"""
gnupg_options = _get_all_gnupg_options()
allowed = _get_options_group("allowed")
@ -220,12 +228,12 @@ def _is_hex(string):
def _is_string(thing):
"""Python character arrays are a mess.
If Python2, check if ``thing`` is a ``unicode()`` or ``str()``.
If Python3, check if ``thing`` is a ``str()``.
If Python2, check if **thing** is an :obj:`unicode` or a :obj:`str`.
If Python3, check if **thing** is a :obj:`str`.
:param thing: The thing to check.
:returns: ``True`` if ``thing`` is a "string" according to whichever
version of Python we're running in.
:returns: ``True`` if **thing** is a string according to whichever version
of Python we're running in.
"""
if _util._py3k: return isinstance(thing, str)
else: return isinstance(thing, basestring)
@ -238,18 +246,20 @@ def _sanitise(*args):
sanitised, allowed options.
Each new option that we support that is not a boolean, but instead has
some extra inputs, i.e. "--encrypt-file foo.txt", will need some basic
safety checks added here.
some additional inputs following it, i.e. "--encrypt-file foo.txt", will
need some basic safety checks added here.
GnuPG has three-hundred and eighteen commandline flags. Also, not all
implementations of OpenPGP parse PGP packets and headers in the same way,
so there is added potential there for messing with calls to GPG.
For information on the PGP message format specification, see:
https://www.ietf.org/rfc/rfc1991.txt
For information on the PGP message format specification, see
:rfc:`1991`.
If you're asking, "Is this *really* necessary?": No, not really -- we could
just do a check as described here: https://xkcd.com/1181/
just follow the security precautions recommended by `this xkcd`__.
__ https://xkcd.com/1181/
:param str args: (optional) The boolean arguments which will be passed to
the GnuPG process.
@ -260,22 +270,23 @@ def _sanitise(*args):
## see TODO file, tag :cleanup:sanitise:
def _check_option(arg, value):
"""
Check that a single :param:arg is an allowed option. If it is allowed,
quote out any escape characters in :param:values, and add the pair to
:ivar:sanitised.
"""Check that a single ``arg`` is an allowed option.
If it is allowed, quote out any escape characters in ``value``, and
add the pair to :ivar:`sanitised`. Otherwise, drop them.
:param str arg: The arguments which will be passed to the GnuPG
process, and, optionally their corresponding values.
The values are any additional arguments following the
GnuPG option or flag. For example, if we wanted to pass
"--encrypt --recipient isis@leap.se" to gpg, then
"--encrypt" would be an arg without a value, and
"--recipient" would also be an arg, with a value of
"isis@leap.se".
GnuPG option or flag. For example, if we wanted to
pass ``"--encrypt --recipient isis@leap.se"`` to
GnuPG, then ``"--encrypt"`` would be an arg without a
value, and ``"--recipient"`` would also be an arg,
with a value of ``"isis@leap.se"``.
:ivar list checked: The sanitised, allowed options and values.
:rtype: str
:returns: A string of the items in ``checked`` delimited by spaces.
:returns: A string of the items in ``checked``, delimited by spaces.
"""
checked = str()
none_options = _get_options_group("none_options")
@ -290,7 +301,7 @@ def _sanitise(*args):
flag = _is_allowed(arg)
assert flag is not None, "_check_option(): got None for flag"
except (AssertionError, ProtectedOption) as error:
log.warn("_check_option(): %s" % error.message)
log.warn("_check_option(): %s" % str(error))
else:
checked += (flag + ' ')
@ -298,7 +309,7 @@ def _sanitise(*args):
values = value.split(' ')
for v in values:
## these can be handled separately, without _fix_unsafe(),
## because they are only allowed if the pass the regex
## because they are only allowed if they pass the regex
if (flag in none_options) and (v is None):
continue
@ -332,8 +343,12 @@ def _sanitise(*args):
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
'--decrypt-files', '--import', '--verify']:
if _util._is_file(val): checked += (val + " ")
else: log.debug("%s not file: %s" % (flag, val))
if ( (_util._is_file(val))
or
((flag == '--verify') and (val == '-')) ):
checked += (val + " ")
else:
log.debug("%s not file: %s" % (flag, val))
elif flag in ['--cipher-algo', '--personal-cipher-prefs',
'--personal-cipher-preferences']:
@ -372,7 +387,8 @@ def _sanitise(*args):
groups[last] = str(filo.pop())
## accept the read-from-stdin arg:
if len(filo) >= 1 and filo[len(filo)-1] == '-':
groups[last] += str(' - \'\'') ## gross hack
groups[last] += str(' - ') ## gross hack
filo.pop()
else:
groups[last] = str()
while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
@ -570,21 +586,22 @@ def _get_all_gnupg_options():
This is hardcoded within a local scope to reduce the chance of a tampered
GnuPG binary reporting falsified option sets, i.e. because certain options
(namedly the '--no-options' option, which prevents the usage of gpg.conf
(namedly the ``--no-options`` option, which prevents the usage of gpg.conf
files) are necessary and statically specified in
:meth:`gnupg.GPG._makeargs`, if the inputs into Python are already
controlled, and we were to summon the GnuPG binary to ask it for its
options, it would be possible to receive a falsified options set missing
the '--no-options' option in response. This seems unlikely, and the method
is stupid and ugly, but at least we'll never have to debug whether or not
an option *actually* disappeared in a different GnuPG version, or some
funny business is happening.
:meth:`gnupg._meta.GPGBase._make_args`, if the inputs into Python are
already controlled, and we were to summon the GnuPG binary to ask it for
its options, it would be possible to receive a falsified options set
missing the ``--no-options`` option in response. This seems unlikely, and
the method is stupid and ugly, but at least we'll never have to debug
whether or not an option *actually* disappeared in a different GnuPG
version, or some funny business is happening.
These are the options as of GnuPG 1.4.12; the current stable branch of the
2.1.x tree contains a few more -- if you need them you'll have to add them
in here.
:ivar frozenset gnupg_options: All known GPG options and flags.
:type gnupg_options: frozenset
:ivar gnupg_options: All known GPG options and flags.
:rtype: frozenset
:returns: ``gnupg_options``
"""
@ -787,8 +804,8 @@ def progress(status_code):
class GenKey(object):
"""Handle status messages for key generation.
Calling the GenKey.__str__() method of this class will return the
generated key's fingerprint, or a status string explaining the results.
Calling the ``__str__()`` method of this class will return the generated
key's fingerprint, or a status string explaining the results.
"""
def __init__(self, gpg):
self._gpg = gpg
@ -799,11 +816,13 @@ class GenKey(object):
self.status = None
self.subkey_created = False
self.primary_created = False
#: This will store the filename of the key's public keyring if
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
#: This will store the key's public keyring filename, if
#: :meth:`~gnupg.GPG.gen_key_input` was called with
#: ``separate_keyring=True``.
self.keyring = None
#: This will store the filename of the key's secret keyring if
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
#: This will store the key's secret keyring filename, if :
#: :meth:`~gnupg.GPG.gen_key_input` was called with
#: ``separate_keyring=True``.
self.secring = None
def __nonzero__(self):
@ -823,7 +842,7 @@ class GenKey(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key in ("GOOD_PASSPHRASE"):
pass
@ -860,7 +879,7 @@ class DeleteResult(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key == "DELETE_PROBLEM":
self.status = self.problem_reason.get(value, "Unknown error: %r"
@ -905,7 +924,7 @@ class Sign(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL",
@ -1008,7 +1027,7 @@ class ImportResult(object):
_fields = '''count no_user_id imported imported_rsa unchanged
n_uids n_subk n_sigs n_revoc sec_read sec_imported sec_dups
not_imported'''.split()
_counts = collections.OrderedDict(
_counts = OrderedDict(
zip(_fields, [int(0) for x in range(len(_fields))]) )
#: A list of strings containing the fingerprints of the GnuPG keyIDs
@ -1037,7 +1056,7 @@ class ImportResult(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key == "IMPORTED":
# this duplicates info we already see in import_ok & import_problem
@ -1183,7 +1202,7 @@ class Verify(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key in self.TRUST_LEVELS:
self.trust_text = key
@ -1278,7 +1297,7 @@ class Crypt(Verify):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
@ -1345,7 +1364,7 @@ class ListPackets(object):
def _handle_status(self, key, value):
"""Parse a status code from the attached GnuPG process.
:raises: :exc:`ValueError` if the status message is unknown.
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
"""
if key == 'NODATA':
self.status = nodata(value)

View File

@ -7,19 +7,17 @@
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
'''trust.py
-----------
Functions for handling trustdb and trust calculations.
'''Functions for handling trustdb and trust calculations.
The functions within this module take an instance of :class:`gnupg.GPGBase` or
a suitable subclass as their first argument.
@ -44,11 +42,12 @@ def _create_trustdb(cls):
def export_ownertrust(cls, trustdb=None):
"""Export ownertrust to a trustdb file.
If there is already a file named 'trustdb.gpg' in the current GnuPG
homedir, it will be renamed to 'trustdb.gpg.bak'.
If there is already a file named :file:`trustdb.gpg` in the current GnuPG
homedir, it will be renamed to :file:`trustdb.gpg.bak`.
:param string trustdb: The path to the trustdb.gpg file. If not given,
defaults to 'trustdb.gpg' in the current GnuPG homedir.
defaults to ``'trustdb.gpg'`` in the current GnuPG
homedir.
"""
if trustdb is None:
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
@ -56,7 +55,7 @@ def export_ownertrust(cls, trustdb=None):
try:
os.rename(trustdb, trustdb + '.bak')
except (OSError, IOError) as err:
log.debug(err.message)
log.debug(str(err))
export_proc = cls._open_subprocess('--export-ownertrust')
tdb = open(trustdb, 'wb')
@ -65,8 +64,9 @@ def export_ownertrust(cls, trustdb=None):
def import_ownertrust(self, trustdb=None):
"""Import ownertrust from a trustdb file.
:param string trustdb: The path to the trustdb.gpg file. If not given,
defaults to 'trustdb.gpg' in the current GnuPG homedir.
:param str trustdb: The path to the trustdb.gpg file. If not given,
defaults to :file:`trustdb.gpg` in the current GnuPG
homedir.
"""
if trustdb is None:
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
@ -78,22 +78,23 @@ def import_ownertrust(self, trustdb=None):
def fix_trustdb(cls, trustdb=None):
"""Attempt to repair a broken trustdb.gpg file.
GnuPG>=2.0.x has this magical-seeming flag: '--fix-trustdb'. You'd think
GnuPG>=2.0.x has this magical-seeming flag: `--fix-trustdb`. You'd think
it would fix the the trustdb. Hah! It doesn't. Here's what it does
instead:
instead::
(python-gnupg)!isiswintermute:(testing/digest-algo *$=)~/code/python-gnupg gpg2 --fix-trustdb
gpg: You may try to re-create the trustdb using the commands:
gpg: cd ~/.gnupg
gpg: gpg2 --export-ownertrust > otrust.tmp
gpg: rm trustdb.gpg
gpg: gpg2 --import-ownertrust < otrust.tmp
gpg: If that does not work, please consult the manual
(gpg)~/code/python-gnupg $ gpg2 --fix-trustdb
gpg: You may try to re-create the trustdb using the commands:
gpg: cd ~/.gnupg
gpg: gpg2 --export-ownertrust > otrust.tmp
gpg: rm trustdb.gpg
gpg: gpg2 --import-ownertrust < otrust.tmp
gpg: If that does not work, please consult the manual
Brilliant piece of software engineering right there.
:param string trustdb: The path to the trustdb.gpg file. If not given,
defaults to 'trustdb.gpg' in the current GnuPG homedir.
:param str trustdb: The path to the trustdb.gpg file. If not given,
defaults to :file:`trustdb.gpg` in the current GnuPG
homedir.
"""
if trustdb is None:
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')

View File

@ -17,10 +17,7 @@
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
'''util.py
----------
Extra utilities for python-gnupg.
'''
'''Extra utilities for python-gnupg.'''
from __future__ import absolute_import
from datetime import datetime
@ -31,6 +28,7 @@ from time import mktime
import codecs
import encodings
import os
import psutil
import threading
import random
import re
@ -150,7 +148,7 @@ def _copy_data(instream, outstream):
break
except IOError as ioe:
# Can get 'broken pipe' errors even when all data was sent
if 'Broken pipe' in ioe.message:
if 'Broken pipe' in str(ioe):
log.error('Error sending data: Broken pipe')
else:
log.exception(ioe)
@ -254,7 +252,8 @@ def _find_binary(binary=None):
our process real uid has exec permissions.
:param str binary: The path to the GnuPG binary.
:raises: :exc:RuntimeError if it appears that GnuPG is not installed.
:raises: :exc:`~exceptions.RuntimeError` if it appears that GnuPG is not
installed.
:rtype: str
:returns: The absolute path to the GnuPG binary to use, if no exceptions
occur.
@ -272,6 +271,8 @@ def _find_binary(binary=None):
except IndexError as ie:
log.info("Could not determine absolute path of binary: '%s'"
% binary)
elif os.access(binary, os.X_OK):
found = binary
if found is None:
try: found = _which('gpg')[0]
except IndexError as ie:
@ -287,7 +288,7 @@ def _find_binary(binary=None):
assert not os.path.islink(found), "Path to gpg binary is symlink"
assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
except (AssertionError, AttributeError) as ae:
log.error(ae.message)
log.error(str(ae))
else:
return found
@ -303,22 +304,31 @@ def _has_readwrite(path):
"""
return os.access(path, os.R_OK ^ os.W_OK)
def _is_file(input):
def _is_file(filename):
"""Check that the size of the thing which is supposed to be a filename has
size greater than zero, without following symbolic links or using
:func:os.path.isfile.
:param input: An object to check.
:param filename: An object to check.
:rtype: bool
:returns: True if :param:input is file-like, False otherwise.
:returns: True if **filename** is file-like, False otherwise.
"""
try:
assert os.lstat(input).st_size > 0, "not a file: %s" % input
except (AssertionError, TypeError, IOError, OSError) as err:
log.error(err.message, exc_info=1)
return False
statinfo = os.lstat(filename)
log.debug("lstat(%r) with type=%s gave us %r"
% (repr(filename), type(filename), repr(statinfo)))
if not (statinfo.st_size > 0):
raise ValueError("'%s' appears to be an empty file!" % filename)
except OSError as oserr:
log.error(oserr)
if filename == '-':
log.debug("Got '-' for filename, assuming sys.stdin...")
return True
except (ValueError, TypeError, IOError) as err:
log.error(err)
else:
return True
return False
def _is_stream(input):
"""Check that the input is a byte stream.
@ -395,7 +405,7 @@ def _make_passphrase(length=None, save=False, file=None):
passphrase = _make_random_string(length)
if save:
ruid, euid, suid = os.getresuid()
ruid, euid, suid = psutil.Process(os.getpid()).uids
gid = os.getgid()
now = mktime(localtime())
@ -518,7 +528,7 @@ def _which(executable, flags=os.X_OK):
def _write_passphrase(stream, passphrase, encoding):
"""Write the passphrase from memory to the GnuPG process' stdin.
:type stream: file, :class:BytesIO, or :class:StringIO
:type stream: file, :class:`~io.BytesIO`, or :class:`~io.StringIO`
:param stream: The input file descriptor to write the password to.
:param str passphrase: The passphrase for the secret key material.
:param str encoding: The data encoding expected by GnuPG. Usually, this
@ -531,39 +541,40 @@ def _write_passphrase(stream, passphrase, encoding):
class InheritableProperty(object):
"""Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
"""Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
return self.fget(obj)
else:
return getattr(obj, self.fget.__name__)()
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
return self.fget(obj)
else:
return getattr(obj, self.fget.__name__)()
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
self.fset(obj, value)
else:
getattr(obj, self.fset.__name__)(value)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
self.fset(obj, value)
else:
getattr(obj, self.fset.__name__)(value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
self.fdel(obj)
else:
getattr(obj, self.fdel.__name__)()
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
self.fdel(obj)
else:
getattr(obj, self.fdel.__name__)()
class Storage(dict):
"""A dictionary where keys are stored as class attributes.
@ -595,7 +606,7 @@ class Storage(dict):
try:
del self[key]
except KeyError as k:
raise AttributeError(k.message)
raise AttributeError(k.args[0])
def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'

View File

@ -7,12 +7,12 @@
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
@ -21,10 +21,10 @@
===========
A Python interface to GnuPG.
.. moduleauthor:: Isis Agora Lovecruft <isis@patternsinthevoid.net>
.. moduleauthor:: Isis Lovecruft <isis@patternsinthevoid.net>
see also :attr:`gnupg.__authors__`
:license: see :attr:`gnupg.__license__`
:info: see <https://www.github.com/isislovecruft/python-gnupg>
.. license:: see :attr:`gnupg.__license__`
.. info:: https://github.com/isislovecruft/python-gnupg
"""
from __future__ import absolute_import
@ -41,7 +41,7 @@ try:
except ImportError:
from cStringIO import StringIO
## see PEP-328 http://docs.python.org/2.5/whatsnew/pep-328.html
#: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html
from . import _parsers
from . import _util
from . import _trust
@ -70,35 +70,36 @@ class GPG(GPGBase):
"""Initialize a GnuPG process wrapper.
:param str binary: Name for GnuPG binary executable. If the absolute
path is not given, the evironment variable $PATH is
searched for the executable and checked that the
real uid/gid of the user has sufficient permissions.
path is not given, the environment variable
``$PATH`` is searched for the executable and
checked that the real uid/gid of the user has
sufficient permissions.
:param str homedir: Full pathname to directory containing the public
and private keyrings. Default is whatever GnuPG
defaults to.
:param str,int,bool verbose: String or numeric value to pass to gpg's
``--debug-level`` option. See the gpg man
page for the list of valid options. If
False, debug output is not generated by
the gpg binary. If True, defaults to
``--debug-level basic.``
:type verbose: :obj:`str` or :obj:`int` or :obj:`bool`
:param verbose: String or numeric value to pass to GnuPG's
``--debug-level`` option. See the GnuPG man page for
the list of valid options. If False, debug output is
not generated by the GnuPG binary. If True, defaults
to ``--debug-level basic.``
:param str keyring: Name of keyring file containing public key data, if
unspecified, defaults to 'pubring.gpg' in the
``homedir`` directory.
:param str keyring: Name of keyring file containing public key data.
If unspecified, defaults to :file:`pubring.gpg` in
the **homedir** directory.
:param str secring: Name of alternative secret keyring file to use. If
left unspecified, this will default to using
'secring.gpg' in the :param:homedir directory, and
create that file if it does not exist.
:file:`secring.gpg` in the **homedir** directory,
and create that file if it does not exist.
:param list options: A list of additional options to pass to the GPG
:param list options: A list of additional options to pass to the GnuPG
binary.
:raises: :exc:`RuntimeError` with explanation message if there is a
problem invoking gpg.
:raises: A :exc:`~exceptions.RuntimeError` with explanation message
if there is a problem invoking GnuPG.
Example:
@ -227,7 +228,7 @@ class GPG(GPGBase):
In simpler terms: this function isn't for signing your friends' keys,
it's for something like signing an email.
:type data: str or file
:type data: :obj:`str` or :obj:`file`
:param data: A string or file stream to sign.
:param str default_key: The key to sign with.
:param str passphrase: The passphrase to pipe to stdin.
@ -236,10 +237,10 @@ class GPG(GPGBase):
:param bool binary: If True, do not ascii armour the output.
:param str digest_algo: The hash digest to use. Again, to see which
hashes your GnuPG is capable of using, do:
``$ gpg --with-colons --list-config digestname``.
:command:`$ gpg --with-colons --list-config digestname`.
The default, if unspecified, is ``'SHA512'``.
"""
if 'default_key' in kwargs.items():
if 'default_key' in kwargs:
log.info("Signing message '%r' with keyid: %s"
% (data, kwargs['default_key']))
else:
@ -306,15 +307,19 @@ class GPG(GPGBase):
return result
log.debug('verify_file(): Handling detached verification')
sig_fh = None
data_fh = None
try:
sig_fh = open(sig_file)
args = ["--verify %s - " % sig_fh.name]
sig_fh = open(sig_file, 'rb')
data_fh = open(file, 'rb')
args = ["--verify %s -" % sig_fh.name]
proc = self._open_subprocess(args)
writer = _util._threaded_copy_data(file, proc.stdin)
self._collect_output(proc, result, stdin=proc.stdin)
writer = _util._threaded_copy_data(data_fh, proc.stdin)
self._collect_output(proc, result, writer, stdin=proc.stdin)
finally:
if sig_fh and not sig_fh.closed:
sig_fh.close()
if data_fh and not data_fh.closed:
data_fh.close()
return result
def import_keys(self, key_data):
@ -373,7 +378,7 @@ class GPG(GPGBase):
:param str keyids: Each ``keyids`` argument should be a string
containing a keyid to request.
:param str keyserver: The keyserver to request the ``keyids`` from;
defaults to :property:`gnupg.GPG.keyserver`.
defaults to `gnupg.GPG.keyserver`.
"""
if keyids:
keys = ' '.join([key for key in keyids])
@ -384,33 +389,33 @@ class GPG(GPGBase):
def delete_keys(self, fingerprints, secret=False, subkeys=False):
"""Delete a key, or list of keys, from the current keyring.
The keys must be refered to by their full fingerprint for GnuPG to
The keys must be referred to by their full fingerprints for GnuPG to
delete them. If ``secret=True``, the corresponding secret keyring will
be deleted from :attr:`GPG.secring`.
:type fingerprints: str or list or tuple
be deleted from :obj:`.secring`.
:type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple`
:param fingerprints: A string, or a list/tuple of strings,
representing the fingerprint(s) for the key(s) to delete.
representing the fingerprint(s) for the key(s)
to delete.
:param bool secret: If True, delete the corresponding secret key(s)
also. (default: False)
also. (default: False)
:param bool subkeys: If True, delete the secret subkey first, then the
public key. (default: False) Same as:
``$ gpg --delete-secret-and-public-key 0x12345678``
public key. (default: False) Same as:
:command:`$gpg --delete-secret-and-public-key 0x12345678`.
"""
which='keys'
which = 'keys'
if secret:
which='secret-keys'
which = 'secret-keys'
if subkeys:
which='secret-and-public-keys'
which = 'secret-and-public-keys'
if _is_list_or_tuple(fingerprints):
fingerprints = ' '.join(fingerprints)
args = ['--batch']
args.append("--delete-{} {}".format(which, fingerprints))
args.append("--delete-{0} {1}".format(which, fingerprints))
result = self._result_map['delete'](self)
p = self._open_subprocess(args)
@ -425,23 +430,23 @@ class GPG(GPGBase):
:param bool secret: If True, export only the secret key.
:param bool subkeys: If True, export the secret subkeys.
"""
which=''
which = ''
if subkeys:
which='-secret-subkeys'
which = '-secret-subkeys'
elif secret:
which='-secret-keys'
which = '-secret-keys'
if _is_list_or_tuple(keyids):
keyids = ' '.join(['%s' % k for k in keyids])
args = ["--armor"]
args.append("--export{} {}".format(which, keyids))
args.append("--export{0} {1}".format(which, keyids))
p = self._open_subprocess(args)
## gpg --export produces no status-fd output; stdout will be empty in
## case of failure
#stdout, stderr = p.communicate()
result = self._result_map['delete'](self) # any result will do
result = self._result_map['delete'](self) # any result will do
self._collect_output(p, result, stdin=p.stdin)
log.debug('Exported:%s%r' % (os.linesep, result.data))
return result.data.decode(self._encoding, self._decode_errors)
@ -467,9 +472,9 @@ class GPG(GPGBase):
>>> assert print2 in pubkeys.fingerprints
"""
which='public-keys'
which = 'public-keys'
if secret:
which='secret-keys'
which = 'secret-keys'
args = "--list-%s --fixed-list-mode --fingerprint " % (which,)
args += "--with-colons --list-options no-show-photos"
args = [args]
@ -547,7 +552,7 @@ class GPG(GPGBase):
:param dict input: A dictionary of parameters and values for the new
key.
:returns: The result mapping with details of the new key, which is a
:class:`parsers.GenKey <GenKey>` object.
:class:`GenKey <gnupg._parsers.GenKey>` object.
"""
args = ["--gen-key --batch"]
key = self._result_map['generate'](self)
@ -557,20 +562,22 @@ class GPG(GPGBase):
fpr = str(key.fingerprint)
if len(fpr) == 20:
if self.temp_keyring or self.temp_secring:
if not os.path.exists(self._keys_dir):
os.makedirs(self._keys_dir)
prefix = os.path.join(self._keys_dir, fpr)
for d in map(lambda x: os.path.dirname(x),
[self.temp_keyring, self.temp_secring]):
if not os.path.exists(d):
os.makedirs(d)
if self.temp_keyring:
if os.path.isfile(self.temp_keyring):
prefix = os.path.join(self.temp_keyring, fpr)
try: os.rename(self.temp_keyring, prefix+".pubring")
except OSError as ose: log.error(ose.message)
except OSError as ose: log.error(str(ose))
if self.temp_secring:
if os.path.isfile(self.temp_secring):
prefix = os.path.join(self.temp_secring, fpr)
try: os.rename(self.temp_secring, prefix+".secring")
except OSError as ose: log.error(ose.message)
except OSError as ose: log.error(str(ose))
log.info("Key created. Fingerprint: %s" % fpr)
key.keyring = self.temp_keyring
@ -582,11 +589,11 @@ class GPG(GPGBase):
def gen_key_input(self, separate_keyring=False, save_batchfile=False,
testing=False, **kwargs):
"""Generate a batch file for input to :meth:`GPG.gen_key()`.
"""Generate a batch file for input to :meth:`~gnupg.GPG.gen_key`.
The GnuPG batch file key generation feature allows unattended key
generation by creating a file with special syntax and then providing it
to: ``gpg --gen-key --batch``. Batch files look like this:
to: :command:`gpg --gen-key --batch`. Batch files look like this:
| Name-Real: Alice
| Name-Email: alice@inter.net
@ -645,9 +652,10 @@ class GPG(GPGBase):
:param bool separate_keyring: Specify for the new key to be written to
a separate pubring.gpg and secring.gpg. If True,
:meth:`GPG.gen_key` will automatically rename the separate keyring
and secring to whatever the fingerprint of the generated key ends
up being, suffixed with '.pubring' and '.secring' respectively.
:meth:`~gnupg.GPG.gen_key` will automatically rename the separate
keyring and secring to whatever the fingerprint of the generated
key ends up being, suffixed with '.pubring' and '.secring'
respectively.
:param bool save_batchfile: Save a copy of the generated batch file to
disk in a file named <name_real>.batch, where <name_real> is the
@ -661,11 +669,12 @@ class GPG(GPGBase):
:param str name_real: The name field of the UID in the generated key.
:param str name_comment: The comment in the UID of the generated key.
:param str name_email: The email in the UID of the generated key.
(default: $USER@$(hostname) ) Remember to use UTF-8 encoding for
the entirety of the UID. At least one of ``name_real``,
``name_comment``, or ``name_email`` must be provided, or else no
user ID is created.
(default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8
encoding for the entirety of the UID. At least one of
``name_real``, ``name_comment``, or ``name_email`` must be
provided, or else no user ID is created.
:param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
(default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts
@ -704,7 +713,7 @@ class GPG(GPGBase):
:param str subkey_usage: Key usage for a subkey; similar to
``key_usage``.
:type expire_date: int or str
:type expire_date: :obj:`int` or :obj:`str`
:param expire_date: Can be specified as an iso-date or as
<int>[d|w|m|y] Set the expiration date for the key (and the
subkey). It may either be entered in ISO date format (2000-08-15)
@ -726,17 +735,17 @@ class GPG(GPGBase):
:param str passphrase: The passphrase for the new key. The default is
to not use any passphrase. Note that GnuPG>=2.1.x will not allow
you to specify a passphrase for batch key generation -- GnuPG will
ignore the ``passphrase`` parameter, stop, and ask the user for
ignore the **passphrase** parameter, stop, and ask the user for
the new passphrase. However, we can put the command
'%no-protection' into the batch key generation file to allow a
``%no-protection`` into the batch key generation file to allow a
passwordless key to be created, which can then have its passphrase
set later with '--edit-key'.
set later with ``--edit-key``.
:param str preferences: Set the cipher, hash, and compression
preference values for this key. This expects the same type of
string as the sub-command setpref in the --edit-key menu.
:param str revoker: Should be given as 'algo:fpr' [case sensitive].
:param str revoker: Should be given as 'algo:fpr' (case sensitive).
Add a designated revoker to the generated key. Algo is the public
key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
fpr is the fingerprint of the designated revoker. The optional
@ -747,18 +756,18 @@ class GPG(GPGBase):
preferred keyserver URL for the key.
:param str handle: This is an optional parameter only used with the
status lines KEY_CREATED and KEY_NOT_CREATED. string may be up to
100 characters and should not contain spaces. It is useful for
batch key generation to associate a key parameter block with a
status line.
status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may
be up to 100 characters and should not contain spaces. It is
useful for batch key generation to associate a key parameter block
with a status line.
:rtype: str
:returns: A suitable input string for the :meth:`GPG.gen_key` method,
the latter of which will create the new keypair.
see
http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
for more details.
See `this GnuPG Manual section`__ for more details.
__ http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
"""
#: A boolean for determining whether to set subkey_type to 'default'
default_type = False
@ -931,20 +940,21 @@ generate keys. Please see
>>> decrypted
'The crow flies at midnight.'
:param str cipher_algo: The cipher algorithm to use. To see available
algorithms with your version of GnuPG, do:
``$ gpg --with-colons --list-config ciphername``.
:command:`$ gpg --with-colons --list-config ciphername`.
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
:param str digest_algo: The hash digest to use. Again, to see which
hashes your GnuPG is capable of using, do:
``$ gpg --with-colons --list-config digestname``.
:command:`$ gpg --with-colons --list-config digestname`.
The default, if unspecified, is ``'SHA512'``.
:param str compress_algo: The compression algorithm to use. Can be one
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
See also: :meth:`GPGBase._encrypt`
.. seealso:: :meth:`._encrypt`
"""
stream = _make_binary_stream(data, self._encoding)
result = self._encrypt(stream, recipients, **kwargs)

124
gnupg/test/test_gnupg.py 100644 → 100755
View File

@ -47,8 +47,8 @@ import tempfile
## these dependencies require Python>=2.6 in order to have proper SSL support.
##
## Use unittest2 if we're on Python2.6 or less:
if sys.version_info.major == 2 and sys.version_info.minor <= 6:
unittest = __import__(unittest2)
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
import unittest2 as unittest
else:
import unittest
@ -62,7 +62,7 @@ try:
import gnupg._parsers as _parsers
import gnupg._logger as _logger
except (ImportError, ValueError) as ierr:
raise SystemExit(ierr.message)
raise SystemExit(str(ierr))
log = _util.log
@ -173,7 +173,6 @@ class GPGTestCase(unittest.TestCase):
self.keyring = self.gpg.keyring
self.secring = self.gpg.secring
self.insecure_prng = False
self.gpg._keys_dir = os.path.join(_files, 'generated-keys')
def tearDown(self):
"""This is called once per self.test_* method after the test run."""
@ -326,15 +325,19 @@ class GPGTestCase(unittest.TestCase):
def test_copy_data_bytesio(self):
"""Test that _copy_data() is able to duplicate byte streams."""
message = "This is a BytesIO string."
message = b"This is a BytesIO string."
instream = io.BytesIO(message)
self.assertEqual(unicode(message), instream.getvalue())
self.assertEqual(message, instream.getvalue())
out_filename = 'test-copy-data-bytesio'
# Create the test file:
outfile = os.path.join(os.getcwdu(), out_filename)
outstream = open(outfile, 'w+')
try:
cwd = os.getcwdu()
except AttributeError:
cwd = os.getcwd() # not present in Python 3
outfile = os.path.join(cwd, out_filename)
outstream = open(outfile, 'wb+')
# _copy_data() will close both file descriptors
_util._copy_data(instream, outstream)
@ -523,7 +526,7 @@ class GPGTestCase(unittest.TestCase):
self.assertIsNotNone(key)
self.assertNotEquals(key, "")
self.assertGreater(len(str(key)), 0)
keyfile = os.path.join(self.gpg._keys_dir, 'test_key_3.pub')
keyfile = os.path.join(_files, 'test_key_3.pub')
log.debug("Storing downloaded key as %s" % keyfile)
with open(keyfile, 'w') as fh:
fh.write(str(key))
@ -673,44 +676,72 @@ class GPGTestCase(unittest.TestCase):
def test_signature_verification_detached(self):
"""Test that verification of a detached signature of a file works."""
key = self.generate_key("Paulo S.L.M. Barreto", "anub.is")
with open(os.path.join(_files, 'cypherpunk_manifesto'), 'rb') as cm:
sig = self.gpg.sign(cm, default_key=key.fingerprint,
passphrase='paulos.l.m.barreto',
detach=True, clearsign=False)
self.assertTrue(sig.data, "File signing should succeed")
sigfilename = os.path.join(_files, 'cypherpunk_manifesto.sig')
with open(sigfilename,'w') as sigfile:
sigfile.write(sig.data)
sigfile.seek(0)
datafn = os.path.join(_files, 'cypherpunk_manifesto')
sigfn = os.path.extsep.join([datafn, 'sig'])
verified = self.gpg.verify_file(cm, sigfilename)
datafd = open(datafn, 'rb')
sig = self.gpg.sign(datafd, default_key=key.fingerprint,
passphrase='paulos.l.m.barreto',
detach=True,
clearsign=False)
if key.fingerprint != verified.fingerprint:
log.warn("key fingerprint: %r", key.fingerprint)
log.warn("verified fingerprint: %r", verified.fingerprint)
self.assertEqual(key.fingerprint, verified.fingerprint)
self.assertTrue(sig.data, "File signing should succeed")
if os.path.isfile(sigfilename):
os.unlink(sigfilename)
sigfd = open(sigfn, 'wb')
sigfd.write(sig.data)
sigfd.flush()
datafd.seek(0)
sigfd.seek(0)
verified = self.gpg.verify_file(datafn, sigfn)
if key.fingerprint != verified.fingerprint:
log.warn("key fingerprint: %r", key.fingerprint)
log.warn("verified fingerprint: %r", verified.fingerprint)
self.assertEqual(key.fingerprint, verified.fingerprint)
if os.path.isfile(sigfn):
os.unlink(sigfn)
def test_signature_verification_detached_binary(self):
"""Test that detached signature verification in binary mode fails."""
key = self.generate_key("Adi Shamir", "rsa.com")
datafile = os.path.join(_files, 'cypherpunk_manifesto')
with open(datafile, 'rb') as cm:
sig = self.gpg.sign(cm, default_key=key.fingerprint,
passphrase='adishamir',
detach=True, binary=True, clearsign=False)
self.assertTrue(sig.data, "File signing should succeed")
with open(datafile+'.sig', 'w') as bs:
bs.write(sig.data)
bs.flush()
with self.assertRaises(UnicodeDecodeError):
print("SIG=%s" % sig)
with open(datafile+'.sig', 'rb') as fsig:
with open(datafile, 'rb') as fdata:
self.gpg.verify_file(fdata, fsig)
datafn = os.path.join(_files, 'cypherpunk_manifesto')
sigfn = os.path.extsep.join([datafn, 'sig'])
datafd = open(datafn, 'rb')
data = datafd.read()
datafd.close()
sig = self.gpg.sign(data, default_key=key.fingerprint,
passphrase='adishamir',
detach=True,
binary=True,
clearsign=False)
self.assertTrue(sig.data, "File signing should succeed")
sigfd = open(sigfn, 'wb')
sigfd.write(sig.data)
sigfd.flush()
sigfd.close()
self.assertTrue(sigfd.closed, "Sigfile '%s' should be closed" % sigfn)
with self.assertRaises(UnicodeDecodeError):
print("SIG=%s" % sig)
verifysig = open(sigfn, 'rb')
verification = self.gpg.verify_file(data, verifysig)
self.assertTrue(isinstance(verification, gnupg._parsers.Verify))
self.assertFalse(verification.valid)
if os.path.isfile(sigfn):
os.unlink(sigfn)
def test_deletion(self):
"""Test that key deletion works."""
@ -793,14 +824,14 @@ authentication."""
riggio_input = self.gpg.gen_key_input(separate_keyring=True, **riggio)
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
riggio = self.gpg.gen_key(riggio_input)
self.gpg.options = ['--keyring {}'.format(riggio.keyring)]
self.gpg.options = ['--keyring {0}'.format(riggio.keyring)]
riggio_key = self.gpg.export_keys(riggio.fingerprint)
self.gpg.import_keys(riggio_key)
sicari_input = self.gpg.gen_key_input(separate_keyring=True, **sicari)
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
sicari = self.gpg.gen_key(sicari_input)
self.gpg.options.append('--keyring {}'.format(sicari.keyring))
self.gpg.options.append('--keyring {0}'.format(sicari.keyring))
sicari_key = self.gpg.export_keys(sicari.fingerprint)
self.gpg.import_keys(sicari_key)
@ -898,15 +929,15 @@ 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."""
enc = self.gpg.encrypt(message, alice_pfpr, bob_pfpr)
encrypted = str(enc.data)
encrypted = str(enc)
log.debug("encryption_decryption_multi_recipient() Ciphertext = %s"
% encrypted)
self.assertNotEquals(message, encrypted)
dec_alice = self.gpg.decrypt(encrypted, passphrase="test")
self.assertEquals(message, str(dec_alice.data))
self.assertEquals(message, str(dec_alice))
dec_bob = self.gpg.decrypt(encrypted, passphrase="test")
self.assertEquals(message, str(dec_bob.data))
self.assertEquals(message, str(dec_bob))
def test_symmetric_encryption_and_decryption(self):
"""Test symmetric encryption and decryption"""
@ -916,7 +947,7 @@ know, maybe you shouldn't be doing it in the first place.
encrypted = str(self.gpg.encrypt(msg, passphrase='quiscustodiet',
symmetric=True, encrypt=False))
decrypt = self.gpg.decrypt(encrypted, passphrase='quiscustodiet')
decrypted = str(decrypt.data)
decrypted = str(decrypt)
log.info("Symmetrically encrypted data:\n%s" % encrypted)
log.info("Symmetrically decrypted data:\n%s" % decrypted)
@ -948,9 +979,8 @@ know, maybe you shouldn't be doing it in the first place.
with open(enc_outf) as enc2:
fdata = enc2.read()
ddata = str(self.gpg.decrypt(fdata, passphrase="overalls"))
ddata = self.gpg.decrypt(fdata, passphrase="overalls").data
data = data.encode(self.gpg._encoding)
if ddata != data:
log.debug("data was: %r" % data)
log.debug("new (from filehandle): %r" % fdata)

View File

@ -1,58 +1,3 @@
#
# python-gnupg/requirements.txt
# -----------------------------
# Pip requirements.txt file. This file is also parsed for distribute to use in
# setup.py.
#_____________________________________________________________________________
# This file is part of python-gnupg, a Python interface to GnuPG.
# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
# © 2013 Andrej B.
# © 2013 LEAP Encryption Access Project
# © 2008-2012 Vinay Sajip
# © 2005 Steve Traugott
# © 2004 A.M. Kuchling
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
#______________________________________________________________________________
#
# Force pip upgrade due to security vulnerabilities.
#
# This has actually has little to do with installing python-gnupg, since
# older versions of pip would install everything just fine. except that, in
# my opinion, using GnuPG for privacy is silly when the installation of
# python-gnupg with an older version of pip is trivially exploitable through
# a MITM attack. see https://github.com/pypa/pip/pull/791
#
# Also, note that SSL package delivery is *not* entirely fixed yet. See
# https://github.com/TheTorProject/ooni-backend/pull/1#discussion_r4084881
#
#pip>=1.3.1
#
# NOTE: setuptools is currently (as of 27 May 2013) being merged back into its
# parent project, distribute. By using the included distribute_setup.py
# script, we make sure that we have a recent version of setuptools/distribute,
# which is the *only* Python packaging framework compatible at this point with
# both Python>=2.4 and Python3.x.
#
# A new version of distribute is necessary due to the merging of setuptools
# back into its parent project, distribute. Also, the only way to package for
# both Python 2 and 3 is to use distribute.
#
#distribute>=0.6.45
#
# Sphinx is only necessary for building documentation, so it is added in
# setup.py under extras_require['docs'].
#
# If you want to build the documentation, uncomment this line:
#Sphinx>=1.1
#
# And, this one is actually used in the gnupg module code:
#
psutil>=0.5.1
# sha256: UI5KRMglOjhqD4bZyb1KG0y7L5TojUmhnBUTZTymbEU
psutil==1.2.1

View File

@ -1,7 +1,12 @@
[upload_docs]
upload-dir = docs/_build/html
show-response = true
verbose = true
[upload]
sign = True
identity = 0xa3adb67a2cdb8b35
[aliases]
upload_all = sdist bdist_egg bdist_wheel upload

View File

@ -23,7 +23,11 @@ from __future__ import absolute_import
from __future__ import print_function
import setuptools
import sys
import os
import versioneer
versioneer.versionfile_source = 'gnupg/_version.py'
versioneer.versionfile_build = 'gnupg/_version.py'
versioneer.tag_prefix = ''
@ -34,6 +38,49 @@ __contact__ = 'isis@patternsinthevoid.net'
__url__ = 'https://github.com/isislovecruft/python-gnupg'
def python26():
"""Returns True if we're running on Python2.6."""
if sys.version[:3] == "2.6":
return True
return False
def get_requirements():
"""Extract the list of requirements from our requirements.txt.
:rtype: 2-tuple
:returns: Two lists, the first is a list of requirements in the form of
pkgname==version. The second is a list of URIs or VCS checkout strings
which specify the dependency links for obtaining a copy of the
requirement.
"""
requirements_file = os.path.join(os.getcwd(), 'requirements.txt')
requirements = []
links=[]
try:
with open(requirements_file) as reqfile:
for line in reqfile.readlines():
line = line.strip()
if line.startswith('#'):
continue
elif line.startswith(
('https://', 'git://', 'hg://', 'svn://')):
links.append(line)
else:
requirements.append(line)
except (IOError, OSError) as error:
print(error)
if python26():
# Required to make `collections.OrderedDict` available on Python<=2.6
requirements.append('ordereddict==1.1#a0ed854ee442051b249bfad0f638bbec')
return requirements, links
requires, deplinks = get_requirements()
setuptools.setup(
name = "gnupg",
description="A Python wrapper for GnuPG",
@ -62,8 +109,10 @@ greater.
scripts=['versioneer.py'],
test_suite='gnupg.test.test_gnupg',
install_requires=['psutil>=0.5.1'],
extras_require={'docs': ["Sphinx>=1.1", "repoze.sphinx"]},
install_requires=requires,
dependency_links=deplinks,
extras_require={'docs': ["Sphinx>=1.1",
"sphinxcontrib-fulltoc==1.0"]},
platforms="Linux, BSD, OSX, Windows",
download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",