From 5215d58ae2521d81c3db0b45dfbdce01a679acab Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 20:05:21 +1100 Subject: 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 (limited to '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 ... -- cgit v1.2.3 From 8a76deb11efd7dadfde6e8e7e69fbcd92577982f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 20:12:26 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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=. -- cgit v1.2.3 From 47d401d159852ea08e90af21d91bb4b93be9000d Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 7 Mar 2018 21:27:54 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. -- cgit v1.2.3 From a98f2c556fe6e33a9cd38279e64e4b09f05cc675 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 8 Mar 2018 15:23:05 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 75463d589522cba427f9e5a3a408192ffad8bb21 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 04:42:41 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From c767a4a3590bd8a224d0268746df443942cb28c2 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 05:25:49 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From fa4927146b68dd045903285f1c45fb64deb2e361 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 07:53:57 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From ab81c2d868bba79fdb8f8d7f576b6bd88c6bdf3c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 15:22:24 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 - -text = """Declaration of ... something. + import gpg -""" + 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 -- cgit v1.2.3 From 01686463948ac6096dd8579a110c478d3a1f9a83 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 16:49:05 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 172baaf4d3e4ed03a4d3437be9efa3dfe6a847bc Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 9 Mar 2018 20:45:14 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 7ebc5a357057d01b7ef965521ab68b7cb7e20a8f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 00:33:11 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. -- cgit v1.2.3 From 0e1300ce777dd0c87f31ac8bc49846b9df242df9 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 04:55:44 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. -- cgit v1.2.3 From 83b1336ceebb86e13a55bbf220df2d750f6b3ec6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 05:42:50 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From a8f48b6f577d562c25fd0191c0cc2cc8e96078c1 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 06:09:53 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. -- cgit v1.2.3 From 484e9a6229ac9c80c6be4df638bce711f08a74c6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 07:42:04 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 36dfbdffea60c529a6d1e1ff3e507be016b6a0f6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 07:49:42 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From f81adeba992a9fd3b5a199e9a2e242a0f53cf639 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 08:26:22 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From c27a7a3f994dad0eccee890185582f4350fbf233 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 11:50:38 +1100 Subject: doc: python bindings howto * Added text description for the decryption example. --- lang/python/docs/GPGMEpythonHOWTOen.org | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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() -- cgit v1.2.3 From f29bda8d7146b4bc0bf73d6e613131545ff86b73 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 15:03:11 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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]) -- cgit v1.2.3 From e489ddd08af29fdad8db8aa0aec0c314daa3678c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 18:32:30 +1100 Subject: 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 | 143 ++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 52 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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,36 +563,38 @@ 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 - text = """Declaration of ... something. - - """ + tfile = open("/path/to/statement.txt", "rb") + text = tfile.read() + tfile.close() c = gpg.Context() - signed = c.sign(text, mode=2) + signed = c.sign(text, mode=0) - 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 = 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 = """Declaration of ... something. + text = b"""Declaration of ... something. """ @@ -548,9 +602,9 @@ c.armor = True signed = c.sign(text, mode=1) - 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 @@ -564,7 +618,6 @@ tfile.close() c = gpg.Context() - c.armor = True signed = c.sign(text, mode=1) afile = open("/path/to/statement.txt.sig", "wb") @@ -572,6 +625,27 @@ afile.close() #+end_src +*** Clearsigning messages or text + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-clear + :END: + + #+begin_src python + import gpg + + text = """Declaration of ... something. + + """ + + c = gpg.Context() + signed = c.sign(text, mode=2) + + 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 + ** 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 -- cgit v1.2.3 From c92da2c7eb148ce9fb06495a8470dd9caf662f9a Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 13 Mar 2018 19:20:44 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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") -- cgit v1.2.3 From 952b6042f78017c476452088261af8d352cfa729 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 01:41:21 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From a10dcb4f138eb5a21881cdbc4806c25129d4ae4e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 02:21:44 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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=), -- cgit v1.2.3 From a71205dc3b58970adf591b4e4553824a33f353db Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 02:40:41 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 423fdcd4653cb01f07f2b0e72cfcf49554930f70 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Wed, 14 Mar 2018 20:36:30 +1100 Subject: doc: python bindings howto * Added recommended method of single encryption with description. --- lang/python/docs/GPGMEpythonHOWTOen.org | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From ada059b07178147821b1598c935aa70ae45e3e6c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 03:51:51 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From e5c85fba25de1187949697e2dae0e89345b71e89 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 04:07:57 +1100 Subject: doc: python bindings howto * Added description for detached signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 46 +++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 6bc12a0eeb20409770cb8b923d08c18c2b730cb8 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 07:20:31 +1100 Subject: doc: python bindings howto * Added 4 signature verification methods and partial text for them. --- lang/python/docs/GPGMEpythonHOWTOen.org | 135 +++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 19 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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) - - 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) + try: + verified = c.verify(open(gpg_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(e) + #+end_src + + 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 -- cgit v1.2.3 From b35aaef7a3b793b8f6f5b42596c0a6a51e87f78c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 11:18:02 +1100 Subject: doc: python bindings howto * Added text for verifying signatures. --- lang/python/docs/GPGMEpythonHOWTOen.org | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 1d05e6aa4ea467c8c5926b827cfcfba357d03312 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 12:14:29 +1100 Subject: doc: python bindings howto * Added c.get_key instructions and examples. --- lang/python/docs/GPGMEpythonHOWTOen.org | 77 +++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 17 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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) + fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" + key = gpg.Context().get_key(fingerprint) + #+end_src - seclist = list(seckeys) - secnum = len(seclist) + Whereas this example demonstrates selecting the author's current + key with the =secret= key word argument set to =True=: - publist = list(pubkeys) - pubnum = len(publist) + #+begin_src python + import gpg - print(""" - Number of secret keys: {0} - Number of public keys: {1} - """.format(secnum, pubnum) + 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: -- cgit v1.2.3 From 5d1dd2abe5cf787875d12afe46c78c75385d7b31 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 12:27:45 +1100 Subject: doc: python bindings howto * Added sections for key generation and key editing. --- lang/python/docs/GPGMEpythonHOWTOen.org | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 5432e5f9d1dfc02812d0b181f8d88cdf4a2bfbfb Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:01:30 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From b02d9d0a7b96b186eb3063d94bde369339181461 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:43:44 +1100 Subject: 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(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From 9e3e4a835c64f5d06de821b1fd648af37827ff26 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 14:59:36 +1100 Subject: doc: python bindings howto * Spell checking and fixing the few errors. --- lang/python/docs/GPGMEpythonHOWTOen.org | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 7ac65b10837740caf68cdade791b8c5ce4eb1b03 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 15:16:23 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From 961aea212ef48914ecbfa169addf951b0854b0b4 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 15:51:01 +1100 Subject: doc: python bindings howto * Added key signing. --- lang/python/docs/GPGMEpythonHOWTOen.org | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From 3d0c7a2202c8e9bd4f284fd00069d34b8d3d3d4c Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 15 Mar 2018 16:13:34 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 94a95ac12364989db7f4be333107f3c023551857 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 01:34:22 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 22e2445beee46ed1e527a98e635153c7cf03786f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 01:48:56 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 431897a4c48fe1bc9d37f655097aabaf5b685d11 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 Mar 2018 03:52:58 +1100 Subject: doc: python bindings howto * Added clarification on why it's not on PyPI. --- lang/python/docs/GPGMEpythonHOWTOen.org | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From b549f69d0520bb74957b95cec9ea918dba2374f6 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 Mar 2018 03:46:02 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 82c5af225f2bdf3acc6fc652a96ee61c9b057395 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 08:43:36 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 4811ff7b6c8ef97c7d4858ce235e9bf8227f4917 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 08:49:17 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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: -- cgit v1.2.3 From 64c5886132aceefc9d9600a3a6dbbbf404b95b81 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 10:00:44 +1100 Subject: 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 | 155 +++++++++++--------------------- 1 file changed, 54 insertions(+), 101 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. - - 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. + 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. + + 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 -- cgit v1.2.3 From 1779d7b9d6769b2e47f1e90260290e25c8c3aa02 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 10:39:53 +1100 Subject: doc: python bindings howto * deconstructing multi-recipient encryption. --- lang/python/docs/GPGMEpythonHOWTOen.org | 102 ++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 43 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 0fb8a5d45c1c77a5928d6e356271da055aa55994 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 13:09:46 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From d5f6dec048d3d4d94f1fcdb3f4249cf6e71c4b92 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Mon, 19 Mar 2018 15:03:00 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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. -- cgit v1.2.3 From 6950a63e63d60685ddb6f4cbff7b826b8acb5b13 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 08:26:57 +1100 Subject: 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 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.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 -- cgit v1.2.3 From 52e262991f1fdf7da93882c3b22c05537376cf49 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 20 Mar 2018 14:57:26 +1100 Subject: doc: python bindings howto * Fixed typos in examples. --- lang/python/docs/GPGMEpythonHOWTOen.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From 1d2746433c9632fc0c7bc10b59280fca15895545 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:12:36 +1100 Subject: doc: python bindings howto * deconstructed and fixed all three signing methods. --- lang/python/docs/GPGMEpythonHOWTOen.org | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3 From e57388a69f61d14e3df3c842d227fb450c96c807 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 22 Mar 2018 01:48:41 +1100 Subject: 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(-) (limited to 'lang/python/docs/GPGMEpythonHOWTOen.org') 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 -- cgit v1.2.3