Merge branch 'release/1.2.5'
commit
3cdfbc2f9b
|
@ -83,3 +83,6 @@ gpg
|
|||
# setuptools/distribute files:
|
||||
PKG-INFO
|
||||
MANIFEST
|
||||
|
||||
# sphinx default build
|
||||
docs/_build
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXOPTS = -E -n
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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
15
docs/conf.py
15
docs/conf.py
|
@ -29,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']
|
||||
|
@ -42,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'
|
||||
|
@ -83,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.
|
||||
|
@ -95,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 ---------------------------------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
@ -86,10 +89,8 @@ def create_logger(level=logging.NOTSET):
|
|||
|
||||
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)
|
||||
|
|
188
gnupg/_meta.py
188
gnupg/_meta.py
|
@ -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
|
||||
|
@ -28,6 +26,7 @@ from __future__ import absolute_import
|
|||
import atexit
|
||||
import codecs
|
||||
import encodings
|
||||
import exceptions
|
||||
## For AOS, the locale module will need to point to a wrapper around the
|
||||
## java.util.Locale class.
|
||||
## See https://code.patternsinthevoid.net/?p=android-locale-hack.git
|
||||
|
@ -52,6 +51,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,7 +72,7 @@ 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,
|
||||
|
@ -87,8 +90,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 +111,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'
|
||||
|
@ -154,15 +183,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 = []
|
||||
|
@ -222,7 +250,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 +298,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.
|
||||
|
@ -324,8 +352,8 @@ class GPGBase(object):
|
|||
: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'"
|
||||
|
@ -365,17 +393,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')
|
||||
|
@ -407,10 +436,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 +519,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 +566,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,7 +635,7 @@ 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
|
||||
|
@ -631,9 +662,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:
|
||||
|
@ -681,46 +713,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 +783,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 = []
|
||||
|
||||
|
|
|
@ -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:
|
||||
import collections
|
||||
except ImportError:
|
||||
import ordereddict as collections
|
||||
|
||||
import re
|
||||
|
||||
from . import _util
|
||||
|
@ -51,7 +54,7 @@ def _check_keyserver(location):
|
|||
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
|
||||
: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")
|
||||
|
@ -332,8 +343,9 @@ def _sanitise(*args):
|
|||
|
||||
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
|
||||
'--decrypt-files', '--import', '--verify']:
|
||||
if _util._is_file(val) or \
|
||||
(flag == '--verify' and val == '-'):
|
||||
if ( (_util._is_file(val))
|
||||
or
|
||||
((flag == '--verify') and (val == '-')) ):
|
||||
checked += (val + " ")
|
||||
else:
|
||||
log.debug("%s not file: %s" % (flag, val))
|
||||
|
@ -574,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``
|
||||
"""
|
||||
|
@ -791,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
|
||||
|
@ -803,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):
|
||||
|
@ -827,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
|
||||
|
@ -864,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"
|
||||
|
@ -909,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",
|
||||
|
@ -1041,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
|
||||
|
@ -1187,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
|
||||
|
@ -1282,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",
|
||||
|
@ -1349,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)
|
||||
|
|
|
@ -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')
|
||||
|
@ -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)∃!isisⒶwintermute:(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')
|
||||
|
|
|
@ -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
|
||||
|
@ -30,6 +27,7 @@ from time import mktime
|
|||
|
||||
import codecs
|
||||
import encodings
|
||||
import exceptions
|
||||
import os
|
||||
import psutil
|
||||
import threading
|
||||
|
@ -255,7 +253,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.
|
||||
|
@ -304,22 +303,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.
|
||||
|
@ -519,7 +527,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
|
||||
|
|
151
gnupg/gnupg.py
151
gnupg/gnupg.py
|
@ -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,16 +21,17 @@
|
|||
===========
|
||||
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
|
||||
from codecs import open as open
|
||||
|
||||
import encodings
|
||||
import exceptions
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
|
@ -41,7 +42,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 +71,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 +229,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,7 +238,7 @@ 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():
|
||||
|
@ -306,15 +308,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)
|
||||
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 +379,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,27 +390,27 @@ 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)
|
||||
|
@ -425,11 +431,11 @@ 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])
|
||||
|
@ -441,7 +447,7 @@ class GPG(GPGBase):
|
|||
## 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 +473,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 +553,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)
|
||||
|
@ -584,11 +590,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
|
||||
|
@ -647,9 +653,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
|
||||
|
@ -663,11 +670,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
|
||||
|
@ -706,7 +714,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)
|
||||
|
@ -728,17 +736,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
|
||||
|
@ -749,18 +757,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
|
||||
|
@ -933,20 +941,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)
|
||||
|
|
|
@ -672,44 +672,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, 'w')
|
||||
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."""
|
||||
|
|
|
@ -7,3 +7,6 @@ verbose = true
|
|||
sign = True
|
||||
identity = 0xa3adb67a2cdb8b35
|
||||
|
||||
[aliases]
|
||||
upload_all = sdist bdist_egg bdist_wheel upload
|
||||
|
||||
|
|
19
setup.py
19
setup.py
|
@ -23,6 +23,7 @@ from __future__ import absolute_import
|
|||
from __future__ import print_function
|
||||
|
||||
import setuptools
|
||||
import sys
|
||||
import os
|
||||
import versioneer
|
||||
|
||||
|
@ -37,6 +38,12 @@ __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.
|
||||
|
||||
|
@ -64,15 +71,14 @@ def get_requirements():
|
|||
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()
|
||||
print('Found requirements:')
|
||||
[print('\t%s' % name) for name in requires]
|
||||
|
||||
print('Found dependency links:')
|
||||
[print('\t%s' % uri) for uri in deplinks]
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
|
@ -105,7 +111,8 @@ greater.
|
|||
|
||||
install_requires=requires,
|
||||
dependency_links=deplinks,
|
||||
extras_require={'docs': ["Sphinx>=1.1", "repoze.sphinx"]},
|
||||
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",
|
||||
|
|
Loading…
Reference in New Issue