From f61d4f585f27c13fabf7a23ad295bdc8bea7c838 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 5 Mar 2018 09:40:41 +1100 Subject: [PATCH 01/89] IDENTIFY * Fixed sp error in docstring. --- src/gpgme-tool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 3e2dc785..e7a7a6f3 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -3101,7 +3101,7 @@ cmd_hash_algo_name (assuan_context_t ctx, char *line) static const char hlp_identify[] = - "IDENTIY\n" + "IDENTIFY\n" "\n" "Identify the type of data set with the INPUT command."; static gpg_error_t From 1516c56ee4da28eb720bbacb026892393d10b24a Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 10:41:18 +1100 Subject: [PATCH 02/89] Removed double * default.profraw didn't need to be listed twice. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index de173b8f..e5bf69dc 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,3 @@ nosetests.xml default.profraw .DS_Store ._.DS_Store -default.profraw \ No newline at end of file From 13d2164cd9f313b409b2210d9e63465681cccc99 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 16:07:24 +1100 Subject: [PATCH 03/89] Nuxed doubles * Just because there's a lot of documentation which needs to be added, doesn't mean it needs to be listed twice. Merged the two lists. --- TODO | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/TODO b/TODO index d02a0d29..ae6ff135 100644 --- a/TODO +++ b/TODO @@ -1,22 +1,5 @@ Hey Emacs, this is -*- org -*- mode! -* Document all the new stuff. - :PROPERTIES: - :CUSTOM_ID: more-docs-is-better - :END: -** TODO Fix this TODO list. - :PROPERTIES: - :CUSTOM_ID: fix-todo - :END: - Clean up the current TODO list. Include properties as relevant (so - if someone does make a PDF or HTML version the TOC will work). - - Also check ans see if some of these ancient things can be removed - (e.g. do we really need to fix things that were broken in GPG - 1.3.x? I'm thinking not so much). - - - * Fix the remaining UI Server problems: :PROPERTIES: :CUSTOM_ID: ui-server-fix @@ -264,27 +247,42 @@ Hey Emacs, this is -*- org -*- mode! :PROPERTIES: :CUSTOM_ID: documentation :END: + ** TODO Document validity and trust issues. :PROPERTIES: :CUSTOM_ID: valid-trust-issues :END: + ** In gpgme.texi: Register callbacks under the right letter in the index. :PROPERTIES: :CUSTOM_ID: gpgme-texi :END: -** TODO Update TODO file + +** Document all the new stuff. :PROPERTIES: - :CUSTOM_ID: todo-update + :CUSTOM_ID: more-docs-is-better :END: -*** DONE fix TODO items - CLOSED: [2018-03-04 Sun 08:55] +*** TODO Fix this TODO list. :PROPERTIES: - :CUSTOM_ID: fix-todo-items + :CUSTOM_ID: fix-todo :END: - Adjust todo items so each can now be referenced by custom-id and - checked off as necessary. + Clean up the current TODO list. Include properties as relevant (so + if someone does make a PDF or HTML version the TOC will work). + + Also check ans see if some of these ancient things can be removed + (e.g. do we really need to fix things that were broken in GPG + 1.3.x? I'm thinking not so much). + +**** DONE fix TODO items + CLOSED: [2018-03-04 Sun 08:55] + :PROPERTIES: + :CUSTOM_ID: fix-todo-items + :END: + + Adjust todo items so each can now be referenced by custom-id and + checked off as necessary. * Engines :PROPERTIES: From 3a746d5d46ffd7d332dc24fd6a4d24efc5fc1230 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 16:11:35 +1100 Subject: [PATCH 04/89] copyright fix * Made the copyright line a new top level org heading in order to prevent it getting folded into other tasks which will eventually get closed (so it doesn't go missing if those items are subsequently archived). --- TODO | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index ae6ff135..a4316512 100644 --- a/TODO +++ b/TODO @@ -578,7 +578,11 @@ Hey Emacs, this is -*- org -*- mode! Write a guide/best practices for maintainers of GPGME packages with third party package management systems. -Copyright 2004, 2005, 2018 g10 Code GmbH + +* Copyright 2004, 2005, 2018 g10 Code GmbH + :PROPERTIES: + :CUSTOM_ID: copyright-and-license + :END: This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without From d4778bb23d0817ee6fbcbe4f0ff0ff0429bf3669 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 17:56:54 +1100 Subject: [PATCH 05/89] TODO * Slightly expanded the list. --- lang/python/docs/TODO.org | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index 9f039d81..9b042d99 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -35,6 +35,11 @@ Write a HOWTO style guide for the current Python bindings. +*** TODO Start python bindings HOWTO + :PROPERTIES: + :CUSTOM_ID: howto-start + :END: + ** TODO Documentation SWIG :PROPERTIES: :CUSTOM_ID: todo-docs-swig @@ -94,6 +99,7 @@ available or for which it is too difficult to create proper bindings. + * Project Task Details :PROPERTIES: :CUSTOM_ID: detailed-tasks From 8f2c0f4534ea2a07f071f360a63e877f60dc52f2 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 19:13:37 +1100 Subject: [PATCH 06/89] TODO - HOWTO * Added suv-entry for the new HOWTO being started and, since it has been started, checked it off. --- lang/python/docs/TODO.org | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index 9b042d99..4e067df8 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -35,7 +35,8 @@ Write a HOWTO style guide for the current Python bindings. -*** TODO Start python bindings HOWTO +*** DONE Start python bindings HOWTO + CLOSED: [2018-03-07 Wed 18:14] :PROPERTIES: :CUSTOM_ID: howto-start :END: From 5215d58ae2521d81c3db0b45dfbdce01a679acab Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 20:05:21 +1100 Subject: [PATCH 07/89] GPGME Python bindings HOWTO * Started work on the GPGME Python bindings HOWTO. * 1,050 words to begin with at approx. 7.5KB. * Got as far as installation. * Includes instruction not to use PyPI for this. --- lang/python/docs/GPGMEpythonHOWTOen.org | 221 ++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 lang/python/docs/GPGMEpythonHOWTOen.org diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org new file mode 100644 index 00000000..b9dc882f --- /dev/null +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -0,0 +1,221 @@ +#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) + +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman} +#+LATEX_HEADER: \author{Ben McGinnes } + + +* Introduction + :PROPERTIES: + :CUSTOM_ID: intro + :END: + +Version: 0.0.1-alpha [2018-03-07 Wed] +Author: Ben McGinnes +Author GPG Key: DB4724E6FA4286C92B4E55C4321E4E2373590E5D + +This document provides basic instruction in how to use the GPGME +Python bindings to programmatically leverage the GPGME library. + + +* GPGME Concepts + :PROPERTIES: + :CUSTOM_ID: gpgme-concepts + :END: + +** A C API + :PROPERTIES: + :CUSTOM_ID: gpgme-c-api + :END: + + Unlike many modern APIs with which programmers will be more + familiar with these days, the GPGME API is a C API. The API is + intended for use by C coders who would be able to access its + features by including the gpgme.h header file eith their own C + source code and then access its functions just as they would any + other C headers. + + This is a very effective method of gaining complete access to the + API and in the most efficient manner possible. It does, however, + have the drawback that it cannot be directly used by other + languages without some means of providing an interface to those + languages. This is where the need for bindings in various + languages stems. + +** Python bindings + :PROPERTIES: + :CUSTOM_ID: gpgme-python-bindings + :END: + + The Python bindings for GPGME provide a higher level means of + accessing the complete feature set of GPGME itself. It also + provides a more pythonic means of calling these API functions. + + The bindings are generated dynamically with SWIG and the copy of + gpgme.h gemerated when GPGME is compiled. + + This means that a version of the Python bindings is fundamentally + tied to the exact same version of GPGME used to gemerate that copy + of gpgme.h. + +** Difference between the Python bindings and other GnuPG Python packages + :PROPERTIES: + :CUSTOM_ID: gpgme-python-bindings-diffs + :END: + + There have been numerous attempts to add GnuPG support to Python + over the years. Some of the most well known are listed here, along + with what differentiates them. + +*** The python-gnupg package maintained by Vinay Sajip + :PROPERTIES: + :CUSTOM_ID: diffs-python-gnupg + :END: + + This is arguably the most popular means of integrating GPG with + Python. The package utilises the =subprocess= module to implement + wrappers for the =gpg= and =gpg2= executables normally invoked on + the command line (=gpg.exe= and =gpg2.exe= on Windows). + + The popularity of this package stemmed from its ease of use and + capability in providing the most commonly required features. + + Unfortunately it has been beset by a number of security issues, + most of which stemmed from using unsafe methods of accessing the + command line via the =subprocess= calls. + + The python-gnupg package is available under the MIT license. + +*** The gnupg package created and maintained by Isis Lovecruft + :PROPERTIES: + :CUSTOM_ID: diffs-isis-gnupg + :END: + + In 2015 Isis Lovecruft from the Tor Project forked and then + re-implemented the python-gnupg package as just gnupg. This new + package also relied on subprocess to call the =gpg= or =gpg2= + binaries, but did so somewhat more securely. + + However the naming and version numbering selected for this package + resulted in conflicts with the original python-gnupg and since its + functions were called in a different manner, the release of this + package also resulted in a great deal of consternation when people + installed what they thought was an upgrade that subsequently broke + the code relying on it. + + The gnupg package is available under the GNU General Public + License version 3.0 (or later). + +*** The PyME package maintained by Martin Albrecht + :PROPERTIES: + :CUSTOM_ID: diffs-pyme + :END: + + This package is the origin of these bindings, though they are + somewhat different now. For details of when and how the PyME + package was folded back into GPGME itself see the [[Short_History.org][Short History]] + document in this Python bindings =docs= directory. + + The PyME package was first released in 2002 and was also the first + attempt to implement a low level binding to GPGME. In doing so it + provided access to considerably more functionality than either the + =python-gnupg= or =gnupg= packages. + + The PyME package is only available for Python 2.6 and 2.7. + + Porting the PyME package to Python 3.4 in 2015 is what resulted in + it being folded into the GPGME project and the current bindings + are the end result of that effort. + + The PyME package is available under the same dual licensing as + GPGME itself: the GNU General Public License version 2.0 (or any + later version) and the GNU Lesser Public License version 2.1 (or + any later version). + + +* GPGME Python bindings installation + :PROPERTIES: + :CUSTOM_ID: gpgme-python-install + :END: + +** No PyPI + :PROPERTIES: + :CUSTOM_ID: do-not-use-pypi + :END: + + Most third-party Python packages and modules are available and + distributed through the Python Package Installer, known as PyPI. + + Due to the nature of what these bindings are and how they work, it + is infeasible to install the GPGME Python bindings in the same way. + +** Requirements + :PROPERTIES: + :CUSTOM_ID: gpgme-python-requirements + :END: + + The GPGME Python bindings only have three requirements: + + 1. A suitable version of Python 2 or Python 3. With Python 2 that + means Python 2.7 and with Python 3 that means Python 3.4 or + higher. + 2. SWIG. + 3. GPGME itself. Which also means that all of GPGME's dependencies + must be installed too. + +** Installation + :PROPERTIES: + :CUSTOM_ID: installation + :END: + + Installing the Python bindings is effectively achieved by compiling + and installing GPGME itself. + + Once SWIG is installed with Python and all the dependencies for + GPGME are installed you only need to confirm that the version(s) of + Python you want the bindings installed for are in your =$PATH=. + + By default GPGME will attempt to install the bindings for the most + recent or highest version number of Python 2 and Python 3 it + detects in =$PATH=. It specifically checks for the =python= and + =python3= executabled first and then checks for specific version + numbers. + + For Python 2 it checks for these executables in this order: + =python=, =python2=, =python2.7= and =python2.6=. + + For Python 3 it checks for these executables in this order: + =python3=, =python3.6=, =python3.5= and =python3.4=. + +*** Installing GPGME + :PROPERTIES: + :CUSTOM_ID: install-gpgme + :END: + + See the [[../../../README][GPGME README file]] for details of how to install GPGME from + source. + + +* Copyright and Licensing + :PROPERTIES: + :CUSTOM_ID: copyright-and-license + :END: + +** Copyright (C) The GnuPG Project, 2018 + :PROPERTIES: + :CUSTOM_ID: copyright + :END: + + Copyright © The GnuPG Project, 2018. + +** License TBA + :PROPERTIES: + :CUSTOM_ID: license + :END: + + Which license shall we use for these docs, hmm? Gotta be free, so + that rules the GFDL out. I'll pick a CC or something later ... From 8a76deb11efd7dadfde6e8e7e69fbcd92577982f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 20:12:26 +1100 Subject: [PATCH 08/89] HOWTO update * removed one bit of whitespace. * Marked up references to gpgme.h. * Fixed one spelling error. * Removed py2.6 from python search order since even if it is supported, it shouldn't be encouraged. --- lang/python/docs/GPGMEpythonHOWTOen.org | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index b9dc882f..1767cd49 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -14,7 +14,7 @@ :CUSTOM_ID: intro :END: -Version: 0.0.1-alpha [2018-03-07 Wed] +Version: 0.0.1-alpha [2018-03-07 Wed] Author: Ben McGinnes Author GPG Key: DB4724E6FA4286C92B4E55C4321E4E2373590E5D @@ -35,7 +35,7 @@ Python bindings to programmatically leverage the GPGME library. Unlike many modern APIs with which programmers will be more familiar with these days, the GPGME API is a C API. The API is intended for use by C coders who would be able to access its - features by including the gpgme.h header file eith their own C + features by including the =gpgme.h= header file eith their own C source code and then access its functions just as they would any other C headers. @@ -56,11 +56,11 @@ Python bindings to programmatically leverage the GPGME library. provides a more pythonic means of calling these API functions. The bindings are generated dynamically with SWIG and the copy of - gpgme.h gemerated when GPGME is compiled. + =gpgme.h= generated when GPGME is compiled. This means that a version of the Python bindings is fundamentally tied to the exact same version of GPGME used to gemerate that copy - of gpgme.h. + of =gpgme.h=. ** Difference between the Python bindings and other GnuPG Python packages :PROPERTIES: @@ -186,7 +186,7 @@ Python bindings to programmatically leverage the GPGME library. numbers. For Python 2 it checks for these executables in this order: - =python=, =python2=, =python2.7= and =python2.6=. + =python=, =python2= and =python2.7=. For Python 3 it checks for these executables in this order: =python3=, =python3.6=, =python3.5= and =python3.4=. From 47d401d159852ea08e90af21d91bb4b93be9000d Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 21:27:54 +1100 Subject: [PATCH 09/89] GPL compatible license for documentation * Added the same, slightly modified GPL based license that is used in other parts of GnuPG. --- lang/python/docs/GPGMEpythonHOWTOen.org | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 1767cd49..28038614 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -212,10 +212,16 @@ Python bindings to programmatically leverage the GPGME library. Copyright © The GnuPG Project, 2018. -** License TBA +** License GPL compatible :PROPERTIES: :CUSTOM_ID: license :END: - Which license shall we use for these docs, hmm? Gotta be free, so - that rules the GFDL out. I'll pick a CC or something later ... + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. From e8adab68f8c0cd865ff220f06dfaff7fe183e8a1 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 8 Mar 2018 14:13:00 +1100 Subject: [PATCH 10/89] doc: Added multiple TODOs for inclusion in the HOWTO * Some instructions to include are fairly obvious; as with encryption, decryption and signature verification. * Some are a little less obvious. * This includes the requests received to specifically include subkey management (adding and revoking subkeys on a primary key that's being retained. * Added the UID equivalents to the list, as well as key selection matters (and may or may not include something for handling group lines since that involves wrapping a CLI binary). * Key control documentation and examples requested by Mike Ingle of confidantmail.org. --- lang/python/docs/TODO.org | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index 4e067df8..897c617a 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -28,11 +28,13 @@ to produce reST versions via Pandoc and DITA XML can be reached through converting to either Markdown or XHTML first. -** TODO Documentation HOWTO +** STARTED Documentation HOWTO :PROPERTIES: :CUSTOM_ID: todo-docs-howto :END: + - State "STARTED" from "TODO" [2018-03-08 Thu 13:59] \\ + Started yesterday. Write a HOWTO style guide for the current Python bindings. *** DONE Start python bindings HOWTO @@ -41,6 +43,46 @@ :CUSTOM_ID: howto-start :END: +*** TODO Include certain specific instructions in the HOWTO + :PROPERTIES: + :CUSTOM_ID: howto-requests + :END: + + Some functions can be worked out from the handful of examples + available, but many more can't and I've already begun receiving + requests for certain functions to be explained. + +**** TODO Standard scenarios + :PROPERTIES: + :CUSTOM_ID: howto-the-basics + :END: + + What everyone expects: encryption, decryption, signing and verifying. + +**** TODO Key control + :PROPERTIES: + :CUSTOM_ID: howto-key-control + :END: + + Generating keys, adding subkeys, revoking subkeys (and keeping + the cert key), adding and revoking UIDs, signing/certifying keys. + +**** TODO Key control + :PROPERTIES: + :CUSTOM_ID: howto-key-selection + :END: + + Selecting keys to encrypt to or manipulate in other ways (e.g. as + with key control or the basics). + +**** TODO S/MIME + :PROPERTIES: + :CUSTOM_ID: howto-s-mime + :END: + + Eventually add some of this, but it the OpenPGP details are far + more important at the moment. + ** TODO Documentation SWIG :PROPERTIES: :CUSTOM_ID: todo-docs-swig From a98f2c556fe6e33a9cd38279e64e4b09f05cc675 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 8 Mar 2018 15:23:05 +1100 Subject: [PATCH 11/89] doc-howto: fundamental aspects of GPGME vs Python * Added a section for those pythonistas who are too used to web programming. Stressed that it's not simply not RESTful, it's not even REST-like. * Letting me move on to drawing a very loose parallel between a session and a context. The differences should become obvious in the subsequent sections. --- lang/python/docs/GPGMEpythonHOWTOen.org | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 28038614..e7dc53de 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1,5 +1,4 @@ #+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) - #+LATEX_COMPILER: xelatex #+LATEX_CLASS: article #+LATEX_CLASS_OPTIONS: [12pt] @@ -200,6 +199,61 @@ Python bindings to programmatically leverage the GPGME library. source. +* Fundamentals + :PROPERTIES: + :CUSTOM_ID: howto-fund-a-mental + :END: + + Before we can get to the fun stuff, there are a few matters + regarding GPGME's design which hold true whether you're dealing with + the C code directly or these Python bindings. + +** No REST + :PROPERTIES: + :CUSTOM_ID: no-rest-for-the-wicked + :END: + + The first part of which is or will be fairly blatantly obvious upon + viewing the first example, but it's worth reiterating anyway. That + being that this API is /*not*/ a REST API. Nor indeed could it + ever be one. + + Most, if not all, Python programmers (and not just Python + programmers) know how easy it is to work with a RESTful API. In + fact they've become so popular that many other APIs attempt to + emulate REST-like behaviour as much as they are able. Right down + to the use of JSON formatted output to facilitate the use of their + API without having to retrain developers. + + This API does not do that. It would not be able to do that and + also provide access to the entire C API on which it's built. It + does, however, provide a very pythonic interface on top of the + direct bindings and it's this pythonic layer with which this HOWTO + deals with. + +** Context + :PROPERTIES: + :CUSTOM_ID: howto-get-context + :END: + + One of the reasons which prevents this API from being RESTful is + that most operations require more than one instruction to the API + to perform the task. Sure, there are certain functions which can + be performed simultaneously, particularly if the result known or + strongly anticipated (e.g selecting and encrypting to a key known + to be in the public keybox). + + There are many more, however, which cannot be manipulated so + readily: they must be performed in a specific sequence and the + result of one operation has a direct bearing on the outcome of + subsequent operations. Not merely by generating an error either. + + When dealing with this type of persistant state on the web, full of + both the RESTful and REST-like, it's most commonly referred to as a + session. In GPGME, however, it is called a context and every + operation type has one. + + * Copyright and Licensing :PROPERTIES: :CUSTOM_ID: copyright-and-license From 75463d589522cba427f9e5a3a408192ffad8bb21 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 04:42:41 +1100 Subject: [PATCH 12/89] doc: Basic operation of the python bindings * Added sample code for encrypting some text to a single key. * Basically I'm just lifting existing production code and changing the key IDs from mine to "0x12345678DEADBEEF" for these first few examples. * I'll fill in the text description after. * Note: due to my regional location, I might split some tasks into more commits in order to be sure no work gets lost in case of emergency (or to put it another way: I know Telstra too well to trust them). --- lang/python/docs/GPGMEpythonHOWTOen.org | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index e7dc53de..8f81511c 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -254,6 +254,57 @@ Python bindings to programmatically leverage the GPGME library. operation type has one. +* Basic Functions + :PROPERTIES: + :CUSTOM_ID: howto-the-basics + :END: + +** Encryption + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption + :END: + + Encrypting to one key: + + #+begin_src python + import gpg + import os + import os.path + + rkey = "0x12345678DEADBEEF" + text = """ + Some plain text to test with. Obtained from any input source Python can read. + + It makes no difference whether it is string or bytes, but the bindings always + produce byte output data. Which is useful to know when writing out either the + encrypted or decrypted results. + + """ + + plain = gpg.core.Data(text) + cipher = gpg.core.Data() + c = gpg.core.Context() + c.set_armor(1) + + c.op_keylist_start(rkey, 0) + r = c.op_keylist_next() + + if r == None: + print("""The key for user "{0}" was not found""".format(rkey)) + else: + try: + c.op_encrypt([r], 1, plain, cipher) + cipher.seek(0, os.SEEK_SET) + del(text) + del(plain) + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher.read()) + afile.close() + except gpg.errors.GPGMEError as ex: + print(ex.getstring()) + #+end_src + + * Copyright and Licensing :PROPERTIES: :CUSTOM_ID: copyright-and-license From c767a4a3590bd8a224d0268746df443942cb28c2 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 05:25:49 +1100 Subject: [PATCH 13/89] doc: python bindings howto update * Added example of decryption. * included some quick notes for myself regarding aspects to explain when I flesh out the explanatory text. --- lang/python/docs/GPGMEpythonHOWTOen.org | 38 +++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 8f81511c..ab7e9db8 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -269,7 +269,6 @@ Python bindings to programmatically leverage the GPGME library. #+begin_src python import gpg import os - import os.path rkey = "0x12345678DEADBEEF" text = """ @@ -297,13 +296,48 @@ Python bindings to programmatically leverage the GPGME library. cipher.seek(0, os.SEEK_SET) del(text) del(plain) - afile = open("secret_plans.txt.asc", "wb") + afile = open("secret_plans.org.asc", "wb") afile.write(cipher.read()) afile.close() except gpg.errors.GPGMEError as ex: print(ex.getstring()) #+end_src +** Decryption + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption + :END: + + Decrypting something encrypted to a key in one's secret keyring + (will display some extra data you normally wouldn't show, but which + may be of use): + + #+begin_src python + import os.path + import gpg + + if os.path.exists("/path/to/secret_plans.org.asc") is True: + ciphertext = "/path/to/secret_plans.org.asc" + elif os.path.exists("/path/to/secret_plans.org.gpg") is True: + ciphertext = "/path/to/secret_plans.org.gpg" + else: + ciphertext = None + + if ciphertext is not None: + afile = open(ciphertext, "rb") + plaintext = gpg.Context().decrypt(afile) + afile.close() + newfile = open("/path/to/secret_plans.org", "wb") + newfile.write(plaintext[0]) + newfile.close() + print(plaintext[0]) + plaintext[1] + plaintext[2] + del(plaintext) + else: + pass + #+end_src + * Copyright and Licensing :PROPERTIES: From fa4927146b68dd045903285f1c45fb64deb2e361 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 07:53:57 +1100 Subject: [PATCH 14/89] docs: python bindings howto update. * Added all four signing code examples that are most likely to be used: armoured, clearsigned, detached armoured and detached binary. * May remove some examples and just discuss the differences, but it depends on the way the text is filled out. --- lang/python/docs/GPGMEpythonHOWTOen.org | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index ab7e9db8..17ec428e 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -338,6 +338,96 @@ Python bindings to programmatically leverage the GPGME library. pass #+end_src +** Signing text + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing + :END: + + Need to determine whether or not to include clearsigning and + detached signing here or give them separate sections. + + #+begin_src python + import gpg + + text = """Declaration of ... something. + + """ + + c = gpg.Context() + c.armor = True + signed = c.sign(text, mode=mode.NORMAL) + + afile = open("/path/to/statement.txt.asc", "w") + for i in range(len(signed[0].splitlines())): + afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) + afile.close() + #+end_src + + Clearsigning: + + #+begin_src python + import gpg + + text = """Declaration of ... something. + + """ + + c = gpg.Context() + c.armor = True + signed = c.sign(text, mode=mode.CLEAR) + + afile = open("/path/to/statement.txt.asc", "w") + for i in range(len(signed[0].splitlines())): + afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) + afile.close() + #+end_src + + Detached ASCII Armoured signing: + + #+begin_src python + import gpg + + text = """Declaration of ... something. + + """ + + c = gpg.Context() + c.armor = True + signed = c.sign(text, mode=mode.DETACH) + + afile = open("/path/to/statement.txt.asc", "w") + for i in range(len(signed[0].splitlines())): + afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) + afile.close() + #+end_src + + Detached binary signing (maybe change text to be reading a file's + content): + + #+begin_src python +import gpg + +text = """Declaration of ... something. + +""" + +c = gpg.Context() +c.armor = True +signed = c.sign(text, mode=mode.DETACH) + +afile = open("/path/to/statement.txt.sig", "wb") +afile.write(signed[0]) +afile.close() + #+end_src + + +** Signature verification + :PROPERTIES: + :CUSTOM_ID: howto-basic-verification + :END: + +x + * Copyright and Licensing :PROPERTIES: From ab81c2d868bba79fdb8f8d7f576b6bd88c6bdf3c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 15:22:24 +1100 Subject: [PATCH 15/89] doc: python bindings howto * Added example for verifying both detached and "in-line" signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 59 +++++++++++++++++-------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 17ec428e..75f1ebd6 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -13,12 +13,12 @@ :CUSTOM_ID: intro :END: -Version: 0.0.1-alpha [2018-03-07 Wed] -Author: Ben McGinnes -Author GPG Key: DB4724E6FA4286C92B4E55C4321E4E2373590E5D + Version: 0.0.1-alpha [2018-03-07 Wed] + Author: Ben McGinnes + Author GPG Key: DB4724E6FA4286C92B4E55C4321E4E2373590E5D -This document provides basic instruction in how to use the GPGME -Python bindings to programmatically leverage the GPGME library. + This document provides basic instruction in how to use the GPGME + Python bindings to programmatically leverage the GPGME library. * GPGME Concepts @@ -401,23 +401,22 @@ Python bindings to programmatically leverage the GPGME library. afile.close() #+end_src - Detached binary signing (maybe change text to be reading a file's - content): + Detached binary signing of a file. #+begin_src python -import gpg + import gpg -text = """Declaration of ... something. + tfile = open("/path/to/statement.txt", "r") + text = tfile.read() + tfile.close() -""" + c = gpg.Context() + c.armor = True + signed = c.sign(text, mode=mode.DETACH) -c = gpg.Context() -c.armor = True -signed = c.sign(text, mode=mode.DETACH) - -afile = open("/path/to/statement.txt.sig", "wb") -afile.write(signed[0]) -afile.close() + afile = open("/path/to/statement.txt.sig", "wb") + afile.write(signed[0]) + afile.close() #+end_src @@ -426,7 +425,31 @@ afile.close() :CUSTOM_ID: howto-basic-verification :END: -x + Verify a signed file, both detached and not: + + #+begin_src python + import gpg + import sys + import time + + c = gpg.Context() + + data, result = c.verify(open(filename), + open(detached_sig_filename) + if detached_sig_filename else None) + + for index, sign in enumerate(result.signatures): + print("signature", index, ":") + print(" summary: %#0x" % (sign.summary)) + print(" status: %#0x" % (sign.status)) + print(" timestamp: ", sign.timestamp) + print(" timestamp: ", time.ctime(sign.timestamp)) + print(" fingerprint:", sign.fpr) + print(" uid: ", c.get_key(sign.fpr).uids[0].uid) + + if data: + sys.stdout.buffer.write(data) + #+end_src * Copyright and Licensing From 93252df9dc4c9932467814745655350a8cab900e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 15:27:40 +1100 Subject: [PATCH 16/89] doc: python bindings TODO list * Updated to reflect the most recent work on the HOWTO for the Python bindings. --- lang/python/docs/TODO.org | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index 897c617a..df1aa4ed 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -43,20 +43,23 @@ :CUSTOM_ID: howto-start :END: -*** TODO Include certain specific instructions in the HOWTO +*** STARTED Include certain specific instructions in the HOWTO :PROPERTIES: :CUSTOM_ID: howto-requests :END: + - State "STARTED" from "TODO" [2018-03-09 Fri 15:27] Some functions can be worked out from the handful of examples available, but many more can't and I've already begun receiving requests for certain functions to be explained. -**** TODO Standard scenarios +**** STARTED Standard scenarios :PROPERTIES: :CUSTOM_ID: howto-the-basics :END: + - State "STARTED" from "TODO" [2018-03-09 Fri 15:26] \\ + Began with the example code, now to add the text. What everyone expects: encryption, decryption, signing and verifying. **** TODO Key control From 01686463948ac6096dd8579a110c478d3a1f9a83 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 16:49:05 +1100 Subject: [PATCH 17/89] doc: python bindings howto * Wrote the text description explaining each step in the most basic encryption operation. * Will need to include additional examples for encrypting to multiple recipients using Context().encrypt instead of Context().op_encrypt. --- lang/python/docs/GPGMEpythonHOWTOen.org | 60 ++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 75f1ebd6..0b882b55 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -259,12 +259,51 @@ :CUSTOM_ID: howto-the-basics :END: + The most frequently called features of any cryptographic library + will be the most fundamental tasks for enxryption software. In this + section we will look at how to programmatically encrypt data, + decrypt it, sign it and verify signatures. + ** Encryption :PROPERTIES: :CUSTOM_ID: howto-basic-encryption :END: - Encrypting to one key: + Encrypting is very straight forward. In the first example below + the message, =text=, is encrypted to a single recipient's key. In + the second example the message will be encrypted to multiple + recipients. + +*** Encrypting to one key + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-single + :END: + + The text is then encapsulated in a GPGME Data object as =plain= and + the =cipher= object is created with another Data object. Then we + create the Context as =c= and set it to use the ASCII armoured + OpenPGP format. In later examples there will be alternative + methods of setting the OpenPGP output to be ASCII armoured. + + Next we prepare a keylist object in our Context and follow it with + specifying the recipients as =r=. Note that the configuration in + one's =gpg.conf= file is honoured, so if you have the options set + to encrypt to one key or to a default key, that will be included + with this operation. + + This is followed by a quick check to be sure that the recipient is + actually selected and that the key is available. Assuming it is, + the encryption can proceed, but if not a message will print stating + the key was not found. + + The encryption operation is invoked within the Context with the + =c.op_encrypt= function, loading the recipien (=r=), the message + (=plain=) and the =cipher=. The =cipher.seek= uses =os.SEEK_SET= + to set the data to the correct byte format for GPGME to use it. + + At this point we no longer need the plaintext material, so we + delete both the =text= and the =plain= objects. Then we write the + encrypted data out to a file, =secret_plans.txt.asc=. #+begin_src python import gpg @@ -296,13 +335,19 @@ cipher.seek(0, os.SEEK_SET) del(text) del(plain) - afile = open("secret_plans.org.asc", "wb") + afile = open("secret_plans.txt.asc", "wb") afile.write(cipher.read()) afile.close() except gpg.errors.GPGMEError as ex: print(ex.getstring()) #+end_src +*** Encrypting to multiple keys + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-multiple + :END: + + ** Decryption :PROPERTIES: :CUSTOM_ID: howto-basic-encryption @@ -316,10 +361,10 @@ import os.path import gpg - if os.path.exists("/path/to/secret_plans.org.asc") is True: - ciphertext = "/path/to/secret_plans.org.asc" - elif os.path.exists("/path/to/secret_plans.org.gpg") is True: - ciphertext = "/path/to/secret_plans.org.gpg" + if os.path.exists("/path/to/secret_plans.txt.asc") is True: + ciphertext = "/path/to/secret_plans.txt.asc" + elif os.path.exists("/path/to/secret_plans.txt.gpg") is True: + ciphertext = "/path/to/secret_plans.txt.gpg" else: ciphertext = None @@ -327,7 +372,7 @@ afile = open(ciphertext, "rb") plaintext = gpg.Context().decrypt(afile) afile.close() - newfile = open("/path/to/secret_plans.org", "wb") + newfile = open("/path/to/secret_plans.txt", "wb") newfile.write(plaintext[0]) newfile.close() print(plaintext[0]) @@ -338,6 +383,7 @@ pass #+end_src + ** Signing text :PROPERTIES: :CUSTOM_ID: howto-basic-signing From f2c1e8d8d54068a7f072efa178fc30460821eff3 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 20:44:02 +1100 Subject: [PATCH 18/89] doc: python TODO list * Slightly tweaked one heading to make it clear it wasn't a duplicate. --- lang/python/docs/TODO.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index df1aa4ed..21d2216f 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -70,7 +70,7 @@ Generating keys, adding subkeys, revoking subkeys (and keeping the cert key), adding and revoking UIDs, signing/certifying keys. -**** TODO Key control +**** TODO More key control :PROPERTIES: :CUSTOM_ID: howto-key-selection :END: From 172baaf4d3e4ed03a4d3437be9efa3dfe6a847bc Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 20:45:14 +1100 Subject: [PATCH 19/89] doc: python bindings HOWTO * Added instructions and code to count the number of public and secret keys available since it was quick and easy. --- lang/python/docs/GPGMEpythonHOWTOen.org | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 0b882b55..4385bc9b 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -498,6 +498,39 @@ #+end_src +* Working with keys + :PROPERTIES: + :CUSTOM_ID: howto-keys + :END: + +** Counting keys + :PROPERTIES: + :CUSTOM_ID: howto-basic-verification + :END: + + Counting the number of keys in your public keybox (=pubring.kbx=), + the format shich has superceded the old keyring format + (=pubring.gpg= and =secring.gpg=) is a very simple task. + + #+begin_src python + import gpg + + c = gpg.Context() + seckeys = c.keylist(pattern=None, secret=True) + pubkeys = c.keylist(pattern=None, secret=False) + + seclist = list(seckeys) + secnum = len(seclist) + + publist = list(pubkeys) + pubnum = len(publist) + + print(""" + Number of secret keys: {0} + Number of public keys: {1} + """.format(secnum, pubnum) + #+end_src + * Copyright and Licensing :PROPERTIES: :CUSTOM_ID: copyright-and-license From 7ebc5a357057d01b7ef965521ab68b7cb7e20a8f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 00:33:11 +1100 Subject: [PATCH 20/89] doc: python bindings howto * Switched from links to some external docs to using footnotes where necessary. * Ideally the howto should be as stand alone as possible. * Also it makes it difficult to convert to another format for proof-reading if there are links that the conversion can't find. --- lang/python/docs/GPGMEpythonHOWTOen.org | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 4385bc9b..42cd3c00 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -116,8 +116,8 @@ This package is the origin of these bindings, though they are somewhat different now. For details of when and how the PyME - package was folded back into GPGME itself see the [[Short_History.org][Short History]] - document in this Python bindings =docs= directory. + package was folded back into GPGME itself see the /Short History/ + document[fn:1] in this Python bindings =docs= directory.[fn:2] The PyME package was first released in 2002 and was also the first attempt to implement a low level binding to GPGME. In doing so it @@ -195,7 +195,7 @@ :CUSTOM_ID: install-gpgme :END: - See the [[../../../README][GPGME README file]] for details of how to install GPGME from + See the GPGME =README= file for details of how to install GPGME from source. @@ -556,3 +556,13 @@ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +* Footnotes + :PROPERTIES: + :CUSTOM_ID: footnotes + :END: + +[fn:1] Short_History.org and/or Short_History.html. + +[fn:2] The =lang/python/docs/= directory in the GPGME source. From 0e1300ce777dd0c87f31ac8bc49846b9df242df9 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 04:55:44 +1100 Subject: [PATCH 21/89] doc: python bindings howto * Added a more complicated encryption example with a few variations on the encryption method to account for untrusted recipient keys, signing or not signing, including or excluding default keys and so on. --- lang/python/docs/GPGMEpythonHOWTOen.org | 86 ++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 42cd3c00..84be8513 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -347,6 +347,83 @@ :CUSTOM_ID: howto-basic-encryption-multiple :END: + Encrypting to multiple keys, in addition to a default key or a key + configured to always encrypt to, is a little different and uses a + slightly different call to the op_encrypt call demonstrated in the + previous section. + + The following example encrypts a message (=text=) to everyone with + an email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt + to a default key or other key which is configured to normally + encrypt to. + + #+begin_src python + import gpg + + text=b"""Oh look, another test message. + + The same rules apply as with the previous example and more likely + than not, the message will actually be drawn from reading the + contents of a file or, maybe, from entering data at an input() + prompt. + + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data + format. + """ + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) + rlogrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + rlogrus.append(rpattern[i]) + + cipher = c.encrypt(text, recipients=rlogrus, sign=False, always_trust=True) + + afile = open("encrypted_file.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() + #+end_src + + All it would take to change the above example to sign the message + and also encrypt the message to any configured default keys would + be to change the =c.encrypt= line to this: + + #+begin_src python + cipher = c.encrypt(text, recipients=rlogrus, always_trust=True, + add_encrypt_to=True) + #+end_src + + The only keyword arguments requiring modification are those for + which the default values are changing. The default value of + =sign= is =True=, the default of =always_trust= is =False=, the + default of =add_encrypt_to= is =False=. + + If =always_trust= is not set to =True= and any of the recipient + keys are not trusted (e.g. not signed or locally signed) then the + encryption will raise an error. It is possible to mitigate this + somewhat with something more like this: + + #+begin_src python + try: + cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) + except gpg.errors.InvalidRecipients as e: + for i in range(len(e.recipients)): + for n in range(len(rlogrus)): + if rlogrus[n].fpr == e.recipients[i].fpr: + rlogrus.remove(e.recipients[i]) + try: + cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) + except: + pass + #+end_src + + This will attempt to encrypt to all the keys searched for, then + remove invalid recipients if it fails and try again. + ** Decryption :PROPERTIES: @@ -531,6 +608,7 @@ """.format(secnum, pubnum) #+end_src + * Copyright and Licensing :PROPERTIES: :CUSTOM_ID: copyright-and-license @@ -559,10 +637,12 @@ * Footnotes - :PROPERTIES: - :CUSTOM_ID: footnotes - :END: [fn:1] Short_History.org and/or Short_History.html. [fn:2] The =lang/python/docs/= directory in the GPGME source. + +[fn:3] You probably don't really want to do this. Searching the +keyservers for "gnupg.org" produces over 400 results, the majority of +which aren't actually at the gnupg.org domain, but just included a +comment regarding the project in their key somewhere. From 83b1336ceebb86e13a55bbf220df2d750f6b3ec6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 05:42:50 +1100 Subject: [PATCH 22/89] doc: python bindings howto * Fixed an error in the encryption try/except statement. --- lang/python/docs/GPGMEpythonHOWTOen.org | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 84be8513..22b47cca 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -414,7 +414,9 @@ for i in range(len(e.recipients)): for n in range(len(rlogrus)): if rlogrus[n].fpr == e.recipients[i].fpr: - rlogrus.remove(e.recipients[i]) + rlogrus.remove(rlogrus[n]) + else: + pass try: cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) except: From a8f48b6f577d562c25fd0191c0cc2cc8e96078c1 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 06:09:53 +1100 Subject: [PATCH 23/89] doc: python bindings howto * error corrections. * multiple typesetting fixes only required due to certain archaic eccentricities of LaTeX. * a couple of minor python PEP8 compliance corrections. --- lang/python/docs/GPGMEpythonHOWTOen.org | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 22b47cca..46bd231b 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -13,9 +13,10 @@ :CUSTOM_ID: intro :END: - Version: 0.0.1-alpha [2018-03-07 Wed] - Author: Ben McGinnes - Author GPG Key: DB4724E6FA4286C92B4E55C4321E4E2373590E5D + | Version: | 0.0.1-alpha | + | Author: | Ben McGinnes | + | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | + | Language: | English | This document provides basic instruction in how to use the GPGME Python bindings to programmatically leverage the GPGME library. @@ -349,7 +350,7 @@ Encrypting to multiple keys, in addition to a default key or a key configured to always encrypt to, is a little different and uses a - slightly different call to the op_encrypt call demonstrated in the + slightly different call to the =op_encrypt call= demonstrated in the previous section. The following example encrypts a message (=text=) to everyone with @@ -360,7 +361,7 @@ #+begin_src python import gpg - text=b"""Oh look, another test message. + text = b"""Oh look, another test message. The same rules apply as with the previous example and more likely than not, the message will actually be drawn from reading the @@ -640,7 +641,7 @@ * Footnotes -[fn:1] Short_History.org and/or Short_History.html. +[fn:1] =Short_History.org= and/or =Short_History.html=. [fn:2] The =lang/python/docs/= directory in the GPGME source. From 484e9a6229ac9c80c6be4df638bce711f08a74c6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 07:42:04 +1100 Subject: [PATCH 24/89] doc: python bindings howto * updated multi-encryption final example to be complete. * second example shows most likely method of reading plaintext. * updated example filenames to stick with running gag (i.e. secret_plans.txt). --- lang/python/docs/GPGMEpythonHOWTOen.org | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 46bd231b..622475f4 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -384,7 +384,7 @@ cipher = c.encrypt(text, recipients=rlogrus, sign=False, always_trust=True) - afile = open("encrypted_file.txt.asc", "wb") + afile = open("secret_plans.txt.asc", "wb") afile.write(cipher[0]) afile.close() #+end_src @@ -409,6 +409,20 @@ somewhat with something more like this: #+begin_src python + import gpg + + afile = open("secret_plans.txt", "rb") + text = afile.read() + afile.close() + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) + rlogrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + rlogrus.append(rpattern[i]) + try: cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) except gpg.errors.InvalidRecipients as e: @@ -422,6 +436,10 @@ cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) except: pass + + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() #+end_src This will attempt to encrypt to all the keys searched for, then From 36dfbdffea60c529a6d1e1ff3e507be016b6a0f6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 07:49:42 +1100 Subject: [PATCH 25/89] doc: python bindings howto * Fixed a spelling error in the key counting text. --- lang/python/docs/GPGMEpythonHOWTOen.org | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 622475f4..979ffa0a 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -607,8 +607,9 @@ :END: Counting the number of keys in your public keybox (=pubring.kbx=), - the format shich has superceded the old keyring format - (=pubring.gpg= and =secring.gpg=) is a very simple task. + the format which has superceded the old keyring format + (=pubring.gpg= and =secring.gpg=), or the number of secret keys is + a very simple task. #+begin_src python import gpg From f81adeba992a9fd3b5a199e9a2e242a0f53cf639 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 08:26:22 +1100 Subject: [PATCH 26/89] doc: python bindings howto * Added a miscellaneous work-arounds section at the end. * Included code in said miscellaneous section for accessing the groups specified in a gpg.conf file. * It's a bit ugly since it does require subprocess (but not call, Popen or shell access and only accesses one command). --- lang/python/docs/GPGMEpythonHOWTOen.org | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 979ffa0a..4d02f97b 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -631,6 +631,56 @@ #+end_src +* Miscellaneous work-arounds + :PROPERTIES: + :CUSTOM_ID: cheats-and-hacks + :END: + +** Group lines + :PROPERTIES: + :CUSTOM_ID: group-lines + :END: + + There is not yet an easy way to access groups configured in the + gpg.conf file from within GPGME. As a consequence these central + groupings of keys cannot be shared amongst multiple programs, such + as MUAs readily. + + The following code, however, provides a work-around for obtaining + this information in Python. + + #+begin_src python + import subprocess + + lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() + + for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + + groups = line.split(":")[-1].replace('"', '').split(',') + + group_lines = groups + for i in range(len(group_lines)): + group_lines[i] = group_lines[i].split("=") + + group_lists = group_lines + for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() + #+end_src + + The result of that code is that =group_lines= is a list of lists + where =group_lines[i][0]= is the name of the group and + =group_lines[i][1]= is the key IDs of the group as a string. + + The =group_lists= result is very similar in that it is a list of + lists. The first part, =group_lists[i][0]= matches + =group_lines[i][0]= as the name of the group, but + =group_lists[i][1]= is the key IDs of the group as a string. + + * Copyright and Licensing :PROPERTIES: :CUSTOM_ID: copyright-and-license From c27a7a3f994dad0eccee890185582f4350fbf233 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 11:50:38 +1100 Subject: [PATCH 27/89] doc: python bindings howto * Added text description for the decryption example. --- lang/python/docs/GPGMEpythonHOWTOen.org | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 4d02f97b..40d28140 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -451,9 +451,14 @@ :CUSTOM_ID: howto-basic-encryption :END: - Decrypting something encrypted to a key in one's secret keyring - (will display some extra data you normally wouldn't show, but which - may be of use): + Decrypting something encrypted to a key in one's secret keyring is + fairly straight forward. + + In this example code, however, preconfiguring either + =gpg.Context()= or =gpg.core.Context()= as =c= is unnecessary + because there is no need to modify the Context prior to conducting + the decryption and since the Context is only used once, setting it + to =c= simply adds lines for no gain. #+begin_src python import os.path @@ -481,6 +486,11 @@ pass #+end_src + The data available in plaintext in this example is the decrypted + content as a byte object in =plaintext[0]=, the recipient key IDs + and algorithms in =plaintext[1]= and the results of verifying any + signatures of the data in =plaintext[0]=. + ** Signing text :PROPERTIES: @@ -550,7 +560,7 @@ #+begin_src python import gpg - tfile = open("/path/to/statement.txt", "r") + tfile = open("/path/to/statement.txt", "rb") text = tfile.read() tfile.close() From f29bda8d7146b4bc0bf73d6e613131545ff86b73 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 15:03:11 +1100 Subject: [PATCH 28/89] doc: python bindings howto * Signatures have changed as a result of the recent update from Justus. * Sample code updated. * Text to follow later. --- lang/python/docs/GPGMEpythonHOWTOen.org | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 40d28140..5d259a6c 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -503,13 +503,13 @@ #+begin_src python import gpg - text = """Declaration of ... something. + text = b"""Declaration of ... something. """ c = gpg.Context() c.armor = True - signed = c.sign(text, mode=mode.NORMAL) + signed = c.sign(text, mode=0) afile = open("/path/to/statement.txt.asc", "w") for i in range(len(signed[0].splitlines())): @@ -527,8 +527,7 @@ """ c = gpg.Context() - c.armor = True - signed = c.sign(text, mode=mode.CLEAR) + signed = c.sign(text, mode=2) afile = open("/path/to/statement.txt.asc", "w") for i in range(len(signed[0].splitlines())): @@ -547,7 +546,7 @@ c = gpg.Context() c.armor = True - signed = c.sign(text, mode=mode.DETACH) + signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.asc", "w") for i in range(len(signed[0].splitlines())): @@ -566,7 +565,7 @@ c = gpg.Context() c.armor = True - signed = c.sign(text, mode=mode.DETACH) + signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.sig", "wb") afile.write(signed[0]) From e489ddd08af29fdad8db8aa0aec0c314daa3678c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 18:32:30 +1100 Subject: [PATCH 29/89] doc: python bindings howto * During the course of working out the updated signature methods, determined that key selection (including counting) will beed to be presented before the basic functions. * Moved "working with keys" up. --- lang/python/docs/GPGMEpythonHOWTOen.org | 193 ++++++++++++++---------- 1 file changed, 116 insertions(+), 77 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 5d259a6c..5ee3a82b 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -255,6 +255,41 @@ operation type has one. +* Working with keys + :PROPERTIES: + :CUSTOM_ID: howto-keys + :END: + +** Counting keys + :PROPERTIES: + :CUSTOM_ID: howto-basic-verification + :END: + + Counting the number of keys in your public keybox (=pubring.kbx=), + the format which has superceded the old keyring format + (=pubring.gpg= and =secring.gpg=), or the number of secret keys is + a very simple task. + + #+begin_src python + import gpg + + c = gpg.Context() + seckeys = c.keylist(pattern=None, secret=True) + pubkeys = c.keylist(pattern=None, secret=False) + + seclist = list(seckeys) + secnum = len(seclist) + + publist = list(pubkeys) + pubnum = len(publist) + + print(""" + Number of secret keys: {0} + Number of public keys: {1} + """.format(secnum, pubnum) + #+end_src + + * Basic Functions :PROPERTIES: :CUSTOM_ID: howto-the-basics @@ -492,13 +527,30 @@ signatures of the data in =plaintext[0]=. -** Signing text +** Signing text and files :PROPERTIES: :CUSTOM_ID: howto-basic-signing :END: Need to determine whether or not to include clearsigning and - detached signing here or give them separate sections. + detached signing here or give them separate sections. Yes, section + them. + +*** Signing key selection + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-signers + :END: + + By default GPGME and the Python bindings will use the default key + configured for the user invoking the GPGME API. If there is no + default key specified and there is more than one secret key + available it may be necessary to specify the key or keys with + which to sign messages and files. + +*** Normal or default signing messages or files + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-normal + :END: #+begin_src python import gpg @@ -511,13 +563,72 @@ c.armor = True signed = c.sign(text, mode=0) - afile = open("/path/to/statement.txt.asc", "w") + afile = open("/path/to/statement.txt.asc", "wb") for i in range(len(signed[0].splitlines())): - afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) + afile.write("{0}\n".format(signed[0].splitlines()[i])) afile.close() #+end_src - Clearsigning: + #+begin_src python + import gpg + + tfile = open("/path/to/statement.txt", "rb") + text = tfile.read() + tfile.close() + + c = gpg.Context() + signed = c.sign(text, mode=0) + + afile = open("/path/to/statement.txt.sig", "wb") + afile.write(signed[0]) + afile.close() + #+end_src + +*** Detached signing messages and files + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-detached + :END: + + Detached ASCII Armoured signing: + + #+begin_src python + import gpg + + text = b"""Declaration of ... something. + + """ + + c = gpg.Context() + c.armor = True + signed = c.sign(text, mode=1) + + afile = open("/path/to/statement.txt.asc", "wb") + for i in range(len(signed[0].splitlines())): + afile.write("{0}\n".format(signed[0].splitlines()[i])) + afile.close() + #+end_src + + Detached binary signing of a file. + + #+begin_src python + import gpg + + tfile = open("/path/to/statement.txt", "rb") + text = tfile.read() + tfile.close() + + c = gpg.Context() + signed = c.sign(text, mode=1) + + afile = open("/path/to/statement.txt.sig", "wb") + afile.write(signed[0]) + afile.close() + #+end_src + +*** Clearsigning messages or text + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-clear + :END: #+begin_src python import gpg @@ -535,43 +646,6 @@ afile.close() #+end_src - Detached ASCII Armoured signing: - - #+begin_src python - import gpg - - text = """Declaration of ... something. - - """ - - c = gpg.Context() - c.armor = True - signed = c.sign(text, mode=1) - - afile = open("/path/to/statement.txt.asc", "w") - for i in range(len(signed[0].splitlines())): - afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) - afile.close() - #+end_src - - Detached binary signing of a file. - - #+begin_src python - import gpg - - tfile = open("/path/to/statement.txt", "rb") - text = tfile.read() - tfile.close() - - c = gpg.Context() - c.armor = True - signed = c.sign(text, mode=1) - - afile = open("/path/to/statement.txt.sig", "wb") - afile.write(signed[0]) - afile.close() - #+end_src - ** Signature verification :PROPERTIES: @@ -605,41 +679,6 @@ #+end_src -* Working with keys - :PROPERTIES: - :CUSTOM_ID: howto-keys - :END: - -** Counting keys - :PROPERTIES: - :CUSTOM_ID: howto-basic-verification - :END: - - Counting the number of keys in your public keybox (=pubring.kbx=), - the format which has superceded the old keyring format - (=pubring.gpg= and =secring.gpg=), or the number of secret keys is - a very simple task. - - #+begin_src python - import gpg - - c = gpg.Context() - seckeys = c.keylist(pattern=None, secret=True) - pubkeys = c.keylist(pattern=None, secret=False) - - seclist = list(seckeys) - secnum = len(seclist) - - publist = list(pubkeys) - pubnum = len(publist) - - print(""" - Number of secret keys: {0} - Number of public keys: {1} - """.format(secnum, pubnum) - #+end_src - - * Miscellaneous work-arounds :PROPERTIES: :CUSTOM_ID: cheats-and-hacks From c92da2c7eb148ce9fb06495a8470dd9caf662f9a Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 19:20:44 +1100 Subject: [PATCH 30/89] doc: python bindings howto * Added key selection for specifying signing key or keys. --- lang/python/docs/GPGMEpythonHOWTOen.org | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 5ee3a82b..ea1b7653 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -532,9 +532,7 @@ :CUSTOM_ID: howto-basic-signing :END: - Need to determine whether or not to include clearsigning and - detached signing here or give them separate sections. Yes, section - them. + X *** Signing key selection :PROPERTIES: @@ -547,6 +545,19 @@ available it may be necessary to specify the key or keys with which to sign messages and files. + #+begin_src python + import gpg + + logrus = input("Enter the email address or string to match signing keys to: ") + hancock = gpg.Context().keylist(pattern=logrus, secret=True) + sig_src = list(hancock) + #+end_src + + The signing examples in the following sections include the + explicitly designated =signers= parameter in two of the five + examples; once where the resulting signature would be ASCII + armoured and once where it would not be armoured. + *** Normal or default signing messages or files :PROPERTIES: :CUSTOM_ID: howto-basic-signing-normal @@ -559,8 +570,7 @@ """ - c = gpg.Context() - c.armor = True + c = gpg.Context(armor=True, signers=sig_src) signed = c.sign(text, mode=0) afile = open("/path/to/statement.txt.asc", "wb") @@ -598,8 +608,7 @@ """ - c = gpg.Context() - c.armor = True + c = gpg.Context(armor=True) signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.asc", "wb") @@ -617,7 +626,7 @@ text = tfile.read() tfile.close() - c = gpg.Context() + c = gpg.Context(signers=sig_src) signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.sig", "wb") From 952b6042f78017c476452088261af8d352cfa729 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 01:41:21 +1100 Subject: [PATCH 31/89] doc: python bindings howto * Added explanation of the ascendance of Python 3 over Python 2 in the guide to the intro. * Expanded key selection description so people know what not to include regarding key IDs with this key selection method. --- lang/python/docs/GPGMEpythonHOWTOen.org | 57 +++++++++++++++++++------ 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index ea1b7653..ae9e9e75 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -21,6 +21,31 @@ This document provides basic instruction in how to use the GPGME Python bindings to programmatically leverage the GPGME library. +** Python 2 versus Python 3 + :PROPERTIES: + :CUSTOM_ID: py2-vs-py3 + :END: + + Though the GPGME Python bindings themselves provide support for + both Python 2 and 3, the focus is unequivocally on Python 3 and + specifically from Python 3.4 and above. As a consequence all the + examples and instructions in this guide use Python 3 code. + + Much of it will work with Python 2, but much of it also deals with + Python 3 byte literals, particularly when reading and writing data. + Developers concentrating on Python 2.7, and possibly even 2.6, will + need to make the approprate modifications to support the older + string and unicode types as opposted to bytes. + + There are multiple reasons for concentrating on Python 3; some of + which relate to the immediate integration of these bindings, some + of which relate to longer term plans for both GPGME and the python + bindings and some of which relate to the impending EOL period for + Python 2.7. Essentially, though, there is little value in tying + the bindings to a version of the language which is a dead end and + the advantages offered by Python 3 over Python 2 make handling the + data types with which GPGME deals considerably easier. + * GPGME Concepts :PROPERTIES: @@ -59,7 +84,7 @@ =gpgme.h= generated when GPGME is compiled. This means that a version of the Python bindings is fundamentally - tied to the exact same version of GPGME used to gemerate that copy + tied to the exact same version of GPGME used to generate that copy of =gpgme.h=. ** Difference between the Python bindings and other GnuPG Python packages @@ -411,13 +436,13 @@ c = gpg.Context(armor=True) rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) - rlogrus = [] + logrus = [] for i in range(len(rpattern)): if rpattern[i].can_encrypt == 1: - rlogrus.append(rpattern[i]) + logrus.append(rpattern[i]) - cipher = c.encrypt(text, recipients=rlogrus, sign=False, always_trust=True) + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) afile = open("secret_plans.txt.asc", "wb") afile.write(cipher[0]) @@ -429,7 +454,7 @@ be to change the =c.encrypt= line to this: #+begin_src python - cipher = c.encrypt(text, recipients=rlogrus, always_trust=True, + cipher = c.encrypt(text, recipients=logrus, always_trust=True, add_encrypt_to=True) #+end_src @@ -452,23 +477,23 @@ c = gpg.Context(armor=True) rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) - rlogrus = [] + logrus = [] for i in range(len(rpattern)): if rpattern[i].can_encrypt == 1: - rlogrus.append(rpattern[i]) + logrus.append(rpattern[i]) try: - cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) + cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) except gpg.errors.InvalidRecipients as e: for i in range(len(e.recipients)): - for n in range(len(rlogrus)): - if rlogrus[n].fpr == e.recipients[i].fpr: - rlogrus.remove(rlogrus[n]) + for n in range(len(logrus)): + if logrus[n].fpr == e.recipients[i].fpr: + logrus.remove(logrus[n]) else: pass try: - cipher = c.encrypt(text, recipients=rlogrus, add_encrypt_to=True) + cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) except: pass @@ -532,7 +557,7 @@ :CUSTOM_ID: howto-basic-signing :END: - X + The following sections demonstrate how to specify *** Signing key selection :PROPERTIES: @@ -558,6 +583,12 @@ examples; once where the resulting signature would be ASCII armoured and once where it would not be armoured. + While it would be possible to enter a key ID or fingerprint here + to match a specific key, it is not possible to enter two + fingerprints and match two keys since the patten expects a string, + bytes or None and not a list. A string with two fingerprints + won't match any single key. + *** Normal or default signing messages or files :PROPERTIES: :CUSTOM_ID: howto-basic-signing-normal From a10dcb4f138eb5a21881cdbc4806c25129d4ae4e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 02:21:44 +1100 Subject: [PATCH 32/89] doc: python bindings howto * Added a section on key selection. * Included recommendation for using fingerprint when selecting one specific key. * Also included the most ironically amusing example of multiple key selection in a GPG guide. Hey, it's public data ... (heh). --- lang/python/docs/GPGMEpythonHOWTOen.org | 61 ++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index ae9e9e75..ea4b1116 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -285,9 +285,68 @@ :CUSTOM_ID: howto-keys :END: +** Key selection + :PROPERTIES: + :CUSTOM_ID: howto-keys-selection + :END: + + Selecting keys to encrypt to or to sign with will be a common + occurrence when working with GPGMe and the means available for + doing so are quite simple. + + They do depend on utilising a Context; however once the data is + recorded in another variable, that Context does not need to be the + same one which subsequent operations are performed. + + The easiest way to select a specific key is by searching for that + key's key ID or fingerprint, preferably the full fingerprint + without any spaces in it. A long key ID will probably be okay, but + is not advised and short key IDs are already a problem with some + being generated to match specific patterns. It does not matter + whether the pattern is upper or lower case. + + So this is the best method: + + #+begin_src python + import gpg + + k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") + keys = list(k) + #+end_src + + This is passable and very likely to be common: + + #+begin_src python + import gpg + + k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") + keys = list(k) + #+end_src + + And this is a really bad idea: + + #+begin_src python + import gpg + + k = gpg.Context().keylist(pattern="0xDEADBEEF") + keys = list(k) + #+end_src + + Alternatively it may be that the intention is to create a list of + keys which all match a particular search string. For instance all + the addresses at a particular domain, like this: + + #+begin_src python + import gpg + + ncsc = gpg.Context().keylist(pattern="ncsc.mil") + nsa = list(ncsc) + #+end_src + + ** Counting keys :PROPERTIES: - :CUSTOM_ID: howto-basic-verification + :CUSTOM_ID: howto-keys-counting :END: Counting the number of keys in your public keybox (=pubring.kbx=), From a71205dc3b58970adf591b4e4553824a33f353db Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 02:40:41 +1100 Subject: [PATCH 33/89] doc: python binding howto * Clarified which English dialects this is written in. * Translating to American can happen *after* it's done. ** The Yank version would probably want to change some of the examples anyway. * Began the description for normal/default signing. --- lang/python/docs/GPGMEpythonHOWTOen.org | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index ea4b1116..e4e2a743 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -16,7 +16,8 @@ | Version: | 0.0.1-alpha | | Author: | Ben McGinnes | | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | - | Language: | English | + | Language: | Australian English, British English | + | xml:lang: | en-AU, en-GB, en | This document provides basic instruction in how to use the GPGME Python bindings to programmatically leverage the GPGME library. @@ -653,6 +654,16 @@ :CUSTOM_ID: howto-basic-signing-normal :END: + The normal or default signing process is essentially the same as + is most often invoked when also encrypting a message or file. So + when the encryption component is not utilised, the result is to + produce an encoded and signed output which may or may not be ASCII + armoured and which may or may not also be compressed. + + By default compression will be used unless GnuPG detects that the + plaintext is already compressed. ASCII armouring will be + determined according to the value of =gpg.Context().armor=. + #+begin_src python import gpg From 423fdcd4653cb01f07f2b0e72cfcf49554930f70 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 20:36:30 +1100 Subject: [PATCH 34/89] doc: python bindings howto * Added recommended method of single encryption with description. --- lang/python/docs/GPGMEpythonHOWTOen.org | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index e4e2a743..360bce91 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -565,6 +565,72 @@ This will attempt to encrypt to all the keys searched for, then remove invalid recipients if it fails and try again. +**** Encrypting to one key using the second method + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-monogamous + :END: + + This example re-creates the first encryption example except it + uses the same =encrypt= method used in the subsequent examples + instead of the =op_encrypt= method. This means that, unlike the + =op_encrypt= method, it /must/ use byte literal input data. + + #+begin_src python + import gpg + + rkey = "0x12345678DEADBEEF" + text = b"""Some text to test with. + + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data + format. + """ + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() + #+end_src + + With one or two exceptions, this method will probably prove to be + easier to implement than the first method and thus it is the + recommended encryption method. Though it is even more likely to + be used like this: + + #+begin_src python + import gpg + + rkey = "0x12345678DEADBEEF" + + afile = open("secret_plans.txt", "rb") + text = afile.read() + afile.close() + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() + #+end_src + ** Decryption :PROPERTIES: From ef27f3781a37e264d0eb7d1745eb2c804ec062c4 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 20:40:50 +1100 Subject: [PATCH 35/89] doc: python bindings todo * minor phrasing fix. --- lang/python/docs/TODO.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index 21d2216f..e85315ab 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -83,8 +83,8 @@ :CUSTOM_ID: howto-s-mime :END: - Eventually add some of this, but it the OpenPGP details are far - more important at the moment. + Eventually add some of this, but the OpenPGP details are far more + important at the moment. ** TODO Documentation SWIG :PROPERTIES: From ada059b07178147821b1598c935aa70ae45e3e6c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 03:51:51 +1100 Subject: [PATCH 36/89] doc: python bindings howto * Fixed multiple sample code examples of writing output to a file. * Added the description of detached signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 73 +++++++++++++++---------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 360bce91..71ddbcfa 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -730,22 +730,34 @@ plaintext is already compressed. ASCII armouring will be determined according to the value of =gpg.Context().armor=. + The compression algorithm is selected in much the same way as the + symmetric encryption algorithm or the hash digest algorithm is + when multiple keys are involved; from the preferences saved into + the key itself or by comparison with the preferences with all + other keys involved. + #+begin_src python import gpg - text = b"""Declaration of ... something. + text0 = """Declaration of ... something. """ + text = text0.encode("utf-8") c = gpg.Context(armor=True, signers=sig_src) signed = c.sign(text, mode=0) - afile = open("/path/to/statement.txt.asc", "wb") - for i in range(len(signed[0].splitlines())): - afile.write("{0}\n".format(signed[0].splitlines()[i])) + afile = open("/path/to/statement.txt.asc", "w") + for line in signed[0]: + afile.write("{0}\n".format(line.decode("utf-8"))) afile.close() #+end_src + Though everything in this example is accurate, it is more likely + that reading the input data from another file and writing the + result to a new file will be perfprmed more like the way it is done + in the next example. Even if the output format is ASCII armoured. + #+begin_src python import gpg @@ -766,40 +778,45 @@ :CUSTOM_ID: howto-basic-signing-detached :END: - Detached ASCII Armoured signing: + Detached signatures will often be needed in programmatic uses of + GPGME, either for signing files (e.g. tarballs of code releases) + or as a component of message signing (e.g. PGP/MIME encoded + email). - #+begin_src python - import gpg + #+begin_src python + import gpg - text = b"""Declaration of ... something. + text0 = """Declaration of ... something. - """ + """ + text = text0.encode("utf-8") - c = gpg.Context(armor=True) - signed = c.sign(text, mode=1) + c = gpg.Context(armor=True) + signed = c.sign(text, mode=1) - afile = open("/path/to/statement.txt.asc", "wb") - for i in range(len(signed[0].splitlines())): - afile.write("{0}\n".format(signed[0].splitlines()[i])) - afile.close() - #+end_src + afile = open("/path/to/statement.txt.asc", "w") + for line in signed[0].splitlines()L + afile.write("{0}\n".format(line.decode("utf-8"))) + afile.close() + #+end_src - Detached binary signing of a file. + As with normal signatures, detached signatures are best handled as + byte literals, even when the output is ASCII armoured. - #+begin_src python - import gpg + #+begin_src python + import gpg - tfile = open("/path/to/statement.txt", "rb") - text = tfile.read() - tfile.close() + tfile = open("/path/to/statement.txt", "rb") + text = tfile.read() + tfile.close() - c = gpg.Context(signers=sig_src) - signed = c.sign(text, mode=1) + c = gpg.Context(signers=sig_src) + signed = c.sign(text, mode=1) - afile = open("/path/to/statement.txt.sig", "wb") - afile.write(signed[0]) - afile.close() - #+end_src + afile = open("/path/to/statement.txt.sig", "wb") + afile.write(signed[0]) + afile.close() + #+end_src *** Clearsigning messages or text :PROPERTIES: From e5c85fba25de1187949697e2dae0e89345b71e89 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 04:07:57 +1100 Subject: [PATCH 37/89] doc: python bindings howto * Added description for detached signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 46 +++++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 71ddbcfa..b3f787a6 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -823,21 +823,45 @@ :CUSTOM_ID: howto-basic-signing-clear :END: - #+begin_src python - import gpg + Though PGP/in-line messages are no longer encouraged in favour of + PGP/MIME, there is still sometimes value in utilising in-line + signatures. This is where clearsigned messages or text is of + value. - text = """Declaration of ... something. + #+begin_src python + import gpg - """ + text0 = """Declaration of ... something. - c = gpg.Context() - signed = c.sign(text, mode=2) + """ + text = text0.encode("utf-8") - afile = open("/path/to/statement.txt.asc", "w") - for i in range(len(signed[0].splitlines())): - afile.write("{0}\n".format(signed[0].splitlines()[i].decode('utf-8'))) - afile.close() - #+end_src + c = gpg.Context() + signed = c.sign(text, mode=2) + + afile = open("/path/to/statement.txt.asc", "w") + for line in signed[0].splitlines(): + afile.write("{0}\n".format(line.decode("utf-8"))) + afile.close() + #+end_src + + In spite of the appearance of a clearsigned message, the data + handled by GPGME in signing it must still be byte literals. + + #+begin_src python + import gpg + + tfile = open("/path/to/statement.txt", "rb") + text = tfile.read() + tfile.close() + + c = gpg.Context() + signed = c.sign(text, mode=2) + + afile = open("/path/to/statement.txt.asc", "wb") + afile.write(signed[0]) + afile.close() + #+end_src ** Signature verification From 6bc12a0eeb20409770cb8b923d08c18c2b730cb8 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 07:20:31 +1100 Subject: [PATCH 38/89] doc: python bindings howto * Added 4 signature verification methods and partial text for them. --- lang/python/docs/GPGMEpythonHOWTOen.org | 131 +++++++++++++++++++++--- 1 file changed, 114 insertions(+), 17 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index b3f787a6..7e7265ff 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -825,7 +825,7 @@ Though PGP/in-line messages are no longer encouraged in favour of PGP/MIME, there is still sometimes value in utilising in-line - signatures. This is where clearsigned messages or text is of + signatures. This is where clear-signed messages or text is of value. #+begin_src python @@ -845,7 +845,7 @@ afile.close() #+end_src - In spite of the appearance of a clearsigned message, the data + In spite of the appearance of a clear-signed message, the data handled by GPGME in signing it must still be byte literals. #+begin_src python @@ -869,30 +869,127 @@ :CUSTOM_ID: howto-basic-verification :END: - Verify a signed file, both detached and not: + Essentially there are two principal methods of verification of a + signature. The first of these is for use with the normal or + default signing method and for clear-signed messages. The second is + for use with files and data with detached signatures. + + The following example is intended for use with the default signing + method where the file was not ASCII armoured: #+begin_src python import gpg - import sys import time + filename = "statement.txt" + gpg_file = "statement.txt.gpg" + c = gpg.Context() - data, result = c.verify(open(filename), - open(detached_sig_filename) - if detached_sig_filename else None) + try: + verified = c.verify(open(gpg_file)) + except gpg.errors.BadSignatures as e: + verified = None + print(e) - for index, sign in enumerate(result.signatures): - print("signature", index, ":") - print(" summary: %#0x" % (sign.summary)) - print(" status: %#0x" % (sign.status)) - print(" timestamp: ", sign.timestamp) - print(" timestamp: ", time.ctime(sign.timestamp)) - print(" fingerprint:", sign.fpr) - print(" uid: ", c.get_key(sign.fpr).uids[0].uid) + if verified is not None: + for i in range(len(verified[1].signatures)): + sign = verified[1].signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, + sign.fpr, time.ctime(sign.timestamp))) + else: + pass(e) + #+end_src - if data: - sys.stdout.buffer.write(data) + Whereas this next example, which is almost identical would work + with normal ASCII armoured files and with clear-signed files: + + #+begin_src python + import gpg + import time + + filename = "statement.txt" + asc_file = "statement.txt.asc" + + c = gpg.Context() + + try: + verified = c.verify(open(asc_file)) + except gpg.errors.BadSignatures as e: + verified = None + print(e) + + if verified is not None: + for i in range(len(verified[1].signatures)): + sign = verified[1].signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, + sign.fpr, time.ctime(sign.timestamp))) + else: + pass + #+end_src + + #+begin_src python + import gpg + import time + + filename = "statement.txt" + sig_file = "statement.txt.sig" + + c = gpg.Context() + + try: + verified = c.verify(open(filename), open(sig_file)) + except gpg.errors.BadSignatures as e: + verified = None + print(e) + + if verified is not None: + for i in range(len(verified[1].signatures)): + sign = verified[1].signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, + sign.fpr, time.ctime(sign.timestamp))) + else: + pass + #+end_src + + #+begin_src python + import gpg + import time + + filename = "statement.txt" + asc_file = "statement.txt.asc" + + c = gpg.Context() + + try: + verified = c.verify(open(filename), open(asc_file)) + except gpg.errors.BadSignatures as e: + verified = None + print(e) + + if verified is not None: + for i in range(len(verified[1].signatures)): + sign = verified[1].signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, + sign.fpr, time.ctime(sign.timestamp))) + else: + pass #+end_src From b35aaef7a3b793b8f6f5b42596c0a6a51e87f78c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 11:18:02 +1100 Subject: [PATCH 39/89] doc: python bindings howto * Added text for verifying signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 7e7265ff..dca69993 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -936,6 +936,27 @@ pass #+end_src + In both of the previous examples it is also possible to compare the + original data that was signed against the signed data in + =verified[0]= to see if it matches with something like this: + + #+begin_src python + afile = open(filename, "rb") + text = afile.read() + afile.close() + + if text == verified[0]: + print("Good signature.") + else: + pass + #+end_src + + The following two examples, however, deal with detached signatures. + With his method of verification the data that was signed does not + get returned since it is already being explicitly referenced in the + first argument of =c.verify=. So =verified[0]= is None and only + the data in =verified[1]= is available. + #+begin_src python import gpg import time From 1d05e6aa4ea467c8c5926b827cfcfba357d03312 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 12:14:29 +1100 Subject: [PATCH 40/89] doc: python bindings howto * Added c.get_key instructions and examples. --- lang/python/docs/GPGMEpythonHOWTOen.org | 83 +++++++++++++++++++------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index dca69993..8f57adbc 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -345,35 +345,78 @@ #+end_src -** Counting keys +*** Counting keys + :PROPERTIES: + :CUSTOM_ID: howto-keys-counting + :END: + + Counting the number of keys in your public keybox (=pubring.kbx=), + the format which has superceded the old keyring format + (=pubring.gpg= and =secring.gpg=), or the number of secret keys is + a very simple task. + + #+begin_src python + import gpg + + c = gpg.Context() + seckeys = c.keylist(pattern=None, secret=True) + pubkeys = c.keylist(pattern=None, secret=False) + + seclist = list(seckeys) + secnum = len(seclist) + + publist = list(pubkeys) + pubnum = len(publist) + + print(""" + Number of secret keys: {0} + Number of public keys: {1} + """.format(secnum, pubnum) + #+end_src + + +** Get key :PROPERTIES: - :CUSTOM_ID: howto-keys-counting + :CUSTOM_ID: howto-get-key :END: - Counting the number of keys in your public keybox (=pubring.kbx=), - the format which has superceded the old keyring format - (=pubring.gpg= and =secring.gpg=), or the number of secret keys is - a very simple task. + An alternative method of getting a single key via its fingerprint + is available directly within a Context with =Context().get_key=. + This is the preferred method of selecting a key in order to modify + it, sign or certify it and for obtaining relevant data about a + single key as a part of other functions; when verifying a signature + made by that key, for instance. + + By default this method will select public keys, but it can select + secret keys as well. + + This first example demonstrates selecting the current key of Werner + Koch, which is due to expire at the end of 2018: #+begin_src python import gpg - c = gpg.Context() - seckeys = c.keylist(pattern=None, secret=True) - pubkeys = c.keylist(pattern=None, secret=False) - - seclist = list(seckeys) - secnum = len(seclist) - - publist = list(pubkeys) - pubnum = len(publist) - - print(""" - Number of secret keys: {0} - Number of public keys: {1} - """.format(secnum, pubnum) + fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" + key = gpg.Context().get_key(fingerprint) #+end_src + Whereas this example demonstrates selecting the author's current + key with the =secret= key word argument set to =True=: + + #+begin_src python + import gpg + + fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" + key = gpg.Context().get_key(fingerprint, secret=True) + #+end_src + + It is, of course, quite possible to select expired, disabled and + revoked keys with this function, but only to effectively display + information about those keys. + + It is also possible to use both unicode or string literals and byte + literals with the fingerprint when getting a key in this way. + * Basic Functions :PROPERTIES: From 5d1dd2abe5cf787875d12afe46c78c75385d7b31 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 12:27:45 +1100 Subject: [PATCH 41/89] doc: python bindings howto * Added sections for key generation and key editing. --- lang/python/docs/GPGMEpythonHOWTOen.org | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 8f57adbc..af5a18c7 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1057,6 +1057,48 @@ #+end_src +* Creating keys and subkeys + :PROPERTIES: + :CUSTOM_ID: key-generation + :END: + + The one thing, aside from GnuPG itself, that GPGME depends on, of + course, is the keys themselves. So it is necessary to be able to + generate them and modify them by adding subkeys, revoking or + disabling them, sometimes deleting them and doing the same for user + IDs. + + +** Primary key + :PROPERTIES: + :CUSTOM_ID: keygen-primary + :END: + + +** Subkeys + :PROPERTIES: + :CUSTOM_ID: keygen-subkeys + :END: + + +** User IDs + :PROPERTIES: + :CUSTOM_ID: keygen-uids + :END: + + +** Key preferences + :PROPERTIES: + :CUSTOM_ID: keygen-prefs + :END: + + +** Key certification + :PROPERTIES: + :CUSTOM_ID: keygen-certify + :END: + + * Miscellaneous work-arounds :PROPERTIES: :CUSTOM_ID: cheats-and-hacks From 5432e5f9d1dfc02812d0b181f8d88cdf4a2bfbfb Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:01:30 +1100 Subject: [PATCH 42/89] doc: python bindings howto * generated a new primary key for Danger Mouse in an alternative homedir. --- lang/python/docs/GPGMEpythonHOWTOen.org | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index af5a18c7..909d9499 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1068,12 +1068,105 @@ disabling them, sometimes deleting them and doing the same for user IDs. + In the following examples a key will be created for the world's + greatest secret agent, Danger Mouse. Since Danger Mouse is a secret + agent he needs to be able to protect information to =SECRET= level + clearance, so his keys will be 3072-bit keys. + ** Primary key :PROPERTIES: :CUSTOM_ID: keygen-primary :END: + Generating a primary key uses the =create_key= method in a Context. + It contains multiple arguments and keyword arguments, including: + =userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=, + =certify=, =authenticate=, =passphrase= and =force=. The defaults + for all of those except =userid=, =algorithm=, =expires_in=, + =expires= and =passphrase= is =False=. The defaults for + =algorithm= and =passphrase= is =None=. The default for + =expires_in= is =0=. The default for =expires= is =True=. There + is no default for =userid=. + + If =passphrase= is left as =None= then the key will not be + generated with a passphrase, if =passphrase= is set to a string + then that will be the passphrase and if =passphrase= is set to + =True= then gpg-agent will launch pinentry to prompt for a + passphrase. For the sake of convenience, these examples will keep + =passphrase= set to =None=. + + #+begin_src python + import gpg + + c = gpg.Context() + + c.home_dir = "/tmp/dmgpg" + userid = "Danger Mouse " + + dmkey = c.create_key(userid, algorithm = "rsa3072", expires_in = 31536000, + sign = True, certify = True) + #+end_src + + One thing to note here is the use of setting the =c.home_dir= + parameter. This enables generating the key or keys in a different + location. In this case to keep the new key data created for this + example in a separate location rather than adding it to existing + and active key store data. + + The successful generation of the key can be confirmed via the + returned =GenkeyResult= object, which includes the following data: + + #+begin_src python + print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} + User IDs: {5} + """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, + dmkey.uid)) + #+end_src + + Alternatively the information can be confirmed using the command + line program: + + #+begin_src shell + bash-4.4$ gpg --homedir /tmp/dmgpg -K + /tmp/dmgpg/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse + + bash-4.4$ + #+end_src + + As with generating keys manually, to preconfigure expanded + preferences for the cipher, digest and compression algorithms, the + =gpg.conf= file must contain those details in the home directory in + which the new key is being generated. I used a cut down version of + my own =gpg.conf= file in order to be able to generate this: + + #+begin_src shell + bash-4.4$ gpg --homedir /tmp/dmgpg --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit + Secret key is available. + + sec rsa3072/026D2F19E99E63AA + created: 2018-03-15 expires: 2019-03-15 usage: SC + trust: ultimate validity: ultimate + [ultimate] (1). Danger Mouse + + [ultimate] (1). Danger Mouse + Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES + Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify + + bash-4.4$ + #+end_src + ** Subkeys :PROPERTIES: From b02d9d0a7b96b186eb3063d94bde369339181461 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:43:44 +1100 Subject: [PATCH 43/89] doc: python bindings howto * Added an encryption subkey to Danger Mouse's primary key. --- lang/python/docs/GPGMEpythonHOWTOen.org | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 909d9499..0e61746d 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1073,6 +1073,28 @@ agent he needs to be able to protect information to =SECRET= level clearance, so his keys will be 3072-bit keys. + The pre-configured =gpg.conf= file which sets cipher, digest and + other preferences contains the following configuration parameters: + + #+begin_src conf + expert + allow-freeform-uid + allow-secret-key-import + trust-model tofu+pgp + tofu-default-policy unknown + # no-auto-check-trustdb + enable-large-rsa + enable-dsa2 + # no-emit-version + # no-comments + # cert-digest-algo SHA256 + cert-digest-algo SHA512 + default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed + personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES + personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 + personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed + #+end_src + ** Primary key :PROPERTIES: @@ -1173,6 +1195,56 @@ :CUSTOM_ID: keygen-subkeys :END: + Adding subkeys to a primary key is fairly similar to creating the + primary key with the =create_subkey= method. Most of the arguments + are the same, but not quite all. Instead of the =userid= argument + there is now a =key= argument for selecting which primary key to + add the subkey to. + + In the following example an encryption subkey will be added to the + primary key. Since Danger Mouse is a security conscious secret + agent, this subkey will only be valid for about six months, half + the length of the primary key. + + #+begin_src python + import gpg + + c = gpg.Context() + c.home_dir = "/tmp/dmgpg" + + key = c.get_key(dmkey.fpr, secret = True) + dmsub = c.create_subkey(key, algorithm = "rsa3072", expires_in = 15768000, + encrypt = True) + #+end_src + + As with the primary key, the results here can be checked with: + + #+begin_src python + print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} + User IDs: {5} + """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, + dmsub.uid)) + #+end_src + + As well as on the command line with: + + #+begin_src shell + bash-4.4$ gpg --homedir /tmp/dmgpg -K + /tmp/dmgpg/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ + #+end_src + ** User IDs :PROPERTIES: From 9e3e4a835c64f5d06de821b1fd648af37827ff26 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:59:36 +1100 Subject: [PATCH 44/89] doc: python bindings howto * Spell checking and fixing the few errors. --- lang/python/docs/GPGMEpythonHOWTOen.org | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 0e61746d..37318fce 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -35,8 +35,8 @@ Much of it will work with Python 2, but much of it also deals with Python 3 byte literals, particularly when reading and writing data. Developers concentrating on Python 2.7, and possibly even 2.6, will - need to make the approprate modifications to support the older - string and unicode types as opposted to bytes. + need to make the appropriate modifications to support the older + string and unicode types as opposed to bytes. There are multiple reasons for concentrating on Python 3; some of which relate to the immediate integration of these bindings, some @@ -61,7 +61,7 @@ Unlike many modern APIs with which programmers will be more familiar with these days, the GPGME API is a C API. The API is intended for use by C coders who would be able to access its - features by including the =gpgme.h= header file eith their own C + features by including the =gpgme.h= header file with their own C source code and then access its functions just as they would any other C headers. @@ -208,7 +208,7 @@ By default GPGME will attempt to install the bindings for the most recent or highest version number of Python 2 and Python 3 it detects in =$PATH=. It specifically checks for the =python= and - =python3= executabled first and then checks for specific version + =python3= executables first and then checks for specific version numbers. For Python 2 it checks for these executables in this order: @@ -275,7 +275,7 @@ result of one operation has a direct bearing on the outcome of subsequent operations. Not merely by generating an error either. - When dealing with this type of persistant state on the web, full of + When dealing with this type of persistent state on the web, full of both the RESTful and REST-like, it's most commonly referred to as a session. In GPGME, however, it is called a context and every operation type has one. @@ -351,7 +351,7 @@ :END: Counting the number of keys in your public keybox (=pubring.kbx=), - the format which has superceded the old keyring format + the format which has superseded the old keyring format (=pubring.gpg= and =secring.gpg=), or the number of secret keys is a very simple task. @@ -424,7 +424,7 @@ :END: The most frequently called features of any cryptographic library - will be the most fundamental tasks for enxryption software. In this + will be the most fundamental tasks for encryption software. In this section we will look at how to programmatically encrypt data, decrypt it, sign it and verify signatures. @@ -461,7 +461,7 @@ the key was not found. The encryption operation is invoked within the Context with the - =c.op_encrypt= function, loading the recipien (=r=), the message + =c.op_encrypt= function, loading the recipients (=r=), the message (=plain=) and the =cipher=. The =cipher.seek= uses =os.SEEK_SET= to set the data to the correct byte format for GPGME to use it. @@ -798,7 +798,7 @@ Though everything in this example is accurate, it is more likely that reading the input data from another file and writing the - result to a new file will be perfprmed more like the way it is done + result to a new file will be performed more like the way it is done in the next example. Even if the output format is ASCII armoured. #+begin_src python @@ -838,7 +838,7 @@ signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.asc", "w") - for line in signed[0].splitlines()L + for line in signed[0].splitlines(): afile.write("{0}\n".format(line.decode("utf-8"))) afile.close() #+end_src From 7ac65b10837740caf68cdade791b8c5ce4eb1b03 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 15:16:23 +1100 Subject: [PATCH 45/89] doc: python bindings howto * Added a new user ID for Danger Mouse. * Removed the empty entry for key preferences since that is handled through gpg.conf and/or editing the key directly. --- lang/python/docs/GPGMEpythonHOWTOen.org | 37 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 37318fce..d22efbe2 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1251,16 +1251,43 @@ :CUSTOM_ID: keygen-uids :END: + By comparison to creating primary keys and subkeys, adding a new + user ID to an existing key is much simpler. The method used to do + this is =key_add_uid= and the only arguments it takes are for the + =key= and the new =uid=. -** Key preferences - :PROPERTIES: - :CUSTOM_ID: keygen-prefs - :END: + #+begin_src python + import gpg + + c = gpg.Context() + c.home_dir = "/tmp/dmgpg" + + dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" + key = c.get_key(dmfpr, secret = True) + uid = "Danger Mouse " + + c.key_add_uid(key, uid) + #+end_src + + Unsurprisingly the result of this is: + + #+begin_src shell + bash-4.4$ gpg --homedir /tmp/dmgpg -K + /tmp/dmgpg/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse + uid [ultimate] Danger Mouse + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ + #+end_src ** Key certification :PROPERTIES: - :CUSTOM_ID: keygen-certify + :CUSTOM_ID: key-sign :END: From 961aea212ef48914ecbfa169addf951b0854b0b4 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 15:51:01 +1100 Subject: [PATCH 46/89] doc: python bindings howto * Added key signing. --- lang/python/docs/GPGMEpythonHOWTOen.org | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index d22efbe2..582a28f1 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1290,6 +1290,38 @@ :CUSTOM_ID: key-sign :END: + Since key certification is more frequently referred to as key + signing, the method used to perform this function is =key_sign=. + + The =key_sign= method takes four arguments: =key=, =uids=, + =expires_in= and =local=. The default value of =uids= is =None= + and which results in all user IDs being selected. The default + values of =expires_in= snd =local= is =False=; which result in the + signature never expiring and being able to be exported. + + The =key= is the key being signed rather than the key doing the + signing. To change the key doing the signing refer to the signing + key selection above for signing messages and files. + + If the =uids= value is not =None= then it must either be a string + to match a single user ID or a list of strings to match multiple + user IDs. In this case the matching of those strings must be + precise and it is case sensitive. + + To sign Danger Mouse's key for just the initial user ID with a + signature which will last a little over a month, do this: + + #+begin_src python + import gpg + + c = gpg.Context() + uid = "Danger Mouse " + + dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" + key = c.get_key(dmfpr, secret = True) + c.key_sign(key, uids = uid, expires_in = 2764800) + #+end_src + * Miscellaneous work-arounds :PROPERTIES: From 3d0c7a2202c8e9bd4f284fd00069d34b8d3d3d4c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 16:13:34 +1100 Subject: [PATCH 47/89] doc: python bindings howto * Fixed a minor typographic error. * Bumped version number in preparation for merge with master. * While there are probably a few more things worthy of being added (mainly how to revoke things), this document is essentially ready for publication now. Signed-off-by: Ben McGinnes --- lang/python/docs/GPGMEpythonHOWTOen.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 582a28f1..71e738ac 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -13,7 +13,7 @@ :CUSTOM_ID: intro :END: - | Version: | 0.0.1-alpha | + | Version: | 0.1.0 | | Author: | Ben McGinnes | | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | | Language: | Australian English, British English | @@ -513,7 +513,7 @@ Encrypting to multiple keys, in addition to a default key or a key configured to always encrypt to, is a little different and uses a - slightly different call to the =op_encrypt call= demonstrated in the + slightly different call to the =op_encrypt= call demonstrated in the previous section. The following example encrypts a message (=text=) to everyone with From 94a95ac12364989db7f4be333107f3c023551857 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 01:34:22 +1100 Subject: [PATCH 48/89] doc: python bindings howto * Promoted final encryption example so that it will appear as heading 6.1.3 when exported to HTML or PDF. --- lang/python/docs/GPGMEpythonHOWTOen.org | 98 ++++++++++++------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 71e738ac..30e10184 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -608,71 +608,71 @@ This will attempt to encrypt to all the keys searched for, then remove invalid recipients if it fails and try again. -**** Encrypting to one key using the second method - :PROPERTIES: - :CUSTOM_ID: howto-basic-encryption-monogamous - :END: +*** Encrypting to one key using the second method + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-monogamous + :END: - This example re-creates the first encryption example except it - uses the same =encrypt= method used in the subsequent examples - instead of the =op_encrypt= method. This means that, unlike the - =op_encrypt= method, it /must/ use byte literal input data. + This example re-creates the first encryption example except it + uses the same =encrypt= method used in the subsequent examples + instead of the =op_encrypt= method. This means that, unlike the + =op_encrypt= method, it /must/ use byte literal input data. - #+begin_src python - import gpg + #+begin_src python + import gpg - rkey = "0x12345678DEADBEEF" - text = b"""Some text to test with. + rkey = "0x12345678DEADBEEF" + text = b"""Some text to test with. - Since the text in this case must be bytes, it is most likely that - the input form will be a separate file which is opened with "rb" - as this is the simplest method of obtaining the correct data - format. - """ + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data + format. + """ - c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() - #+end_src + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() + #+end_src - With one or two exceptions, this method will probably prove to be - easier to implement than the first method and thus it is the - recommended encryption method. Though it is even more likely to - be used like this: + With one or two exceptions, this method will probably prove to be + easier to implement than the first method and thus it is the + recommended encryption method. Though it is even more likely to + be used like this: - #+begin_src python - import gpg + #+begin_src python + import gpg - rkey = "0x12345678DEADBEEF" + rkey = "0x12345678DEADBEEF" - afile = open("secret_plans.txt", "rb") - text = afile.read() - afile.close() + afile = open("secret_plans.txt", "rb") + text = afile.read() + afile.close() - c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() - #+end_src + afile = open("secret_plans.txt.asc", "wb") + afile.write(cipher[0]) + afile.close() + #+end_src ** Decryption From 22e2445beee46ed1e527a98e635153c7cf03786f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 01:48:56 +1100 Subject: [PATCH 49/89] doc: python bindings howto * fixed custom_id for decryption so the XHTML validates. --- lang/python/docs/GPGMEpythonHOWTOen.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 30e10184..4aa4398c 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -677,7 +677,7 @@ ** Decryption :PROPERTIES: - :CUSTOM_ID: howto-basic-encryption + :CUSTOM_ID: howto-basic-decryption :END: Decrypting something encrypted to a key in one's secret keyring is From 431897a4c48fe1bc9d37f655097aabaf5b685d11 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 03:52:58 +1100 Subject: [PATCH 50/89] doc: python bindings howto * Added clarification on why it's not on PyPI. --- lang/python/docs/GPGMEpythonHOWTOen.org | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 4aa4398c..28d2e25d 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -179,6 +179,14 @@ Due to the nature of what these bindings are and how they work, it is infeasible to install the GPGME Python bindings in the same way. + This is because the bindings use SWIG to dynamically generate C + bindings against =gpgme.h= and =gpgme.h= is generated from + =gpgme.h.in= at compile time when GPGME is built from source. Thus + to include a package in PyPI which actually built correctly would + require either statically built libraries for every architecture + bundled with it or a full implementation of C for each + architecture. + ** Requirements :PROPERTIES: :CUSTOM_ID: gpgme-python-requirements From b549f69d0520bb74957b95cec9ea918dba2374f6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 Mar 2018 03:46:02 +1100 Subject: [PATCH 51/89] doc: python bindings howto * Made the changes suggested by Jakub Wilk on gnupg-devel. * Still need to make the far more comprehensive changes suggested by Justus. --- lang/python/docs/GPGMEpythonHOWTOen.org | 123 +++++++++++------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 28d2e25d..d27f5620 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -13,7 +13,7 @@ :CUSTOM_ID: intro :END: - | Version: | 0.1.0 | + | Version: | 0.1.0-draft | | Author: | Ben McGinnes | | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | | Language: | Australian English, British English | @@ -159,8 +159,8 @@ The PyME package is available under the same dual licensing as GPGME itself: the GNU General Public License version 2.0 (or any - later version) and the GNU Lesser Public License version 2.1 (or - any later version). + later version) and the GNU Lesser General Public License version + 2.1 (or any later version). * GPGME Python bindings installation @@ -275,7 +275,7 @@ that most operations require more than one instruction to the API to perform the task. Sure, there are certain functions which can be performed simultaneously, particularly if the result known or - strongly anticipated (e.g selecting and encrypting to a key known + strongly anticipated (e.g. selecting and encrypting to a key known to be in the public keybox). There are many more, however, which cannot be manipulated so @@ -505,11 +505,8 @@ try: c.op_encrypt([r], 1, plain, cipher) cipher.seek(0, os.SEEK_SET) - del(text) - del(plain) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher.read()) - afile.close() + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher.read()) except gpg.errors.GPGMEError as ex: print(ex.getstring()) #+end_src @@ -555,9 +552,8 @@ cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) #+end_src All it would take to change the above example to sign the message @@ -582,9 +578,8 @@ #+begin_src python import gpg - afile = open("secret_plans.txt", "rb") - text = afile.read() - afile.close() + with open("secret_plans.txt.asc", "rb") as afile: + text = afile.read() c = gpg.Context(armor=True) rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) @@ -608,9 +603,8 @@ except: pass - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) #+end_src This will attempt to encrypt to all the keys searched for, then @@ -648,9 +642,8 @@ cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) #+end_src With one or two exceptions, this method will probably prove to be @@ -677,9 +670,8 @@ cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - afile = open("secret_plans.txt.asc", "wb") - afile.write(cipher[0]) - afile.close() + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) #+end_src @@ -718,7 +710,6 @@ print(plaintext[0]) plaintext[1] plaintext[2] - del(plaintext) else: pass #+end_src @@ -793,15 +784,14 @@ text0 = """Declaration of ... something. """ - text = text0.encode("utf-8") + text = text0.encode() c = gpg.Context(armor=True, signers=sig_src) signed = c.sign(text, mode=0) - afile = open("/path/to/statement.txt.asc", "w") - for line in signed[0]: - afile.write("{0}\n".format(line.decode("utf-8"))) - afile.close() + with open("/path/to/statement.txt.asc", "w") as afile: + for line in signed[0]: + afile.write("{0}\n".format(line.decode())) #+end_src Though everything in this example is accurate, it is more likely @@ -812,16 +802,14 @@ #+begin_src python import gpg - tfile = open("/path/to/statement.txt", "rb") - text = tfile.read() - tfile.close() + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() c = gpg.Context() signed = c.sign(text, mode=0) - afile = open("/path/to/statement.txt.sig", "wb") - afile.write(signed[0]) - afile.close() + with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed[0]) #+end_src *** Detached signing messages and files @@ -840,15 +828,14 @@ text0 = """Declaration of ... something. """ - text = text0.encode("utf-8") + text = text0.encode() c = gpg.Context(armor=True) signed = c.sign(text, mode=1) - afile = open("/path/to/statement.txt.asc", "w") - for line in signed[0].splitlines(): - afile.write("{0}\n".format(line.decode("utf-8"))) - afile.close() + with open("/path/to/statement.txt.asc", "w") as afile: + for line in signed[0].splitlines(): + afile.write("{0}\n".format(line.decode())) #+end_src As with normal signatures, detached signatures are best handled as @@ -857,16 +844,14 @@ #+begin_src python import gpg - tfile = open("/path/to/statement.txt", "rb") - text = tfile.read() - tfile.close() + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() c = gpg.Context(signers=sig_src) signed = c.sign(text, mode=1) - afile = open("/path/to/statement.txt.sig", "wb") - afile.write(signed[0]) - afile.close() + with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed[0]) #+end_src *** Clearsigning messages or text @@ -885,15 +870,14 @@ text0 = """Declaration of ... something. """ - text = text0.encode("utf-8") + text = text0.encode() c = gpg.Context() signed = c.sign(text, mode=2) - afile = open("/path/to/statement.txt.asc", "w") - for line in signed[0].splitlines(): - afile.write("{0}\n".format(line.decode("utf-8"))) - afile.close() + with open("/path/to/statement.txt.asc", "w") as afile: + for line in signed[0].splitlines(): + afile.write("{0}\n".format(line.decode())) #+end_src In spite of the appearance of a clear-signed message, the data @@ -902,16 +886,14 @@ #+begin_src python import gpg - tfile = open("/path/to/statement.txt", "rb") - text = tfile.read() - tfile.close() + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() c = gpg.Context() signed = c.sign(text, mode=2) - afile = open("/path/to/statement.txt.asc", "wb") - afile.write(signed[0]) - afile.close() + with open("/path/to/statement.txt.asc", "wb") as afile: + afile.write(signed[0]) #+end_src @@ -1131,7 +1113,7 @@ c = gpg.Context() - c.home_dir = "/tmp/dmgpg" + c.home_dir = "~/.gnupg-dm" userid = "Danger Mouse " dmkey = c.create_key(userid, algorithm = "rsa3072", expires_in = 31536000, @@ -1142,7 +1124,10 @@ parameter. This enables generating the key or keys in a different location. In this case to keep the new key data created for this example in a separate location rather than adding it to existing - and active key store data. + and active key store data. As with the default directory, + =~/.gnupg=, any temporary or separate directory needs the + permissions set to only permit access by the directory owner. On + posix systems this means setting the directory permissions to 700. The successful generation of the key can be confirmed via the returned =GenkeyResult= object, which includes the following data: @@ -1163,8 +1148,8 @@ line program: #+begin_src shell - bash-4.4$ gpg --homedir /tmp/dmgpg -K - /tmp/dmgpg/pubring.kbx + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx ---------------------- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] 177B7C25DB99745EE2EE13ED026D2F19E99E63AA @@ -1180,7 +1165,7 @@ my own =gpg.conf= file in order to be able to generate this: #+begin_src shell - bash-4.4$ gpg --homedir /tmp/dmgpg --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit + bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit Secret key is available. sec rsa3072/026D2F19E99E63AA @@ -1218,7 +1203,7 @@ import gpg c = gpg.Context() - c.home_dir = "/tmp/dmgpg" + c.home_dir = "~/.gnupg-dm" key = c.get_key(dmkey.fpr, secret = True) dmsub = c.create_subkey(key, algorithm = "rsa3072", expires_in = 15768000, @@ -1242,8 +1227,8 @@ As well as on the command line with: #+begin_src shell - bash-4.4$ gpg --homedir /tmp/dmgpg -K - /tmp/dmgpg/pubring.kbx + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx ---------------------- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] 177B7C25DB99745EE2EE13ED026D2F19E99E63AA @@ -1268,7 +1253,7 @@ import gpg c = gpg.Context() - c.home_dir = "/tmp/dmgpg" + c.home_dir = "~/.gnupg-dm" dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" key = c.get_key(dmfpr, secret = True) @@ -1280,8 +1265,8 @@ Unsurprisingly the result of this is: #+begin_src shell - bash-4.4$ gpg --homedir /tmp/dmgpg -K - /tmp/dmgpg/pubring.kbx + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx ---------------------- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] 177B7C25DB99745EE2EE13ED026D2F19E99E63AA From 82c5af225f2bdf3acc6fc652a96ee61c9b057395 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 08:43:36 +1100 Subject: [PATCH 52/89] doc: python bindings howto * Stripped decryption example to the bare bones as suggested by Justus. --- lang/python/docs/GPGMEpythonHOWTOen.org | 26 ++++++------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index d27f5620..a2144235 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -690,28 +690,14 @@ to =c= simply adds lines for no gain. #+begin_src python - import os.path import gpg - if os.path.exists("/path/to/secret_plans.txt.asc") is True: - ciphertext = "/path/to/secret_plans.txt.asc" - elif os.path.exists("/path/to/secret_plans.txt.gpg") is True: - ciphertext = "/path/to/secret_plans.txt.gpg" - else: - ciphertext = None - - if ciphertext is not None: - afile = open(ciphertext, "rb") - plaintext = gpg.Context().decrypt(afile) - afile.close() - newfile = open("/path/to/secret_plans.txt", "wb") - newfile.write(plaintext[0]) - newfile.close() - print(plaintext[0]) - plaintext[1] - plaintext[2] - else: - pass + ciphertext = input("Enter path and filename of encrypted file: ") + newfile = input("Enter path and filename of file to save decrypted data to: ") + with open(ciphertext, "rb") as cfile: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + with open(newfile, "wb" as nfile: + nfile.write(plaintext) #+end_src The data available in plaintext in this example is the decrypted From 4811ff7b6c8ef97c7d4858ce235e9bf8227f4917 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 08:49:17 +1100 Subject: [PATCH 53/89] doc: python bindings howto * moved single encrytion examples up to the first ones, pending merge and major cut. * This is basically just to make future checks of revisions a little easier. --- lang/python/docs/GPGMEpythonHOWTOen.org | 128 ++++++++++++------------ 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index a2144235..c0606dd9 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -511,6 +511,70 @@ print(ex.getstring()) #+end_src +*** Encrypting to one key using the second method + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-monogamous + :END: + + This example re-creates the first encryption example except it + uses the same =encrypt= method used in the subsequent examples + instead of the =op_encrypt= method. This means that, unlike the + =op_encrypt= method, it /must/ use byte literal input data. + + #+begin_src python + import gpg + + rkey = "0x12345678DEADBEEF" + text = b"""Some text to test with. + + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data + format. + """ + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) + #+end_src + + With one or two exceptions, this method will probably prove to be + easier to implement than the first method and thus it is the + recommended encryption method. Though it is even more likely to + be used like this: + + #+begin_src python + import gpg + + rkey = "0x12345678DEADBEEF" + + afile = open("secret_plans.txt", "rb") + text = afile.read() + afile.close() + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern=rkey, secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(cipher[0]) + #+end_src + *** Encrypting to multiple keys :PROPERTIES: :CUSTOM_ID: howto-basic-encryption-multiple @@ -610,70 +674,6 @@ This will attempt to encrypt to all the keys searched for, then remove invalid recipients if it fails and try again. -*** Encrypting to one key using the second method - :PROPERTIES: - :CUSTOM_ID: howto-basic-encryption-monogamous - :END: - - This example re-creates the first encryption example except it - uses the same =encrypt= method used in the subsequent examples - instead of the =op_encrypt= method. This means that, unlike the - =op_encrypt= method, it /must/ use byte literal input data. - - #+begin_src python - import gpg - - rkey = "0x12345678DEADBEEF" - text = b"""Some text to test with. - - Since the text in this case must be bytes, it is most likely that - the input form will be a separate file which is opened with "rb" - as this is the simplest method of obtaining the correct data - format. - """ - - c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] - - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) - - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - - with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) - #+end_src - - With one or two exceptions, this method will probably prove to be - easier to implement than the first method and thus it is the - recommended encryption method. Though it is even more likely to - be used like this: - - #+begin_src python - import gpg - - rkey = "0x12345678DEADBEEF" - - afile = open("secret_plans.txt", "rb") - text = afile.read() - afile.close() - - c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] - - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) - - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) - - with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) - #+end_src - ** Decryption :PROPERTIES: From 64c5886132aceefc9d9600a3a6dbbbf404b95b81 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 10:00:44 +1100 Subject: [PATCH 54/89] doc: python bindings howto * Replaced the single encryption methods with one main way (i.e. cut the low level stuff involving SEEK_SET instructions). --- lang/python/docs/GPGMEpythonHOWTOen.org | 153 ++++++++---------------- 1 file changed, 53 insertions(+), 100 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index c0606dd9..a9608304 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -436,6 +436,7 @@ section we will look at how to programmatically encrypt data, decrypt it, sign it and verify signatures. + ** Encryption :PROPERTIES: :CUSTOM_ID: howto-basic-encryption @@ -446,85 +447,35 @@ the second example the message will be encrypted to multiple recipients. + *** Encrypting to one key :PROPERTIES: :CUSTOM_ID: howto-basic-encryption-single :END: - The text is then encapsulated in a GPGME Data object as =plain= and - the =cipher= object is created with another Data object. Then we - create the Context as =c= and set it to use the ASCII armoured - OpenPGP format. In later examples there will be alternative - methods of setting the OpenPGP output to be ASCII armoured. + Once the the Context is set the main issues with encrypting data + is essentially reduced to key selection and the keyword arguments + specified in the =gpg.Context().encrypt()= method. - Next we prepare a keylist object in our Context and follow it with - specifying the recipients as =r=. Note that the configuration in - one's =gpg.conf= file is honoured, so if you have the options set - to encrypt to one key or to a default key, that will be included - with this operation. - - This is followed by a quick check to be sure that the recipient is - actually selected and that the key is available. Assuming it is, - the encryption can proceed, but if not a message will print stating - the key was not found. - - The encryption operation is invoked within the Context with the - =c.op_encrypt= function, loading the recipients (=r=), the message - (=plain=) and the =cipher=. The =cipher.seek= uses =os.SEEK_SET= - to set the data to the correct byte format for GPGME to use it. - - At this point we no longer need the plaintext material, so we - delete both the =text= and the =plain= objects. Then we write the - encrypted data out to a file, =secret_plans.txt.asc=. - - #+begin_src python - import gpg - import os - - rkey = "0x12345678DEADBEEF" - text = """ - Some plain text to test with. Obtained from any input source Python can read. - - It makes no difference whether it is string or bytes, but the bindings always - produce byte output data. Which is useful to know when writing out either the - encrypted or decrypted results. - - """ - - plain = gpg.core.Data(text) - cipher = gpg.core.Data() - c = gpg.core.Context() - c.set_armor(1) - - c.op_keylist_start(rkey, 0) - r = c.op_keylist_next() - - if r == None: - print("""The key for user "{0}" was not found""".format(rkey)) - else: - try: - c.op_encrypt([r], 1, plain, cipher) - cipher.seek(0, os.SEEK_SET) - with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher.read()) - except gpg.errors.GPGMEError as ex: - print(ex.getstring()) - #+end_src - -*** Encrypting to one key using the second method - :PROPERTIES: - :CUSTOM_ID: howto-basic-encryption-monogamous - :END: - - This example re-creates the first encryption example except it - uses the same =encrypt= method used in the subsequent examples - instead of the =op_encrypt= method. This means that, unlike the - =op_encrypt= method, it /must/ use byte literal input data. + Those keyword arguments are: =recipients=, a list of keys + encrypted to (covered in greater detail in the following section); + =sign=, whether or not to sign the plaintext data, see subsequent + sections on signing and verifying signatures below (defaults to + =True=); =sink=, to write results or partial results to a secure + sink instead of returning it (defaults to =None=); =passphrase=, + only used when utilising symmetric encryption (defaults to + =None=); =always_trust=, used to override the trust model settings + for recipient keys (defaults to =False=); =add_encrypt_to=, + utilises any preconfigured =encrypt-to= or =default-key= settings + in the user's =gpg.conf= file (defaults to =False=); =prepare=, + prepare for encryption (defaults to =False=); =expect_sign=, + prepare for signing (defaults to =False=); =compress=, compresses + the plaintext prior to encryption (defaults to =True=). #+begin_src python import gpg - rkey = "0x12345678DEADBEEF" + a_key = "0x12345678DEADBEEF" text = b"""Some text to test with. Since the text in this case must be bytes, it is most likely that @@ -534,56 +485,51 @@ """ c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] - - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) - - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + rkey = list(c.keylist(pattern=a_key, secret=False)) + ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) + afile.write(ciphertext) #+end_src - With one or two exceptions, this method will probably prove to be - easier to implement than the first method and thus it is the - recommended encryption method. Though it is even more likely to - be used like this: + Though this is even more likely to be used like this; with the + plaintext input read from a file, the recipient keys used for + encryption regardless of key trust status and the encrypted output + also encrypted to any preconfigured keys set in the =gpg.conf= + file: #+begin_src python import gpg - rkey = "0x12345678DEADBEEF" + a_key = "0x12345678DEADBEEF" - afile = open("secret_plans.txt", "rb") - text = afile.read() - afile.close() + with open("secret_plans.txt", "rb") as afile: + text = afile.read() c = gpg.Context(armor=True) - rpattern = list(c.keylist(pattern=rkey, secret=False)) - logrus = [] - - for i in range(len(rpattern)): - if rpattern[i].can_encrypt == 1: - logrus.append(rpattern[i]) - - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + rkey = list(c.keylist(pattern=a_key, secret=False)) + ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, + sign=True, always_trust=True, + add_encrypt_to=True) with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) + afile.write(ciphertext) #+end_src + If the =recipients= paramater is empty then the plaintext is + encrypted symmetrically. If no =passphrase= is supplied as a + parameter or via a callback registered with the =Context()= then + an out-of-band prompt for the passphrase via pinentry will be + invoked. + + *** Encrypting to multiple keys :PROPERTIES: :CUSTOM_ID: howto-basic-encryption-multiple :END: - Encrypting to multiple keys, in addition to a default key or a key - configured to always encrypt to, is a little different and uses a - slightly different call to the =op_encrypt= call demonstrated in the - previous section. + Encrypting to multiple keys essentially just expands upon the key + selection process and the recipients from the previous examples. The following example encrypts a message (=text=) to everyone with an email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt @@ -711,7 +657,8 @@ :CUSTOM_ID: howto-basic-signing :END: - The following sections demonstrate how to specify + The following sections demonstrate how to specify keys to sign with. + *** Signing key selection :PROPERTIES: @@ -743,6 +690,7 @@ bytes or None and not a list. A string with two fingerprints won't match any single key. + *** Normal or default signing messages or files :PROPERTIES: :CUSTOM_ID: howto-basic-signing-normal @@ -798,6 +746,7 @@ afile.write(signed[0]) #+end_src + *** Detached signing messages and files :PROPERTIES: :CUSTOM_ID: howto-basic-signing-detached @@ -840,6 +789,7 @@ afile.write(signed[0]) #+end_src + *** Clearsigning messages or text :PROPERTIES: :CUSTOM_ID: howto-basic-signing-clear @@ -1307,6 +1257,7 @@ :CUSTOM_ID: cheats-and-hacks :END: + ** Group lines :PROPERTIES: :CUSTOM_ID: group-lines @@ -1357,6 +1308,7 @@ :CUSTOM_ID: copyright-and-license :END: + ** Copyright (C) The GnuPG Project, 2018 :PROPERTIES: :CUSTOM_ID: copyright @@ -1364,6 +1316,7 @@ Copyright © The GnuPG Project, 2018. + ** License GPL compatible :PROPERTIES: :CUSTOM_ID: license From 1779d7b9d6769b2e47f1e90260290e25c8c3aa02 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 10:39:53 +1100 Subject: [PATCH 55/89] doc: python bindings howto * deconstructing multi-recipient encryption. --- lang/python/docs/GPGMEpythonHOWTOen.org | 102 ++++++++++++++---------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index a9608304..f5192f4c 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -22,6 +22,7 @@ This document provides basic instruction in how to use the GPGME Python bindings to programmatically leverage the GPGME library. + ** Python 2 versus Python 3 :PROPERTIES: :CUSTOM_ID: py2-vs-py3 @@ -53,6 +54,7 @@ :CUSTOM_ID: gpgme-concepts :END: + ** A C API :PROPERTIES: :CUSTOM_ID: gpgme-c-api @@ -72,6 +74,7 @@ languages. This is where the need for bindings in various languages stems. + ** Python bindings :PROPERTIES: :CUSTOM_ID: gpgme-python-bindings @@ -88,6 +91,7 @@ tied to the exact same version of GPGME used to generate that copy of =gpgme.h=. + ** Difference between the Python bindings and other GnuPG Python packages :PROPERTIES: :CUSTOM_ID: gpgme-python-bindings-diffs @@ -97,6 +101,7 @@ over the years. Some of the most well known are listed here, along with what differentiates them. + *** The python-gnupg package maintained by Vinay Sajip :PROPERTIES: :CUSTOM_ID: diffs-python-gnupg @@ -116,6 +121,7 @@ The python-gnupg package is available under the MIT license. + *** The gnupg package created and maintained by Isis Lovecruft :PROPERTIES: :CUSTOM_ID: diffs-isis-gnupg @@ -136,6 +142,7 @@ The gnupg package is available under the GNU General Public License version 3.0 (or later). + *** The PyME package maintained by Martin Albrecht :PROPERTIES: :CUSTOM_ID: diffs-pyme @@ -168,6 +175,7 @@ :CUSTOM_ID: gpgme-python-install :END: + ** No PyPI :PROPERTIES: :CUSTOM_ID: do-not-use-pypi @@ -187,6 +195,7 @@ bundled with it or a full implementation of C for each architecture. + ** Requirements :PROPERTIES: :CUSTOM_ID: gpgme-python-requirements @@ -201,6 +210,7 @@ 3. GPGME itself. Which also means that all of GPGME's dependencies must be installed too. + ** Installation :PROPERTIES: :CUSTOM_ID: installation @@ -225,6 +235,7 @@ For Python 3 it checks for these executables in this order: =python3=, =python3.6=, =python3.5= and =python3.4=. + *** Installing GPGME :PROPERTIES: :CUSTOM_ID: install-gpgme @@ -243,6 +254,7 @@ regarding GPGME's design which hold true whether you're dealing with the C code directly or these Python bindings. + ** No REST :PROPERTIES: :CUSTOM_ID: no-rest-for-the-wicked @@ -266,6 +278,7 @@ direct bindings and it's this pythonic layer with which this HOWTO deals with. + ** Context :PROPERTIES: :CUSTOM_ID: howto-get-context @@ -294,6 +307,7 @@ :CUSTOM_ID: howto-keys :END: + ** Key selection :PROPERTIES: :CUSTOM_ID: howto-keys-selection @@ -560,10 +574,11 @@ if rpattern[i].can_encrypt == 1: logrus.append(rpattern[i]) - cipher = c.encrypt(text, recipients=logrus, sign=False, always_trust=True) + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, sign=False, + always_trust=True) with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) + afile.write(ciphertext) #+end_src All it would take to change the above example to sign the message @@ -571,8 +586,9 @@ be to change the =c.encrypt= line to this: #+begin_src python - cipher = c.encrypt(text, recipients=logrus, always_trust=True, - add_encrypt_to=True) + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) #+end_src The only keyword arguments requiring modification are those for @@ -600,7 +616,7 @@ logrus.append(rpattern[i]) try: - cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True) except gpg.errors.InvalidRecipients as e: for i in range(len(e.recipients)): for n in range(len(logrus)): @@ -609,12 +625,12 @@ else: pass try: - cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True) except: pass with open("secret_plans.txt.asc", "wb") as afile: - afile.write(cipher[0]) + afile.write(ciphertext) #+end_src This will attempt to encrypt to all the keys searched for, then @@ -721,7 +737,7 @@ text = text0.encode() c = gpg.Context(armor=True, signers=sig_src) - signed = c.sign(text, mode=0) + signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) with open("/path/to/statement.txt.asc", "w") as afile: for line in signed[0]: @@ -740,7 +756,7 @@ text = tfile.read() c = gpg.Context() - signed = c.sign(text, mode=0) + signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) with open("/path/to/statement.txt.sig", "wb") as afile: afile.write(signed[0]) @@ -766,7 +782,7 @@ text = text0.encode() c = gpg.Context(armor=True) - signed = c.sign(text, mode=1) + signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH) with open("/path/to/statement.txt.asc", "w") as afile: for line in signed[0].splitlines(): @@ -783,7 +799,7 @@ text = tfile.read() c = gpg.Context(signers=sig_src) - signed = c.sign(text, mode=1) + signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH) with open("/path/to/statement.txt.sig", "wb") as afile: afile.write(signed[0]) @@ -809,7 +825,7 @@ text = text0.encode() c = gpg.Context() - signed = c.sign(text, mode=2) + signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) with open("/path/to/statement.txt.asc", "w") as afile: for line in signed[0].splitlines(): @@ -826,7 +842,7 @@ text = tfile.read() c = gpg.Context() - signed = c.sign(text, mode=2) + signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) with open("/path/to/statement.txt.asc", "wb") as afile: afile.write(signed[0]) @@ -856,14 +872,15 @@ c = gpg.Context() try: - verified = c.verify(open(gpg_file)) + data, result = c.verify(open(gpg_file)) + verified = True except gpg.errors.BadSignatures as e: - verified = None + verified = False print(e) - if verified is not None: - for i in range(len(verified[1].signatures)): - sign = verified[1].signatures[i] + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] print("""Good signature from: {0} with key {1} @@ -887,14 +904,15 @@ c = gpg.Context() try: - verified = c.verify(open(asc_file)) + data, result = c.verify(open(asc_file)) + verified = True except gpg.errors.BadSignatures as e: - verified = None + verified = False print(e) - if verified is not None: - for i in range(len(verified[1].signatures)): - sign = verified[1].signatures[i] + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] print("""Good signature from: {0} with key {1} @@ -906,15 +924,14 @@ #+end_src In both of the previous examples it is also possible to compare the - original data that was signed against the signed data in - =verified[0]= to see if it matches with something like this: + original data that was signed against the signed data in =data= to + see if it matches with something like this: #+begin_src python - afile = open(filename, "rb") - text = afile.read() - afile.close() + with open(filename, "rb") as afile: + text = afile.read() - if text == verified[0]: + if text == data: print("Good signature.") else: pass @@ -923,8 +940,8 @@ The following two examples, however, deal with detached signatures. With his method of verification the data that was signed does not get returned since it is already being explicitly referenced in the - first argument of =c.verify=. So =verified[0]= is None and only - the data in =verified[1]= is available. + first argument of =c.verify=. So =data= is =None= and only the + information in =result= is available. #+begin_src python import gpg @@ -936,14 +953,15 @@ c = gpg.Context() try: - verified = c.verify(open(filename), open(sig_file)) + data, result = c.verify(open(filename), open(sig_file)) + verified = True except gpg.errors.BadSignatures as e: - verified = None + verified = False print(e) - if verified is not None: - for i in range(len(verified[1].signatures)): - sign = verified[1].signatures[i] + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] print("""Good signature from: {0} with key {1} @@ -964,14 +982,15 @@ c = gpg.Context() try: - verified = c.verify(open(filename), open(asc_file)) + data, result = c.verify(open(filename), open(asc_file)) + verified = True except gpg.errors.BadSignatures as e: - verified = None + verified = False print(e) if verified is not None: - for i in range(len(verified[1].signatures)): - sign = verified[1].signatures[i] + for i in range(len(result.signatures)): + sign = result.signatures[i] print("""Good signature from: {0} with key {1} @@ -1008,11 +1027,8 @@ allow-secret-key-import trust-model tofu+pgp tofu-default-policy unknown - # no-auto-check-trustdb enable-large-rsa enable-dsa2 - # no-emit-version - # no-comments # cert-digest-algo SHA256 cert-digest-algo SHA512 default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed From bf67cf433fe82924ed40e79785e95403c07cc068 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 12:46:41 +1100 Subject: [PATCH 56/89] doc: python bindings todo list * Checked off several points of howto coverage as completed. * Reorganised to move S/MIME coverage to its own separate group of tasks. * Noted only revocation remains for howto completion. --- lang/python/docs/TODO.org | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org index e85315ab..add8f4ff 100644 --- a/lang/python/docs/TODO.org +++ b/lang/python/docs/TODO.org @@ -28,6 +28,7 @@ to produce reST versions via Pandoc and DITA XML can be reached through converting to either Markdown or XHTML first. + ** STARTED Documentation HOWTO :PROPERTIES: :CUSTOM_ID: todo-docs-howto @@ -43,48 +44,57 @@ :CUSTOM_ID: howto-start :END: + *** STARTED Include certain specific instructions in the HOWTO :PROPERTIES: :CUSTOM_ID: howto-requests :END: + Note: moved the S/MIME bits out to their own section of the TODO + list and may be served better by separate HOWTO documentation + anyway. + - State "STARTED" from "TODO" [2018-03-09 Fri 15:27] Some functions can be worked out from the handful of examples available, but many more can't and I've already begun receiving requests for certain functions to be explained. -**** STARTED Standard scenarios + +**** DONE Standard scenarios + CLOSED: [2018-03-19 Mon 12:34] :PROPERTIES: :CUSTOM_ID: howto-the-basics :END: + - State "DONE" from "STARTED" [2018-03-19 Mon 12:34] \\ + All four of those are done. - State "STARTED" from "TODO" [2018-03-09 Fri 15:26] \\ Began with the example code, now to add the text. What everyone expects: encryption, decryption, signing and verifying. -**** TODO Key control + +**** STARTED Key control :PROPERTIES: :CUSTOM_ID: howto-key-control :END: + - State "STARTED" from "TODO" [2018-03-19 Mon 12:35] \\ + Generating keys and subkeys are done, but revocation is still to be done. Generating keys, adding subkeys, revoking subkeys (and keeping the cert key), adding and revoking UIDs, signing/certifying keys. -**** TODO More key control + +**** DONE More key control + CLOSED: [2018-03-19 Mon 12:36] :PROPERTIES: :CUSTOM_ID: howto-key-selection :END: + - State "DONE" from "TODO" [2018-03-19 Mon 12:36] \\ + Key selection, searching, matching and counting is done. Selecting keys to encrypt to or manipulate in other ways (e.g. as with key control or the basics). -**** TODO S/MIME - :PROPERTIES: - :CUSTOM_ID: howto-s-mime - :END: - - Eventually add some of this, but the OpenPGP details are far more - important at the moment. ** TODO Documentation SWIG :PROPERTIES: @@ -98,6 +108,7 @@ something to be used in conjunction with the existing GPGME documentation which makes it easier for Python developers to use. + ** TODO GUI examples :PROPERTIES: :CUSTOM_ID: todo-gui-examples @@ -107,6 +118,7 @@ to either match or be similar to the old GTK2 examples available with PyME. + ** TODO Replace SWIG :PROPERTIES: :CUSTOM_ID: todo-replace-swig @@ -122,6 +134,7 @@ bindings using a more suitable means of interfacing with the GPGME C source code. + *** TODO Replacement for SWIG :PROPERTIES: :CUSTOM_ID: todo-replace-swig-replacement @@ -131,6 +144,7 @@ the most viable candidate, but some additional testing and checks are yet to be completed. + ** TODO API for an API :PROPERTIES: :CUSTOM_ID: todo-api-squared @@ -146,6 +160,15 @@ bindings. +** TODO S/MIME + :PROPERTIES: + :CUSTOM_ID: s-mime + :END: + + Eventually add some of this, but the OpenPGP details are far more + important at the moment. + + * Project Task Details :PROPERTIES: :CUSTOM_ID: detailed-tasks From 0fb8a5d45c1c77a5928d6e356271da055aa55994 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 13:09:46 +1100 Subject: [PATCH 57/89] doc: python bindings howto * Adjusted the python-gnupg so the comments regarding insecure invocation of commands via subprocess (shell=True) were a major historical issue and not a a current issue. * Not including Vinay Sajip's requested change to say it is now secure since no audit of the current code base has been performed and my last major inspection of that code was around the time I first ported PyME to Python 3 in 2015. --- lang/python/docs/GPGMEpythonHOWTOen.org | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index f5192f4c..4a215544 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -117,7 +117,11 @@ Unfortunately it has been beset by a number of security issues, most of which stemmed from using unsafe methods of accessing the - command line via the =subprocess= calls. + command line via the =subprocess= calls. While some effort has + been made over the last two to three years (as of 2018) to + mitigate this, particularly by no longer providing shell access + through those subprocess calls, the wrapper is still somewhat + limited in the scope of its GnuPG features coverage. The python-gnupg package is available under the MIT license. @@ -132,15 +136,15 @@ package also relied on subprocess to call the =gpg= or =gpg2= binaries, but did so somewhat more securely. - However the naming and version numbering selected for this package - resulted in conflicts with the original python-gnupg and since its - functions were called in a different manner, the release of this - package also resulted in a great deal of consternation when people - installed what they thought was an upgrade that subsequently broke - the code relying on it. + The naming and version numbering selected for this package, + however, resulted in conflicts with the original python-gnupg and + since its functions were called in a different manner to + python-gnupg, the release of this package also resulted in a great + deal of consternation when people installed what they thought was + an upgrade that subsequently broke the code relying on it. The gnupg package is available under the GNU General Public - License version 3.0 (or later). + License version 3.0 (or any later version). *** The PyME package maintained by Martin Albrecht From d5f6dec048d3d4d94f1fcdb3f4249cf6e71c4b92 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 15:03:00 +1100 Subject: [PATCH 58/89] doc: python bindings howto * Slight python-gnupg clarification. See also this thread: https://lists.gnupg.org/pipermail/gnupg-devel/2018-March/033528.html --- lang/python/docs/GPGMEpythonHOWTOen.org | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 4a215544..b364b516 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -115,11 +115,11 @@ The popularity of this package stemmed from its ease of use and capability in providing the most commonly required features. - Unfortunately it has been beset by a number of security issues, - most of which stemmed from using unsafe methods of accessing the - command line via the =subprocess= calls. While some effort has - been made over the last two to three years (as of 2018) to - mitigate this, particularly by no longer providing shell access + Unfortunately it has been beset by a number of security issues in + the past; most of which stemmed from using unsafe methods of + accessing the command line via the =subprocess= calls. While some + effort has been made over the last two to three years (as of 2018) + to mitigate this, particularly by no longer providing shell access through those subprocess calls, the wrapper is still somewhat limited in the scope of its GnuPG features coverage. From 3e0f68fdff1998dae9cb6f8510a3e945a268d1f6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:07:22 +1100 Subject: [PATCH 59/89] example: python bindings encryption * Since we don't want to encourage accessing the low level functions (e.g. op_encrypt), but since this example can still be useful to understand, renaming it and will add new encryption examples to match the instructions in the HOWTO. --- .../examples/{encrypt-to-all.py => low_level-encrypt_to_all.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lang/python/examples/{encrypt-to-all.py => low_level-encrypt_to_all.py} (100%) diff --git a/lang/python/examples/encrypt-to-all.py b/lang/python/examples/low_level-encrypt_to_all.py similarity index 100% rename from lang/python/examples/encrypt-to-all.py rename to lang/python/examples/low_level-encrypt_to_all.py From 6950a63e63d60685ddb6f4cbff7b826b8acb5b13 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:26:57 +1100 Subject: [PATCH 60/89] docs: python bindings examples * Added reference to location where all the examples included in the HOWTO will be available as executable scripts. * Included a short README file in that location. --- lang/python/docs/GPGMEpythonHOWTOen.org | 11 ++++++++++- lang/python/examples/howto/README.org | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 lang/python/examples/howto/README.org diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index b364b516..770c2784 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1,4 +1,4 @@ -#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) #+LATEX_COMPILER: xelatex #+LATEX_CLASS: article #+LATEX_CLASS_OPTIONS: [12pt] @@ -49,6 +49,15 @@ data types with which GPGME deals considerably easier. +** Examples + :PROPERTIES: + :CUSTOM_ID: howto-python3-examples + :END: + + All of the examples found in this document can be found as Python 3 + scripts in the =lang/python/examples/howto= directory. + + * GPGME Concepts :PROPERTIES: :CUSTOM_ID: gpgme-concepts diff --git a/lang/python/examples/howto/README.org b/lang/python/examples/howto/README.org new file mode 100644 index 00000000..ee8c9863 --- /dev/null +++ b/lang/python/examples/howto/README.org @@ -0,0 +1,24 @@ +#+TITLE: GPGME Python Bindings HOWTO Examples +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman} +#+LATEX_HEADER: \author{Ben McGinnes } + + +* Examples + :PROPERTIES: + :CUSTOM_ID: gpgme-python3-examples + :END: + + The contents of this directory are the examples included in the /GNU + Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO/ file. Each + script is explicitly for Python 3 and specifically for Python 3.4 or + later. + + Some of these scripts may work with Python 2.7, but there are no + guarantees. They will include the relevant imports from the + =__future__= module to facilitate that if possible. + From 8f7672ad1b267f122f647bb5f984734d0ff66a5c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:31:53 +1100 Subject: [PATCH 61/89] doc: python bindings example README * Added the same license as used with the HOWTO. * Since these examples are so basic, they'll be dual licensed the same as GPGME itself (otherwise it would slip too dangerously against the need for permissive licensing of crypto libraries). --- lang/python/examples/howto/README.org | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lang/python/examples/howto/README.org b/lang/python/examples/howto/README.org index ee8c9863..604037f1 100644 --- a/lang/python/examples/howto/README.org +++ b/lang/python/examples/howto/README.org @@ -22,3 +22,31 @@ guarantees. They will include the relevant imports from the =__future__= module to facilitate that if possible. + +* Copyright and Licensing + :PROPERTIES: + :CUSTOM_ID: copyright-and-license + :END: + + +** Copyright (C) The GnuPG Project, 2018 + :PROPERTIES: + :CUSTOM_ID: copyright + :END: + + Copyright © The GnuPG Project, 2018. + + +** License GPL compatible + :PROPERTIES: + :CUSTOM_ID: license + :END: + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. From b30ebf89725641018b3b08f77876530f9b983fa2 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:39:49 +1100 Subject: [PATCH 62/89] doc: python bindings examples * Explicitly stated that all this code is released under the GPLv2+ and the LGPLv2.1+. --- lang/python/examples/howto/README.org | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lang/python/examples/howto/README.org b/lang/python/examples/howto/README.org index 604037f1..b74ae7e2 100644 --- a/lang/python/examples/howto/README.org +++ b/lang/python/examples/howto/README.org @@ -28,6 +28,12 @@ :CUSTOM_ID: copyright-and-license :END: + Unless otherwise stated, all the examples in this directory are + released under the same terms as GPGME itself; that is they are dual + licensed under the terms of both the GNU General Public License + version 2.0 (or any later version) *and* the GNU Lesser General + Public License version 2.1 (or any later version). + ** Copyright (C) The GnuPG Project, 2018 :PROPERTIES: From cfbdcb7fb3fa438cafba82e4fb8f327df596f98e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:55:01 +1100 Subject: [PATCH 63/89] example: python bindings key count * Added script wo count the number of keys in both the public and secret key stores. --- lang/python/examples/howto/keycount.py | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100755 lang/python/examples/howto/keycount.py diff --git a/lang/python/examples/howto/keycount.py b/lang/python/examples/howto/keycount.py new file mode 100755 index 00000000..7dd5e778 --- /dev/null +++ b/lang/python/examples/howto/keycount.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" +Number of secret keys: {0} +Number of public keys: {1} +""".format(secnum, pubnum) From 7ab42e79ade89f28507ea42d51148a40b4bfc736 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 09:25:34 +1100 Subject: [PATCH 64/89] example: encrypt file * Example to encrypt a file to a single key. * Takes key ID and/or fpr as a CLI parameter. * Takes path and filename as a CLI parameter. * Encrypts to specified key only, no signing and writes the output in both ASCII armoured and GPG binary formats with output filenames based on input filename. --- lang/python/examples/howto/encrypt-file.py | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 lang/python/examples/howto/encrypt-file.py diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py new file mode 100755 index 00000000..718c7157 --- /dev/null +++ b/lang/python/examples/howto/encrypt-file.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Encrypts a file to a specified key. If entering both the key and the filename +on the command line, the key must be entered first. + +Will produce both an ASCII armoured and GPG binary format copy of the encrypted file. +""" + +if len(sys.argv) > 3: + a_key = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + a_key = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + a_key = sys.argv[1] + filename = input("Enter the path and filename to encrypt: ") +else: + a_key = input("Enter the fingerprint or key ID to encrypt to: ") + filename = input("Enter the path and filename to encrypt: ") + +rkey = list(c.keylist(pattern=a_key, secret=False)) +with open(filename, "rb") as f: + text = f.read() + +with gpg.Context(armor=True) as ca: + ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, + sign=False) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(ciphertext) + +with gpg.Context() as cg: + ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, + sign=False) + with open("{0}.gpg".format(filename), "wb") as fg: + fg.write(ciphertext) From f0790f224d7af9521efe96e69a8f719fb89a5af2 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 09:39:48 +1100 Subject: [PATCH 65/89] example: encrypt file * Fixed typo in second encryption call. --- lang/python/examples/howto/encrypt-file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py index 718c7157..8aee52ad 100755 --- a/lang/python/examples/howto/encrypt-file.py +++ b/lang/python/examples/howto/encrypt-file.py @@ -58,7 +58,7 @@ with gpg.Context(armor=True) as ca: fa.write(ciphertext) with gpg.Context() as cg: - ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, + ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey, sign=False) with open("{0}.gpg".format(filename), "wb") as fg: fg.write(ciphertext) From f3fe47e8fd2e7bc748016befcae494421223368c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 09:47:39 +1100 Subject: [PATCH 66/89] example: sign and encrypt file * Example to sign and encrypt a file. * Similar to encrypt-file.py except all keys are considered trusted and signs with the default key. * Also encrypts to the default key. --- .../examples/howto/encrypt-sign-file.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 lang/python/examples/howto/encrypt-sign-file.py diff --git a/lang/python/examples/howto/encrypt-sign-file.py b/lang/python/examples/howto/encrypt-sign-file.py new file mode 100755 index 00000000..c8850b24 --- /dev/null +++ b/lang/python/examples/howto/encrypt-sign-file.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Signs and encrypts a file to a specified key. If entering both the key and the +filename on the command line, the key must be entered first. + +Signs with and also encrypts to the default key of the user invoking the +script. Will treat all recipients as trusted to permit encryption. + +Will produce both an ASCII armoured and GPG binary format copy of the encrypted +file. +""" + +if len(sys.argv) > 3: + a_key = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + a_key = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + a_key = sys.argv[1] + filename = input("Enter the path and filename to encrypt: ") +else: + a_key = input("Enter the fingerprint or key ID to encrypt to: ") + filename = input("Enter the path and filename to encrypt: ") + +rkey = list(c.keylist(pattern=a_key, secret=False)) +with open(filename, "rb") as f: + text = f.read() + +with gpg.Context(armor=True) as ca: + ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, + always_trust=True, + add_encrypt_to=True) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(ciphertext) + +with gpg.Context() as cg: + ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey, + always_trust=True, + add_encrypt_to=True) + with open("{0}.gpg".format(filename), "wb") as fg: + fg.write(ciphertext) From 7221bb67642eb01a07957d66d0cbcd4ef8aadbf8 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 09:53:27 +1100 Subject: [PATCH 67/89] example: encrypt file * Nested encryption in try/except statement in case recipient key is untrusted or invalid. --- lang/python/examples/howto/encrypt-file.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py index 8aee52ad..017a3421 100755 --- a/lang/python/examples/howto/encrypt-file.py +++ b/lang/python/examples/howto/encrypt-file.py @@ -52,13 +52,19 @@ with open(filename, "rb") as f: text = f.read() with gpg.Context(armor=True) as ca: - ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, - sign=False) - with open("{0}.asc".format(filename), "wb") as fa: - fa.write(ciphertext) + try: + ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, + sign=False) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(ciphertext) + except gpg.errors.InvalidRecipients as e: + print(e) with gpg.Context() as cg: - ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey, - sign=False) - with open("{0}.gpg".format(filename), "wb") as fg: - fg.write(ciphertext) + try: + ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey, + sign=False) + with open("{0}.gpg".format(filename), "wb") as fg: + fg.write(ciphertext) + except gpg.errors.InvalidRecipients as e: + print(e) From 29e918171f352c71a90a16c04d4a3dcafa5db682 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 14:19:16 +1100 Subject: [PATCH 68/89] example: groups work-around * Added groups selection work around code. * Intended for use as a module to be imported by other scripts, usually with "from groups import group_lists" or "from groups import group_lines" or similar. --- lang/python/examples/howto/groups.py | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lang/python/examples/howto/groups.py diff --git a/lang/python/examples/howto/groups.py b/lang/python/examples/howto/groups.py new file mode 100644 index 00000000..67fd7838 --- /dev/null +++ b/lang/python/examples/howto/groups.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import subprocess + +lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() + +for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + +groups = line.split(":")[-1].replace('"', '').split(',') + +group_lines = groups +for i in range(len(group_lines)): + group_lines[i] = group_lines[i].split("=") + +group_lists = group_lines +for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() From 51258975d763c9471859d635e6080c2ec02e8647 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 14:32:53 +1100 Subject: [PATCH 69/89] example: decrypt file * Decrypts a file taking file names as command line parameters. --- lang/python/examples/howto/decrypt-file.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 lang/python/examples/howto/decrypt-file.py diff --git a/lang/python/examples/howto/decrypt-file.py b/lang/python/examples/howto/decrypt-file.py new file mode 100755 index 00000000..60a050bd --- /dev/null +++ b/lang/python/examples/howto/decrypt-file.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +if len(sys.argv) == 3: + ciphertext = sys.argv[1] + newfile = sys.argv[2] +elif len(sys.argv) == 2: + ciphertext = sys.argv[1] + newfile = input("Enter path and filename of file to save decrypted data to: ") +else: + ciphertext = input("Enter path and filename of encrypted file: ") + newfile = input("Enter path and filename of file to save decrypted data to: ") + +with open(ciphertext, "rb") as cfile: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + +with open(newfile, "wb") as nfile: + nfile.write(plaintext) From 96d0395bccbbff91f73c06cb7bd6c131f04b8a9a Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 14:55:05 +1100 Subject: [PATCH 70/89] example: keycount * Fixed missing parenthesis. --- lang/python/examples/howto/keycount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/examples/howto/keycount.py b/lang/python/examples/howto/keycount.py index 7dd5e778..8e25454c 100755 --- a/lang/python/examples/howto/keycount.py +++ b/lang/python/examples/howto/keycount.py @@ -39,4 +39,4 @@ pubnum = len(publist) print(""" Number of secret keys: {0} Number of public keys: {1} -""".format(secnum, pubnum) +""".format(secnum, pubnum)) From 52e262991f1fdf7da93882c3b22c05537376cf49 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 14:57:26 +1100 Subject: [PATCH 71/89] doc: python bindings howto * Fixed typos in examples. --- lang/python/docs/GPGMEpythonHOWTOen.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 770c2784..2a200bb7 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -406,7 +406,7 @@ print(""" Number of secret keys: {0} Number of public keys: {1} - """.format(secnum, pubnum) + """.format(secnum, pubnum)) #+end_src @@ -671,7 +671,7 @@ newfile = input("Enter path and filename of file to save decrypted data to: ") with open(ciphertext, "rb") as cfile: plaintext, result, verify_result = gpg.Context().decrypt(cfile) - with open(newfile, "wb" as nfile: + with open(newfile, "wb") as nfile: nfile.write(plaintext) #+end_src From 0390ede18696520be9cc1a42f628e23159b7c2eb Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 21 Mar 2018 12:28:03 +1100 Subject: [PATCH 72/89] example: sign file * Similar to encrypt file except for signing a file in normal mode. * Noticed additional changes to be made to the howto to match this, but they will have to wait due to a power outage (currently running on battery and a mobile connection, but that won't last). --- lang/python/examples/howto/sign-file.py | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 lang/python/examples/howto/sign-file.py diff --git a/lang/python/examples/howto/sign-file.py b/lang/python/examples/howto/sign-file.py new file mode 100755 index 00000000..8e2cdc2d --- /dev/null +++ b/lang/python/examples/howto/sign-file.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Signs a file with a specified key. If entering both the key and the filename +on the command line, the key must be entered first. + +Will produce both an ASCII armoured and GPG binary format copy of the encrypted file. +""" + +if len(sys.argv) > 3: + logrus = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + logrus = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + logrus = sys.argv[1] + filename = input("Enter the path and filename to sign: ") +else: + logrus = input("Enter the fingerprint or key ID to sign with: ") + filename = input("Enter the path and filename to sign: ") + +with open(filename, "rb") as f: + text = f.read() + +key = list(gpg.Context().keylist(pattern=logrus)) + +with gpg.Context(armor=True, signers=key) as ca: + signed_data, result = ca.sign(text, mode=gpg.constants.sig.mode.NORMAL) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(signed_data) + +with gpg.Context(signers=key) as cg: + signed_data, result = cg.sign(text, mode=gpg.constants.sig.mode.NORMAL) + with open("{0}.gpg".format(filename), "wb") as fg: + fg.write(signed_data) From 1d2746433c9632fc0c7bc10b59280fca15895545 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:12:36 +1100 Subject: [PATCH 73/89] doc: python bindings howto * deconstructed and fixed all three signing methods. --- lang/python/docs/GPGMEpythonHOWTOen.org | 27 +++++++++++-------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 2a200bb7..0c151495 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -750,11 +750,10 @@ text = text0.encode() c = gpg.Context(armor=True, signers=sig_src) - signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) with open("/path/to/statement.txt.asc", "w") as afile: - for line in signed[0]: - afile.write("{0}\n".format(line.decode())) + afile.write(signed_data.decode()) #+end_src Though everything in this example is accurate, it is more likely @@ -769,10 +768,10 @@ text = tfile.read() c = gpg.Context() - signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed[0]) + afile.write(signed_data) #+end_src @@ -795,11 +794,10 @@ text = text0.encode() c = gpg.Context(armor=True) - signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) with open("/path/to/statement.txt.asc", "w") as afile: - for line in signed[0].splitlines(): - afile.write("{0}\n".format(line.decode())) + afile.write(signed_data.decode()) #+end_src As with normal signatures, detached signatures are best handled as @@ -812,10 +810,10 @@ text = tfile.read() c = gpg.Context(signers=sig_src) - signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed[0]) + afile.write(signed_data) #+end_src @@ -838,11 +836,10 @@ text = text0.encode() c = gpg.Context() - signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) with open("/path/to/statement.txt.asc", "w") as afile: - for line in signed[0].splitlines(): - afile.write("{0}\n".format(line.decode())) + afile.write(signed_data.decode()) #+end_src In spite of the appearance of a clear-signed message, the data @@ -855,10 +852,10 @@ text = tfile.read() c = gpg.Context() - signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) with open("/path/to/statement.txt.asc", "wb") as afile: - afile.write(signed[0]) + afile.write(signed_data) #+end_src From 1fdd1f306d45f6aeee91c7f016f7c37286ee3b3b Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:18:37 +1100 Subject: [PATCH 74/89] example: clear signing * Added example to clear sign a file with signing key selection. --- lang/python/examples/howto/clear-sign-file.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 lang/python/examples/howto/clear-sign-file.py diff --git a/lang/python/examples/howto/clear-sign-file.py b/lang/python/examples/howto/clear-sign-file.py new file mode 100755 index 00000000..597bbc59 --- /dev/null +++ b/lang/python/examples/howto/clear-sign-file.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Clear-signs a file with a specified key. If entering both the key and the +filename on the command line, the key must be entered first. +""" + +if len(sys.argv) > 3: + logrus = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + logrus = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + logrus = sys.argv[1] + filename = input("Enter the path and filename to sign: ") +else: + logrus = input("Enter the fingerprint or key ID to sign with: ") + filename = input("Enter the path and filename to sign: ") + +with open(filename, "rb") as f: + text = f.read() + +key = list(gpg.Context().keylist(pattern=logrus)) + +with gpg.Context(armor=True, signers=key) as c: + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + with open("{0}.asc".format(filename), "wb") as f: + f.write(signed_data) From 6fa2a344282e369e6aca8155bc77dd2c12a29414 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:24:52 +1100 Subject: [PATCH 75/89] examples: doc strings * Fixed minor errors in two doc strings. --- lang/python/examples/howto/encrypt-file.py | 3 ++- lang/python/examples/howto/sign-file.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py index 017a3421..877226d0 100755 --- a/lang/python/examples/howto/encrypt-file.py +++ b/lang/python/examples/howto/encrypt-file.py @@ -31,7 +31,8 @@ import sys Encrypts a file to a specified key. If entering both the key and the filename on the command line, the key must be entered first. -Will produce both an ASCII armoured and GPG binary format copy of the encrypted file. +Will produce both an ASCII armoured and GPG binary format copy of the encrypted +file. """ if len(sys.argv) > 3: diff --git a/lang/python/examples/howto/sign-file.py b/lang/python/examples/howto/sign-file.py index 8e2cdc2d..01006df0 100755 --- a/lang/python/examples/howto/sign-file.py +++ b/lang/python/examples/howto/sign-file.py @@ -31,7 +31,8 @@ import sys Signs a file with a specified key. If entering both the key and the filename on the command line, the key must be entered first. -Will produce both an ASCII armoured and GPG binary format copy of the encrypted file. +Will produce both an ASCII armoured and GPG binary format copy of the signed +file. """ if len(sys.argv) > 3: From af6cbba18ba5e2bbecce5f8268c146282cd12367 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:26:43 +1100 Subject: [PATCH 76/89] example: encrypt-sign-file.py * Adjusted the doc string. --- lang/python/examples/howto/encrypt-sign-file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/python/examples/howto/encrypt-sign-file.py b/lang/python/examples/howto/encrypt-sign-file.py index c8850b24..4b29b27b 100755 --- a/lang/python/examples/howto/encrypt-sign-file.py +++ b/lang/python/examples/howto/encrypt-sign-file.py @@ -34,8 +34,8 @@ filename on the command line, the key must be entered first. Signs with and also encrypts to the default key of the user invoking the script. Will treat all recipients as trusted to permit encryption. -Will produce both an ASCII armoured and GPG binary format copy of the encrypted -file. +Will produce both an ASCII armoured and GPG binary format copy of the signed +and encrypted file. """ if len(sys.argv) > 3: From ac6a552c37147a000de74f49d1bff34dad52252e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:30:32 +1100 Subject: [PATCH 77/89] example: detach sign file * Added example to make detached signatures of a file with key selection. --- .../python/examples/howto/detach-sign-file.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 lang/python/examples/howto/detach-sign-file.py diff --git a/lang/python/examples/howto/detach-sign-file.py b/lang/python/examples/howto/detach-sign-file.py new file mode 100755 index 00000000..99fbe65e --- /dev/null +++ b/lang/python/examples/howto/detach-sign-file.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Signs a file with a specified key. If entering both the key and the filename +on the command line, the key must be entered first. + +Will produce both an ASCII armoured and GPG binary format copy of the detached +signature file. +""" + +if len(sys.argv) > 3: + logrus = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + logrus = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + logrus = sys.argv[1] + filename = input("Enter the path and filename to sign: ") +else: + logrus = input("Enter the fingerprint or key ID to sign with: ") + filename = input("Enter the path and filename to sign: ") + +with open(filename, "rb") as f: + text = f.read() + +key = list(gpg.Context().keylist(pattern=logrus)) + +with gpg.Context(armor=True, signers=key) as ca: + signed_data, result = ca.sign(text, mode=gpg.constants.sig.mode.DETACH) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(signed_data) + +with gpg.Context(signers=key) as cb: + signed_data, result = cb.sign(text, mode=gpg.constants.sig.mode.DETACH) + with open("{0}.sig".format(filename), "wb") as fb: + fb.write(signed_data) From e57388a69f61d14e3df3c842d227fb450c96c807 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:48:41 +1100 Subject: [PATCH 78/89] doc: python bindings howto * Fixed minor error in one of the verification examples. --- lang/python/docs/GPGMEpythonHOWTOen.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index 0c151495..655209ac 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -898,7 +898,7 @@ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, time.ctime(sign.timestamp))) else: - pass(e) + pass #+end_src Whereas this next example, which is almost identical would work From ae2767eb27b6a76284ee4403e575869afe2e80a8 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:50:08 +1100 Subject: [PATCH 79/89] example: verify signed file * Added example to verify normal and clearsigned files. --- .../examples/howto/verify-signed-file.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 lang/python/examples/howto/verify-signed-file.py diff --git a/lang/python/examples/howto/verify-signed-file.py b/lang/python/examples/howto/verify-signed-file.py new file mode 100755 index 00000000..9f8702f5 --- /dev/null +++ b/lang/python/examples/howto/verify-signed-file.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys +import time + +""" +Verifies a signed file which has been signed with either NORMAL or CLEAR modes. +""" + +if len(sys.argv) > 2: + filename = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: + filename = sys.argv[1] +else: + filename = input("Enter the path and filename to sign: ") + +c = gpg.Context() + +try: + data, result = c.verify(open(filename)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass From ad6cb4f9b8b97a2bc501c17fc542a84b725dedea Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 03:58:58 +1100 Subject: [PATCH 80/89] example: verify signatures * Added example for verifying detached signatures against the files they're the signatures for. --- .../examples/howto/verify-signatures.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 lang/python/examples/howto/verify-signatures.py diff --git a/lang/python/examples/howto/verify-signatures.py b/lang/python/examples/howto/verify-signatures.py new file mode 100755 index 00000000..8aafc3ba --- /dev/null +++ b/lang/python/examples/howto/verify-signatures.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys +import time + +""" +Verifies a signed file which has been signed with a detached signature. +""" + +if len(sys.argv) > 2: + filename = sys.argv[1] + sig_file = sys.argv[2] +elif len(sys.argv) == 2: + filename = sys.argv[1] + sig_file = input("Enter the path and filename of the detached signature: ") +else: + filename = input("Enter the path and filename to verify: ") + sig_file = input("Enter the path and filename of the detached signature: ") + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(sig_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass From a4e3f827652c59d850b4e5506a92c1ecd190c1bb Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 04:04:05 +1100 Subject: [PATCH 81/89] example: groups * Added a docstring. --- lang/python/examples/howto/groups.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lang/python/examples/howto/groups.py b/lang/python/examples/howto/groups.py index 67fd7838..e7adc440 100644 --- a/lang/python/examples/howto/groups.py +++ b/lang/python/examples/howto/groups.py @@ -25,6 +25,12 @@ from __future__ import absolute_import, division, unicode_literals import subprocess +""" +Intended for use with other scripts. + +Usage: from groups import group_lines, group_lists +""" + lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() for i in range(len(lines)): From 6c6af9a7b0ae4e7182d669bec282c6edaaa7eaa1 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 05:07:56 +1100 Subject: [PATCH 82/89] example groups work around * Updated usage so it only references importing the final list of lists produced. Trying to use some of the mid-points can have unpredictable results (this is part of the problem with work arounds). --- lang/python/examples/howto/groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/examples/howto/groups.py b/lang/python/examples/howto/groups.py index e7adc440..5e7fdf60 100644 --- a/lang/python/examples/howto/groups.py +++ b/lang/python/examples/howto/groups.py @@ -28,7 +28,7 @@ import subprocess """ Intended for use with other scripts. -Usage: from groups import group_lines, group_lists +Usage: from groups import group_lists """ lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() From 8b401bfc76eac762553f76faab53c2f4cd117a8d Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 05:20:51 +1100 Subject: [PATCH 83/89] example: group key selection * Example of preparing a keylist object using an existing group line from the gpg.conf file. --- .../examples/howto/group-key-selection.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 lang/python/examples/howto/group-key-selection.py diff --git a/lang/python/examples/howto/group-key-selection.py b/lang/python/examples/howto/group-key-selection.py new file mode 100755 index 00000000..2e3d2ebb --- /dev/null +++ b/lang/python/examples/howto/group-key-selection.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +from groups import group_lists + +""" +Takes an existing group specified as a command line parameter and converts it +to a list object 'keys' as expected by the gpg module. + +Requires the groups module in this directory. +""" + +if len(sys.argv) == 2: + group = sys.argv[1] +elif len(sys.argv) == 1: + group = input("Enter the name of the group to select keys for: ") +else: + group = input("Enter the name of the group to select keys for: ") + +keys = [] +a = [] + +for i in range(len(group_lists)): + a.append(group_lists[i][0]) + +b = a.index(group) + +for i in range(len(group_lists[b][1])): + logrus = group_lists[b][1][i] + keys.append(gpg.Context().keylist(pattern=logrus)) From 0ccc57c9512246d82d46e7732bfb0f95c18ca9d3 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 05:40:02 +1100 Subject: [PATCH 84/89] example: sign and encrypt to group * Begins to string together some of the simpler examples to do more useful things. * Signs and encrypts a file while encrypting to every key in a group specified in the gpg.conf file. --- .../examples/howto/group-encrypt-sign-file.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100755 lang/python/examples/howto/group-encrypt-sign-file.py diff --git a/lang/python/examples/howto/group-encrypt-sign-file.py b/lang/python/examples/howto/group-encrypt-sign-file.py new file mode 100755 index 00000000..920e50da --- /dev/null +++ b/lang/python/examples/howto/group-encrypt-sign-file.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +from groups import group_lists + +""" +Signs and encrypts a file to a specified group of keys. If entering both the +group and the filename on the command line, the group must be entered first. + +Signs with and also encrypts to the default key of the user invoking the +script. Will treat all recipients as trusted to permit encryption. + +Will produce both an ASCII armoured and GPG binary format copy of the signed +and encrypted file. +""" + +if len(sys.argv) > 3: + group = sys.argv[1] + filename = " ".join(sys.argv[2:]) +elif len(sys.argv) == 3: + group = sys.argv[1] + filename = sys.argv[2] +elif len(sys.argv) == 2: + group = sys.argv[1] + filename = input("Enter the path and filename to encrypt: ") +else: + group = input("Enter the name of the group to select keys for: ") + filename = input("Enter the path and filename to encrypt: ") + +keys = [] +a = [] + +for i in range(len(group_lists)): + a.append(group_lists[i][0]) + +b = a.index(group) + +for i in range(len(group_lists[b][1])): + logrus = group_lists[b][1][i] + keys.append(gpg.Context().keylist(pattern=logrus)) + +with open(filename, "rb") as f: + text = f.read() + +with gpg.Context(armor=True) as ca: + ciphertext, result, sign_result = ca.encrypt(text, recipients=keys, + always_trust=True, + add_encrypt_to=True) + with open("{0}.asc".format(filename), "wb") as fa: + fa.write(ciphertext) + +with gpg.Context() as cg: + ciphertext, result, sign_result = cg.encrypt(text, recipients=keys, + always_trust=True, + add_encrypt_to=True) + with open("{0}.gpg".format(filename), "wb") as fg: + fg.write(ciphertext) From 0a0d57fd41380cd797d29e11cec8a77c7404e960 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 05:52:55 +1100 Subject: [PATCH 85/89] example: key selection * Similar to group-key-selection.py, but does not use an existing group from gpg.conf; instead takes multiple key IDs, fingerprints or patterns on the command line and adds them to a keylist object. --- lang/python/examples/howto/key-selection.py | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 lang/python/examples/howto/key-selection.py diff --git a/lang/python/examples/howto/key-selection.py b/lang/python/examples/howto/key-selection.py new file mode 100755 index 00000000..8c2d7b58 --- /dev/null +++ b/lang/python/examples/howto/key-selection.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes +# +# 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 2 of the License, or (at your option) any later +# version. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 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 GNU General Public License and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# . + +import gpg +import sys + +""" +Uses key IDs, fingerprints or other patterns as space separated input and +creates a keylist object for use by the gpg module. + +Similar to the group-key-selection.py script, but does not require an existing +group in the gpg.conf file. +""" + +if len(sys.argv) < 2: + key_ids_str = sys.argv[1:] +elif len(sys.argv) == 2: + key_ids_str = sys.argv[1] +elif len(sys.argv) == 1: + key_ids_str = input("Enter the keys to select by key ID or fingerprint: ") +else: + key_ids_str = input("Enter the keys to select by key ID or fingerprint: ") + +key_ids = key_ids_str.split() +keys = [] +for i in range(len(key_ids)): + logrus = key_ids[i] + keys.append(gpg.Context().keylist(pattern=logrus)) + From c6a0395f0a3a57071f0c943f7815f58a02f9d2f3 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 05:55:53 +1100 Subject: [PATCH 86/89] example: key selection * Removed extraneous blank line. --- lang/python/examples/howto/key-selection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/python/examples/howto/key-selection.py b/lang/python/examples/howto/key-selection.py index 8c2d7b58..a007219e 100755 --- a/lang/python/examples/howto/key-selection.py +++ b/lang/python/examples/howto/key-selection.py @@ -49,4 +49,3 @@ keys = [] for i in range(len(key_ids)): logrus = key_ids[i] keys.append(gpg.Context().keylist(pattern=logrus)) - From 7ddff71908a85111c8e0da41312197b3b1a77da6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 06:05:10 +1100 Subject: [PATCH 87/89] examples: encryption * Fixed two incorrect Context() objects. --- lang/python/examples/howto/encrypt-file.py | 2 +- lang/python/examples/howto/encrypt-sign-file.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py index 877226d0..ad4e1cef 100755 --- a/lang/python/examples/howto/encrypt-file.py +++ b/lang/python/examples/howto/encrypt-file.py @@ -48,7 +48,7 @@ else: a_key = input("Enter the fingerprint or key ID to encrypt to: ") filename = input("Enter the path and filename to encrypt: ") -rkey = list(c.keylist(pattern=a_key, secret=False)) +rkey = list(gpg.Context().keylist(pattern=a_key, secret=False)) with open(filename, "rb") as f: text = f.read() diff --git a/lang/python/examples/howto/encrypt-sign-file.py b/lang/python/examples/howto/encrypt-sign-file.py index 4b29b27b..41aaac86 100755 --- a/lang/python/examples/howto/encrypt-sign-file.py +++ b/lang/python/examples/howto/encrypt-sign-file.py @@ -51,7 +51,7 @@ else: a_key = input("Enter the fingerprint or key ID to encrypt to: ") filename = input("Enter the path and filename to encrypt: ") -rkey = list(c.keylist(pattern=a_key, secret=False)) +rkey = list(gpg.Context().keylist(pattern=a_key, secret=False)) with open(filename, "rb") as f: text = f.read() From 61a988036bd3f0d43f7d55bfa43f5f05bec978c4 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 06:18:13 +1100 Subject: [PATCH 88/89] example: group encryption * Troubleshooting. --- lang/python/examples/howto/group-encrypt-sign-file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/python/examples/howto/group-encrypt-sign-file.py b/lang/python/examples/howto/group-encrypt-sign-file.py index 920e50da..da73e4cd 100755 --- a/lang/python/examples/howto/group-encrypt-sign-file.py +++ b/lang/python/examples/howto/group-encrypt-sign-file.py @@ -63,7 +63,7 @@ b = a.index(group) for i in range(len(group_lists[b][1])): logrus = group_lists[b][1][i] - keys.append(gpg.Context().keylist(pattern=logrus)) + keys.append(list(gpg.Context().keylist(pattern=logrus))) with open(filename, "rb") as f: text = f.read() From 05e59933056ee8ef8ba7579351a58ed25dd7f754 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 06:19:36 +1100 Subject: [PATCH 89/89] examples: multi-key selection operations * Temporarily removing multi-key selection based examples. * There are a few issues with getting the key selections to play nicely with gpg.Context().keylist object types. * Will troubleshoot them separately and restore them when that's worked out, but I don't want these more complicated examples to delay merging the HOWTO with master. --- .../examples/howto/group-encrypt-sign-file.py | 83 ------------------- .../examples/howto/group-key-selection.py | 56 ------------- lang/python/examples/howto/key-selection.py | 51 ------------ 3 files changed, 190 deletions(-) delete mode 100755 lang/python/examples/howto/group-encrypt-sign-file.py delete mode 100755 lang/python/examples/howto/group-key-selection.py delete mode 100755 lang/python/examples/howto/key-selection.py diff --git a/lang/python/examples/howto/group-encrypt-sign-file.py b/lang/python/examples/howto/group-encrypt-sign-file.py deleted file mode 100755 index da73e4cd..00000000 --- a/lang/python/examples/howto/group-encrypt-sign-file.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, unicode_literals - -# Copyright (C) 2018 Ben McGinnes -# -# 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 2 of the License, or (at your option) any later -# version. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 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 GNU General Public License and the GNU -# Lesser General Public Licensefor more details. -# -# You should have received a copy of the GNU General Public License and the GNU -# Lesser General Public along with this program; if not, see -# . - -import gpg -import sys - -from groups import group_lists - -""" -Signs and encrypts a file to a specified group of keys. If entering both the -group and the filename on the command line, the group must be entered first. - -Signs with and also encrypts to the default key of the user invoking the -script. Will treat all recipients as trusted to permit encryption. - -Will produce both an ASCII armoured and GPG binary format copy of the signed -and encrypted file. -""" - -if len(sys.argv) > 3: - group = sys.argv[1] - filename = " ".join(sys.argv[2:]) -elif len(sys.argv) == 3: - group = sys.argv[1] - filename = sys.argv[2] -elif len(sys.argv) == 2: - group = sys.argv[1] - filename = input("Enter the path and filename to encrypt: ") -else: - group = input("Enter the name of the group to select keys for: ") - filename = input("Enter the path and filename to encrypt: ") - -keys = [] -a = [] - -for i in range(len(group_lists)): - a.append(group_lists[i][0]) - -b = a.index(group) - -for i in range(len(group_lists[b][1])): - logrus = group_lists[b][1][i] - keys.append(list(gpg.Context().keylist(pattern=logrus))) - -with open(filename, "rb") as f: - text = f.read() - -with gpg.Context(armor=True) as ca: - ciphertext, result, sign_result = ca.encrypt(text, recipients=keys, - always_trust=True, - add_encrypt_to=True) - with open("{0}.asc".format(filename), "wb") as fa: - fa.write(ciphertext) - -with gpg.Context() as cg: - ciphertext, result, sign_result = cg.encrypt(text, recipients=keys, - always_trust=True, - add_encrypt_to=True) - with open("{0}.gpg".format(filename), "wb") as fg: - fg.write(ciphertext) diff --git a/lang/python/examples/howto/group-key-selection.py b/lang/python/examples/howto/group-key-selection.py deleted file mode 100755 index 2e3d2ebb..00000000 --- a/lang/python/examples/howto/group-key-selection.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, unicode_literals - -# Copyright (C) 2018 Ben McGinnes -# -# 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 2 of the License, or (at your option) any later -# version. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 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 GNU General Public License and the GNU -# Lesser General Public Licensefor more details. -# -# You should have received a copy of the GNU General Public License and the GNU -# Lesser General Public along with this program; if not, see -# . - -import gpg -import sys - -from groups import group_lists - -""" -Takes an existing group specified as a command line parameter and converts it -to a list object 'keys' as expected by the gpg module. - -Requires the groups module in this directory. -""" - -if len(sys.argv) == 2: - group = sys.argv[1] -elif len(sys.argv) == 1: - group = input("Enter the name of the group to select keys for: ") -else: - group = input("Enter the name of the group to select keys for: ") - -keys = [] -a = [] - -for i in range(len(group_lists)): - a.append(group_lists[i][0]) - -b = a.index(group) - -for i in range(len(group_lists[b][1])): - logrus = group_lists[b][1][i] - keys.append(gpg.Context().keylist(pattern=logrus)) diff --git a/lang/python/examples/howto/key-selection.py b/lang/python/examples/howto/key-selection.py deleted file mode 100755 index a007219e..00000000 --- a/lang/python/examples/howto/key-selection.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, unicode_literals - -# Copyright (C) 2018 Ben McGinnes -# -# 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 2 of the License, or (at your option) any later -# version. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 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 GNU General Public License and the GNU -# Lesser General Public Licensefor more details. -# -# You should have received a copy of the GNU General Public License and the GNU -# Lesser General Public along with this program; if not, see -# . - -import gpg -import sys - -""" -Uses key IDs, fingerprints or other patterns as space separated input and -creates a keylist object for use by the gpg module. - -Similar to the group-key-selection.py script, but does not require an existing -group in the gpg.conf file. -""" - -if len(sys.argv) < 2: - key_ids_str = sys.argv[1:] -elif len(sys.argv) == 2: - key_ids_str = sys.argv[1] -elif len(sys.argv) == 1: - key_ids_str = input("Enter the keys to select by key ID or fingerprint: ") -else: - key_ids_str = input("Enter the keys to select by key ID or fingerprint: ") - -key_ids = key_ids_str.split() -keys = [] -for i in range(len(key_ids)): - logrus = key_ids[i] - keys.append(gpg.Context().keylist(pattern=logrus))