Merge branch 'master' of github.com:isislovecruft/python-gnupg
commit
ff96904233
|
@ -83,3 +83,9 @@ gpg
|
||||||
# setuptools/distribute files:
|
# setuptools/distribute files:
|
||||||
PKG-INFO
|
PKG-INFO
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
|
# sphinx default build
|
||||||
|
docs/_build
|
||||||
|
|
||||||
|
# ignore keys which have been generated example scripts:
|
||||||
|
8192-bit-key/*
|
||||||
|
|
23
Makefile
23
Makefile
|
@ -2,6 +2,11 @@ SHELL=/bin/sh
|
||||||
TESTDIR=./gnupg/test
|
TESTDIR=./gnupg/test
|
||||||
TESTHANDLE=$(TESTDIR)/test_gnupg.py
|
TESTHANDLE=$(TESTDIR)/test_gnupg.py
|
||||||
FILES=$(SHELL find ./gnupg/ -name "*.py" -printf "%p,")
|
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
|
.PHONY=all
|
||||||
all: uninstall install test
|
all: uninstall install test
|
||||||
|
@ -73,11 +78,15 @@ py3k-uninstall: uninstall
|
||||||
reinstall: uninstall install
|
reinstall: uninstall install
|
||||||
py3k-reinstall: py3k-uninstall py3k-install
|
py3k-reinstall: py3k-uninstall py3k-install
|
||||||
|
|
||||||
cleandocs:
|
docs-clean:
|
||||||
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" \
|
-rm -rf $(DOC_BUILD_DIR)
|
||||||
-o docs gnupg/ tests/
|
|
||||||
|
|
||||||
docs:
|
docs-completely-new:
|
||||||
cd docs && \
|
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" -o $(DOC_DIR) gnupg/ tests/
|
||||||
make clean && \
|
|
||||||
make html
|
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)"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS = -E -n
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
PAPER =
|
PAPER =
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
|
@ -25,6 +25,16 @@ div.header-wrapper {
|
||||||
border-bottom: 3px solid #2e3436;
|
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 */
|
/* Default body styles */
|
||||||
a {
|
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
16
docs/conf.py
16
docs/conf.py
|
@ -12,6 +12,7 @@
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
import psutil
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
@ -28,11 +29,16 @@ autoclass_content = 'both'
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# 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
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# 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.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_static']
|
templates_path = ['_static']
|
||||||
|
@ -41,7 +47,7 @@ templates_path = ['_static']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8-sig'
|
source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
@ -82,7 +88,7 @@ add_function_parentheses = True
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
#add_module_names = True
|
add_module_names = False
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
|
@ -94,6 +100,8 @@ pygments_style = 'monokai'
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
#modindex_common_prefix = []
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ do:
|
||||||
:private-members:
|
:private-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. _meta:
|
||||||
|
|
||||||
meta module
|
meta module
|
||||||
-----------
|
-----------
|
||||||
|
@ -28,10 +29,13 @@ doing some serious hacking.
|
||||||
|
|
||||||
.. automodule:: gnupg._meta
|
.. automodule:: gnupg._meta
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
|
||||||
:private-members:
|
:private-members:
|
||||||
|
:special-members:
|
||||||
|
:exclude-members: _agent_proc, __module__, __dict__, _decode_errors, init,
|
||||||
|
__weakref__, _result_map, __metaclass__
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. _parsers:
|
||||||
|
|
||||||
parsers module
|
parsers module
|
||||||
--------------
|
--------------
|
||||||
|
@ -39,10 +43,11 @@ parsers module
|
||||||
These are classes for parsing both user inputs and status file descriptor
|
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
|
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
|
GnuPG process is doing and retrieve information about its operations, which
|
||||||
are stored in corresponding classes in :attr:`gnupg.GPG._result_dict`. Some
|
are stored in corresponding classes in
|
||||||
status flags aren't handled yet -- infomation on *all* of the flags (well, at
|
:attr:`~gnupg._meta.GPGBase._result_map`. Some status flags aren't handled yet
|
||||||
least the documented ones…) can be found in the docs/DETAILS file in GnuPG's
|
-- information on *all* of the flags (well, at least the documented ones…) can
|
||||||
source_, which has been included here_ as well.
|
be found in the :file:`docs/DETAILS` file in GnuPG's source_, which has been
|
||||||
|
included here_ as well.
|
||||||
|
|
||||||
|
|
||||||
.. automodule:: gnupg._parsers
|
.. automodule:: gnupg._parsers
|
||||||
|
@ -52,6 +57,8 @@ source_, which has been included here_ as well.
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
.. _util:
|
||||||
|
|
||||||
util module
|
util module
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -75,17 +82,16 @@ by Steve Traugott, which in turn is a modification of the pycrypto GnuPG
|
||||||
interface written by A.M. Kuchling.
|
interface written by A.M. Kuchling.
|
||||||
|
|
||||||
This version is patched to sanitize untrusted inputs, due to the necessity of
|
This version is patched to sanitize untrusted inputs, due to the necessity of
|
||||||
executing :class:`subprocess.Popen([...], shell=True)` in order to communicate
|
executing ``subprocess.Popen([...], shell=True)`` in order to communicate with
|
||||||
with GnuPG. Several speed improvements were also made based on code profiling,
|
GnuPG. Several speed improvements were also made based on code profiling, and
|
||||||
and the API has been cleaned up to support an easier, more Pythonic,
|
the API has been cleaned up to support an easier, more Pythonic, interaction.
|
||||||
interaction.
|
|
||||||
|
|
||||||
|
|
||||||
Previous Authors' Documentation
|
Previous Authors' Documentation
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Steve Traugott's documentation:
|
Steve Traugott's documentation:
|
||||||
|
|
|
||||||
| Portions of this module are derived from A.M. Kuchling's well-designed
|
| 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
|
| GPG.py, using Richard Jones' updated version 1.3, which can be found in
|
||||||
| the pycrypto CVS repository on Sourceforge:
|
| the pycrypto CVS repository on Sourceforge:
|
||||||
|
@ -107,7 +113,7 @@ Steve Traugott's documentation:
|
||||||
|
|
||||||
|
|
||||||
Vinay Sajip's documentation:
|
Vinay Sajip's documentation:
|
||||||
|
|
|
||||||
| This version of the module has been modified from Steve Traugott's version
|
| 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
|
| (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
|
| Vinay Sajip to make use of the subprocess module (Steve's version uses
|
||||||
|
@ -122,4 +128,4 @@ Vinay Sajip's documentation:
|
||||||
.. _GnuPG: http://gnupg.org
|
.. _GnuPG: http://gnupg.org
|
||||||
.. _python-gnupg: https://code.google.com/p/python-gnupg/
|
.. _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
|
.. _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
|
Source, license, & bug reports
|
||||||
==============================
|
==============================
|
||||||
The source code which was used to generate this documentation is accessible by
|
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
|
be found in this github repository_. The **master** branch always reflects the
|
||||||
latest release, all releases are tagged with signed, annotated git tags, and
|
latest release, all releases are tagged with signed, annotated git tags, and
|
||||||
the **develop** branch represents the state of the next release.
|
the **develop** branch represents the state of the next release.
|
||||||
|
|
|
@ -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)
|
|
@ -17,10 +17,7 @@
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
# 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 absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
@ -51,10 +48,15 @@ def status(self, message, *args, **kwargs):
|
||||||
def create_logger(level=logging.NOTSET):
|
def create_logger(level=logging.NOTSET):
|
||||||
"""Create a logger for python-gnupg at a specific message level.
|
"""Create a logger for python-gnupg at a specific message level.
|
||||||
|
|
||||||
:type level: int or str
|
:type level: :obj:`int` or :obj:`str`
|
||||||
:param level: A string or an integer for the lowest level to log.
|
:param level: A string or an integer for the lowest level to include in
|
||||||
Available levels:
|
logs.
|
||||||
|
|
||||||
|
**Available levels:**
|
||||||
|
|
||||||
|
==== ======== ========================================
|
||||||
int str description
|
int str description
|
||||||
|
==== ======== ========================================
|
||||||
0 NOTSET Disable all logging.
|
0 NOTSET Disable all logging.
|
||||||
9 GNUPG Log GnuPG's internal status messages.
|
9 GNUPG Log GnuPG's internal status messages.
|
||||||
10 DEBUG Log module level debuging messages.
|
10 DEBUG Log module level debuging messages.
|
||||||
|
@ -62,6 +64,7 @@ def create_logger(level=logging.NOTSET):
|
||||||
30 WARN Warning messages.
|
30 WARN Warning messages.
|
||||||
40 ERROR Error messages and tracebacks.
|
40 ERROR Error messages and tracebacks.
|
||||||
50 CRITICAL Unhandled exceptions and tracebacks.
|
50 CRITICAL Unhandled exceptions and tracebacks.
|
||||||
|
==== ======== ========================================
|
||||||
"""
|
"""
|
||||||
_test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test')
|
_test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test')
|
||||||
_now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
_now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||||
|
@ -75,21 +78,19 @@ def create_logger(level=logging.NOTSET):
|
||||||
if level > logging.NOTSET:
|
if level > logging.NOTSET:
|
||||||
logging.basicConfig(level=level, filename=_fn,
|
logging.basicConfig(level=level, filename=_fn,
|
||||||
filemode="a", format=_fmt)
|
filemode="a", format=_fmt)
|
||||||
logging.captureWarnings(True)
|
|
||||||
logging.logThreads = True
|
logging.logThreads = True
|
||||||
|
if hasattr(logging,'captureWarnings'):
|
||||||
|
logging.captureWarnings(True)
|
||||||
colouriser = _ansistrm.ColorizingStreamHandler
|
colouriser = _ansistrm.ColorizingStreamHandler
|
||||||
colouriser.level_map[9] = (None, 'blue', False)
|
colouriser.level_map[9] = (None, 'blue', False)
|
||||||
colouriser.level_map[10] = (None, 'cyan', False)
|
colouriser.level_map[10] = (None, 'cyan', False)
|
||||||
handler = colouriser(stream=sys.stderr)
|
handler = colouriser(sys.stderr)
|
||||||
handler.setLevel(level)
|
handler.setLevel(level)
|
||||||
|
|
||||||
formatr = logging.Formatter(_fmt)
|
formatr = logging.Formatter(_fmt)
|
||||||
handler.setFormatter(formatr)
|
handler.setFormatter(formatr)
|
||||||
print("Starting the logger...")
|
|
||||||
else:
|
else:
|
||||||
handler = NullHandler()
|
handler = NullHandler()
|
||||||
print("GnuPG logging disabled...")
|
|
||||||
|
|
||||||
log = logging.getLogger('gnupg')
|
log = logging.getLogger('gnupg')
|
||||||
log.addHandler(handler)
|
log.addHandler(handler)
|
||||||
|
|
207
gnupg/_meta.py
207
gnupg/_meta.py
|
@ -17,10 +17,8 @@
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
# 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
|
from __future__ import absolute_import
|
||||||
|
@ -52,6 +50,10 @@ class GPGMeta(type):
|
||||||
Detects running gpg-agent processes and the presence of a pinentry
|
Detects running gpg-agent processes and the presence of a pinentry
|
||||||
program, and disables pinentry so that python-gnupg can write the
|
program, and disables pinentry so that python-gnupg can write the
|
||||||
passphrase to the controlled GnuPG process without killing the agent.
|
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):
|
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`
|
If there is a matching gpg-agent process, set a :class:`psutil.Process`
|
||||||
instance containing the gpg-agent process' information to
|
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
|
:returns: True if there exists a gpg-agent process running under the
|
||||||
same effective user ID as that of this program. Otherwise,
|
same effective user ID as that of this program. Otherwise,
|
||||||
returns None.
|
returns None.
|
||||||
"""
|
"""
|
||||||
identity = os.getresuid()
|
identity = psutil.Process(os.getpid()).uids
|
||||||
for proc in psutil.process_iter():
|
for proc in psutil.process_iter():
|
||||||
if (proc.name == "gpg-agent") and proc.is_running:
|
if (proc.name == "gpg-agent") and proc.is_running:
|
||||||
log.debug("Found gpg-agent process with pid %d" % proc.pid)
|
log.debug("Found gpg-agent process with pid %d" % proc.pid)
|
||||||
|
@ -87,8 +89,13 @@ class GPGMeta(type):
|
||||||
|
|
||||||
|
|
||||||
class GPGBase(object):
|
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
|
__metaclass__ = GPGMeta
|
||||||
_decode_errors = 'strict'
|
_decode_errors = 'strict'
|
||||||
_result_map = { 'crypt': _parsers.Crypt,
|
_result_map = { 'crypt': _parsers.Crypt,
|
||||||
|
@ -103,7 +110,28 @@ class GPGBase(object):
|
||||||
def __init__(self, binary=None, home=None, keyring=None, secring=None,
|
def __init__(self, binary=None, home=None, keyring=None, secring=None,
|
||||||
use_agent=False, default_preference_list=None,
|
use_agent=False, default_preference_list=None,
|
||||||
verbose=False, options=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.binary = _util._find_binary(binary)
|
||||||
self.homedir = home if home else _util._conf
|
self.homedir = home if home else _util._conf
|
||||||
pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg'
|
pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg'
|
||||||
|
@ -125,7 +153,7 @@ class GPGBase(object):
|
||||||
self._filesystemencoding = encodings.normalize_encoding(
|
self._filesystemencoding = encodings.normalize_encoding(
|
||||||
sys.getfilesystemencoding().lower())
|
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')
|
self.__generated_keys = os.path.join(self.homedir, 'generated-keys')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -136,8 +164,8 @@ class GPGBase(object):
|
||||||
if self.options is not None:
|
if self.options is not None:
|
||||||
assert isinstance(self.options, str), "options not string"
|
assert isinstance(self.options, str), "options not string"
|
||||||
except (AssertionError, AttributeError) as ae:
|
except (AssertionError, AttributeError) as ae:
|
||||||
log.error("GPGBase.__init__(): %s" % ae.message)
|
log.error("GPGBase.__init__(): %s" % str(ae))
|
||||||
raise RuntimeError(ae.message)
|
raise RuntimeError(str(ae))
|
||||||
else:
|
else:
|
||||||
if verbose is True:
|
if verbose is True:
|
||||||
# The caller wants logging, but we need a valid --debug-level
|
# The caller wants logging, but we need a valid --debug-level
|
||||||
|
@ -154,15 +182,14 @@ class GPGBase(object):
|
||||||
self.__remove_path__('pinentry')
|
self.__remove_path__('pinentry')
|
||||||
|
|
||||||
def __remove_path__(self, prog=None, at_exit=True):
|
def __remove_path__(self, prog=None, at_exit=True):
|
||||||
"""Remove a the directories containing a program from the system's
|
"""Remove the directories containing a program from the system's
|
||||||
``$PATH``. If :attr:`GPG.binary` is in a directory being removed, it
|
``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it
|
||||||
is symlinked to './gpg'
|
is linked to :file:'./gpg' in the current directory.
|
||||||
|
|
||||||
:param str prog: The program to remove from ``$PATH``.
|
:param str prog: The program to remove from ``$PATH``.
|
||||||
|
|
||||||
:param bool at_exit: Add the program back into the ``$PATH`` when the
|
:param bool at_exit: Add the program back into the ``$PATH`` when the
|
||||||
Python interpreter exits, and delete any symlinks
|
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.
|
#: A list of ``$PATH`` entries which were removed to disable pinentry.
|
||||||
self._removed_path_entries = []
|
self._removed_path_entries = []
|
||||||
|
@ -173,7 +200,7 @@ class GPGBase(object):
|
||||||
try:
|
try:
|
||||||
program = _util._which(prog)[0]
|
program = _util._which(prog)[0]
|
||||||
except (OSError, IOError, IndexError) as err:
|
except (OSError, IOError, IndexError) as err:
|
||||||
log.err(err.message)
|
log.err(str(err))
|
||||||
log.err("Cannot find program '%s', not changing PATH." % prog)
|
log.err("Cannot find program '%s', not changing PATH." % prog)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -222,7 +249,7 @@ class GPGBase(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_path(environment, path):
|
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 str environment: The environment mapping to update.
|
||||||
:param list path: A list of strings to update the PATH with.
|
: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
|
Note that "original state" does not mean the default preference
|
||||||
list for whichever version of GnuPG is being used. It means the
|
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
|
Using BZIP2 is avoided due to not interacting well with some versions
|
||||||
of GnuPG>=2.0.0.
|
of GnuPG>=2.0.0.
|
||||||
|
@ -293,14 +320,14 @@ class GPGBase(object):
|
||||||
should contain the desired keyserver protocol
|
should contain the desired keyserver protocol
|
||||||
which is supported by the keyserver, for example,
|
which is supported by the keyserver, for example,
|
||||||
``'hkps://keys.mayfirst.org'``. The default
|
``'hkps://keys.mayfirst.org'``. The default
|
||||||
keyserver is ``'hkp://subkeys.pgp.net'``.
|
keyserver is ``'hkp://wwwkeys.pgp.net'``.
|
||||||
"""
|
"""
|
||||||
self._keyserver = location
|
self._keyserver = location
|
||||||
|
|
||||||
@keyserver.deleter
|
@keyserver.deleter
|
||||||
def keyserver(self):
|
def keyserver(self):
|
||||||
"""Reset the keyserver to the default setting."""
|
"""Reset the keyserver to the default setting."""
|
||||||
self._keyserver = 'hkp://subkeys.pgp.net'
|
self._keyserver = 'hkp://wwwkeys.pgp.net'
|
||||||
|
|
||||||
def _homedir_getter(self):
|
def _homedir_getter(self):
|
||||||
"""Get the directory currently being used as GnuPG's homedir.
|
"""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
|
created. Lastly, the ``direcory`` will be checked that the EUID has
|
||||||
read and write permissions for it.
|
read and write permissions for it.
|
||||||
|
|
||||||
:param str homedir: A relative or absolute path to the directory to use
|
:param str directory: A relative or absolute path to the directory to
|
||||||
for storing/accessing GnuPG's files, including
|
use for storing/accessing GnuPG's files, including
|
||||||
keyrings and the trustdb.
|
keyrings and the trustdb.
|
||||||
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
|
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
|
||||||
use.
|
directory to use.
|
||||||
"""
|
"""
|
||||||
if not directory:
|
if not directory:
|
||||||
log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'"
|
log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'"
|
||||||
|
@ -346,8 +373,8 @@ class GPGBase(object):
|
||||||
except AssertionError as ae:
|
except AssertionError as ae:
|
||||||
msg = ("Unable to set '%s' as GnuPG homedir" % directory)
|
msg = ("Unable to set '%s' as GnuPG homedir" % directory)
|
||||||
log.debug("GPGBase.homedir.setter(): %s" % msg)
|
log.debug("GPGBase.homedir.setter(): %s" % msg)
|
||||||
log.debug(ae.message)
|
log.debug(str(ae))
|
||||||
raise RuntimeError(ae.message)
|
raise RuntimeError(str(ae))
|
||||||
else:
|
else:
|
||||||
log.info("Setting homedir to '%s'" % hd)
|
log.info("Setting homedir to '%s'" % hd)
|
||||||
self._homedir = hd
|
self._homedir = hd
|
||||||
|
@ -365,17 +392,18 @@ class GPGBase(object):
|
||||||
def _generated_keys_setter(self, directory):
|
def _generated_keys_setter(self, directory):
|
||||||
"""Set the directory for storing generated keys.
|
"""Set the directory for storing generated keys.
|
||||||
|
|
||||||
If unspecified, use $GNUPGHOME/generated-keys. If specified, ensure
|
If unspecified, use
|
||||||
that the ``directory`` does not contain various shell escape
|
:meth:`~gnupg._meta.GPGBase.homedir`/generated-keys. If specified,
|
||||||
characters. If ``directory`` is not found, it will be automatically
|
ensure that the ``directory`` does not contain various shell escape
|
||||||
created. Lastly, the ``direcory`` will be checked that the EUID has
|
characters. If ``directory`` isn't found, it will be automatically
|
||||||
read and write permissions for it.
|
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
|
:param str directory: A relative or absolute path to the directory to
|
||||||
use for storing/accessing GnuPG's files, including keyrings and
|
use for storing/accessing GnuPG's files, including keyrings and
|
||||||
the trustdb.
|
the trustdb.
|
||||||
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
|
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
|
||||||
use.
|
directory to use.
|
||||||
"""
|
"""
|
||||||
if not directory:
|
if not directory:
|
||||||
directory = os.path.join(self.homedir, 'generated-keys')
|
directory = os.path.join(self.homedir, 'generated-keys')
|
||||||
|
@ -397,8 +425,8 @@ class GPGBase(object):
|
||||||
except AssertionError as ae:
|
except AssertionError as ae:
|
||||||
msg = ("Unable to set '%s' as generated keys dir" % directory)
|
msg = ("Unable to set '%s' as generated keys dir" % directory)
|
||||||
log.debug("GPGBase._generated_keys_setter(): %s" % msg)
|
log.debug("GPGBase._generated_keys_setter(): %s" % msg)
|
||||||
log.debug(ae.message)
|
log.debug(str(ae))
|
||||||
raise RuntimeError(ae.message)
|
raise RuntimeError(str(ae))
|
||||||
else:
|
else:
|
||||||
log.info("Setting homedir to '%s'" % hd)
|
log.info("Setting homedir to '%s'" % hd)
|
||||||
self.__generated_keys = hd
|
self.__generated_keys = hd
|
||||||
|
@ -407,10 +435,11 @@ class GPGBase(object):
|
||||||
_generated_keys_setter)
|
_generated_keys_setter)
|
||||||
|
|
||||||
def _make_args(self, args, passphrase=False):
|
def _make_args(self, args, passphrase=False):
|
||||||
"""Make a list of command line elements for GPG. The value of ``args``
|
"""Make a list of command line elements for GPG.
|
||||||
will be appended only if it passes the checks in
|
|
||||||
:func:`parsers._sanitise`. The ``passphrase`` argument needs to be True
|
The value of ``args`` will be appended only if it passes the checks in
|
||||||
if a passphrase will be sent to GPG, else False.
|
: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
|
:param list args: A list of strings of options and flags to pass to
|
||||||
``GPG.binary``. This is input safe, meaning that
|
``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
|
Calls methods on the response object for each valid token found, with
|
||||||
the arg being the remainder of the status line.
|
the arg being the remainder of the status line.
|
||||||
|
|
||||||
:param stream: A byte-stream, file handle, or :class:`subprocess.PIPE`
|
:param stream: A byte-stream, file handle, or a
|
||||||
to parse the for status codes from the GnuPG process.
|
:data:`subprocess.PIPE` for parsing the status codes
|
||||||
|
from the GnuPG process.
|
||||||
|
|
||||||
:param result: The result parser class from :mod:`_parsers` with which
|
:param result: The result parser class from :mod:`~gnupg._parsers` ―
|
||||||
to call ``handle_status`` and parse the output of
|
the ``handle_status()`` method of that class will be
|
||||||
``stream``.
|
called in order to parse the output of ``stream``.
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
while True:
|
while True:
|
||||||
|
@ -535,8 +565,8 @@ class GPGBase(object):
|
||||||
and stored as ``result.data``.
|
and stored as ``result.data``.
|
||||||
|
|
||||||
:param stream: An open file-like object to read() from.
|
:param stream: An open file-like object to read() from.
|
||||||
:param result: An instance of one of the result parsing classes from
|
:param result: An instance of one of the :ref:`result parsing classes
|
||||||
:attr:`GPGBase._result_mapping`.
|
<parsers>` from :const:`~gnupg._meta.GPGBase._result_map`.
|
||||||
"""
|
"""
|
||||||
chunks = []
|
chunks = []
|
||||||
log.debug("Reading data from stream %r..." % stream.__repr__())
|
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
|
:param str keyids: A space-delimited string containing the keyids to
|
||||||
request.
|
request.
|
||||||
:param str keyserver: The keyserver to request the ``keyids`` from;
|
:param str keyserver: The keyserver to request the ``keyids`` from;
|
||||||
defaults to :property:`gnupg.GPG.keyserver`.
|
defaults to `gnupg.GPG.keyserver`.
|
||||||
"""
|
"""
|
||||||
if not keyserver:
|
if not keyserver:
|
||||||
keyserver = self.keyserver
|
keyserver = self.keyserver
|
||||||
|
|
||||||
args = ['--keyserver {}'.format(keyserver),
|
args = ['--keyserver {0}'.format(keyserver),
|
||||||
'--recv-keys {}'.format(keyids)]
|
'--recv-keys {0}'.format(keyids)]
|
||||||
log.info('Requesting keys from %s: %s' % (keyserver, keyids))
|
log.info('Requesting keys from %s: %s' % (keyserver, keyids))
|
||||||
|
|
||||||
result = self._result_map['import'](self)
|
result = self._result_map['import'](self)
|
||||||
|
@ -632,8 +662,9 @@ class GPGBase(object):
|
||||||
:param bool binary: If True, do not ascii armour the output.
|
:param bool binary: If True, do not ascii armour the output.
|
||||||
:param str digest_algo: The hash digest to use. Again, to see which
|
:param str digest_algo: The hash digest to use. Again, to see which
|
||||||
hashes your GnuPG is capable of using, do:
|
hashes your GnuPG is capable of using, do:
|
||||||
``$ gpg --with-colons --list-config digestname``.
|
``$ gpg --with-colons --list-config
|
||||||
The default, if unspecified, is ``'SHA512'``.
|
digestname``. The default, if unspecified, is
|
||||||
|
``'SHA512'``.
|
||||||
"""
|
"""
|
||||||
log.debug("_sign_file():")
|
log.debug("_sign_file():")
|
||||||
if binary:
|
if binary:
|
||||||
|
@ -665,7 +696,7 @@ class GPGBase(object):
|
||||||
_util._write_passphrase(proc.stdin, passphrase, self._encoding)
|
_util._write_passphrase(proc.stdin, passphrase, self._encoding)
|
||||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
writer = _util._threaded_copy_data(file, proc.stdin)
|
||||||
except IOError as ioe:
|
except IOError as ioe:
|
||||||
log.exception("Error writing message: %s" % ioe.message)
|
log.exception("Error writing message: %s" % str(ioe))
|
||||||
writer = None
|
writer = None
|
||||||
self._collect_output(proc, result, writer, proc.stdin)
|
self._collect_output(proc, result, writer, proc.stdin)
|
||||||
return result
|
return result
|
||||||
|
@ -681,46 +712,55 @@ class GPGBase(object):
|
||||||
cipher_algo='AES256',
|
cipher_algo='AES256',
|
||||||
digest_algo='SHA512',
|
digest_algo='SHA512',
|
||||||
compress_algo='ZLIB'):
|
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 data: The file or bytestream to encrypt.
|
||||||
|
|
||||||
:param str recipients: The recipients to encrypt to. Recipients must
|
:param str recipients: The recipients to encrypt to. Recipients must
|
||||||
be specified keyID/fingerprint. Care should be taken in Python2.x
|
be specified keyID/fingerprint.
|
||||||
to make sure that the given fingerprint is in fact a string and
|
|
||||||
not a unicode object.
|
.. 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
|
: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,
|
:param str passphrase: If given, and **default_key** is also given,
|
||||||
use this passphrase to unlock the secret portion of the
|
use this passphrase to unlock the secret
|
||||||
``default_key`` to sign the encrypted ``data``. Otherwise, if
|
portion of the **default_key** to sign the
|
||||||
``default_key`` is not given, but ``symmetric=True``, then use
|
encrypted **data**. Otherwise, if
|
||||||
this passphrase as the passphrase for symmetric
|
**default_key** is not given, but **symmetric**
|
||||||
encryption. Signing and symmetric encryption should *not* be
|
is ``True``, then use this passphrase as the
|
||||||
combined when sending the ``data`` to other recipients, else the
|
passphrase for symmetric encryption. Signing
|
||||||
passphrase to the secret key would be shared with them.
|
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
|
: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
|
:param bool encrypt: If True, encrypt the **data** using the
|
||||||
``recipients`` public keys. (Default: True)
|
**recipients** public keys. (Default: True)
|
||||||
|
|
||||||
:param bool symmetric: If True, encrypt the ``data`` to ``recipients``
|
:param bool symmetric: If True, encrypt the **data** to **recipients**
|
||||||
using a symmetric key. See the ``passphrase`` parameter. Symmetric
|
using a symmetric key. See the **passphrase**
|
||||||
encryption and public key encryption can be used simultaneously,
|
parameter. Symmetric encryption and public key
|
||||||
and will result in a ciphertext which is decryptable with either
|
encryption can be used simultaneously, and will
|
||||||
the symmetric ``passphrase`` or one of the corresponding private
|
result in a ciphertext which is decryptable
|
||||||
keys.
|
with either the symmetric **passphrase** or one
|
||||||
|
of the corresponding private keys.
|
||||||
|
|
||||||
:param bool always_trust: If True, ignore trust warnings on recipient
|
:param bool always_trust: If True, ignore trust warnings on
|
||||||
keys. If False, display trust warnings. (default: True)
|
**recipients** keys. If False, display trust
|
||||||
|
warnings. (default: True)
|
||||||
|
|
||||||
:param str output: The output file to write to. If not specified, the
|
:param str output: The output file to write to. If not specified, the
|
||||||
encrypted output is returned, and thus should be stored as an
|
encrypted output is returned, and thus should be
|
||||||
object in Python. For example:
|
stored as an object in Python. For example:
|
||||||
|
|
||||||
|
|
||||||
>>> import shutil
|
>>> import shutil
|
||||||
>>> import gnupg
|
>>> import gnupg
|
||||||
|
@ -743,16 +783,19 @@ class GPGBase(object):
|
||||||
|
|
||||||
:param str cipher_algo: The cipher algorithm to use. To see available
|
:param str cipher_algo: The cipher algorithm to use. To see available
|
||||||
algorithms with your version of GnuPG, do:
|
algorithms with your version of GnuPG, do:
|
||||||
``$ gpg --with-colons --list-config ciphername``.
|
:command:`$ gpg --with-colons --list-config
|
||||||
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
|
ciphername`. The default **cipher_algo**, if
|
||||||
|
unspecified, is ``'AES256'``.
|
||||||
|
|
||||||
:param str digest_algo: The hash digest to use. Again, to see which
|
:param str digest_algo: The hash digest to use. Again, to see which
|
||||||
hashes your GnuPG is capable of using, do:
|
hashes your GnuPG is capable of using, do:
|
||||||
``$ gpg --with-colons --list-config digestname``.
|
:command:`$ gpg --with-colons --list-config
|
||||||
The default, if unspecified, is ``'SHA512'``.
|
digestname`. The default, if unspecified, is
|
||||||
|
``'SHA512'``.
|
||||||
|
|
||||||
:param str compress_algo: The compression algorithm to use. Can be one
|
: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 = []
|
args = []
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,18 @@
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
# 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 absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import collections
|
try:
|
||||||
|
from collections import OrderedDict
|
||||||
|
except ImportError:
|
||||||
|
from ordereddict import OrderedDict
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from . import _util
|
from . import _util
|
||||||
|
@ -50,8 +53,8 @@ def _check_keyserver(location):
|
||||||
:param str location: A string containing the default keyserver. This
|
:param str location: A string containing the default keyserver. This
|
||||||
should contain the desired keyserver protocol which
|
should contain the desired keyserver protocol which
|
||||||
is supported by the keyserver, for example, the
|
is supported by the keyserver, for example, the
|
||||||
default is ``'hkp://subkeys.pgp.net'``.
|
default is ``'hkp://wwwkeys .pgp.net'``.
|
||||||
:rtype: str or None
|
:rtype: :obj:`str` or :obj:`None`
|
||||||
:returns: A string specifying the protocol and keyserver hostname, if the
|
:returns: A string specifying the protocol and keyserver hostname, if the
|
||||||
checks passed. If not, returns None.
|
checks passed. If not, returns None.
|
||||||
"""
|
"""
|
||||||
|
@ -74,10 +77,11 @@ def _check_keyserver(location):
|
||||||
def _check_preferences(prefs, pref_type=None):
|
def _check_preferences(prefs, pref_type=None):
|
||||||
"""Check cipher, digest, and compression preference settings.
|
"""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
|
__ http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
|
||||||
[1]: http://eprint.iacr.org/2008/469.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
|
if prefs is None: return
|
||||||
|
|
||||||
|
@ -115,8 +119,8 @@ def _check_preferences(prefs, pref_type=None):
|
||||||
return allowed
|
return allowed
|
||||||
|
|
||||||
def _fix_unsafe(shell_input):
|
def _fix_unsafe(shell_input):
|
||||||
"""Find characters used to escape from a string into a shell, and wrap them
|
"""Find characters used to escape from a string into a shell, and wrap them in
|
||||||
in quotes if they exist. Regex pilfered from python-3.x shlex module.
|
quotes if they exist. Regex pilfered from Python3 :mod:`shlex` module.
|
||||||
|
|
||||||
:param str shell_input: The input intended for the GnuPG process.
|
: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
|
class and its name will need to be added to this
|
||||||
set.
|
set.
|
||||||
|
|
||||||
:rtype: Exception or str
|
:raises: :exc:`UsageError` if **input** is not a subset of the hard-coded
|
||||||
:raise: :exc:UsageError if ``_allowed`` is not a subset of ``_possible``.
|
set of all GnuPG options in :func:`_get_all_gnupg_options`.
|
||||||
ProtectedOption if ``input`` is not in the set ``_allowed``.
|
|
||||||
:return: The original parameter ``input``, unmodified and unsanitized,
|
:exc:`ProtectedOption` if **input** is not in the set of allowed
|
||||||
if no errors occur.
|
options.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:return: The original **input** parameter, unmodified and unsanitized, if
|
||||||
|
no errors occur.
|
||||||
"""
|
"""
|
||||||
gnupg_options = _get_all_gnupg_options()
|
gnupg_options = _get_all_gnupg_options()
|
||||||
allowed = _get_options_group("allowed")
|
allowed = _get_options_group("allowed")
|
||||||
|
@ -220,12 +228,12 @@ def _is_hex(string):
|
||||||
def _is_string(thing):
|
def _is_string(thing):
|
||||||
"""Python character arrays are a mess.
|
"""Python character arrays are a mess.
|
||||||
|
|
||||||
If Python2, check if ``thing`` is a ``unicode()`` or ``str()``.
|
If Python2, check if **thing** is an :obj:`unicode` or a :obj:`str`.
|
||||||
If Python3, check if ``thing`` is a ``str()``.
|
If Python3, check if **thing** is a :obj:`str`.
|
||||||
|
|
||||||
:param thing: The thing to check.
|
:param thing: The thing to check.
|
||||||
:returns: ``True`` if ``thing`` is a "string" according to whichever
|
:returns: ``True`` if **thing** is a string according to whichever version
|
||||||
version of Python we're running in.
|
of Python we're running in.
|
||||||
"""
|
"""
|
||||||
if _util._py3k: return isinstance(thing, str)
|
if _util._py3k: return isinstance(thing, str)
|
||||||
else: return isinstance(thing, basestring)
|
else: return isinstance(thing, basestring)
|
||||||
|
@ -238,18 +246,20 @@ def _sanitise(*args):
|
||||||
sanitised, allowed options.
|
sanitised, allowed options.
|
||||||
|
|
||||||
Each new option that we support that is not a boolean, but instead has
|
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
|
some additional inputs following it, i.e. "--encrypt-file foo.txt", will
|
||||||
safety checks added here.
|
need some basic safety checks added here.
|
||||||
|
|
||||||
GnuPG has three-hundred and eighteen commandline flags. Also, not all
|
GnuPG has three-hundred and eighteen commandline flags. Also, not all
|
||||||
implementations of OpenPGP parse PGP packets and headers in the same way,
|
implementations of OpenPGP parse PGP packets and headers in the same way,
|
||||||
so there is added potential there for messing with calls to GPG.
|
so there is added potential there for messing with calls to GPG.
|
||||||
|
|
||||||
For information on the PGP message format specification, see:
|
For information on the PGP message format specification, see
|
||||||
https://www.ietf.org/rfc/rfc1991.txt
|
:rfc:`1991`.
|
||||||
|
|
||||||
If you're asking, "Is this *really* necessary?": No, not really -- we could
|
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
|
:param str args: (optional) The boolean arguments which will be passed to
|
||||||
the GnuPG process.
|
the GnuPG process.
|
||||||
|
@ -260,22 +270,23 @@ def _sanitise(*args):
|
||||||
## see TODO file, tag :cleanup:sanitise:
|
## see TODO file, tag :cleanup:sanitise:
|
||||||
|
|
||||||
def _check_option(arg, value):
|
def _check_option(arg, value):
|
||||||
"""
|
"""Check that a single ``arg`` is an allowed option.
|
||||||
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
|
If it is allowed, quote out any escape characters in ``value``, and
|
||||||
:ivar:sanitised.
|
add the pair to :ivar:`sanitised`. Otherwise, drop them.
|
||||||
|
|
||||||
:param str arg: The arguments which will be passed to the GnuPG
|
:param str arg: The arguments which will be passed to the GnuPG
|
||||||
process, and, optionally their corresponding values.
|
process, and, optionally their corresponding values.
|
||||||
The values are any additional arguments following the
|
The values are any additional arguments following the
|
||||||
GnuPG option or flag. For example, if we wanted to pass
|
GnuPG option or flag. For example, if we wanted to
|
||||||
"--encrypt --recipient isis@leap.se" to gpg, then
|
pass ``"--encrypt --recipient isis@leap.se"`` to
|
||||||
"--encrypt" would be an arg without a value, and
|
GnuPG, then ``"--encrypt"`` would be an arg without a
|
||||||
"--recipient" would also be an arg, with a value of
|
value, and ``"--recipient"`` would also be an arg,
|
||||||
"isis@leap.se".
|
with a value of ``"isis@leap.se"``.
|
||||||
|
|
||||||
:ivar list checked: The sanitised, allowed options and values.
|
:ivar list checked: The sanitised, allowed options and values.
|
||||||
:rtype: str
|
: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()
|
checked = str()
|
||||||
none_options = _get_options_group("none_options")
|
none_options = _get_options_group("none_options")
|
||||||
|
@ -290,7 +301,7 @@ def _sanitise(*args):
|
||||||
flag = _is_allowed(arg)
|
flag = _is_allowed(arg)
|
||||||
assert flag is not None, "_check_option(): got None for flag"
|
assert flag is not None, "_check_option(): got None for flag"
|
||||||
except (AssertionError, ProtectedOption) as error:
|
except (AssertionError, ProtectedOption) as error:
|
||||||
log.warn("_check_option(): %s" % error.message)
|
log.warn("_check_option(): %s" % str(error))
|
||||||
else:
|
else:
|
||||||
checked += (flag + ' ')
|
checked += (flag + ' ')
|
||||||
|
|
||||||
|
@ -298,7 +309,7 @@ def _sanitise(*args):
|
||||||
values = value.split(' ')
|
values = value.split(' ')
|
||||||
for v in values:
|
for v in values:
|
||||||
## these can be handled separately, without _fix_unsafe(),
|
## 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):
|
if (flag in none_options) and (v is None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -332,8 +343,12 @@ def _sanitise(*args):
|
||||||
|
|
||||||
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
|
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
|
||||||
'--decrypt-files', '--import', '--verify']:
|
'--decrypt-files', '--import', '--verify']:
|
||||||
if _util._is_file(val): checked += (val + " ")
|
if ( (_util._is_file(val))
|
||||||
else: log.debug("%s not file: %s" % (flag, 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',
|
elif flag in ['--cipher-algo', '--personal-cipher-prefs',
|
||||||
'--personal-cipher-preferences']:
|
'--personal-cipher-preferences']:
|
||||||
|
@ -372,7 +387,8 @@ def _sanitise(*args):
|
||||||
groups[last] = str(filo.pop())
|
groups[last] = str(filo.pop())
|
||||||
## accept the read-from-stdin arg:
|
## accept the read-from-stdin arg:
|
||||||
if len(filo) >= 1 and filo[len(filo)-1] == '-':
|
if len(filo) >= 1 and filo[len(filo)-1] == '-':
|
||||||
groups[last] += str(' - \'\'') ## gross hack
|
groups[last] += str(' - ') ## gross hack
|
||||||
|
filo.pop()
|
||||||
else:
|
else:
|
||||||
groups[last] = str()
|
groups[last] = str()
|
||||||
while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
|
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
|
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
|
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
|
files) are necessary and statically specified in
|
||||||
:meth:`gnupg.GPG._makeargs`, if the inputs into Python are already
|
:meth:`gnupg._meta.GPGBase._make_args`, if the inputs into Python are
|
||||||
controlled, and we were to summon the GnuPG binary to ask it for its
|
already controlled, and we were to summon the GnuPG binary to ask it for
|
||||||
options, it would be possible to receive a falsified options set missing
|
its options, it would be possible to receive a falsified options set
|
||||||
the '--no-options' option in response. This seems unlikely, and the method
|
missing the ``--no-options`` option in response. This seems unlikely, and
|
||||||
is stupid and ugly, but at least we'll never have to debug whether or not
|
the method is stupid and ugly, but at least we'll never have to debug
|
||||||
an option *actually* disappeared in a different GnuPG version, or some
|
whether or not an option *actually* disappeared in a different GnuPG
|
||||||
funny business is happening.
|
version, or some funny business is happening.
|
||||||
|
|
||||||
These are the options as of GnuPG 1.4.12; the current stable branch of the
|
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
|
2.1.x tree contains a few more -- if you need them you'll have to add them
|
||||||
in here.
|
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
|
:rtype: frozenset
|
||||||
:returns: ``gnupg_options``
|
:returns: ``gnupg_options``
|
||||||
"""
|
"""
|
||||||
|
@ -787,8 +804,8 @@ def progress(status_code):
|
||||||
class GenKey(object):
|
class GenKey(object):
|
||||||
"""Handle status messages for key generation.
|
"""Handle status messages for key generation.
|
||||||
|
|
||||||
Calling the GenKey.__str__() method of this class will return the
|
Calling the ``__str__()`` method of this class will return the generated
|
||||||
generated key's fingerprint, or a status string explaining the results.
|
key's fingerprint, or a status string explaining the results.
|
||||||
"""
|
"""
|
||||||
def __init__(self, gpg):
|
def __init__(self, gpg):
|
||||||
self._gpg = gpg
|
self._gpg = gpg
|
||||||
|
@ -799,11 +816,13 @@ class GenKey(object):
|
||||||
self.status = None
|
self.status = None
|
||||||
self.subkey_created = False
|
self.subkey_created = False
|
||||||
self.primary_created = False
|
self.primary_created = False
|
||||||
#: This will store the filename of the key's public keyring if
|
#: This will store the key's public keyring filename, if
|
||||||
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
|
#: :meth:`~gnupg.GPG.gen_key_input` was called with
|
||||||
|
#: ``separate_keyring=True``.
|
||||||
self.keyring = None
|
self.keyring = None
|
||||||
#: This will store the filename of the key's secret keyring if
|
#: This will store the key's secret keyring filename, if :
|
||||||
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
|
#: :meth:`~gnupg.GPG.gen_key_input` was called with
|
||||||
|
#: ``separate_keyring=True``.
|
||||||
self.secring = None
|
self.secring = None
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
|
@ -823,7 +842,7 @@ class GenKey(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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"):
|
if key in ("GOOD_PASSPHRASE"):
|
||||||
pass
|
pass
|
||||||
|
@ -860,7 +879,7 @@ class DeleteResult(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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":
|
if key == "DELETE_PROBLEM":
|
||||||
self.status = self.problem_reason.get(value, "Unknown error: %r"
|
self.status = self.problem_reason.get(value, "Unknown error: %r"
|
||||||
|
@ -905,7 +924,7 @@ class Sign(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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",
|
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
|
||||||
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL",
|
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL",
|
||||||
|
@ -1008,7 +1027,7 @@ class ImportResult(object):
|
||||||
_fields = '''count no_user_id imported imported_rsa unchanged
|
_fields = '''count no_user_id imported imported_rsa unchanged
|
||||||
n_uids n_subk n_sigs n_revoc sec_read sec_imported sec_dups
|
n_uids n_subk n_sigs n_revoc sec_read sec_imported sec_dups
|
||||||
not_imported'''.split()
|
not_imported'''.split()
|
||||||
_counts = collections.OrderedDict(
|
_counts = OrderedDict(
|
||||||
zip(_fields, [int(0) for x in range(len(_fields))]) )
|
zip(_fields, [int(0) for x in range(len(_fields))]) )
|
||||||
|
|
||||||
#: A list of strings containing the fingerprints of the GnuPG keyIDs
|
#: A list of strings containing the fingerprints of the GnuPG keyIDs
|
||||||
|
@ -1037,7 +1056,7 @@ class ImportResult(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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":
|
if key == "IMPORTED":
|
||||||
# this duplicates info we already see in import_ok & import_problem
|
# this duplicates info we already see in import_ok & import_problem
|
||||||
|
@ -1183,7 +1202,7 @@ class Verify(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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:
|
if key in self.TRUST_LEVELS:
|
||||||
self.trust_text = key
|
self.trust_text = key
|
||||||
|
@ -1278,7 +1297,7 @@ class Crypt(Verify):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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",
|
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
|
||||||
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
|
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
|
||||||
|
@ -1345,7 +1364,7 @@ class ListPackets(object):
|
||||||
def _handle_status(self, key, value):
|
def _handle_status(self, key, value):
|
||||||
"""Parse a status code from the attached GnuPG process.
|
"""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':
|
if key == 'NODATA':
|
||||||
self.status = nodata(value)
|
self.status = nodata(value)
|
||||||
|
|
|
@ -17,9 +17,7 @@
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
# 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
|
The functions within this module take an instance of :class:`gnupg.GPGBase` or
|
||||||
a suitable subclass as their first argument.
|
a suitable subclass as their first argument.
|
||||||
|
@ -44,11 +42,12 @@ def _create_trustdb(cls):
|
||||||
def export_ownertrust(cls, trustdb=None):
|
def export_ownertrust(cls, trustdb=None):
|
||||||
"""Export ownertrust to a trustdb file.
|
"""Export ownertrust to a trustdb file.
|
||||||
|
|
||||||
If there is already a file named 'trustdb.gpg' in the current GnuPG
|
If there is already a file named :file:`trustdb.gpg` in the current GnuPG
|
||||||
homedir, it will be renamed to 'trustdb.gpg.bak'.
|
homedir, it will be renamed to :file:`trustdb.gpg.bak`.
|
||||||
|
|
||||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
: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:
|
if trustdb is None:
|
||||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||||
|
@ -56,7 +55,7 @@ def export_ownertrust(cls, trustdb=None):
|
||||||
try:
|
try:
|
||||||
os.rename(trustdb, trustdb + '.bak')
|
os.rename(trustdb, trustdb + '.bak')
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
log.debug(err.message)
|
log.debug(str(err))
|
||||||
|
|
||||||
export_proc = cls._open_subprocess('--export-ownertrust')
|
export_proc = cls._open_subprocess('--export-ownertrust')
|
||||||
tdb = open(trustdb, 'wb')
|
tdb = open(trustdb, 'wb')
|
||||||
|
@ -65,8 +64,9 @@ def export_ownertrust(cls, trustdb=None):
|
||||||
def import_ownertrust(self, trustdb=None):
|
def import_ownertrust(self, trustdb=None):
|
||||||
"""Import ownertrust from a trustdb file.
|
"""Import ownertrust from a trustdb file.
|
||||||
|
|
||||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
:param str trustdb: The path to the trustdb.gpg file. If not given,
|
||||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
defaults to :file:`trustdb.gpg` in the current GnuPG
|
||||||
|
homedir.
|
||||||
"""
|
"""
|
||||||
if trustdb is None:
|
if trustdb is None:
|
||||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||||
|
@ -78,11 +78,11 @@ def import_ownertrust(self, trustdb=None):
|
||||||
def fix_trustdb(cls, trustdb=None):
|
def fix_trustdb(cls, trustdb=None):
|
||||||
"""Attempt to repair a broken trustdb.gpg file.
|
"""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
|
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)~/code/python-gnupg $ gpg2 --fix-trustdb
|
||||||
gpg: You may try to re-create the trustdb using the commands:
|
gpg: You may try to re-create the trustdb using the commands:
|
||||||
gpg: cd ~/.gnupg
|
gpg: cd ~/.gnupg
|
||||||
gpg: gpg2 --export-ownertrust > otrust.tmp
|
gpg: gpg2 --export-ownertrust > otrust.tmp
|
||||||
|
@ -92,8 +92,9 @@ def fix_trustdb(cls, trustdb=None):
|
||||||
|
|
||||||
Brilliant piece of software engineering right there.
|
Brilliant piece of software engineering right there.
|
||||||
|
|
||||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
:param str trustdb: The path to the trustdb.gpg file. If not given,
|
||||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
defaults to :file:`trustdb.gpg` in the current GnuPG
|
||||||
|
homedir.
|
||||||
"""
|
"""
|
||||||
if trustdb is None:
|
if trustdb is None:
|
||||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||||
|
|
|
@ -17,10 +17,7 @@
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
# 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 __future__ import absolute_import
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -31,6 +28,7 @@ from time import mktime
|
||||||
import codecs
|
import codecs
|
||||||
import encodings
|
import encodings
|
||||||
import os
|
import os
|
||||||
|
import psutil
|
||||||
import threading
|
import threading
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -150,7 +148,7 @@ def _copy_data(instream, outstream):
|
||||||
break
|
break
|
||||||
except IOError as ioe:
|
except IOError as ioe:
|
||||||
# Can get 'broken pipe' errors even when all data was sent
|
# 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')
|
log.error('Error sending data: Broken pipe')
|
||||||
else:
|
else:
|
||||||
log.exception(ioe)
|
log.exception(ioe)
|
||||||
|
@ -254,7 +252,8 @@ def _find_binary(binary=None):
|
||||||
our process real uid has exec permissions.
|
our process real uid has exec permissions.
|
||||||
|
|
||||||
:param str binary: The path to the GnuPG binary.
|
: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
|
:rtype: str
|
||||||
:returns: The absolute path to the GnuPG binary to use, if no exceptions
|
:returns: The absolute path to the GnuPG binary to use, if no exceptions
|
||||||
occur.
|
occur.
|
||||||
|
@ -272,6 +271,8 @@ def _find_binary(binary=None):
|
||||||
except IndexError as ie:
|
except IndexError as ie:
|
||||||
log.info("Could not determine absolute path of binary: '%s'"
|
log.info("Could not determine absolute path of binary: '%s'"
|
||||||
% binary)
|
% binary)
|
||||||
|
elif os.access(binary, os.X_OK):
|
||||||
|
found = binary
|
||||||
if found is None:
|
if found is None:
|
||||||
try: found = _which('gpg')[0]
|
try: found = _which('gpg')[0]
|
||||||
except IndexError as ie:
|
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 not os.path.islink(found), "Path to gpg binary is symlink"
|
||||||
assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
|
assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
|
||||||
except (AssertionError, AttributeError) as ae:
|
except (AssertionError, AttributeError) as ae:
|
||||||
log.error(ae.message)
|
log.error(str(ae))
|
||||||
else:
|
else:
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
@ -303,22 +304,31 @@ def _has_readwrite(path):
|
||||||
"""
|
"""
|
||||||
return os.access(path, os.R_OK ^ os.W_OK)
|
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
|
"""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
|
size greater than zero, without following symbolic links or using
|
||||||
:func:os.path.isfile.
|
:func:os.path.isfile.
|
||||||
|
|
||||||
:param input: An object to check.
|
:param filename: An object to check.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
:returns: True if :param:input is file-like, False otherwise.
|
:returns: True if **filename** is file-like, False otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
assert os.lstat(input).st_size > 0, "not a file: %s" % input
|
statinfo = os.lstat(filename)
|
||||||
except (AssertionError, TypeError, IOError, OSError) as err:
|
log.debug("lstat(%r) with type=%s gave us %r"
|
||||||
log.error(err.message, exc_info=1)
|
% (repr(filename), type(filename), repr(statinfo)))
|
||||||
return False
|
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:
|
else:
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _is_stream(input):
|
def _is_stream(input):
|
||||||
"""Check that the input is a byte stream.
|
"""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)
|
passphrase = _make_random_string(length)
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
ruid, euid, suid = os.getresuid()
|
ruid, euid, suid = psutil.Process(os.getpid()).uids
|
||||||
gid = os.getgid()
|
gid = os.getgid()
|
||||||
now = mktime(localtime())
|
now = mktime(localtime())
|
||||||
|
|
||||||
|
@ -518,7 +528,7 @@ def _which(executable, flags=os.X_OK):
|
||||||
def _write_passphrase(stream, passphrase, encoding):
|
def _write_passphrase(stream, passphrase, encoding):
|
||||||
"""Write the passphrase from memory to the GnuPG process' stdin.
|
"""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 stream: The input file descriptor to write the password to.
|
||||||
:param str passphrase: The passphrase for the secret key material.
|
:param str passphrase: The passphrase for the secret key material.
|
||||||
:param str encoding: The data encoding expected by GnuPG. Usually, this
|
:param str encoding: The data encoding expected by GnuPG. Usually, this
|
||||||
|
@ -565,6 +575,7 @@ class InheritableProperty(object):
|
||||||
else:
|
else:
|
||||||
getattr(obj, self.fdel.__name__)()
|
getattr(obj, self.fdel.__name__)()
|
||||||
|
|
||||||
|
|
||||||
class Storage(dict):
|
class Storage(dict):
|
||||||
"""A dictionary where keys are stored as class attributes.
|
"""A dictionary where keys are stored as class attributes.
|
||||||
|
|
||||||
|
@ -595,7 +606,7 @@ class Storage(dict):
|
||||||
try:
|
try:
|
||||||
del self[key]
|
del self[key]
|
||||||
except KeyError as k:
|
except KeyError as k:
|
||||||
raise AttributeError(k.message)
|
raise AttributeError(k.args[0])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Storage ' + dict.__repr__(self) + '>'
|
return '<Storage ' + dict.__repr__(self) + '>'
|
||||||
|
|
144
gnupg/gnupg.py
144
gnupg/gnupg.py
|
@ -21,10 +21,10 @@
|
||||||
===========
|
===========
|
||||||
A Python interface to GnuPG.
|
A Python interface to GnuPG.
|
||||||
|
|
||||||
.. moduleauthor:: Isis Agora Lovecruft <isis@patternsinthevoid.net>
|
.. moduleauthor:: Isis Lovecruft <isis@patternsinthevoid.net>
|
||||||
see also :attr:`gnupg.__authors__`
|
see also :attr:`gnupg.__authors__`
|
||||||
:license: see :attr:`gnupg.__license__`
|
.. license:: see :attr:`gnupg.__license__`
|
||||||
:info: see <https://www.github.com/isislovecruft/python-gnupg>
|
.. info:: https://github.com/isislovecruft/python-gnupg
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
@ -41,7 +41,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from cStringIO import StringIO
|
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 _parsers
|
||||||
from . import _util
|
from . import _util
|
||||||
from . import _trust
|
from . import _trust
|
||||||
|
@ -70,35 +70,36 @@ class GPG(GPGBase):
|
||||||
"""Initialize a GnuPG process wrapper.
|
"""Initialize a GnuPG process wrapper.
|
||||||
|
|
||||||
:param str binary: Name for GnuPG binary executable. If the absolute
|
:param str binary: Name for GnuPG binary executable. If the absolute
|
||||||
path is not given, the evironment variable $PATH is
|
path is not given, the environment variable
|
||||||
searched for the executable and checked that the
|
``$PATH`` is searched for the executable and
|
||||||
real uid/gid of the user has sufficient permissions.
|
checked that the real uid/gid of the user has
|
||||||
|
sufficient permissions.
|
||||||
|
|
||||||
:param str homedir: Full pathname to directory containing the public
|
:param str homedir: Full pathname to directory containing the public
|
||||||
and private keyrings. Default is whatever GnuPG
|
and private keyrings. Default is whatever GnuPG
|
||||||
defaults to.
|
defaults to.
|
||||||
|
|
||||||
:param str,int,bool verbose: String or numeric value to pass to gpg's
|
:type verbose: :obj:`str` or :obj:`int` or :obj:`bool`
|
||||||
``--debug-level`` option. See the gpg man
|
:param verbose: String or numeric value to pass to GnuPG's
|
||||||
page for the list of valid options. If
|
``--debug-level`` option. See the GnuPG man page for
|
||||||
False, debug output is not generated by
|
the list of valid options. If False, debug output is
|
||||||
the gpg binary. If True, defaults to
|
not generated by the GnuPG binary. If True, defaults
|
||||||
``--debug-level basic.``
|
to ``--debug-level basic.``
|
||||||
|
|
||||||
:param str keyring: Name of keyring file containing public key data, if
|
:param str keyring: Name of keyring file containing public key data.
|
||||||
unspecified, defaults to 'pubring.gpg' in the
|
If unspecified, defaults to :file:`pubring.gpg` in
|
||||||
``homedir`` directory.
|
the **homedir** directory.
|
||||||
|
|
||||||
:param str secring: Name of alternative secret keyring file to use. If
|
:param str secring: Name of alternative secret keyring file to use. If
|
||||||
left unspecified, this will default to using
|
left unspecified, this will default to using
|
||||||
'secring.gpg' in the :param:homedir directory, and
|
:file:`secring.gpg` in the **homedir** directory,
|
||||||
create that file if it does not exist.
|
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.
|
binary.
|
||||||
|
|
||||||
:raises: :exc:`RuntimeError` with explanation message if there is a
|
:raises: A :exc:`~exceptions.RuntimeError` with explanation message
|
||||||
problem invoking gpg.
|
if there is a problem invoking GnuPG.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -227,7 +228,7 @@ class GPG(GPGBase):
|
||||||
In simpler terms: this function isn't for signing your friends' keys,
|
In simpler terms: this function isn't for signing your friends' keys,
|
||||||
it's for something like signing an email.
|
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 data: A string or file stream to sign.
|
||||||
:param str default_key: The key to sign with.
|
:param str default_key: The key to sign with.
|
||||||
:param str passphrase: The passphrase to pipe to stdin.
|
: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 bool binary: If True, do not ascii armour the output.
|
||||||
:param str digest_algo: The hash digest to use. Again, to see which
|
:param str digest_algo: The hash digest to use. Again, to see which
|
||||||
hashes your GnuPG is capable of using, do:
|
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'``.
|
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"
|
log.info("Signing message '%r' with keyid: %s"
|
||||||
% (data, kwargs['default_key']))
|
% (data, kwargs['default_key']))
|
||||||
else:
|
else:
|
||||||
|
@ -306,15 +307,19 @@ class GPG(GPGBase):
|
||||||
return result
|
return result
|
||||||
log.debug('verify_file(): Handling detached verification')
|
log.debug('verify_file(): Handling detached verification')
|
||||||
sig_fh = None
|
sig_fh = None
|
||||||
|
data_fh = None
|
||||||
try:
|
try:
|
||||||
sig_fh = open(sig_file)
|
sig_fh = open(sig_file, 'rb')
|
||||||
|
data_fh = open(file, 'rb')
|
||||||
args = ["--verify %s -" % sig_fh.name]
|
args = ["--verify %s -" % sig_fh.name]
|
||||||
proc = self._open_subprocess(args)
|
proc = self._open_subprocess(args)
|
||||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
writer = _util._threaded_copy_data(data_fh, proc.stdin)
|
||||||
self._collect_output(proc, result, stdin=proc.stdin)
|
self._collect_output(proc, result, writer, stdin=proc.stdin)
|
||||||
finally:
|
finally:
|
||||||
if sig_fh and not sig_fh.closed:
|
if sig_fh and not sig_fh.closed:
|
||||||
sig_fh.close()
|
sig_fh.close()
|
||||||
|
if data_fh and not data_fh.closed:
|
||||||
|
data_fh.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def import_keys(self, key_data):
|
def import_keys(self, key_data):
|
||||||
|
@ -373,7 +378,7 @@ class GPG(GPGBase):
|
||||||
:param str keyids: Each ``keyids`` argument should be a string
|
:param str keyids: Each ``keyids`` argument should be a string
|
||||||
containing a keyid to request.
|
containing a keyid to request.
|
||||||
:param str keyserver: The keyserver to request the ``keyids`` from;
|
:param str keyserver: The keyserver to request the ``keyids`` from;
|
||||||
defaults to :property:`gnupg.GPG.keyserver`.
|
defaults to `gnupg.GPG.keyserver`.
|
||||||
"""
|
"""
|
||||||
if keyids:
|
if keyids:
|
||||||
keys = ' '.join([key for key in keyids])
|
keys = ' '.join([key for key in keyids])
|
||||||
|
@ -384,21 +389,21 @@ class GPG(GPGBase):
|
||||||
def delete_keys(self, fingerprints, secret=False, subkeys=False):
|
def delete_keys(self, fingerprints, secret=False, subkeys=False):
|
||||||
"""Delete a key, or list of keys, from the current keyring.
|
"""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
|
delete them. If ``secret=True``, the corresponding secret keyring will
|
||||||
be deleted from :attr:`GPG.secring`.
|
be deleted from :obj:`.secring`.
|
||||||
|
|
||||||
:type fingerprints: str or list or tuple
|
|
||||||
|
|
||||||
|
:type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple`
|
||||||
:param fingerprints: A string, or a list/tuple of strings,
|
: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)
|
: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
|
:param bool subkeys: If True, delete the secret subkey first, then the
|
||||||
public key. (default: False) Same as:
|
public key. (default: False) Same as:
|
||||||
``$ gpg --delete-secret-and-public-key 0x12345678``
|
:command:`$gpg --delete-secret-and-public-key 0x12345678`.
|
||||||
"""
|
"""
|
||||||
which = 'keys'
|
which = 'keys'
|
||||||
if secret:
|
if secret:
|
||||||
|
@ -410,7 +415,7 @@ class GPG(GPGBase):
|
||||||
fingerprints = ' '.join(fingerprints)
|
fingerprints = ' '.join(fingerprints)
|
||||||
|
|
||||||
args = ['--batch']
|
args = ['--batch']
|
||||||
args.append("--delete-{} {}".format(which, fingerprints))
|
args.append("--delete-{0} {1}".format(which, fingerprints))
|
||||||
|
|
||||||
result = self._result_map['delete'](self)
|
result = self._result_map['delete'](self)
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
|
@ -435,7 +440,7 @@ class GPG(GPGBase):
|
||||||
keyids = ' '.join(['%s' % k for k in keyids])
|
keyids = ' '.join(['%s' % k for k in keyids])
|
||||||
|
|
||||||
args = ["--armor"]
|
args = ["--armor"]
|
||||||
args.append("--export{} {}".format(which, keyids))
|
args.append("--export{0} {1}".format(which, keyids))
|
||||||
|
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
## gpg --export produces no status-fd output; stdout will be empty in
|
## gpg --export produces no status-fd output; stdout will be empty in
|
||||||
|
@ -547,7 +552,7 @@ class GPG(GPGBase):
|
||||||
:param dict input: A dictionary of parameters and values for the new
|
:param dict input: A dictionary of parameters and values for the new
|
||||||
key.
|
key.
|
||||||
:returns: The result mapping with details of the new key, which is a
|
: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"]
|
args = ["--gen-key --batch"]
|
||||||
key = self._result_map['generate'](self)
|
key = self._result_map['generate'](self)
|
||||||
|
@ -557,20 +562,22 @@ class GPG(GPGBase):
|
||||||
|
|
||||||
fpr = str(key.fingerprint)
|
fpr = str(key.fingerprint)
|
||||||
if len(fpr) == 20:
|
if len(fpr) == 20:
|
||||||
if self.temp_keyring or self.temp_secring:
|
for d in map(lambda x: os.path.dirname(x),
|
||||||
if not os.path.exists(self._keys_dir):
|
[self.temp_keyring, self.temp_secring]):
|
||||||
os.makedirs(self._keys_dir)
|
if not os.path.exists(d):
|
||||||
prefix = os.path.join(self._keys_dir, fpr)
|
os.makedirs(d)
|
||||||
|
|
||||||
if self.temp_keyring:
|
if self.temp_keyring:
|
||||||
if os.path.isfile(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")
|
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 self.temp_secring:
|
||||||
if os.path.isfile(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")
|
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)
|
log.info("Key created. Fingerprint: %s" % fpr)
|
||||||
key.keyring = self.temp_keyring
|
key.keyring = self.temp_keyring
|
||||||
|
@ -582,11 +589,11 @@ class GPG(GPGBase):
|
||||||
|
|
||||||
def gen_key_input(self, separate_keyring=False, save_batchfile=False,
|
def gen_key_input(self, separate_keyring=False, save_batchfile=False,
|
||||||
testing=False, **kwargs):
|
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
|
The GnuPG batch file key generation feature allows unattended key
|
||||||
generation by creating a file with special syntax and then providing it
|
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-Real: Alice
|
||||||
| Name-Email: alice@inter.net
|
| 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
|
:param bool separate_keyring: Specify for the new key to be written to
|
||||||
a separate pubring.gpg and secring.gpg. If True,
|
a separate pubring.gpg and secring.gpg. If True,
|
||||||
:meth:`GPG.gen_key` will automatically rename the separate keyring
|
:meth:`~gnupg.GPG.gen_key` will automatically rename the separate
|
||||||
and secring to whatever the fingerprint of the generated key ends
|
keyring and secring to whatever the fingerprint of the generated
|
||||||
up being, suffixed with '.pubring' and '.secring' respectively.
|
key ends up being, suffixed with '.pubring' and '.secring'
|
||||||
|
respectively.
|
||||||
|
|
||||||
:param bool save_batchfile: Save a copy of the generated batch file to
|
: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
|
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_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_comment: The comment in the UID of the generated key.
|
||||||
|
|
||||||
:param str name_email: The email 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
|
(default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8
|
||||||
the entirety of the UID. At least one of ``name_real``,
|
encoding for the entirety of the UID. At least one of
|
||||||
``name_comment``, or ``name_email`` must be provided, or else no
|
``name_real``, ``name_comment``, or ``name_email`` must be
|
||||||
user ID is created.
|
provided, or else no user ID is created.
|
||||||
|
|
||||||
:param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
|
:param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
|
||||||
(default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts
|
(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
|
:param str subkey_usage: Key usage for a subkey; similar to
|
||||||
``key_usage``.
|
``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
|
: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
|
<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)
|
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
|
: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
|
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
|
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
|
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
|
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
|
:param str preferences: Set the cipher, hash, and compression
|
||||||
preference values for this key. This expects the same type of
|
preference values for this key. This expects the same type of
|
||||||
string as the sub-command ‘setpref’ in the --edit-key menu.
|
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
|
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.)
|
key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
|
||||||
fpr is the fingerprint of the designated revoker. The optional
|
fpr is the fingerprint of the designated revoker. The optional
|
||||||
|
@ -747,18 +756,18 @@ class GPG(GPGBase):
|
||||||
preferred keyserver URL for the key.
|
preferred keyserver URL for the key.
|
||||||
|
|
||||||
:param str handle: This is an optional parameter only used with the
|
: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
|
status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may
|
||||||
100 characters and should not contain spaces. It is useful for
|
be up to 100 characters and should not contain spaces. It is
|
||||||
batch key generation to associate a key parameter block with a
|
useful for batch key generation to associate a key parameter block
|
||||||
status line.
|
with a status line.
|
||||||
|
|
||||||
:rtype: str
|
:rtype: str
|
||||||
:returns: A suitable input string for the :meth:`GPG.gen_key` method,
|
:returns: A suitable input string for the :meth:`GPG.gen_key` method,
|
||||||
the latter of which will create the new keypair.
|
the latter of which will create the new keypair.
|
||||||
|
|
||||||
see
|
See `this GnuPG Manual section`__ for more details.
|
||||||
http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
|
|
||||||
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'
|
#: A boolean for determining whether to set subkey_type to 'default'
|
||||||
default_type = False
|
default_type = False
|
||||||
|
@ -931,20 +940,21 @@ generate keys. Please see
|
||||||
>>> decrypted
|
>>> decrypted
|
||||||
'The crow flies at midnight.'
|
'The crow flies at midnight.'
|
||||||
|
|
||||||
|
|
||||||
:param str cipher_algo: The cipher algorithm to use. To see available
|
:param str cipher_algo: The cipher algorithm to use. To see available
|
||||||
algorithms with your version of GnuPG, do:
|
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'``.
|
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
|
||||||
|
|
||||||
:param str digest_algo: The hash digest to use. Again, to see which
|
:param str digest_algo: The hash digest to use. Again, to see which
|
||||||
hashes your GnuPG is capable of using, do:
|
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'``.
|
The default, if unspecified, is ``'SHA512'``.
|
||||||
|
|
||||||
:param str compress_algo: The compression algorithm to use. Can be one
|
:param str compress_algo: The compression algorithm to use. Can be one
|
||||||
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
|
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
|
||||||
|
|
||||||
See also: :meth:`GPGBase._encrypt`
|
.. seealso:: :meth:`._encrypt`
|
||||||
"""
|
"""
|
||||||
stream = _make_binary_stream(data, self._encoding)
|
stream = _make_binary_stream(data, self._encoding)
|
||||||
result = self._encrypt(stream, recipients, **kwargs)
|
result = self._encrypt(stream, recipients, **kwargs)
|
||||||
|
|
|
@ -47,8 +47,8 @@ import tempfile
|
||||||
## these dependencies require Python>=2.6 in order to have proper SSL support.
|
## these dependencies require Python>=2.6 in order to have proper SSL support.
|
||||||
##
|
##
|
||||||
## Use unittest2 if we're on Python2.6 or less:
|
## Use unittest2 if we're on Python2.6 or less:
|
||||||
if sys.version_info.major == 2 and sys.version_info.minor <= 6:
|
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
|
||||||
unittest = __import__(unittest2)
|
import unittest2 as unittest
|
||||||
else:
|
else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ try:
|
||||||
import gnupg._parsers as _parsers
|
import gnupg._parsers as _parsers
|
||||||
import gnupg._logger as _logger
|
import gnupg._logger as _logger
|
||||||
except (ImportError, ValueError) as ierr:
|
except (ImportError, ValueError) as ierr:
|
||||||
raise SystemExit(ierr.message)
|
raise SystemExit(str(ierr))
|
||||||
|
|
||||||
|
|
||||||
log = _util.log
|
log = _util.log
|
||||||
|
@ -173,7 +173,6 @@ class GPGTestCase(unittest.TestCase):
|
||||||
self.keyring = self.gpg.keyring
|
self.keyring = self.gpg.keyring
|
||||||
self.secring = self.gpg.secring
|
self.secring = self.gpg.secring
|
||||||
self.insecure_prng = False
|
self.insecure_prng = False
|
||||||
self.gpg._keys_dir = os.path.join(_files, 'generated-keys')
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""This is called once per self.test_* method after the test run."""
|
"""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):
|
def test_copy_data_bytesio(self):
|
||||||
"""Test that _copy_data() is able to duplicate byte streams."""
|
"""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)
|
instream = io.BytesIO(message)
|
||||||
self.assertEqual(unicode(message), instream.getvalue())
|
self.assertEqual(message, instream.getvalue())
|
||||||
|
|
||||||
out_filename = 'test-copy-data-bytesio'
|
out_filename = 'test-copy-data-bytesio'
|
||||||
|
|
||||||
# Create the test file:
|
# Create the test file:
|
||||||
outfile = os.path.join(os.getcwdu(), out_filename)
|
try:
|
||||||
outstream = open(outfile, 'w+')
|
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
|
# _copy_data() will close both file descriptors
|
||||||
_util._copy_data(instream, outstream)
|
_util._copy_data(instream, outstream)
|
||||||
|
@ -523,7 +526,7 @@ class GPGTestCase(unittest.TestCase):
|
||||||
self.assertIsNotNone(key)
|
self.assertIsNotNone(key)
|
||||||
self.assertNotEquals(key, "")
|
self.assertNotEquals(key, "")
|
||||||
self.assertGreater(len(str(key)), 0)
|
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)
|
log.debug("Storing downloaded key as %s" % keyfile)
|
||||||
with open(keyfile, 'w') as fh:
|
with open(keyfile, 'w') as fh:
|
||||||
fh.write(str(key))
|
fh.write(str(key))
|
||||||
|
@ -673,44 +676,72 @@ class GPGTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_signature_verification_detached(self):
|
def test_signature_verification_detached(self):
|
||||||
"""Test that verification of a detached signature of a file works."""
|
"""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)
|
|
||||||
|
|
||||||
verified = self.gpg.verify_file(cm, sigfilename)
|
key = self.generate_key("Paulo S.L.M. Barreto", "anub.is")
|
||||||
|
datafn = os.path.join(_files, 'cypherpunk_manifesto')
|
||||||
|
sigfn = os.path.extsep.join([datafn, 'sig'])
|
||||||
|
|
||||||
|
datafd = open(datafn, 'rb')
|
||||||
|
sig = self.gpg.sign(datafd, default_key=key.fingerprint,
|
||||||
|
passphrase='paulos.l.m.barreto',
|
||||||
|
detach=True,
|
||||||
|
clearsign=False)
|
||||||
|
|
||||||
|
self.assertTrue(sig.data, "File signing should succeed")
|
||||||
|
|
||||||
|
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:
|
if key.fingerprint != verified.fingerprint:
|
||||||
log.warn("key fingerprint: %r", key.fingerprint)
|
log.warn("key fingerprint: %r", key.fingerprint)
|
||||||
log.warn("verified fingerprint: %r", verified.fingerprint)
|
log.warn("verified fingerprint: %r", verified.fingerprint)
|
||||||
self.assertEqual(key.fingerprint, verified.fingerprint)
|
self.assertEqual(key.fingerprint, verified.fingerprint)
|
||||||
|
|
||||||
if os.path.isfile(sigfilename):
|
if os.path.isfile(sigfn):
|
||||||
os.unlink(sigfilename)
|
os.unlink(sigfn)
|
||||||
|
|
||||||
def test_signature_verification_detached_binary(self):
|
def test_signature_verification_detached_binary(self):
|
||||||
"""Test that detached signature verification in binary mode fails."""
|
"""Test that detached signature verification in binary mode fails."""
|
||||||
|
|
||||||
key = self.generate_key("Adi Shamir", "rsa.com")
|
key = self.generate_key("Adi Shamir", "rsa.com")
|
||||||
datafile = os.path.join(_files, 'cypherpunk_manifesto')
|
datafn = os.path.join(_files, 'cypherpunk_manifesto')
|
||||||
with open(datafile, 'rb') as cm:
|
sigfn = os.path.extsep.join([datafn, 'sig'])
|
||||||
sig = self.gpg.sign(cm, default_key=key.fingerprint,
|
|
||||||
|
datafd = open(datafn, 'rb')
|
||||||
|
data = datafd.read()
|
||||||
|
datafd.close()
|
||||||
|
|
||||||
|
sig = self.gpg.sign(data, default_key=key.fingerprint,
|
||||||
passphrase='adishamir',
|
passphrase='adishamir',
|
||||||
detach=True, binary=True, clearsign=False)
|
detach=True,
|
||||||
|
binary=True,
|
||||||
|
clearsign=False)
|
||||||
|
|
||||||
self.assertTrue(sig.data, "File signing should succeed")
|
self.assertTrue(sig.data, "File signing should succeed")
|
||||||
with open(datafile+'.sig', 'w') as bs:
|
|
||||||
bs.write(sig.data)
|
sigfd = open(sigfn, 'wb')
|
||||||
bs.flush()
|
sigfd.write(sig.data)
|
||||||
|
sigfd.flush()
|
||||||
|
sigfd.close()
|
||||||
|
|
||||||
|
self.assertTrue(sigfd.closed, "Sigfile '%s' should be closed" % sigfn)
|
||||||
with self.assertRaises(UnicodeDecodeError):
|
with self.assertRaises(UnicodeDecodeError):
|
||||||
print("SIG=%s" % sig)
|
print("SIG=%s" % sig)
|
||||||
with open(datafile+'.sig', 'rb') as fsig:
|
|
||||||
with open(datafile, 'rb') as fdata:
|
verifysig = open(sigfn, 'rb')
|
||||||
self.gpg.verify_file(fdata, fsig)
|
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):
|
def test_deletion(self):
|
||||||
"""Test that key deletion works."""
|
"""Test that key deletion works."""
|
||||||
|
@ -793,14 +824,14 @@ authentication."""
|
||||||
riggio_input = self.gpg.gen_key_input(separate_keyring=True, **riggio)
|
riggio_input = self.gpg.gen_key_input(separate_keyring=True, **riggio)
|
||||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||||
riggio = self.gpg.gen_key(riggio_input)
|
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)
|
riggio_key = self.gpg.export_keys(riggio.fingerprint)
|
||||||
self.gpg.import_keys(riggio_key)
|
self.gpg.import_keys(riggio_key)
|
||||||
|
|
||||||
sicari_input = self.gpg.gen_key_input(separate_keyring=True, **sicari)
|
sicari_input = self.gpg.gen_key_input(separate_keyring=True, **sicari)
|
||||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||||
sicari = self.gpg.gen_key(sicari_input)
|
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)
|
sicari_key = self.gpg.export_keys(sicari.fingerprint)
|
||||||
self.gpg.import_keys(sicari_key)
|
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
|
a WSN while ensuring both end-to-end encryption and hop-by-hop
|
||||||
authentication."""
|
authentication."""
|
||||||
enc = self.gpg.encrypt(message, alice_pfpr, bob_pfpr)
|
enc = self.gpg.encrypt(message, alice_pfpr, bob_pfpr)
|
||||||
encrypted = str(enc.data)
|
encrypted = str(enc)
|
||||||
log.debug("encryption_decryption_multi_recipient() Ciphertext = %s"
|
log.debug("encryption_decryption_multi_recipient() Ciphertext = %s"
|
||||||
% encrypted)
|
% encrypted)
|
||||||
|
|
||||||
self.assertNotEquals(message, encrypted)
|
self.assertNotEquals(message, encrypted)
|
||||||
dec_alice = self.gpg.decrypt(encrypted, passphrase="test")
|
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")
|
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):
|
def test_symmetric_encryption_and_decryption(self):
|
||||||
"""Test symmetric encryption and decryption"""
|
"""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',
|
encrypted = str(self.gpg.encrypt(msg, passphrase='quiscustodiet',
|
||||||
symmetric=True, encrypt=False))
|
symmetric=True, encrypt=False))
|
||||||
decrypt = self.gpg.decrypt(encrypted, passphrase='quiscustodiet')
|
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 encrypted data:\n%s" % encrypted)
|
||||||
log.info("Symmetrically decrypted data:\n%s" % decrypted)
|
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:
|
with open(enc_outf) as enc2:
|
||||||
fdata = enc2.read()
|
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:
|
if ddata != data:
|
||||||
log.debug("data was: %r" % data)
|
log.debug("data was: %r" % data)
|
||||||
log.debug("new (from filehandle): %r" % fdata)
|
log.debug("new (from filehandle): %r" % fdata)
|
||||||
|
|
|
@ -1,58 +1,3 @@
|
||||||
#
|
# sha256: UI5KRMglOjhqD4bZyb1KG0y7L5TojUmhnBUTZTymbEU
|
||||||
# python-gnupg/requirements.txt
|
psutil==1.2.1
|
||||||
# -----------------------------
|
|
||||||
# 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
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
[upload_docs]
|
[upload_docs]
|
||||||
upload-dir = docs/_build/html
|
upload-dir = docs/_build/html
|
||||||
|
show-response = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
[upload]
|
[upload]
|
||||||
sign = True
|
sign = True
|
||||||
identity = 0xa3adb67a2cdb8b35
|
identity = 0xa3adb67a2cdb8b35
|
||||||
|
|
||||||
|
[aliases]
|
||||||
|
upload_all = sdist bdist_egg bdist_wheel upload
|
||||||
|
|
||||||
|
|
53
setup.py
53
setup.py
|
@ -23,7 +23,11 @@ from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import setuptools
|
import setuptools
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
import versioneer
|
import versioneer
|
||||||
|
|
||||||
|
|
||||||
versioneer.versionfile_source = 'gnupg/_version.py'
|
versioneer.versionfile_source = 'gnupg/_version.py'
|
||||||
versioneer.versionfile_build = 'gnupg/_version.py'
|
versioneer.versionfile_build = 'gnupg/_version.py'
|
||||||
versioneer.tag_prefix = ''
|
versioneer.tag_prefix = ''
|
||||||
|
@ -34,6 +38,49 @@ __contact__ = 'isis@patternsinthevoid.net'
|
||||||
__url__ = 'https://github.com/isislovecruft/python-gnupg'
|
__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(
|
setuptools.setup(
|
||||||
name = "gnupg",
|
name = "gnupg",
|
||||||
description="A Python wrapper for GnuPG",
|
description="A Python wrapper for GnuPG",
|
||||||
|
@ -62,8 +109,10 @@ greater.
|
||||||
scripts=['versioneer.py'],
|
scripts=['versioneer.py'],
|
||||||
test_suite='gnupg.test.test_gnupg',
|
test_suite='gnupg.test.test_gnupg',
|
||||||
|
|
||||||
install_requires=['psutil>=0.5.1'],
|
install_requires=requires,
|
||||||
extras_require={'docs': ["Sphinx>=1.1", "repoze.sphinx"]},
|
dependency_links=deplinks,
|
||||||
|
extras_require={'docs': ["Sphinx>=1.1",
|
||||||
|
"sphinxcontrib-fulltoc==1.0"]},
|
||||||
|
|
||||||
platforms="Linux, BSD, OSX, Windows",
|
platforms="Linux, BSD, OSX, Windows",
|
||||||
download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",
|
download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",
|
||||||
|
|
Loading…
Reference in New Issue