Merge branch 'master' of github.com:isislovecruft/python-gnupg
commit
ff96904233
|
@ -83,3 +83,9 @@ gpg
|
|||
# setuptools/distribute files:
|
||||
PKG-INFO
|
||||
MANIFEST
|
||||
|
||||
# sphinx default build
|
||||
docs/_build
|
||||
|
||||
# ignore keys which have been generated example scripts:
|
||||
8192-bit-key/*
|
||||
|
|
23
Makefile
23
Makefile
|
@ -2,6 +2,11 @@ SHELL=/bin/sh
|
|||
TESTDIR=./gnupg/test
|
||||
TESTHANDLE=$(TESTDIR)/test_gnupg.py
|
||||
FILES=$(SHELL find ./gnupg/ -name "*.py" -printf "%p,")
|
||||
PKG_NAME=python-gnupg
|
||||
DOC_DIR=docs
|
||||
DOC_BUILD_DIR:=$(DOC_DIR)/_build
|
||||
DOC_HTML_DIR:=$(DOC_BUILD_DIR)/html
|
||||
DOC_BUILD_ZIP:=$(PKG_NAME)-docs.zip
|
||||
|
||||
.PHONY=all
|
||||
all: uninstall install test
|
||||
|
@ -73,11 +78,15 @@ py3k-uninstall: uninstall
|
|||
reinstall: uninstall install
|
||||
py3k-reinstall: py3k-uninstall py3k-install
|
||||
|
||||
cleandocs:
|
||||
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" \
|
||||
-o docs gnupg/ tests/
|
||||
docs-clean:
|
||||
-rm -rf $(DOC_BUILD_DIR)
|
||||
|
||||
docs:
|
||||
cd docs && \
|
||||
make clean && \
|
||||
make html
|
||||
docs-completely-new:
|
||||
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" -o $(DOC_DIR) gnupg/ tests/
|
||||
|
||||
docs-html:
|
||||
cd $(DOC_DIR) && make clean && make html
|
||||
|
||||
docs-zipfile: docs-html
|
||||
cd $(DOC_HTML_DIR) && { find . -name '*' | zip -@ -v ../$(DOC_BUILD_ZIP) ;};
|
||||
@echo "Built documentation in $(DOC_BUILD_DIR)/$(DOC_BUILD_ZIP)"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXOPTS = -E -n
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
|
|
@ -25,6 +25,16 @@ div.header-wrapper {
|
|||
border-bottom: 3px solid #2e3436;
|
||||
}
|
||||
|
||||
div.headertitle a {
|
||||
font-family: "Georgia", "Times New Roman", serif;
|
||||
font-size: 2em;
|
||||
color: rgb(252, 175, 62);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #204a87;
|
||||
}
|
||||
|
||||
/* Default body styles */
|
||||
a {
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
/// XXX: make it cross browser
|
||||
|
||||
/**
|
||||
* make the code below compatible with browsers without
|
||||
* an installed firebug like debugger
|
||||
*/
|
||||
if (!window.console || !console.firebug) {
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
|
||||
"group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
|
||||
window.console = {};
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
window.console[names[i]] = function() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* small helper function to urldecode strings
|
||||
*/
|
||||
jQuery.urldecode = function(x) {
|
||||
return decodeURIComponent(x).replace(/\+/g, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* small helper function to urlencode strings
|
||||
*/
|
||||
jQuery.urlencode = encodeURIComponent;
|
||||
|
||||
/**
|
||||
* This function returns the parsed url parameters of the
|
||||
* current request. Multiple values per key are supported,
|
||||
* it will always return arrays of strings for the value parts.
|
||||
*/
|
||||
jQuery.getQueryParameters = function(s) {
|
||||
if (typeof s == 'undefined')
|
||||
s = document.location.search;
|
||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var tmp = parts[i].split('=', 2);
|
||||
var key = jQuery.urldecode(tmp[0]);
|
||||
var value = jQuery.urldecode(tmp[1]);
|
||||
if (key in result)
|
||||
result[key].push(value);
|
||||
else
|
||||
result[key] = [value];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* small function to check if an array contains
|
||||
* a given item.
|
||||
*/
|
||||
jQuery.contains = function(arr, item) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i] == item)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* highlight a given string on a jquery object by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
jQuery.fn.highlightText = function(text, className) {
|
||||
function highlight(node) {
|
||||
if (node.nodeType == 3) {
|
||||
var val = node.nodeValue;
|
||||
var pos = val.toLowerCase().indexOf(text);
|
||||
if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) {
|
||||
var span = document.createElement("span");
|
||||
span.className = className;
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
||||
document.createTextNode(val.substr(pos + text.length)),
|
||||
node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
}
|
||||
}
|
||||
else if (!jQuery(node).is("button, select, textarea")) {
|
||||
jQuery.each(node.childNodes, function() {
|
||||
highlight(this)
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.each(function() {
|
||||
highlight(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
var Documentation = {
|
||||
|
||||
init : function() {
|
||||
this.fixFirefoxAnchorBug();
|
||||
this.highlightSearchWords();
|
||||
this.initModIndex();
|
||||
},
|
||||
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
TRANSLATIONS : {},
|
||||
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
|
||||
LOCALE : 'unknown',
|
||||
|
||||
// gettext and ngettext don't access this so that the functions
|
||||
// can savely bound to a different name (_ = Documentation.gettext)
|
||||
gettext : function(string) {
|
||||
var translated = Documentation.TRANSLATIONS[string];
|
||||
if (typeof translated == 'undefined')
|
||||
return string;
|
||||
return (typeof translated == 'string') ? translated : translated[0];
|
||||
},
|
||||
|
||||
ngettext : function(singular, plural, n) {
|
||||
var translated = Documentation.TRANSLATIONS[singular];
|
||||
if (typeof translated == 'undefined')
|
||||
return (n == 1) ? singular : plural;
|
||||
return translated[Documentation.PLURALEXPR(n)];
|
||||
},
|
||||
|
||||
addTranslations : function(catalog) {
|
||||
for (var key in catalog.messages)
|
||||
this.TRANSLATIONS[key] = catalog.messages[key];
|
||||
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
|
||||
this.LOCALE = catalog.locale;
|
||||
},
|
||||
|
||||
/**
|
||||
* add context elements like header anchor links
|
||||
*/
|
||||
addContextElements : function() {
|
||||
$('div[id] > :header:first').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this headline')).
|
||||
appendTo(this);
|
||||
});
|
||||
$('dt[id]').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this definition')).
|
||||
appendTo(this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* workaround a firefox stupidity
|
||||
*/
|
||||
fixFirefoxAnchorBug : function() {
|
||||
if (document.location.hash && $.browser.mozilla)
|
||||
window.setTimeout(function() {
|
||||
document.location.href += '';
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* highlight the search words provided in the url in the text
|
||||
*/
|
||||
highlightSearchWords : function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
|
||||
if (terms.length) {
|
||||
var body = $('div.body');
|
||||
window.setTimeout(function() {
|
||||
$.each(terms, function() {
|
||||
body.highlightText(this.toLowerCase(), 'highlight');
|
||||
});
|
||||
}, 10);
|
||||
$('<li class="highlight-link"><a href="javascript:Documentation.' +
|
||||
'hideSearchWords()">' + _('Hide Search Matches') + '</a></li>')
|
||||
.appendTo($('.sidebar .this-page-menu'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* init the modindex toggle buttons
|
||||
*/
|
||||
initModIndex : function() {
|
||||
var togglers = $('img.toggler').click(function() {
|
||||
var src = $(this).attr('src');
|
||||
var idnum = $(this).attr('id').substr(7);
|
||||
console.log($('tr.cg-' + idnum).toggle());
|
||||
if (src.substr(-9) == 'minus.png')
|
||||
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
|
||||
else
|
||||
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
|
||||
}).css('display', '');
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) {
|
||||
togglers.click();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to hide the search marks again
|
||||
*/
|
||||
hideSearchWords : function() {
|
||||
$('.sidebar .this-page-menu li.highlight-link').fadeOut(300);
|
||||
$('span.highlight').removeClass('highlight');
|
||||
},
|
||||
|
||||
/**
|
||||
* make the url absolute
|
||||
*/
|
||||
makeURL : function(relativeURL) {
|
||||
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the current relative url
|
||||
*/
|
||||
getCurrentURL : function() {
|
||||
var path = document.location.pathname;
|
||||
var parts = path.split(/\//);
|
||||
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
|
||||
if (this == '..')
|
||||
parts.pop();
|
||||
});
|
||||
var url = parts.join('/');
|
||||
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// quick alias for translations
|
||||
_ = Documentation.gettext;
|
||||
|
||||
$(document).ready(function() {
|
||||
Documentation.init();
|
||||
});
|
|
@ -1,386 +0,0 @@
|
|||
/* custom stuff I put in FIXME where is it "supposed" to go? */
|
||||
|
||||
div.admonition-todo
|
||||
{
|
||||
border: 1px solid red;
|
||||
background-color: #Fdd;
|
||||
}
|
||||
|
||||
div.admonition-todo p.admonition-title
|
||||
{
|
||||
margin: 0;
|
||||
color: red;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
p.admonition-title
|
||||
{
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dl.class>dt, dl.interface>dt, dl.function>dt, dl.staticmethod>dt
|
||||
{
|
||||
font-size: 150%;
|
||||
background-color:#ddd;
|
||||
}
|
||||
|
||||
dl.method>dt
|
||||
{
|
||||
background-color: #eee;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
dl.method:hover
|
||||
{
|
||||
background-color:#ffd;
|
||||
}
|
||||
|
||||
/** end custom */
|
||||
|
||||
html {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background: #FFF url(bg-page.png) top left repeat-x;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.5;
|
||||
margin: auto;
|
||||
padding: 0px;
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
min-width: 59em;
|
||||
max-width: 70em;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
padding: 8px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* link colors and text decoration */
|
||||
|
||||
a:link {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #892601;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
/* Some headers act as anchors, don't give them a hover effect */
|
||||
|
||||
h1 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h2 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h3 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h4 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
color: #a7ce38;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #a7ce38;
|
||||
}
|
||||
|
||||
/* basic text elements */
|
||||
|
||||
div.content {
|
||||
margin-top: 20px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 50px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* heading and navigation */
|
||||
|
||||
div.header {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 85px;
|
||||
/* background: #eeeeee; */
|
||||
padding: 0 40px;
|
||||
}
|
||||
div.header h1 {
|
||||
font-size: 1.6em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
color: #0c3762;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
div.header h1 a {
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
}
|
||||
div.header h2 {
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
color: #aaa;
|
||||
border: 0;
|
||||
margin-top: -3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.header img.rightlogo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
div.title {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
div.topnav {
|
||||
/* background: #e0e0e0; */
|
||||
}
|
||||
div.topnav p {
|
||||
margin-top: 0;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 0px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
div.bottomnav {
|
||||
background: #eeeeee;
|
||||
}
|
||||
div.bottomnav p {
|
||||
margin-right: 40px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
a.uplink {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
/* contents box */
|
||||
|
||||
table.index {
|
||||
margin: 0px 0px 30px 30px;
|
||||
padding: 1px;
|
||||
border-width: 1px;
|
||||
border-style: dotted;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
table.index tr.heading {
|
||||
background-color: #e0e0e0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
table.index tr.index {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
table.index td {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
table.index a:link, table.index a:visited {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
table.index a:hover, table.index a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
|
||||
/* Haiku User Guide styles and layout */
|
||||
|
||||
/* Rounded corner boxes */
|
||||
/* Common declarations */
|
||||
div.admonition {
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
border-color: #dcdcdc;
|
||||
padding: 10px 15px 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
div.note {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.warning {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.seealso {
|
||||
background: #e4ffde;
|
||||
}
|
||||
|
||||
/* More layout and styles */
|
||||
h1 {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.0em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
div.content ul > li {
|
||||
-moz-background-clip:border;
|
||||
-moz-background-inline-policy:continuous;
|
||||
-moz-background-origin:padding;
|
||||
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
padding: 0 0 0 1.666em;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #e2e2e2;
|
||||
font-size: 1.0em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-color: #0c3762;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 0.8em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* printer only pretty stuff */
|
||||
@media print {
|
||||
.noprint {
|
||||
display: none;
|
||||
}
|
||||
/* for acronyms we want their definitions inlined at print time */
|
||||
acronym[title]:after {
|
||||
font-size: small;
|
||||
content: " (" attr(title) ")";
|
||||
font-style: italic;
|
||||
}
|
||||
/* and not have mozilla dotted underline */
|
||||
acronym {
|
||||
border: none;
|
||||
}
|
||||
div.topnav, div.bottomnav, div.header, table.index {
|
||||
display: none;
|
||||
}
|
||||
div.content {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
html {
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
margin: -1px -12px;
|
||||
padding: 0 12px;
|
||||
}
|
File diff suppressed because one or more lines are too long
16
docs/conf.py
16
docs/conf.py
|
@ -12,6 +12,7 @@
|
|||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import psutil
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
@ -28,11 +29,16 @@ autoclass_content = 'both'
|
|||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
needs_sphinx = '1.0'
|
||||
needs_sphinx = '1.1'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinxcontrib.fulltoc',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_static']
|
||||
|
@ -41,7 +47,7 @@ templates_path = ['_static']
|
|||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
@ -82,7 +88,7 @@ add_function_parentheses = True
|
|||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
add_module_names = False
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
|
@ -94,6 +100,8 @@ pygments_style = 'monokai'
|
|||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ do:
|
|||
:private-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _meta:
|
||||
|
||||
meta module
|
||||
-----------
|
||||
|
@ -28,10 +29,13 @@ doing some serious hacking.
|
|||
|
||||
.. automodule:: gnupg._meta
|
||||
:members:
|
||||
:undoc-members:
|
||||
:private-members:
|
||||
:special-members:
|
||||
:exclude-members: _agent_proc, __module__, __dict__, _decode_errors, init,
|
||||
__weakref__, _result_map, __metaclass__
|
||||
:show-inheritance:
|
||||
|
||||
.. _parsers:
|
||||
|
||||
parsers module
|
||||
--------------
|
||||
|
@ -39,10 +43,11 @@ parsers module
|
|||
These are classes for parsing both user inputs and status file descriptor
|
||||
flags from GnuPG's output. The latter are used in order to determine what our
|
||||
GnuPG process is doing and retrieve information about its operations, which
|
||||
are stored in corresponding classes in :attr:`gnupg.GPG._result_dict`. Some
|
||||
status flags aren't handled yet -- infomation on *all* of the flags (well, at
|
||||
least the documented ones…) can be found in the docs/DETAILS file in GnuPG's
|
||||
source_, which has been included here_ as well.
|
||||
are stored in corresponding classes in
|
||||
:attr:`~gnupg._meta.GPGBase._result_map`. Some status flags aren't handled yet
|
||||
-- information on *all* of the flags (well, at least the documented ones…) can
|
||||
be found in the :file:`docs/DETAILS` file in GnuPG's source_, which has been
|
||||
included here_ as well.
|
||||
|
||||
|
||||
.. automodule:: gnupg._parsers
|
||||
|
@ -52,6 +57,8 @@ source_, which has been included here_ as well.
|
|||
:show-inheritance:
|
||||
|
||||
|
||||
.. _util:
|
||||
|
||||
util module
|
||||
-----------
|
||||
|
||||
|
@ -75,51 +82,50 @@ by Steve Traugott, which in turn is a modification of the pycrypto GnuPG
|
|||
interface written by A.M. Kuchling.
|
||||
|
||||
This version is patched to sanitize untrusted inputs, due to the necessity of
|
||||
executing :class:`subprocess.Popen([...], shell=True)` in order to communicate
|
||||
with GnuPG. Several speed improvements were also made based on code profiling,
|
||||
and the API has been cleaned up to support an easier, more Pythonic,
|
||||
interaction.
|
||||
executing ``subprocess.Popen([...], shell=True)`` in order to communicate with
|
||||
GnuPG. Several speed improvements were also made based on code profiling, and
|
||||
the API has been cleaned up to support an easier, more Pythonic, interaction.
|
||||
|
||||
|
||||
Previous Authors' Documentation
|
||||
-------------------------------
|
||||
|
||||
Steve Traugott's documentation:
|
||||
|
||||
| Portions of this module are derived from A.M. Kuchling's well-designed
|
||||
| GPG.py, using Richard Jones' updated version 1.3, which can be found in
|
||||
| the pycrypto CVS repository on Sourceforge:
|
||||
|
|
||||
| http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
|
||||
|
|
||||
| This module is *not* forward-compatible with amk's; some of the old
|
||||
| interface has changed. For instance, since I've added decrypt
|
||||
| functionality, I elected to initialize with a 'gpghome' argument instead
|
||||
| of 'keyring', so that gpg can find both the public and secret keyrings.
|
||||
| I've also altered some of the returned objects in order for the caller to
|
||||
| not have to know as much about the internals of the result classes.
|
||||
|
|
||||
| While the rest of ISconf is released under the GPL, I am releasing this
|
||||
| single file under the same terms that A.M. Kuchling used for pycrypto.
|
||||
|
|
||||
| Steve Traugott, stevegt@terraluna.org
|
||||
| Thu Jun 23 21:27:20 PDT 2005
|
||||
|
|
||||
| Portions of this module are derived from A.M. Kuchling's well-designed
|
||||
| GPG.py, using Richard Jones' updated version 1.3, which can be found in
|
||||
| the pycrypto CVS repository on Sourceforge:
|
||||
|
|
||||
| http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
|
||||
|
|
||||
| This module is *not* forward-compatible with amk's; some of the old
|
||||
| interface has changed. For instance, since I've added decrypt
|
||||
| functionality, I elected to initialize with a 'gpghome' argument instead
|
||||
| of 'keyring', so that gpg can find both the public and secret keyrings.
|
||||
| I've also altered some of the returned objects in order for the caller to
|
||||
| not have to know as much about the internals of the result classes.
|
||||
|
|
||||
| While the rest of ISconf is released under the GPL, I am releasing this
|
||||
| single file under the same terms that A.M. Kuchling used for pycrypto.
|
||||
|
|
||||
| Steve Traugott, stevegt@terraluna.org
|
||||
| Thu Jun 23 21:27:20 PDT 2005
|
||||
|
||||
|
||||
Vinay Sajip's documentation:
|
||||
|
||||
| This version of the module has been modified from Steve Traugott's version
|
||||
| (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
|
||||
| Vinay Sajip to make use of the subprocess module (Steve's version uses
|
||||
| os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid
|
||||
| confusion with the previous versions.
|
||||
|
|
||||
| A unittest harness (test_gnupg.py) has also been added.
|
||||
|
|
||||
| Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
|
||||
|
|
||||
| This version of the module has been modified from Steve Traugott's version
|
||||
| (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
|
||||
| Vinay Sajip to make use of the subprocess module (Steve's version uses
|
||||
| os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid
|
||||
| confusion with the previous versions.
|
||||
|
|
||||
| A unittest harness (test_gnupg.py) has also been added.
|
||||
|
|
||||
| Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
|
||||
|
||||
|
||||
.. _GnuPG: http://gnupg.org
|
||||
.. _python-gnupg: https://code.google.com/p/python-gnupg/
|
||||
.. _source: http://http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=shortlog;h=refs/heads/master
|
||||
.. _here: ./DETAILS.html
|
||||
.. _here: ./_static/DETAILS.html
|
||||
|
|
|
@ -21,7 +21,7 @@ Contents:
|
|||
Source, license, & bug reports
|
||||
==============================
|
||||
The source code which was used to generate this documentation is accessible by
|
||||
clicking the little [source]_ links next to the docs. Current source code can
|
||||
clicking the little `source` links next to the docs. Current source code can
|
||||
be found in this github repository_. The **master** branch always reflects the
|
||||
latest release, all releases are tagged with signed, annotated git tags, and
|
||||
the **develop** branch represents the state of the next release.
|
||||
|
|
|
@ -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)
|
|
@ -8,31 +8,31 @@
|
|||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from . import gnupg
|
||||
from . import copyleft
|
||||
from . import _ansistrm
|
||||
from . import _logger
|
||||
from . import _meta
|
||||
from . import _parsers
|
||||
from . import _util
|
||||
from .gnupg import GPG
|
||||
from . import gnupg
|
||||
from . import copyleft
|
||||
from . import _ansistrm
|
||||
from . import _logger
|
||||
from . import _meta
|
||||
from . import _parsers
|
||||
from . import _util
|
||||
from .gnupg import GPG
|
||||
from ._version import get_versions
|
||||
|
||||
__version__ = get_versions()['version']
|
||||
__authors__ = copyleft.authors
|
||||
__license__ = copyleft.full_text
|
||||
__version__ = get_versions()['version']
|
||||
__authors__ = copyleft.authors
|
||||
__license__ = copyleft.full_text
|
||||
__copyleft__ = copyleft.copyright
|
||||
|
||||
## do not set __package__ = "gnupg", else we will end up with
|
||||
|
|
|
@ -17,10 +17,7 @@
|
|||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
'''log.py
|
||||
----------
|
||||
Logging module for python-gnupg.
|
||||
'''
|
||||
'''Logging module for python-gnupg.'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
@ -51,17 +48,23 @@ def status(self, message, *args, **kwargs):
|
|||
def create_logger(level=logging.NOTSET):
|
||||
"""Create a logger for python-gnupg at a specific message level.
|
||||
|
||||
:type level: int or str
|
||||
:param level: A string or an integer for the lowest level to log.
|
||||
Available levels:
|
||||
int str description
|
||||
0 NOTSET Disable all logging.
|
||||
9 GNUPG Log GnuPG's internal status messages.
|
||||
10 DEBUG Log module level debuging messages.
|
||||
20 INFO Normal user-level messages.
|
||||
30 WARN Warning messages.
|
||||
40 ERROR Error messages and tracebacks.
|
||||
50 CRITICAL Unhandled exceptions and tracebacks.
|
||||
:type level: :obj:`int` or :obj:`str`
|
||||
:param level: A string or an integer for the lowest level to include in
|
||||
logs.
|
||||
|
||||
**Available levels:**
|
||||
|
||||
==== ======== ========================================
|
||||
int str description
|
||||
==== ======== ========================================
|
||||
0 NOTSET Disable all logging.
|
||||
9 GNUPG Log GnuPG's internal status messages.
|
||||
10 DEBUG Log module level debuging messages.
|
||||
20 INFO Normal user-level messages.
|
||||
30 WARN Warning messages.
|
||||
40 ERROR Error messages and tracebacks.
|
||||
50 CRITICAL Unhandled exceptions and tracebacks.
|
||||
==== ======== ========================================
|
||||
"""
|
||||
_test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test')
|
||||
_now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
|
@ -75,21 +78,19 @@ def create_logger(level=logging.NOTSET):
|
|||
if level > logging.NOTSET:
|
||||
logging.basicConfig(level=level, filename=_fn,
|
||||
filemode="a", format=_fmt)
|
||||
logging.captureWarnings(True)
|
||||
logging.logThreads = True
|
||||
|
||||
if hasattr(logging,'captureWarnings'):
|
||||
logging.captureWarnings(True)
|
||||
colouriser = _ansistrm.ColorizingStreamHandler
|
||||
colouriser.level_map[9] = (None, 'blue', False)
|
||||
colouriser.level_map[10] = (None, 'cyan', False)
|
||||
handler = colouriser(stream=sys.stderr)
|
||||
handler = colouriser(sys.stderr)
|
||||
handler.setLevel(level)
|
||||
|
||||
formatr = logging.Formatter(_fmt)
|
||||
handler.setFormatter(formatr)
|
||||
print("Starting the logger...")
|
||||
else:
|
||||
handler = NullHandler()
|
||||
print("GnuPG logging disabled...")
|
||||
|
||||
log = logging.getLogger('gnupg')
|
||||
log.addHandler(handler)
|
||||
|
|
219
gnupg/_meta.py
219
gnupg/_meta.py
|
@ -7,20 +7,18 @@
|
|||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
'''meta.py
|
||||
----------
|
||||
Meta and base classes for hiding internal functions, and controlling attribute
|
||||
creation and handling.
|
||||
'''Meta and base classes for hiding internal functions, and controlling
|
||||
attribute creation and handling.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
@ -52,6 +50,10 @@ class GPGMeta(type):
|
|||
Detects running gpg-agent processes and the presence of a pinentry
|
||||
program, and disables pinentry so that python-gnupg can write the
|
||||
passphrase to the controlled GnuPG process without killing the agent.
|
||||
|
||||
:attr _agent_proc: If a :program:`gpg-agent` process is currently running
|
||||
for the effective userid, then **_agent_proc** will be
|
||||
set to a ``psutil.Process`` for that process.
|
||||
"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
|
@ -69,13 +71,13 @@ class GPGMeta(type):
|
|||
|
||||
If there is a matching gpg-agent process, set a :class:`psutil.Process`
|
||||
instance containing the gpg-agent process' information to
|
||||
:attr:`GPG._agent_proc`.
|
||||
``cls._agent_proc``.
|
||||
|
||||
:returns: True if there exists a gpg-agent process running under the
|
||||
same effective user ID as that of this program. Otherwise,
|
||||
returns None.
|
||||
"""
|
||||
identity = os.getresuid()
|
||||
identity = psutil.Process(os.getpid()).uids
|
||||
for proc in psutil.process_iter():
|
||||
if (proc.name == "gpg-agent") and proc.is_running:
|
||||
log.debug("Found gpg-agent process with pid %d" % proc.pid)
|
||||
|
@ -87,8 +89,13 @@ class GPGMeta(type):
|
|||
|
||||
|
||||
class GPGBase(object):
|
||||
"""Base class for property storage and to control process initialisation."""
|
||||
"""Base class for storing properties and controlling process initialisation.
|
||||
|
||||
:const _result_map: A *dict* containing classes from
|
||||
:mod:`~gnupg._parsers`, used for parsing results
|
||||
obtained from GnuPG commands.
|
||||
:const _decode_errors: How to handle encoding errors.
|
||||
"""
|
||||
__metaclass__ = GPGMeta
|
||||
_decode_errors = 'strict'
|
||||
_result_map = { 'crypt': _parsers.Crypt,
|
||||
|
@ -103,7 +110,28 @@ class GPGBase(object):
|
|||
def __init__(self, binary=None, home=None, keyring=None, secring=None,
|
||||
use_agent=False, default_preference_list=None,
|
||||
verbose=False, options=None):
|
||||
"""Create a ``GPGBase``.
|
||||
|
||||
This class is used to set up properties for controlling the behaviour
|
||||
of configuring various options for GnuPG, such as setting GnuPG's
|
||||
**homedir** , and the paths to its **binary** and **keyring** .
|
||||
|
||||
:const binary: (:obj:`str`) The full path to the GnuPG binary.
|
||||
|
||||
:ivar homedir: (:class:`~gnupg._util.InheritableProperty`) The full
|
||||
path to the current setting for the GnuPG
|
||||
``--homedir``.
|
||||
|
||||
:ivar _generated_keys: (:class:`~gnupg._util.InheritableProperty`)
|
||||
Controls setting the directory for storing any
|
||||
keys which are generated with
|
||||
:meth:`~gnupg.GPG.gen_key`.
|
||||
|
||||
:ivar str keyring: The filename in **homedir** to use as the keyring
|
||||
file for public keys.
|
||||
:ivar str secring: The filename in **homedir** to use as the keyring
|
||||
file for secret keys.
|
||||
"""
|
||||
self.binary = _util._find_binary(binary)
|
||||
self.homedir = home if home else _util._conf
|
||||
pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg'
|
||||
|
@ -125,7 +153,7 @@ class GPGBase(object):
|
|||
self._filesystemencoding = encodings.normalize_encoding(
|
||||
sys.getfilesystemencoding().lower())
|
||||
|
||||
self._keyserver = 'hkp://subkeys.pgp.net'
|
||||
self._keyserver = 'hkp://wwwkeys.pgp.net'
|
||||
self.__generated_keys = os.path.join(self.homedir, 'generated-keys')
|
||||
|
||||
try:
|
||||
|
@ -136,8 +164,8 @@ class GPGBase(object):
|
|||
if self.options is not None:
|
||||
assert isinstance(self.options, str), "options not string"
|
||||
except (AssertionError, AttributeError) as ae:
|
||||
log.error("GPGBase.__init__(): %s" % ae.message)
|
||||
raise RuntimeError(ae.message)
|
||||
log.error("GPGBase.__init__(): %s" % str(ae))
|
||||
raise RuntimeError(str(ae))
|
||||
else:
|
||||
if verbose is True:
|
||||
# The caller wants logging, but we need a valid --debug-level
|
||||
|
@ -154,15 +182,14 @@ class GPGBase(object):
|
|||
self.__remove_path__('pinentry')
|
||||
|
||||
def __remove_path__(self, prog=None, at_exit=True):
|
||||
"""Remove a the directories containing a program from the system's
|
||||
``$PATH``. If :attr:`GPG.binary` is in a directory being removed, it
|
||||
is symlinked to './gpg'
|
||||
"""Remove the directories containing a program from the system's
|
||||
``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it
|
||||
is linked to :file:'./gpg' in the current directory.
|
||||
|
||||
:param str prog: The program to remove from ``$PATH``.
|
||||
|
||||
:param bool at_exit: Add the program back into the ``$PATH`` when the
|
||||
Python interpreter exits, and delete any symlinks
|
||||
to :attr:`GPG.binary` which were created.
|
||||
to ``GPGBase.binary`` which were created.
|
||||
"""
|
||||
#: A list of ``$PATH`` entries which were removed to disable pinentry.
|
||||
self._removed_path_entries = []
|
||||
|
@ -173,7 +200,7 @@ class GPGBase(object):
|
|||
try:
|
||||
program = _util._which(prog)[0]
|
||||
except (OSError, IOError, IndexError) as err:
|
||||
log.err(err.message)
|
||||
log.err(str(err))
|
||||
log.err("Cannot find program '%s', not changing PATH." % prog)
|
||||
return
|
||||
|
||||
|
@ -222,7 +249,7 @@ class GPGBase(object):
|
|||
|
||||
@staticmethod
|
||||
def update_path(environment, path):
|
||||
"""Add paths to the string at os.environ['PATH'].
|
||||
"""Add paths to the string at ``os.environ['PATH']``.
|
||||
|
||||
:param str environment: The environment mapping to update.
|
||||
:param list path: A list of strings to update the PATH with.
|
||||
|
@ -270,7 +297,7 @@ class GPGBase(object):
|
|||
|
||||
Note that "original state" does not mean the default preference
|
||||
list for whichever version of GnuPG is being used. It means the
|
||||
default preference list defined by :attr:`GPGBase._preferences`.
|
||||
default preference list defined by :attr:`GPGBase._prefs`.
|
||||
|
||||
Using BZIP2 is avoided due to not interacting well with some versions
|
||||
of GnuPG>=2.0.0.
|
||||
|
@ -293,14 +320,14 @@ class GPGBase(object):
|
|||
should contain the desired keyserver protocol
|
||||
which is supported by the keyserver, for example,
|
||||
``'hkps://keys.mayfirst.org'``. The default
|
||||
keyserver is ``'hkp://subkeys.pgp.net'``.
|
||||
keyserver is ``'hkp://wwwkeys.pgp.net'``.
|
||||
"""
|
||||
self._keyserver = location
|
||||
|
||||
@keyserver.deleter
|
||||
def keyserver(self):
|
||||
"""Reset the keyserver to the default setting."""
|
||||
self._keyserver = 'hkp://subkeys.pgp.net'
|
||||
self._keyserver = 'hkp://wwwkeys.pgp.net'
|
||||
|
||||
def _homedir_getter(self):
|
||||
"""Get the directory currently being used as GnuPG's homedir.
|
||||
|
@ -321,11 +348,11 @@ class GPGBase(object):
|
|||
created. Lastly, the ``direcory`` will be checked that the EUID has
|
||||
read and write permissions for it.
|
||||
|
||||
:param str homedir: A relative or absolute path to the directory to use
|
||||
for storing/accessing GnuPG's files, including
|
||||
:param str directory: A relative or absolute path to the directory to
|
||||
use for storing/accessing GnuPG's files, including
|
||||
keyrings and the trustdb.
|
||||
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
|
||||
use.
|
||||
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
|
||||
directory to use.
|
||||
"""
|
||||
if not directory:
|
||||
log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'"
|
||||
|
@ -346,8 +373,8 @@ class GPGBase(object):
|
|||
except AssertionError as ae:
|
||||
msg = ("Unable to set '%s' as GnuPG homedir" % directory)
|
||||
log.debug("GPGBase.homedir.setter(): %s" % msg)
|
||||
log.debug(ae.message)
|
||||
raise RuntimeError(ae.message)
|
||||
log.debug(str(ae))
|
||||
raise RuntimeError(str(ae))
|
||||
else:
|
||||
log.info("Setting homedir to '%s'" % hd)
|
||||
self._homedir = hd
|
||||
|
@ -365,17 +392,18 @@ class GPGBase(object):
|
|||
def _generated_keys_setter(self, directory):
|
||||
"""Set the directory for storing generated keys.
|
||||
|
||||
If unspecified, use $GNUPGHOME/generated-keys. If specified, ensure
|
||||
that the ``directory`` does not contain various shell escape
|
||||
characters. If ``directory`` is not found, it will be automatically
|
||||
created. Lastly, the ``direcory`` will be checked that the EUID has
|
||||
read and write permissions for it.
|
||||
If unspecified, use
|
||||
:meth:`~gnupg._meta.GPGBase.homedir`/generated-keys. If specified,
|
||||
ensure that the ``directory`` does not contain various shell escape
|
||||
characters. If ``directory`` isn't found, it will be automatically
|
||||
created. Lastly, the ``directory`` will be checked to ensure that the
|
||||
current EUID has read and write permissions for it.
|
||||
|
||||
:param str directory: A relative or absolute path to the directory to
|
||||
use for storing/accessing GnuPG's files, including keyrings and
|
||||
the trustdb.
|
||||
:raises: :exc:`RuntimeError` if unable to find a suitable directory to
|
||||
use.
|
||||
:raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
|
||||
directory to use.
|
||||
"""
|
||||
if not directory:
|
||||
directory = os.path.join(self.homedir, 'generated-keys')
|
||||
|
@ -397,8 +425,8 @@ class GPGBase(object):
|
|||
except AssertionError as ae:
|
||||
msg = ("Unable to set '%s' as generated keys dir" % directory)
|
||||
log.debug("GPGBase._generated_keys_setter(): %s" % msg)
|
||||
log.debug(ae.message)
|
||||
raise RuntimeError(ae.message)
|
||||
log.debug(str(ae))
|
||||
raise RuntimeError(str(ae))
|
||||
else:
|
||||
log.info("Setting homedir to '%s'" % hd)
|
||||
self.__generated_keys = hd
|
||||
|
@ -407,10 +435,11 @@ class GPGBase(object):
|
|||
_generated_keys_setter)
|
||||
|
||||
def _make_args(self, args, passphrase=False):
|
||||
"""Make a list of command line elements for GPG. The value of ``args``
|
||||
will be appended only if it passes the checks in
|
||||
:func:`parsers._sanitise`. The ``passphrase`` argument needs to be True
|
||||
if a passphrase will be sent to GPG, else False.
|
||||
"""Make a list of command line elements for GPG.
|
||||
|
||||
The value of ``args`` will be appended only if it passes the checks in
|
||||
:func:`gnupg._parsers._sanitise`. The ``passphrase`` argument needs to
|
||||
be True if a passphrase will be sent to GnuPG, else False.
|
||||
|
||||
:param list args: A list of strings of options and flags to pass to
|
||||
``GPG.binary``. This is input safe, meaning that
|
||||
|
@ -489,12 +518,13 @@ class GPGBase(object):
|
|||
Calls methods on the response object for each valid token found, with
|
||||
the arg being the remainder of the status line.
|
||||
|
||||
:param stream: A byte-stream, file handle, or :class:`subprocess.PIPE`
|
||||
to parse the for status codes from the GnuPG process.
|
||||
:param stream: A byte-stream, file handle, or a
|
||||
:data:`subprocess.PIPE` for parsing the status codes
|
||||
from the GnuPG process.
|
||||
|
||||
:param result: The result parser class from :mod:`_parsers` with which
|
||||
to call ``handle_status`` and parse the output of
|
||||
``stream``.
|
||||
:param result: The result parser class from :mod:`~gnupg._parsers` ―
|
||||
the ``handle_status()`` method of that class will be
|
||||
called in order to parse the output of ``stream``.
|
||||
"""
|
||||
lines = []
|
||||
while True:
|
||||
|
@ -535,8 +565,8 @@ class GPGBase(object):
|
|||
and stored as ``result.data``.
|
||||
|
||||
:param stream: An open file-like object to read() from.
|
||||
:param result: An instance of one of the result parsing classes from
|
||||
:attr:`GPGBase._result_mapping`.
|
||||
:param result: An instance of one of the :ref:`result parsing classes
|
||||
<parsers>` from :const:`~gnupg._meta.GPGBase._result_map`.
|
||||
"""
|
||||
chunks = []
|
||||
log.debug("Reading data from stream %r..." % stream.__repr__())
|
||||
|
@ -604,13 +634,13 @@ class GPGBase(object):
|
|||
:param str keyids: A space-delimited string containing the keyids to
|
||||
request.
|
||||
:param str keyserver: The keyserver to request the ``keyids`` from;
|
||||
defaults to :property:`gnupg.GPG.keyserver`.
|
||||
defaults to `gnupg.GPG.keyserver`.
|
||||
"""
|
||||
if not keyserver:
|
||||
keyserver = self.keyserver
|
||||
|
||||
args = ['--keyserver {}'.format(keyserver),
|
||||
'--recv-keys {}'.format(keyids)]
|
||||
args = ['--keyserver {0}'.format(keyserver),
|
||||
'--recv-keys {0}'.format(keyids)]
|
||||
log.info('Requesting keys from %s: %s' % (keyserver, keyids))
|
||||
|
||||
result = self._result_map['import'](self)
|
||||
|
@ -631,9 +661,10 @@ class GPGBase(object):
|
|||
:param bool detach: If True, create a detached signature.
|
||||
:param bool binary: If True, do not ascii armour the output.
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config
|
||||
digestname``. The default, if unspecified, is
|
||||
``'SHA512'``.
|
||||
"""
|
||||
log.debug("_sign_file():")
|
||||
if binary:
|
||||
|
@ -665,7 +696,7 @@ class GPGBase(object):
|
|||
_util._write_passphrase(proc.stdin, passphrase, self._encoding)
|
||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
||||
except IOError as ioe:
|
||||
log.exception("Error writing message: %s" % ioe.message)
|
||||
log.exception("Error writing message: %s" % str(ioe))
|
||||
writer = None
|
||||
self._collect_output(proc, result, writer, proc.stdin)
|
||||
return result
|
||||
|
@ -681,46 +712,55 @@ class GPGBase(object):
|
|||
cipher_algo='AES256',
|
||||
digest_algo='SHA512',
|
||||
compress_algo='ZLIB'):
|
||||
"""Encrypt the message read from the file-like object ``data``.
|
||||
"""Encrypt the message read from the file-like object **data**.
|
||||
|
||||
:param str data: The file or bytestream to encrypt.
|
||||
|
||||
:param str recipients: The recipients to encrypt to. Recipients must
|
||||
be specified keyID/fingerprint. Care should be taken in Python2.x
|
||||
to make sure that the given fingerprint is in fact a string and
|
||||
not a unicode object.
|
||||
be specified keyID/fingerprint.
|
||||
|
||||
.. warning:: Care should be taken in Python2 to make sure that the
|
||||
given fingerprints for **recipients** are in fact strings
|
||||
and not unicode objects.
|
||||
|
||||
:param str default_key: The keyID/fingerprint of the key to use for
|
||||
signing. If given, ``data`` will be encrypted and signed.
|
||||
signing. If given, **data** will be encrypted
|
||||
*and* signed.
|
||||
|
||||
:param str passphrase: If given, and ``default_key`` is also given,
|
||||
use this passphrase to unlock the secret portion of the
|
||||
``default_key`` to sign the encrypted ``data``. Otherwise, if
|
||||
``default_key`` is not given, but ``symmetric=True``, then use
|
||||
this passphrase as the passphrase for symmetric
|
||||
encryption. Signing and symmetric encryption should *not* be
|
||||
combined when sending the ``data`` to other recipients, else the
|
||||
passphrase to the secret key would be shared with them.
|
||||
:param str passphrase: If given, and **default_key** is also given,
|
||||
use this passphrase to unlock the secret
|
||||
portion of the **default_key** to sign the
|
||||
encrypted **data**. Otherwise, if
|
||||
**default_key** is not given, but **symmetric**
|
||||
is ``True``, then use this passphrase as the
|
||||
passphrase for symmetric encryption. Signing
|
||||
and symmetric encryption should *not* be
|
||||
combined when sending the **data** to other
|
||||
recipients, else the passphrase to the secret
|
||||
key would be shared with them.
|
||||
|
||||
:param bool armor: If True, ascii armor the output; otherwise, the
|
||||
output will be in binary format. (Default: True)
|
||||
output will be in binary format. (Default: True)
|
||||
|
||||
:param bool encrypt: If True, encrypt the ``data`` using the
|
||||
``recipients`` public keys. (Default: True)
|
||||
:param bool encrypt: If True, encrypt the **data** using the
|
||||
**recipients** public keys. (Default: True)
|
||||
|
||||
:param bool symmetric: If True, encrypt the ``data`` to ``recipients``
|
||||
using a symmetric key. See the ``passphrase`` parameter. Symmetric
|
||||
encryption and public key encryption can be used simultaneously,
|
||||
and will result in a ciphertext which is decryptable with either
|
||||
the symmetric ``passphrase`` or one of the corresponding private
|
||||
keys.
|
||||
:param bool symmetric: If True, encrypt the **data** to **recipients**
|
||||
using a symmetric key. See the **passphrase**
|
||||
parameter. Symmetric encryption and public key
|
||||
encryption can be used simultaneously, and will
|
||||
result in a ciphertext which is decryptable
|
||||
with either the symmetric **passphrase** or one
|
||||
of the corresponding private keys.
|
||||
|
||||
:param bool always_trust: If True, ignore trust warnings on recipient
|
||||
keys. If False, display trust warnings. (default: True)
|
||||
:param bool always_trust: If True, ignore trust warnings on
|
||||
**recipients** keys. If False, display trust
|
||||
warnings. (default: True)
|
||||
|
||||
:param str output: The output file to write to. If not specified, the
|
||||
encrypted output is returned, and thus should be stored as an
|
||||
object in Python. For example:
|
||||
encrypted output is returned, and thus should be
|
||||
stored as an object in Python. For example:
|
||||
|
||||
|
||||
>>> import shutil
|
||||
>>> import gnupg
|
||||
|
@ -742,17 +782,20 @@ class GPGBase(object):
|
|||
'The crow flies at midnight.'
|
||||
|
||||
:param str cipher_algo: The cipher algorithm to use. To see available
|
||||
algorithms with your version of GnuPG, do:
|
||||
``$ gpg --with-colons --list-config ciphername``.
|
||||
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
|
||||
algorithms with your version of GnuPG, do:
|
||||
:command:`$ gpg --with-colons --list-config
|
||||
ciphername`. The default **cipher_algo**, if
|
||||
unspecified, is ``'AES256'``.
|
||||
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
hashes your GnuPG is capable of using, do:
|
||||
:command:`$ gpg --with-colons --list-config
|
||||
digestname`. The default, if unspecified, is
|
||||
``'SHA512'``.
|
||||
|
||||
:param str compress_algo: The compression algorithm to use. Can be one
|
||||
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
|
||||
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or
|
||||
``'Uncompressed'``.
|
||||
"""
|
||||
args = []
|
||||
|
||||
|
|
|
@ -7,25 +7,28 @@
|
|||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
'''parsers.py
|
||||
-------------
|
||||
Classes for parsing GnuPG status messages and sanitising commandline options.
|
||||
'''Classes for parsing GnuPG status messages and sanitising commandline
|
||||
options.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
import re
|
||||
|
||||
from . import _util
|
||||
|
@ -50,8 +53,8 @@ def _check_keyserver(location):
|
|||
:param str location: A string containing the default keyserver. This
|
||||
should contain the desired keyserver protocol which
|
||||
is supported by the keyserver, for example, the
|
||||
default is ``'hkp://subkeys.pgp.net'``.
|
||||
:rtype: str or None
|
||||
default is ``'hkp://wwwkeys .pgp.net'``.
|
||||
:rtype: :obj:`str` or :obj:`None`
|
||||
:returns: A string specifying the protocol and keyserver hostname, if the
|
||||
checks passed. If not, returns None.
|
||||
"""
|
||||
|
@ -74,10 +77,11 @@ def _check_keyserver(location):
|
|||
def _check_preferences(prefs, pref_type=None):
|
||||
"""Check cipher, digest, and compression preference settings.
|
||||
|
||||
MD5 is not allowed. This is not 1994.[0] SHA1 is allowed grudgingly.[1]
|
||||
MD5 is not allowed. This is `not 1994`__. SHA1 is allowed_ grudgingly_.
|
||||
|
||||
[0]: http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
|
||||
[1]: http://eprint.iacr.org/2008/469.pdf
|
||||
__ http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
|
||||
.. _allowed: http://eprint.iacr.org/2008/469.pdf
|
||||
.. _grudgingly: https://www.schneier.com/blog/archives/2012/10/when_will_we_se.html
|
||||
"""
|
||||
if prefs is None: return
|
||||
|
||||
|
@ -115,8 +119,8 @@ def _check_preferences(prefs, pref_type=None):
|
|||
return allowed
|
||||
|
||||
def _fix_unsafe(shell_input):
|
||||
"""Find characters used to escape from a string into a shell, and wrap them
|
||||
in quotes if they exist. Regex pilfered from python-3.x shlex module.
|
||||
"""Find characters used to escape from a string into a shell, and wrap them in
|
||||
quotes if they exist. Regex pilfered from Python3 :mod:`shlex` module.
|
||||
|
||||
:param str shell_input: The input intended for the GnuPG process.
|
||||
"""
|
||||
|
@ -161,11 +165,15 @@ def _is_allowed(input):
|
|||
class and its name will need to be added to this
|
||||
set.
|
||||
|
||||
:rtype: Exception or str
|
||||
:raise: :exc:UsageError if ``_allowed`` is not a subset of ``_possible``.
|
||||
ProtectedOption if ``input`` is not in the set ``_allowed``.
|
||||
:return: The original parameter ``input``, unmodified and unsanitized,
|
||||
if no errors occur.
|
||||
:raises: :exc:`UsageError` if **input** is not a subset of the hard-coded
|
||||
set of all GnuPG options in :func:`_get_all_gnupg_options`.
|
||||
|
||||
:exc:`ProtectedOption` if **input** is not in the set of allowed
|
||||
options.
|
||||
|
||||
:rtype: str
|
||||
:return: The original **input** parameter, unmodified and unsanitized, if
|
||||
no errors occur.
|
||||
"""
|
||||
gnupg_options = _get_all_gnupg_options()
|
||||
allowed = _get_options_group("allowed")
|
||||
|
@ -220,12 +228,12 @@ def _is_hex(string):
|
|||
def _is_string(thing):
|
||||
"""Python character arrays are a mess.
|
||||
|
||||
If Python2, check if ``thing`` is a ``unicode()`` or ``str()``.
|
||||
If Python3, check if ``thing`` is a ``str()``.
|
||||
If Python2, check if **thing** is an :obj:`unicode` or a :obj:`str`.
|
||||
If Python3, check if **thing** is a :obj:`str`.
|
||||
|
||||
:param thing: The thing to check.
|
||||
:returns: ``True`` if ``thing`` is a "string" according to whichever
|
||||
version of Python we're running in.
|
||||
:returns: ``True`` if **thing** is a string according to whichever version
|
||||
of Python we're running in.
|
||||
"""
|
||||
if _util._py3k: return isinstance(thing, str)
|
||||
else: return isinstance(thing, basestring)
|
||||
|
@ -238,18 +246,20 @@ def _sanitise(*args):
|
|||
sanitised, allowed options.
|
||||
|
||||
Each new option that we support that is not a boolean, but instead has
|
||||
some extra inputs, i.e. "--encrypt-file foo.txt", will need some basic
|
||||
safety checks added here.
|
||||
some additional inputs following it, i.e. "--encrypt-file foo.txt", will
|
||||
need some basic safety checks added here.
|
||||
|
||||
GnuPG has three-hundred and eighteen commandline flags. Also, not all
|
||||
implementations of OpenPGP parse PGP packets and headers in the same way,
|
||||
so there is added potential there for messing with calls to GPG.
|
||||
|
||||
For information on the PGP message format specification, see:
|
||||
https://www.ietf.org/rfc/rfc1991.txt
|
||||
For information on the PGP message format specification, see
|
||||
:rfc:`1991`.
|
||||
|
||||
If you're asking, "Is this *really* necessary?": No, not really -- we could
|
||||
just do a check as described here: https://xkcd.com/1181/
|
||||
just follow the security precautions recommended by `this xkcd`__.
|
||||
|
||||
__ https://xkcd.com/1181/
|
||||
|
||||
:param str args: (optional) The boolean arguments which will be passed to
|
||||
the GnuPG process.
|
||||
|
@ -260,22 +270,23 @@ def _sanitise(*args):
|
|||
## see TODO file, tag :cleanup:sanitise:
|
||||
|
||||
def _check_option(arg, value):
|
||||
"""
|
||||
Check that a single :param:arg is an allowed option. If it is allowed,
|
||||
quote out any escape characters in :param:values, and add the pair to
|
||||
:ivar:sanitised.
|
||||
"""Check that a single ``arg`` is an allowed option.
|
||||
|
||||
If it is allowed, quote out any escape characters in ``value``, and
|
||||
add the pair to :ivar:`sanitised`. Otherwise, drop them.
|
||||
|
||||
:param str arg: The arguments which will be passed to the GnuPG
|
||||
process, and, optionally their corresponding values.
|
||||
The values are any additional arguments following the
|
||||
GnuPG option or flag. For example, if we wanted to pass
|
||||
"--encrypt --recipient isis@leap.se" to gpg, then
|
||||
"--encrypt" would be an arg without a value, and
|
||||
"--recipient" would also be an arg, with a value of
|
||||
"isis@leap.se".
|
||||
GnuPG option or flag. For example, if we wanted to
|
||||
pass ``"--encrypt --recipient isis@leap.se"`` to
|
||||
GnuPG, then ``"--encrypt"`` would be an arg without a
|
||||
value, and ``"--recipient"`` would also be an arg,
|
||||
with a value of ``"isis@leap.se"``.
|
||||
|
||||
:ivar list checked: The sanitised, allowed options and values.
|
||||
:rtype: str
|
||||
:returns: A string of the items in ``checked`` delimited by spaces.
|
||||
:returns: A string of the items in ``checked``, delimited by spaces.
|
||||
"""
|
||||
checked = str()
|
||||
none_options = _get_options_group("none_options")
|
||||
|
@ -290,7 +301,7 @@ def _sanitise(*args):
|
|||
flag = _is_allowed(arg)
|
||||
assert flag is not None, "_check_option(): got None for flag"
|
||||
except (AssertionError, ProtectedOption) as error:
|
||||
log.warn("_check_option(): %s" % error.message)
|
||||
log.warn("_check_option(): %s" % str(error))
|
||||
else:
|
||||
checked += (flag + ' ')
|
||||
|
||||
|
@ -298,7 +309,7 @@ def _sanitise(*args):
|
|||
values = value.split(' ')
|
||||
for v in values:
|
||||
## these can be handled separately, without _fix_unsafe(),
|
||||
## because they are only allowed if the pass the regex
|
||||
## because they are only allowed if they pass the regex
|
||||
if (flag in none_options) and (v is None):
|
||||
continue
|
||||
|
||||
|
@ -332,8 +343,12 @@ def _sanitise(*args):
|
|||
|
||||
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
|
||||
'--decrypt-files', '--import', '--verify']:
|
||||
if _util._is_file(val): checked += (val + " ")
|
||||
else: log.debug("%s not file: %s" % (flag, val))
|
||||
if ( (_util._is_file(val))
|
||||
or
|
||||
((flag == '--verify') and (val == '-')) ):
|
||||
checked += (val + " ")
|
||||
else:
|
||||
log.debug("%s not file: %s" % (flag, val))
|
||||
|
||||
elif flag in ['--cipher-algo', '--personal-cipher-prefs',
|
||||
'--personal-cipher-preferences']:
|
||||
|
@ -372,7 +387,8 @@ def _sanitise(*args):
|
|||
groups[last] = str(filo.pop())
|
||||
## accept the read-from-stdin arg:
|
||||
if len(filo) >= 1 and filo[len(filo)-1] == '-':
|
||||
groups[last] += str(' - \'\'') ## gross hack
|
||||
groups[last] += str(' - ') ## gross hack
|
||||
filo.pop()
|
||||
else:
|
||||
groups[last] = str()
|
||||
while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
|
||||
|
@ -570,21 +586,22 @@ def _get_all_gnupg_options():
|
|||
|
||||
This is hardcoded within a local scope to reduce the chance of a tampered
|
||||
GnuPG binary reporting falsified option sets, i.e. because certain options
|
||||
(namedly the '--no-options' option, which prevents the usage of gpg.conf
|
||||
(namedly the ``--no-options`` option, which prevents the usage of gpg.conf
|
||||
files) are necessary and statically specified in
|
||||
:meth:`gnupg.GPG._makeargs`, if the inputs into Python are already
|
||||
controlled, and we were to summon the GnuPG binary to ask it for its
|
||||
options, it would be possible to receive a falsified options set missing
|
||||
the '--no-options' option in response. This seems unlikely, and the method
|
||||
is stupid and ugly, but at least we'll never have to debug whether or not
|
||||
an option *actually* disappeared in a different GnuPG version, or some
|
||||
funny business is happening.
|
||||
:meth:`gnupg._meta.GPGBase._make_args`, if the inputs into Python are
|
||||
already controlled, and we were to summon the GnuPG binary to ask it for
|
||||
its options, it would be possible to receive a falsified options set
|
||||
missing the ``--no-options`` option in response. This seems unlikely, and
|
||||
the method is stupid and ugly, but at least we'll never have to debug
|
||||
whether or not an option *actually* disappeared in a different GnuPG
|
||||
version, or some funny business is happening.
|
||||
|
||||
These are the options as of GnuPG 1.4.12; the current stable branch of the
|
||||
2.1.x tree contains a few more -- if you need them you'll have to add them
|
||||
in here.
|
||||
|
||||
:ivar frozenset gnupg_options: All known GPG options and flags.
|
||||
:type gnupg_options: frozenset
|
||||
:ivar gnupg_options: All known GPG options and flags.
|
||||
:rtype: frozenset
|
||||
:returns: ``gnupg_options``
|
||||
"""
|
||||
|
@ -787,8 +804,8 @@ def progress(status_code):
|
|||
class GenKey(object):
|
||||
"""Handle status messages for key generation.
|
||||
|
||||
Calling the GenKey.__str__() method of this class will return the
|
||||
generated key's fingerprint, or a status string explaining the results.
|
||||
Calling the ``__str__()`` method of this class will return the generated
|
||||
key's fingerprint, or a status string explaining the results.
|
||||
"""
|
||||
def __init__(self, gpg):
|
||||
self._gpg = gpg
|
||||
|
@ -799,11 +816,13 @@ class GenKey(object):
|
|||
self.status = None
|
||||
self.subkey_created = False
|
||||
self.primary_created = False
|
||||
#: This will store the filename of the key's public keyring if
|
||||
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
|
||||
#: This will store the key's public keyring filename, if
|
||||
#: :meth:`~gnupg.GPG.gen_key_input` was called with
|
||||
#: ``separate_keyring=True``.
|
||||
self.keyring = None
|
||||
#: This will store the filename of the key's secret keyring if
|
||||
#: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True``
|
||||
#: This will store the key's secret keyring filename, if :
|
||||
#: :meth:`~gnupg.GPG.gen_key_input` was called with
|
||||
#: ``separate_keyring=True``.
|
||||
self.secring = None
|
||||
|
||||
def __nonzero__(self):
|
||||
|
@ -823,7 +842,7 @@ class GenKey(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key in ("GOOD_PASSPHRASE"):
|
||||
pass
|
||||
|
@ -860,7 +879,7 @@ class DeleteResult(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key == "DELETE_PROBLEM":
|
||||
self.status = self.problem_reason.get(value, "Unknown error: %r"
|
||||
|
@ -905,7 +924,7 @@ class Sign(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
|
||||
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL",
|
||||
|
@ -1008,7 +1027,7 @@ class ImportResult(object):
|
|||
_fields = '''count no_user_id imported imported_rsa unchanged
|
||||
n_uids n_subk n_sigs n_revoc sec_read sec_imported sec_dups
|
||||
not_imported'''.split()
|
||||
_counts = collections.OrderedDict(
|
||||
_counts = OrderedDict(
|
||||
zip(_fields, [int(0) for x in range(len(_fields))]) )
|
||||
|
||||
#: A list of strings containing the fingerprints of the GnuPG keyIDs
|
||||
|
@ -1037,7 +1056,7 @@ class ImportResult(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key == "IMPORTED":
|
||||
# this duplicates info we already see in import_ok & import_problem
|
||||
|
@ -1183,7 +1202,7 @@ class Verify(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key in self.TRUST_LEVELS:
|
||||
self.trust_text = key
|
||||
|
@ -1278,7 +1297,7 @@ class Crypt(Verify):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
|
||||
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
|
||||
|
@ -1345,7 +1364,7 @@ class ListPackets(object):
|
|||
def _handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
||||
:raises: :exc:`ValueError` if the status message is unknown.
|
||||
:raises: :exc:`~exceptions.ValueError` if the status message is unknown.
|
||||
"""
|
||||
if key == 'NODATA':
|
||||
self.status = nodata(value)
|
||||
|
|
|
@ -7,19 +7,17 @@
|
|||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
'''trust.py
|
||||
-----------
|
||||
Functions for handling trustdb and trust calculations.
|
||||
'''Functions for handling trustdb and trust calculations.
|
||||
|
||||
The functions within this module take an instance of :class:`gnupg.GPGBase` or
|
||||
a suitable subclass as their first argument.
|
||||
|
@ -44,11 +42,12 @@ def _create_trustdb(cls):
|
|||
def export_ownertrust(cls, trustdb=None):
|
||||
"""Export ownertrust to a trustdb file.
|
||||
|
||||
If there is already a file named 'trustdb.gpg' in the current GnuPG
|
||||
homedir, it will be renamed to 'trustdb.gpg.bak'.
|
||||
If there is already a file named :file:`trustdb.gpg` in the current GnuPG
|
||||
homedir, it will be renamed to :file:`trustdb.gpg.bak`.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
defaults to ``'trustdb.gpg'`` in the current GnuPG
|
||||
homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
|
@ -56,7 +55,7 @@ def export_ownertrust(cls, trustdb=None):
|
|||
try:
|
||||
os.rename(trustdb, trustdb + '.bak')
|
||||
except (OSError, IOError) as err:
|
||||
log.debug(err.message)
|
||||
log.debug(str(err))
|
||||
|
||||
export_proc = cls._open_subprocess('--export-ownertrust')
|
||||
tdb = open(trustdb, 'wb')
|
||||
|
@ -65,8 +64,9 @@ def export_ownertrust(cls, trustdb=None):
|
|||
def import_ownertrust(self, trustdb=None):
|
||||
"""Import ownertrust from a trustdb file.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
:param str trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to :file:`trustdb.gpg` in the current GnuPG
|
||||
homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
|
@ -78,22 +78,23 @@ def import_ownertrust(self, trustdb=None):
|
|||
def fix_trustdb(cls, trustdb=None):
|
||||
"""Attempt to repair a broken trustdb.gpg file.
|
||||
|
||||
GnuPG>=2.0.x has this magical-seeming flag: '--fix-trustdb'. You'd think
|
||||
GnuPG>=2.0.x has this magical-seeming flag: `--fix-trustdb`. You'd think
|
||||
it would fix the the trustdb. Hah! It doesn't. Here's what it does
|
||||
instead:
|
||||
instead::
|
||||
|
||||
(python-gnupg)∃!isisⒶwintermute:(testing/digest-algo *$=)~/code/python-gnupg ∴ gpg2 --fix-trustdb
|
||||
gpg: You may try to re-create the trustdb using the commands:
|
||||
gpg: cd ~/.gnupg
|
||||
gpg: gpg2 --export-ownertrust > otrust.tmp
|
||||
gpg: rm trustdb.gpg
|
||||
gpg: gpg2 --import-ownertrust < otrust.tmp
|
||||
gpg: If that does not work, please consult the manual
|
||||
(gpg)~/code/python-gnupg $ gpg2 --fix-trustdb
|
||||
gpg: You may try to re-create the trustdb using the commands:
|
||||
gpg: cd ~/.gnupg
|
||||
gpg: gpg2 --export-ownertrust > otrust.tmp
|
||||
gpg: rm trustdb.gpg
|
||||
gpg: gpg2 --import-ownertrust < otrust.tmp
|
||||
gpg: If that does not work, please consult the manual
|
||||
|
||||
Brilliant piece of software engineering right there.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
:param str trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to :file:`trustdb.gpg` in the current GnuPG
|
||||
homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
|
|
103
gnupg/_util.py
103
gnupg/_util.py
|
@ -17,10 +17,7 @@
|
|||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
||||
'''util.py
|
||||
----------
|
||||
Extra utilities for python-gnupg.
|
||||
'''
|
||||
'''Extra utilities for python-gnupg.'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime
|
||||
|
@ -31,6 +28,7 @@ from time import mktime
|
|||
import codecs
|
||||
import encodings
|
||||
import os
|
||||
import psutil
|
||||
import threading
|
||||
import random
|
||||
import re
|
||||
|
@ -150,7 +148,7 @@ def _copy_data(instream, outstream):
|
|||
break
|
||||
except IOError as ioe:
|
||||
# Can get 'broken pipe' errors even when all data was sent
|
||||
if 'Broken pipe' in ioe.message:
|
||||
if 'Broken pipe' in str(ioe):
|
||||
log.error('Error sending data: Broken pipe')
|
||||
else:
|
||||
log.exception(ioe)
|
||||
|
@ -254,7 +252,8 @@ def _find_binary(binary=None):
|
|||
our process real uid has exec permissions.
|
||||
|
||||
:param str binary: The path to the GnuPG binary.
|
||||
:raises: :exc:RuntimeError if it appears that GnuPG is not installed.
|
||||
:raises: :exc:`~exceptions.RuntimeError` if it appears that GnuPG is not
|
||||
installed.
|
||||
:rtype: str
|
||||
:returns: The absolute path to the GnuPG binary to use, if no exceptions
|
||||
occur.
|
||||
|
@ -272,6 +271,8 @@ def _find_binary(binary=None):
|
|||
except IndexError as ie:
|
||||
log.info("Could not determine absolute path of binary: '%s'"
|
||||
% binary)
|
||||
elif os.access(binary, os.X_OK):
|
||||
found = binary
|
||||
if found is None:
|
||||
try: found = _which('gpg')[0]
|
||||
except IndexError as ie:
|
||||
|
@ -287,7 +288,7 @@ def _find_binary(binary=None):
|
|||
assert not os.path.islink(found), "Path to gpg binary is symlink"
|
||||
assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
|
||||
except (AssertionError, AttributeError) as ae:
|
||||
log.error(ae.message)
|
||||
log.error(str(ae))
|
||||
else:
|
||||
return found
|
||||
|
||||
|
@ -303,22 +304,31 @@ def _has_readwrite(path):
|
|||
"""
|
||||
return os.access(path, os.R_OK ^ os.W_OK)
|
||||
|
||||
def _is_file(input):
|
||||
def _is_file(filename):
|
||||
"""Check that the size of the thing which is supposed to be a filename has
|
||||
size greater than zero, without following symbolic links or using
|
||||
:func:os.path.isfile.
|
||||
|
||||
:param input: An object to check.
|
||||
:param filename: An object to check.
|
||||
:rtype: bool
|
||||
:returns: True if :param:input is file-like, False otherwise.
|
||||
:returns: True if **filename** is file-like, False otherwise.
|
||||
"""
|
||||
try:
|
||||
assert os.lstat(input).st_size > 0, "not a file: %s" % input
|
||||
except (AssertionError, TypeError, IOError, OSError) as err:
|
||||
log.error(err.message, exc_info=1)
|
||||
return False
|
||||
statinfo = os.lstat(filename)
|
||||
log.debug("lstat(%r) with type=%s gave us %r"
|
||||
% (repr(filename), type(filename), repr(statinfo)))
|
||||
if not (statinfo.st_size > 0):
|
||||
raise ValueError("'%s' appears to be an empty file!" % filename)
|
||||
except OSError as oserr:
|
||||
log.error(oserr)
|
||||
if filename == '-':
|
||||
log.debug("Got '-' for filename, assuming sys.stdin...")
|
||||
return True
|
||||
except (ValueError, TypeError, IOError) as err:
|
||||
log.error(err)
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_stream(input):
|
||||
"""Check that the input is a byte stream.
|
||||
|
@ -395,7 +405,7 @@ def _make_passphrase(length=None, save=False, file=None):
|
|||
passphrase = _make_random_string(length)
|
||||
|
||||
if save:
|
||||
ruid, euid, suid = os.getresuid()
|
||||
ruid, euid, suid = psutil.Process(os.getpid()).uids
|
||||
gid = os.getgid()
|
||||
now = mktime(localtime())
|
||||
|
||||
|
@ -518,7 +528,7 @@ def _which(executable, flags=os.X_OK):
|
|||
def _write_passphrase(stream, passphrase, encoding):
|
||||
"""Write the passphrase from memory to the GnuPG process' stdin.
|
||||
|
||||
:type stream: file, :class:BytesIO, or :class:StringIO
|
||||
:type stream: file, :class:`~io.BytesIO`, or :class:`~io.StringIO`
|
||||
:param stream: The input file descriptor to write the password to.
|
||||
:param str passphrase: The passphrase for the secret key material.
|
||||
:param str encoding: The data encoding expected by GnuPG. Usually, this
|
||||
|
@ -531,39 +541,40 @@ def _write_passphrase(stream, passphrase, encoding):
|
|||
|
||||
|
||||
class InheritableProperty(object):
|
||||
"""Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
|
||||
"""Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.fget is None:
|
||||
raise AttributeError("unreadable attribute")
|
||||
if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
|
||||
return self.fget(obj)
|
||||
else:
|
||||
return getattr(obj, self.fget.__name__)()
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.fget is None:
|
||||
raise AttributeError("unreadable attribute")
|
||||
if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
|
||||
return self.fget(obj)
|
||||
else:
|
||||
return getattr(obj, self.fget.__name__)()
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError("can't set attribute")
|
||||
if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
|
||||
self.fset(obj, value)
|
||||
else:
|
||||
getattr(obj, self.fset.__name__)(value)
|
||||
def __set__(self, obj, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError("can't set attribute")
|
||||
if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
|
||||
self.fset(obj, value)
|
||||
else:
|
||||
getattr(obj, self.fset.__name__)(value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.fdel is None:
|
||||
raise AttributeError("can't delete attribute")
|
||||
if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
|
||||
self.fdel(obj)
|
||||
else:
|
||||
getattr(obj, self.fdel.__name__)()
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.fdel is None:
|
||||
raise AttributeError("can't delete attribute")
|
||||
if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
|
||||
self.fdel(obj)
|
||||
else:
|
||||
getattr(obj, self.fdel.__name__)()
|
||||
|
||||
class Storage(dict):
|
||||
"""A dictionary where keys are stored as class attributes.
|
||||
|
@ -595,7 +606,7 @@ class Storage(dict):
|
|||
try:
|
||||
del self[key]
|
||||
except KeyError as k:
|
||||
raise AttributeError(k.message)
|
||||
raise AttributeError(k.args[0])
|
||||
|
||||
def __repr__(self):
|
||||
return '<Storage ' + dict.__repr__(self) + '>'
|
||||
|
|
172
gnupg/gnupg.py
172
gnupg/gnupg.py
|
@ -7,12 +7,12 @@
|
|||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
|
@ -21,10 +21,10 @@
|
|||
===========
|
||||
A Python interface to GnuPG.
|
||||
|
||||
.. moduleauthor:: Isis Agora Lovecruft <isis@patternsinthevoid.net>
|
||||
.. moduleauthor:: Isis Lovecruft <isis@patternsinthevoid.net>
|
||||
see also :attr:`gnupg.__authors__`
|
||||
:license: see :attr:`gnupg.__license__`
|
||||
:info: see <https://www.github.com/isislovecruft/python-gnupg>
|
||||
.. license:: see :attr:`gnupg.__license__`
|
||||
.. info:: https://github.com/isislovecruft/python-gnupg
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
@ -41,7 +41,7 @@ try:
|
|||
except ImportError:
|
||||
from cStringIO import StringIO
|
||||
|
||||
## see PEP-328 http://docs.python.org/2.5/whatsnew/pep-328.html
|
||||
#: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html
|
||||
from . import _parsers
|
||||
from . import _util
|
||||
from . import _trust
|
||||
|
@ -70,35 +70,36 @@ class GPG(GPGBase):
|
|||
"""Initialize a GnuPG process wrapper.
|
||||
|
||||
:param str binary: Name for GnuPG binary executable. If the absolute
|
||||
path is not given, the evironment variable $PATH is
|
||||
searched for the executable and checked that the
|
||||
real uid/gid of the user has sufficient permissions.
|
||||
path is not given, the environment variable
|
||||
``$PATH`` is searched for the executable and
|
||||
checked that the real uid/gid of the user has
|
||||
sufficient permissions.
|
||||
|
||||
:param str homedir: Full pathname to directory containing the public
|
||||
and private keyrings. Default is whatever GnuPG
|
||||
defaults to.
|
||||
|
||||
:param str,int,bool verbose: String or numeric value to pass to gpg's
|
||||
``--debug-level`` option. See the gpg man
|
||||
page for the list of valid options. If
|
||||
False, debug output is not generated by
|
||||
the gpg binary. If True, defaults to
|
||||
``--debug-level basic.``
|
||||
:type verbose: :obj:`str` or :obj:`int` or :obj:`bool`
|
||||
:param verbose: String or numeric value to pass to GnuPG's
|
||||
``--debug-level`` option. See the GnuPG man page for
|
||||
the list of valid options. If False, debug output is
|
||||
not generated by the GnuPG binary. If True, defaults
|
||||
to ``--debug-level basic.``
|
||||
|
||||
:param str keyring: Name of keyring file containing public key data, if
|
||||
unspecified, defaults to 'pubring.gpg' in the
|
||||
``homedir`` directory.
|
||||
:param str keyring: Name of keyring file containing public key data.
|
||||
If unspecified, defaults to :file:`pubring.gpg` in
|
||||
the **homedir** directory.
|
||||
|
||||
:param str secring: Name of alternative secret keyring file to use. If
|
||||
left unspecified, this will default to using
|
||||
'secring.gpg' in the :param:homedir directory, and
|
||||
create that file if it does not exist.
|
||||
:file:`secring.gpg` in the **homedir** directory,
|
||||
and create that file if it does not exist.
|
||||
|
||||
:param list options: A list of additional options to pass to the GPG
|
||||
:param list options: A list of additional options to pass to the GnuPG
|
||||
binary.
|
||||
|
||||
:raises: :exc:`RuntimeError` with explanation message if there is a
|
||||
problem invoking gpg.
|
||||
:raises: A :exc:`~exceptions.RuntimeError` with explanation message
|
||||
if there is a problem invoking GnuPG.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -227,7 +228,7 @@ class GPG(GPGBase):
|
|||
In simpler terms: this function isn't for signing your friends' keys,
|
||||
it's for something like signing an email.
|
||||
|
||||
:type data: str or file
|
||||
:type data: :obj:`str` or :obj:`file`
|
||||
:param data: A string or file stream to sign.
|
||||
:param str default_key: The key to sign with.
|
||||
:param str passphrase: The passphrase to pipe to stdin.
|
||||
|
@ -236,10 +237,10 @@ class GPG(GPGBase):
|
|||
:param bool binary: If True, do not ascii armour the output.
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
:command:`$ gpg --with-colons --list-config digestname`.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
"""
|
||||
if 'default_key' in kwargs.items():
|
||||
if 'default_key' in kwargs:
|
||||
log.info("Signing message '%r' with keyid: %s"
|
||||
% (data, kwargs['default_key']))
|
||||
else:
|
||||
|
@ -306,15 +307,19 @@ class GPG(GPGBase):
|
|||
return result
|
||||
log.debug('verify_file(): Handling detached verification')
|
||||
sig_fh = None
|
||||
data_fh = None
|
||||
try:
|
||||
sig_fh = open(sig_file)
|
||||
args = ["--verify %s - " % sig_fh.name]
|
||||
sig_fh = open(sig_file, 'rb')
|
||||
data_fh = open(file, 'rb')
|
||||
args = ["--verify %s -" % sig_fh.name]
|
||||
proc = self._open_subprocess(args)
|
||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
||||
self._collect_output(proc, result, stdin=proc.stdin)
|
||||
writer = _util._threaded_copy_data(data_fh, proc.stdin)
|
||||
self._collect_output(proc, result, writer, stdin=proc.stdin)
|
||||
finally:
|
||||
if sig_fh and not sig_fh.closed:
|
||||
sig_fh.close()
|
||||
if data_fh and not data_fh.closed:
|
||||
data_fh.close()
|
||||
return result
|
||||
|
||||
def import_keys(self, key_data):
|
||||
|
@ -373,7 +378,7 @@ class GPG(GPGBase):
|
|||
:param str keyids: Each ``keyids`` argument should be a string
|
||||
containing a keyid to request.
|
||||
:param str keyserver: The keyserver to request the ``keyids`` from;
|
||||
defaults to :property:`gnupg.GPG.keyserver`.
|
||||
defaults to `gnupg.GPG.keyserver`.
|
||||
"""
|
||||
if keyids:
|
||||
keys = ' '.join([key for key in keyids])
|
||||
|
@ -384,33 +389,33 @@ class GPG(GPGBase):
|
|||
def delete_keys(self, fingerprints, secret=False, subkeys=False):
|
||||
"""Delete a key, or list of keys, from the current keyring.
|
||||
|
||||
The keys must be refered to by their full fingerprint for GnuPG to
|
||||
The keys must be referred to by their full fingerprints for GnuPG to
|
||||
delete them. If ``secret=True``, the corresponding secret keyring will
|
||||
be deleted from :attr:`GPG.secring`.
|
||||
|
||||
:type fingerprints: str or list or tuple
|
||||
be deleted from :obj:`.secring`.
|
||||
|
||||
:type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple`
|
||||
:param fingerprints: A string, or a list/tuple of strings,
|
||||
representing the fingerprint(s) for the key(s) to delete.
|
||||
representing the fingerprint(s) for the key(s)
|
||||
to delete.
|
||||
|
||||
:param bool secret: If True, delete the corresponding secret key(s)
|
||||
also. (default: False)
|
||||
also. (default: False)
|
||||
|
||||
:param bool subkeys: If True, delete the secret subkey first, then the
|
||||
public key. (default: False) Same as:
|
||||
``$ gpg --delete-secret-and-public-key 0x12345678``
|
||||
public key. (default: False) Same as:
|
||||
:command:`$gpg --delete-secret-and-public-key 0x12345678`.
|
||||
"""
|
||||
which='keys'
|
||||
which = 'keys'
|
||||
if secret:
|
||||
which='secret-keys'
|
||||
which = 'secret-keys'
|
||||
if subkeys:
|
||||
which='secret-and-public-keys'
|
||||
which = 'secret-and-public-keys'
|
||||
|
||||
if _is_list_or_tuple(fingerprints):
|
||||
fingerprints = ' '.join(fingerprints)
|
||||
|
||||
args = ['--batch']
|
||||
args.append("--delete-{} {}".format(which, fingerprints))
|
||||
args.append("--delete-{0} {1}".format(which, fingerprints))
|
||||
|
||||
result = self._result_map['delete'](self)
|
||||
p = self._open_subprocess(args)
|
||||
|
@ -425,23 +430,23 @@ class GPG(GPGBase):
|
|||
:param bool secret: If True, export only the secret key.
|
||||
:param bool subkeys: If True, export the secret subkeys.
|
||||
"""
|
||||
which=''
|
||||
which = ''
|
||||
if subkeys:
|
||||
which='-secret-subkeys'
|
||||
which = '-secret-subkeys'
|
||||
elif secret:
|
||||
which='-secret-keys'
|
||||
which = '-secret-keys'
|
||||
|
||||
if _is_list_or_tuple(keyids):
|
||||
keyids = ' '.join(['%s' % k for k in keyids])
|
||||
|
||||
args = ["--armor"]
|
||||
args.append("--export{} {}".format(which, keyids))
|
||||
args.append("--export{0} {1}".format(which, keyids))
|
||||
|
||||
p = self._open_subprocess(args)
|
||||
## gpg --export produces no status-fd output; stdout will be empty in
|
||||
## case of failure
|
||||
#stdout, stderr = p.communicate()
|
||||
result = self._result_map['delete'](self) # any result will do
|
||||
result = self._result_map['delete'](self) # any result will do
|
||||
self._collect_output(p, result, stdin=p.stdin)
|
||||
log.debug('Exported:%s%r' % (os.linesep, result.data))
|
||||
return result.data.decode(self._encoding, self._decode_errors)
|
||||
|
@ -467,9 +472,9 @@ class GPG(GPGBase):
|
|||
>>> assert print2 in pubkeys.fingerprints
|
||||
"""
|
||||
|
||||
which='public-keys'
|
||||
which = 'public-keys'
|
||||
if secret:
|
||||
which='secret-keys'
|
||||
which = 'secret-keys'
|
||||
args = "--list-%s --fixed-list-mode --fingerprint " % (which,)
|
||||
args += "--with-colons --list-options no-show-photos"
|
||||
args = [args]
|
||||
|
@ -547,7 +552,7 @@ class GPG(GPGBase):
|
|||
:param dict input: A dictionary of parameters and values for the new
|
||||
key.
|
||||
:returns: The result mapping with details of the new key, which is a
|
||||
:class:`parsers.GenKey <GenKey>` object.
|
||||
:class:`GenKey <gnupg._parsers.GenKey>` object.
|
||||
"""
|
||||
args = ["--gen-key --batch"]
|
||||
key = self._result_map['generate'](self)
|
||||
|
@ -557,20 +562,22 @@ class GPG(GPGBase):
|
|||
|
||||
fpr = str(key.fingerprint)
|
||||
if len(fpr) == 20:
|
||||
if self.temp_keyring or self.temp_secring:
|
||||
if not os.path.exists(self._keys_dir):
|
||||
os.makedirs(self._keys_dir)
|
||||
prefix = os.path.join(self._keys_dir, fpr)
|
||||
for d in map(lambda x: os.path.dirname(x),
|
||||
[self.temp_keyring, self.temp_secring]):
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
|
||||
if self.temp_keyring:
|
||||
if os.path.isfile(self.temp_keyring):
|
||||
prefix = os.path.join(self.temp_keyring, fpr)
|
||||
try: os.rename(self.temp_keyring, prefix+".pubring")
|
||||
except OSError as ose: log.error(ose.message)
|
||||
except OSError as ose: log.error(str(ose))
|
||||
|
||||
if self.temp_secring:
|
||||
if os.path.isfile(self.temp_secring):
|
||||
prefix = os.path.join(self.temp_secring, fpr)
|
||||
try: os.rename(self.temp_secring, prefix+".secring")
|
||||
except OSError as ose: log.error(ose.message)
|
||||
except OSError as ose: log.error(str(ose))
|
||||
|
||||
log.info("Key created. Fingerprint: %s" % fpr)
|
||||
key.keyring = self.temp_keyring
|
||||
|
@ -582,11 +589,11 @@ class GPG(GPGBase):
|
|||
|
||||
def gen_key_input(self, separate_keyring=False, save_batchfile=False,
|
||||
testing=False, **kwargs):
|
||||
"""Generate a batch file for input to :meth:`GPG.gen_key()`.
|
||||
"""Generate a batch file for input to :meth:`~gnupg.GPG.gen_key`.
|
||||
|
||||
The GnuPG batch file key generation feature allows unattended key
|
||||
generation by creating a file with special syntax and then providing it
|
||||
to: ``gpg --gen-key --batch``. Batch files look like this:
|
||||
to: :command:`gpg --gen-key --batch`. Batch files look like this:
|
||||
|
||||
| Name-Real: Alice
|
||||
| Name-Email: alice@inter.net
|
||||
|
@ -645,9 +652,10 @@ class GPG(GPGBase):
|
|||
|
||||
:param bool separate_keyring: Specify for the new key to be written to
|
||||
a separate pubring.gpg and secring.gpg. If True,
|
||||
:meth:`GPG.gen_key` will automatically rename the separate keyring
|
||||
and secring to whatever the fingerprint of the generated key ends
|
||||
up being, suffixed with '.pubring' and '.secring' respectively.
|
||||
:meth:`~gnupg.GPG.gen_key` will automatically rename the separate
|
||||
keyring and secring to whatever the fingerprint of the generated
|
||||
key ends up being, suffixed with '.pubring' and '.secring'
|
||||
respectively.
|
||||
|
||||
:param bool save_batchfile: Save a copy of the generated batch file to
|
||||
disk in a file named <name_real>.batch, where <name_real> is the
|
||||
|
@ -661,11 +669,12 @@ class GPG(GPGBase):
|
|||
|
||||
:param str name_real: The name field of the UID in the generated key.
|
||||
:param str name_comment: The comment in the UID of the generated key.
|
||||
|
||||
:param str name_email: The email in the UID of the generated key.
|
||||
(default: $USER@$(hostname) ) Remember to use UTF-8 encoding for
|
||||
the entirety of the UID. At least one of ``name_real``,
|
||||
``name_comment``, or ``name_email`` must be provided, or else no
|
||||
user ID is created.
|
||||
(default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8
|
||||
encoding for the entirety of the UID. At least one of
|
||||
``name_real``, ``name_comment``, or ``name_email`` must be
|
||||
provided, or else no user ID is created.
|
||||
|
||||
:param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
|
||||
(default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts
|
||||
|
@ -704,7 +713,7 @@ class GPG(GPGBase):
|
|||
:param str subkey_usage: Key usage for a subkey; similar to
|
||||
``key_usage``.
|
||||
|
||||
:type expire_date: int or str
|
||||
:type expire_date: :obj:`int` or :obj:`str`
|
||||
:param expire_date: Can be specified as an iso-date or as
|
||||
<int>[d|w|m|y] Set the expiration date for the key (and the
|
||||
subkey). It may either be entered in ISO date format (2000-08-15)
|
||||
|
@ -726,17 +735,17 @@ class GPG(GPGBase):
|
|||
:param str passphrase: The passphrase for the new key. The default is
|
||||
to not use any passphrase. Note that GnuPG>=2.1.x will not allow
|
||||
you to specify a passphrase for batch key generation -- GnuPG will
|
||||
ignore the ``passphrase`` parameter, stop, and ask the user for
|
||||
ignore the **passphrase** parameter, stop, and ask the user for
|
||||
the new passphrase. However, we can put the command
|
||||
'%no-protection' into the batch key generation file to allow a
|
||||
``%no-protection`` into the batch key generation file to allow a
|
||||
passwordless key to be created, which can then have its passphrase
|
||||
set later with '--edit-key'.
|
||||
set later with ``--edit-key``.
|
||||
|
||||
:param str preferences: Set the cipher, hash, and compression
|
||||
preference values for this key. This expects the same type of
|
||||
string as the sub-command ‘setpref’ in the --edit-key menu.
|
||||
|
||||
:param str revoker: Should be given as 'algo:fpr' [case sensitive].
|
||||
:param str revoker: Should be given as 'algo:fpr' (case sensitive).
|
||||
Add a designated revoker to the generated key. Algo is the public
|
||||
key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
|
||||
fpr is the fingerprint of the designated revoker. The optional
|
||||
|
@ -747,18 +756,18 @@ class GPG(GPGBase):
|
|||
preferred keyserver URL for the key.
|
||||
|
||||
:param str handle: This is an optional parameter only used with the
|
||||
status lines KEY_CREATED and KEY_NOT_CREATED. string may be up to
|
||||
100 characters and should not contain spaces. It is useful for
|
||||
batch key generation to associate a key parameter block with a
|
||||
status line.
|
||||
status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may
|
||||
be up to 100 characters and should not contain spaces. It is
|
||||
useful for batch key generation to associate a key parameter block
|
||||
with a status line.
|
||||
|
||||
:rtype: str
|
||||
:returns: A suitable input string for the :meth:`GPG.gen_key` method,
|
||||
the latter of which will create the new keypair.
|
||||
|
||||
see
|
||||
http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
|
||||
for more details.
|
||||
See `this GnuPG Manual section`__ for more details.
|
||||
|
||||
__ http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
|
||||
"""
|
||||
#: A boolean for determining whether to set subkey_type to 'default'
|
||||
default_type = False
|
||||
|
@ -931,20 +940,21 @@ generate keys. Please see
|
|||
>>> decrypted
|
||||
'The crow flies at midnight.'
|
||||
|
||||
|
||||
:param str cipher_algo: The cipher algorithm to use. To see available
|
||||
algorithms with your version of GnuPG, do:
|
||||
``$ gpg --with-colons --list-config ciphername``.
|
||||
:command:`$ gpg --with-colons --list-config ciphername`.
|
||||
The default ``cipher_algo``, if unspecified, is ``'AES256'``.
|
||||
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
:command:`$ gpg --with-colons --list-config digestname`.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
|
||||
:param str compress_algo: The compression algorithm to use. Can be one
|
||||
of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
|
||||
|
||||
See also: :meth:`GPGBase._encrypt`
|
||||
.. seealso:: :meth:`._encrypt`
|
||||
"""
|
||||
stream = _make_binary_stream(data, self._encoding)
|
||||
result = self._encrypt(stream, recipients, **kwargs)
|
||||
|
|
|
@ -47,8 +47,8 @@ import tempfile
|
|||
## these dependencies require Python>=2.6 in order to have proper SSL support.
|
||||
##
|
||||
## Use unittest2 if we're on Python2.6 or less:
|
||||
if sys.version_info.major == 2 and sys.version_info.minor <= 6:
|
||||
unittest = __import__(unittest2)
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
|
||||
import unittest2 as unittest
|
||||
else:
|
||||
import unittest
|
||||
|
||||
|
@ -62,7 +62,7 @@ try:
|
|||
import gnupg._parsers as _parsers
|
||||
import gnupg._logger as _logger
|
||||
except (ImportError, ValueError) as ierr:
|
||||
raise SystemExit(ierr.message)
|
||||
raise SystemExit(str(ierr))
|
||||
|
||||
|
||||
log = _util.log
|
||||
|
@ -173,7 +173,6 @@ class GPGTestCase(unittest.TestCase):
|
|||
self.keyring = self.gpg.keyring
|
||||
self.secring = self.gpg.secring
|
||||
self.insecure_prng = False
|
||||
self.gpg._keys_dir = os.path.join(_files, 'generated-keys')
|
||||
|
||||
def tearDown(self):
|
||||
"""This is called once per self.test_* method after the test run."""
|
||||
|
@ -326,15 +325,19 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_copy_data_bytesio(self):
|
||||
"""Test that _copy_data() is able to duplicate byte streams."""
|
||||
message = "This is a BytesIO string."
|
||||
message = b"This is a BytesIO string."
|
||||
instream = io.BytesIO(message)
|
||||
self.assertEqual(unicode(message), instream.getvalue())
|
||||
self.assertEqual(message, instream.getvalue())
|
||||
|
||||
out_filename = 'test-copy-data-bytesio'
|
||||
|
||||
# Create the test file:
|
||||
outfile = os.path.join(os.getcwdu(), out_filename)
|
||||
outstream = open(outfile, 'w+')
|
||||
try:
|
||||
cwd = os.getcwdu()
|
||||
except AttributeError:
|
||||
cwd = os.getcwd() # not present in Python 3
|
||||
outfile = os.path.join(cwd, out_filename)
|
||||
outstream = open(outfile, 'wb+')
|
||||
|
||||
# _copy_data() will close both file descriptors
|
||||
_util._copy_data(instream, outstream)
|
||||
|
@ -523,7 +526,7 @@ class GPGTestCase(unittest.TestCase):
|
|||
self.assertIsNotNone(key)
|
||||
self.assertNotEquals(key, "")
|
||||
self.assertGreater(len(str(key)), 0)
|
||||
keyfile = os.path.join(self.gpg._keys_dir, 'test_key_3.pub')
|
||||
keyfile = os.path.join(_files, 'test_key_3.pub')
|
||||
log.debug("Storing downloaded key as %s" % keyfile)
|
||||
with open(keyfile, 'w') as fh:
|
||||
fh.write(str(key))
|
||||
|
@ -673,44 +676,72 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_signature_verification_detached(self):
|
||||
"""Test that verification of a detached signature of a file works."""
|
||||
|
||||
key = self.generate_key("Paulo S.L.M. Barreto", "anub.is")
|
||||
with open(os.path.join(_files, 'cypherpunk_manifesto'), 'rb') as cm:
|
||||
sig = self.gpg.sign(cm, default_key=key.fingerprint,
|
||||
passphrase='paulos.l.m.barreto',
|
||||
detach=True, clearsign=False)
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
sigfilename = os.path.join(_files, 'cypherpunk_manifesto.sig')
|
||||
with open(sigfilename,'w') as sigfile:
|
||||
sigfile.write(sig.data)
|
||||
sigfile.seek(0)
|
||||
datafn = os.path.join(_files, 'cypherpunk_manifesto')
|
||||
sigfn = os.path.extsep.join([datafn, 'sig'])
|
||||
|
||||
verified = self.gpg.verify_file(cm, sigfilename)
|
||||
datafd = open(datafn, 'rb')
|
||||
sig = self.gpg.sign(datafd, default_key=key.fingerprint,
|
||||
passphrase='paulos.l.m.barreto',
|
||||
detach=True,
|
||||
clearsign=False)
|
||||
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
log.warn("key fingerprint: %r", key.fingerprint)
|
||||
log.warn("verified fingerprint: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint)
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
|
||||
if os.path.isfile(sigfilename):
|
||||
os.unlink(sigfilename)
|
||||
sigfd = open(sigfn, 'wb')
|
||||
sigfd.write(sig.data)
|
||||
sigfd.flush()
|
||||
|
||||
datafd.seek(0)
|
||||
sigfd.seek(0)
|
||||
|
||||
verified = self.gpg.verify_file(datafn, sigfn)
|
||||
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
log.warn("key fingerprint: %r", key.fingerprint)
|
||||
log.warn("verified fingerprint: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint)
|
||||
|
||||
if os.path.isfile(sigfn):
|
||||
os.unlink(sigfn)
|
||||
|
||||
def test_signature_verification_detached_binary(self):
|
||||
"""Test that detached signature verification in binary mode fails."""
|
||||
|
||||
key = self.generate_key("Adi Shamir", "rsa.com")
|
||||
datafile = os.path.join(_files, 'cypherpunk_manifesto')
|
||||
with open(datafile, 'rb') as cm:
|
||||
sig = self.gpg.sign(cm, default_key=key.fingerprint,
|
||||
passphrase='adishamir',
|
||||
detach=True, binary=True, clearsign=False)
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
with open(datafile+'.sig', 'w') as bs:
|
||||
bs.write(sig.data)
|
||||
bs.flush()
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
print("SIG=%s" % sig)
|
||||
with open(datafile+'.sig', 'rb') as fsig:
|
||||
with open(datafile, 'rb') as fdata:
|
||||
self.gpg.verify_file(fdata, fsig)
|
||||
datafn = os.path.join(_files, 'cypherpunk_manifesto')
|
||||
sigfn = os.path.extsep.join([datafn, 'sig'])
|
||||
|
||||
datafd = open(datafn, 'rb')
|
||||
data = datafd.read()
|
||||
datafd.close()
|
||||
|
||||
sig = self.gpg.sign(data, default_key=key.fingerprint,
|
||||
passphrase='adishamir',
|
||||
detach=True,
|
||||
binary=True,
|
||||
clearsign=False)
|
||||
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
|
||||
sigfd = open(sigfn, 'wb')
|
||||
sigfd.write(sig.data)
|
||||
sigfd.flush()
|
||||
sigfd.close()
|
||||
|
||||
self.assertTrue(sigfd.closed, "Sigfile '%s' should be closed" % sigfn)
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
print("SIG=%s" % sig)
|
||||
|
||||
verifysig = open(sigfn, 'rb')
|
||||
verification = self.gpg.verify_file(data, verifysig)
|
||||
|
||||
self.assertTrue(isinstance(verification, gnupg._parsers.Verify))
|
||||
self.assertFalse(verification.valid)
|
||||
|
||||
if os.path.isfile(sigfn):
|
||||
os.unlink(sigfn)
|
||||
|
||||
def test_deletion(self):
|
||||
"""Test that key deletion works."""
|
||||
|
@ -793,14 +824,14 @@ authentication."""
|
|||
riggio_input = self.gpg.gen_key_input(separate_keyring=True, **riggio)
|
||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||
riggio = self.gpg.gen_key(riggio_input)
|
||||
self.gpg.options = ['--keyring {}'.format(riggio.keyring)]
|
||||
self.gpg.options = ['--keyring {0}'.format(riggio.keyring)]
|
||||
riggio_key = self.gpg.export_keys(riggio.fingerprint)
|
||||
self.gpg.import_keys(riggio_key)
|
||||
|
||||
sicari_input = self.gpg.gen_key_input(separate_keyring=True, **sicari)
|
||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||
sicari = self.gpg.gen_key(sicari_input)
|
||||
self.gpg.options.append('--keyring {}'.format(sicari.keyring))
|
||||
self.gpg.options.append('--keyring {0}'.format(sicari.keyring))
|
||||
sicari_key = self.gpg.export_keys(sicari.fingerprint)
|
||||
self.gpg.import_keys(sicari_key)
|
||||
|
||||
|
@ -898,15 +929,15 @@ analysis of different kinds of data (temperature, humidity, etc.) coming from
|
|||
a WSN while ensuring both end-to-end encryption and hop-by-hop
|
||||
authentication."""
|
||||
enc = self.gpg.encrypt(message, alice_pfpr, bob_pfpr)
|
||||
encrypted = str(enc.data)
|
||||
encrypted = str(enc)
|
||||
log.debug("encryption_decryption_multi_recipient() Ciphertext = %s"
|
||||
% encrypted)
|
||||
|
||||
self.assertNotEquals(message, encrypted)
|
||||
dec_alice = self.gpg.decrypt(encrypted, passphrase="test")
|
||||
self.assertEquals(message, str(dec_alice.data))
|
||||
self.assertEquals(message, str(dec_alice))
|
||||
dec_bob = self.gpg.decrypt(encrypted, passphrase="test")
|
||||
self.assertEquals(message, str(dec_bob.data))
|
||||
self.assertEquals(message, str(dec_bob))
|
||||
|
||||
def test_symmetric_encryption_and_decryption(self):
|
||||
"""Test symmetric encryption and decryption"""
|
||||
|
@ -916,7 +947,7 @@ know, maybe you shouldn't be doing it in the first place.
|
|||
encrypted = str(self.gpg.encrypt(msg, passphrase='quiscustodiet',
|
||||
symmetric=True, encrypt=False))
|
||||
decrypt = self.gpg.decrypt(encrypted, passphrase='quiscustodiet')
|
||||
decrypted = str(decrypt.data)
|
||||
decrypted = str(decrypt)
|
||||
|
||||
log.info("Symmetrically encrypted data:\n%s" % encrypted)
|
||||
log.info("Symmetrically decrypted data:\n%s" % decrypted)
|
||||
|
@ -948,9 +979,8 @@ know, maybe you shouldn't be doing it in the first place.
|
|||
|
||||
with open(enc_outf) as enc2:
|
||||
fdata = enc2.read()
|
||||
ddata = str(self.gpg.decrypt(fdata, passphrase="overalls"))
|
||||
ddata = self.gpg.decrypt(fdata, passphrase="overalls").data
|
||||
|
||||
data = data.encode(self.gpg._encoding)
|
||||
if ddata != data:
|
||||
log.debug("data was: %r" % data)
|
||||
log.debug("new (from filehandle): %r" % fdata)
|
||||
|
|
|
@ -1,58 +1,3 @@
|
|||
#
|
||||
# python-gnupg/requirements.txt
|
||||
# -----------------------------
|
||||
# Pip requirements.txt file. This file is also parsed for distribute to use in
|
||||
# setup.py.
|
||||
#_____________________________________________________________________________
|
||||
# This file is part of python-gnupg, a Python interface to GnuPG.
|
||||
# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
|
||||
# © 2013 Andrej B.
|
||||
# © 2013 LEAP Encryption Access Project
|
||||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
#______________________________________________________________________________
|
||||
#
|
||||
# Force pip upgrade due to security vulnerabilities.
|
||||
#
|
||||
# This has actually has little to do with installing python-gnupg, since
|
||||
# older versions of pip would install everything just fine. except that, in
|
||||
# my opinion, using GnuPG for privacy is silly when the installation of
|
||||
# python-gnupg with an older version of pip is trivially exploitable through
|
||||
# a MITM attack. see https://github.com/pypa/pip/pull/791
|
||||
#
|
||||
# Also, note that SSL package delivery is *not* entirely fixed yet. See
|
||||
# https://github.com/TheTorProject/ooni-backend/pull/1#discussion_r4084881
|
||||
#
|
||||
#pip>=1.3.1
|
||||
#
|
||||
# NOTE: setuptools is currently (as of 27 May 2013) being merged back into its
|
||||
# parent project, distribute. By using the included distribute_setup.py
|
||||
# script, we make sure that we have a recent version of setuptools/distribute,
|
||||
# which is the *only* Python packaging framework compatible at this point with
|
||||
# both Python>=2.4 and Python3.x.
|
||||
#
|
||||
# A new version of distribute is necessary due to the merging of setuptools
|
||||
# back into its parent project, distribute. Also, the only way to package for
|
||||
# both Python 2 and 3 is to use distribute.
|
||||
#
|
||||
#distribute>=0.6.45
|
||||
#
|
||||
# Sphinx is only necessary for building documentation, so it is added in
|
||||
# setup.py under extras_require['docs'].
|
||||
#
|
||||
# If you want to build the documentation, uncomment this line:
|
||||
#Sphinx>=1.1
|
||||
#
|
||||
# And, this one is actually used in the gnupg module code:
|
||||
#
|
||||
psutil>=0.5.1
|
||||
# sha256: UI5KRMglOjhqD4bZyb1KG0y7L5TojUmhnBUTZTymbEU
|
||||
psutil==1.2.1
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
[upload_docs]
|
||||
upload-dir = docs/_build/html
|
||||
show-response = true
|
||||
verbose = true
|
||||
|
||||
[upload]
|
||||
sign = True
|
||||
identity = 0xa3adb67a2cdb8b35
|
||||
|
||||
[aliases]
|
||||
upload_all = sdist bdist_egg bdist_wheel upload
|
||||
|
||||
|
|
53
setup.py
53
setup.py
|
@ -23,7 +23,11 @@ from __future__ import absolute_import
|
|||
from __future__ import print_function
|
||||
|
||||
import setuptools
|
||||
import sys
|
||||
import os
|
||||
import versioneer
|
||||
|
||||
|
||||
versioneer.versionfile_source = 'gnupg/_version.py'
|
||||
versioneer.versionfile_build = 'gnupg/_version.py'
|
||||
versioneer.tag_prefix = ''
|
||||
|
@ -34,6 +38,49 @@ __contact__ = 'isis@patternsinthevoid.net'
|
|||
__url__ = 'https://github.com/isislovecruft/python-gnupg'
|
||||
|
||||
|
||||
def python26():
|
||||
"""Returns True if we're running on Python2.6."""
|
||||
if sys.version[:3] == "2.6":
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_requirements():
|
||||
"""Extract the list of requirements from our requirements.txt.
|
||||
|
||||
:rtype: 2-tuple
|
||||
:returns: Two lists, the first is a list of requirements in the form of
|
||||
pkgname==version. The second is a list of URIs or VCS checkout strings
|
||||
which specify the dependency links for obtaining a copy of the
|
||||
requirement.
|
||||
"""
|
||||
requirements_file = os.path.join(os.getcwd(), 'requirements.txt')
|
||||
requirements = []
|
||||
links=[]
|
||||
try:
|
||||
with open(requirements_file) as reqfile:
|
||||
for line in reqfile.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
elif line.startswith(
|
||||
('https://', 'git://', 'hg://', 'svn://')):
|
||||
links.append(line)
|
||||
else:
|
||||
requirements.append(line)
|
||||
|
||||
except (IOError, OSError) as error:
|
||||
print(error)
|
||||
|
||||
if python26():
|
||||
# Required to make `collections.OrderedDict` available on Python<=2.6
|
||||
requirements.append('ordereddict==1.1#a0ed854ee442051b249bfad0f638bbec')
|
||||
|
||||
return requirements, links
|
||||
|
||||
|
||||
requires, deplinks = get_requirements()
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
name = "gnupg",
|
||||
description="A Python wrapper for GnuPG",
|
||||
|
@ -62,8 +109,10 @@ greater.
|
|||
scripts=['versioneer.py'],
|
||||
test_suite='gnupg.test.test_gnupg',
|
||||
|
||||
install_requires=['psutil>=0.5.1'],
|
||||
extras_require={'docs': ["Sphinx>=1.1", "repoze.sphinx"]},
|
||||
install_requires=requires,
|
||||
dependency_links=deplinks,
|
||||
extras_require={'docs': ["Sphinx>=1.1",
|
||||
"sphinxcontrib-fulltoc==1.0"]},
|
||||
|
||||
platforms="Linux, BSD, OSX, Windows",
|
||||
download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",
|
||||
|
|
Loading…
Reference in New Issue