From dda54cc851490be045832d5ee0b03be082529d17 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Thu, 28 Jun 2018 18:02:43 +1000 Subject: [PATCH] python bindings howto: dita version * Drafts of instructions for exporting public and secret keys ready, along in addition to the code. --- .../docs/dita/gpgme-python-howto-footer.xhtml | 9 + .../docs/dita/gpgme-python-howto.ditamap | 8 + lang/python/docs/dita/gpgme-python.ditamap | 1 + lang/python/docs/dita/gpgmePython.xpr | 470 +++++++++++++++++- .../docs/dita/howto/part01/docs-source.dita | 11 +- .../dita/howto/part03/exporting-pubkeys.dita | 120 +++++ .../dita/howto/part03/exporting-seckeys.dita | 161 ++++++ .../docs/dita/howto/part03/exporting.dita | 12 + .../docs/dita/howto/part03/importing.dita | 67 +++ .../docs/dita/howto/part04/decryption.dita | 17 +- .../docs/dita/howto/part06/group-lines.dita | 4 + lang/python/docs/dita/howto/version-info.dita | 6 +- 12 files changed, 873 insertions(+), 13 deletions(-) create mode 100644 lang/python/docs/dita/gpgme-python-howto-footer.xhtml create mode 100644 lang/python/docs/dita/howto/part03/exporting-pubkeys.dita create mode 100644 lang/python/docs/dita/howto/part03/exporting-seckeys.dita create mode 100644 lang/python/docs/dita/howto/part03/exporting.dita create mode 100644 lang/python/docs/dita/howto/part03/importing.dita diff --git a/lang/python/docs/dita/gpgme-python-howto-footer.xhtml b/lang/python/docs/dita/gpgme-python-howto-footer.xhtml new file mode 100644 index 00000000..77d681a1 --- /dev/null +++ b/lang/python/docs/dita/gpgme-python-howto-footer.xhtml @@ -0,0 +1,9 @@ + + + + + + +
Copyright © Benjamin D. McGinnes, 2018
for the GnuPG Project
+ + \ No newline at end of file diff --git a/lang/python/docs/dita/gpgme-python-howto.ditamap b/lang/python/docs/dita/gpgme-python-howto.ditamap index e66c9f4a..1809acb3 100644 --- a/lang/python/docs/dita/gpgme-python-howto.ditamap +++ b/lang/python/docs/dita/gpgme-python-howto.ditamap @@ -58,6 +58,11 @@ + + + + + @@ -87,4 +92,7 @@ + + + diff --git a/lang/python/docs/dita/gpgme-python.ditamap b/lang/python/docs/dita/gpgme-python.ditamap index 5232dca8..6b8926b4 100644 --- a/lang/python/docs/dita/gpgme-python.ditamap +++ b/lang/python/docs/dita/gpgme-python.ditamap @@ -3,4 +3,5 @@ GPGME Python Bindings + diff --git a/lang/python/docs/dita/gpgmePython.xpr b/lang/python/docs/dita/gpgmePython.xpr index e8756fca..59fe9770 100644 --- a/lang/python/docs/dita/gpgmePython.xpr +++ b/lang/python/docs/dita/gpgmePython.xpr @@ -1,20 +1,35 @@ - + - + scenario.associations + + + gpgme-python.ditamap + + + + DITA Map WebHelp - TS/HC - GPGME + + + + + DITAMAP + + + gpgme-python-howto.ditamap - gpgme-python-howto (WebHelp Responsive) + DITA Map WebHelp - TS/HC - GPGME @@ -28,6 +43,455 @@ scenarios + + + false + + + false + + + ${cfd} + + + ${cfd}/out/webhelp-${ddt} + + + ${cfd}/temp/webhelp-${ddt} + + + webhelp + + + + + false + + + true + + + + + + + + + + + + + + true + + + file:/usr/local/oXygenXML/custom/webhelp-skins/high-contrast-skin.css + + + file:/usr/local/oXygenXML/custom/webhelp-skins/high-contrast-skin.css + + + + + + + + + + + + + + + + + + + + + args.xhtml.classattr + + + + + + yes + + + yes + + + 4 + + + + yes + no + + + + + + + + + + + + + webhelp.footer.include + + + + + + yes + + + yes + + + 4 + + + + yes + no + + + + + + If the "webhelp.footer.file" parameter has a value, the content of that file is used as footer. If "webhelp.footer.file" has no value, the default Oxygen footer is inserted in each Webhelp page. + No footer is added to the Webhelp pages. + + + + + + webhelp.footer.file + + + + + + /Users/ben/dev/hgit/mine/gnupg/dita/gpgme/python/gpgme-python-howto-footer.xhtml + + + + + + 2 + + + + + + + + + + + webhelp.copyright + + + + + + Copyright © Benjamin D. McGinnes, 2018 + + + + + + 0 + + + + + + + + + + + + -Xmx384m + + + false + + + + + + false + + + + + + + + + true + + + + + + DITA Map WebHelp - TS/HC - GPGME + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + DITAMAP + + + true + + + true + + + + + + + + + false + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + + + + DITA-OT + + + + + + + + false + + + false + + + ${cfd} + + + ${cfd}/out/html5-${ddt} + + + ${cfd}/temp/html5-${ddt} + + + html5 + + + + + false + + + true + + + + + + + + + + + + + + true + + + file:/usr/local/oXygenXML/custom/webhelp-skins/high-contrast-skin.css + + + file:/usr/local/oXygenXML/custom/webhelp-skins/high-contrast-skin.css + + + + + + + + + + + + + + + + + + + + + -Xmx384m + + + false + + + + + + false + + + + + + + + + true + + + + + + gpgme-python (HTML5) + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + DITAMAP + + + true + + + true + + + + + + + + + false + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + + + + DITA-OT + + + + + false diff --git a/lang/python/docs/dita/howto/part01/docs-source.dita b/lang/python/docs/dita/howto/part01/docs-source.dita index 8e97a353..7c90b59c 100644 --- a/lang/python/docs/dita/howto/part01/docs-source.dita +++ b/lang/python/docs/dita/howto/part01/docs-source.dita @@ -14,15 +14,16 @@
  1. A bug in either Org-Mode or Babel prevented the more complex examples included in the HOWTO - from displaying correctly.
  2. + from displaying correctly while also retaining syntax highlighting.
  3. To demonstrate some of the advantages of DITA XML over existing documentation production software used in the project (particularly Texinfo and LaTeX).

-

The XML format definitely supports displaying all the more complex Python code correctly, - as well as being designed to produce standards compliant print and HTML output. Whereas - currently the existing tools utilised by the GnuPG Project can't display the example code in - a way which would actually pass the project's own git commit ruleset.

+

The XML format definitely supports displaying all the more complex Python code correctly + with syntax highlighting, as well as being designed to produce standards compliant print and + HTML output. Whereas currently the existing tools utilised by the GnuPG Project can't + display the example code in a way which would actually pass the project's own git commit + ruleset.

diff --git a/lang/python/docs/dita/howto/part03/exporting-pubkeys.dita b/lang/python/docs/dita/howto/part03/exporting-pubkeys.dita new file mode 100644 index 00000000..8ae4f5bd --- /dev/null +++ b/lang/python/docs/dita/howto/part03/exporting-pubkeys.dita @@ -0,0 +1,120 @@ + + + + + Exporting Public Keys + +

There are two methods of exporting public keys, both of which are very similar to the + other. The default method, key_export() will export a public key or keys + matching a specified pattern as normal. The alternative, the + key_export_minimal() method will do the same thing except producing a + minimised output with extra signatures and third party signatures or certifications + removed.

+

+ import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export(pattern=logrus) +except: + result = c.key_export(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass + +

+

It is important to note that the result will only return + None when a pattern has been entered for logrus, but + it has not matched any keys. When the search pattern itself is set to None + this triggers the exporting of the entire public keybox.

+

+ import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys in minimised form. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_minimal(pattern=logrus) +except: + result = c.key_export_minimal(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass + +

+

+ + + diff --git a/lang/python/docs/dita/howto/part03/exporting-seckeys.dita b/lang/python/docs/dita/howto/part03/exporting-seckeys.dita new file mode 100644 index 00000000..9093aa08 --- /dev/null +++ b/lang/python/docs/dita/howto/part03/exporting-seckeys.dita @@ -0,0 +1,161 @@ + + + + + Exporting Secret Keys + +

Exporting secret keys is, functionally, very similar to exporting public keys; save for the + invocation of pinentry via gpg-agent in order to + securely enter the key's passphrase and authorise the export.

+

The following example exports the secret key to a file which is then set with the same + permissions as the output files created by the command line secret key export options.

+

+ import gpg +import os +import os.path +import sys + +print(""" +This script exports one or more secret keys. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_secret(pattern=logrus) +except: + result = c.key_export_secret(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + os.chmod(keyfile, 0o600) +else: + pass + +

+

Alternatively the approach of the following script can be used. This longer example saves + the exported secret key(s) in files in the GnuPG home directory, in addition to setting the + file permissions as only readable and writable by the user. It also exports the secret + key(s) twice in order to output both GPG binary (.gpg) and ASCII armoured + (.asc) files.

+

+ import gpg +import os +import os.path +import subprocess +import sys + +print(""" +This script exports one or more secret keys as both ASCII armored and binary +file formats, saved in files within the user's GPG home directory. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-dirs homedir" +else: + gpgconfcmd = "gpgconf --list-dirs homedir" + +a = gpg.Context(armor=True) +b = gpg.Context() +c = gpg.Context() + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +if c.home_dir is not None: + if c.home_dir.endswith("/"): + gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}{1}.asc".format(c.home_dir, keyfile) + else: + gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile) +else: + if os.path.exists(os.environ["GNUPGHOME"]) is True: + hd = os.environ["GNUPGHOME"] + else: + hd = subprocess.getoutput(gpgconfcmd) + gpgfile = "{0}/{1}.gpg".format(hd, keyfile) + ascfile = "{0}/{1}.asc".format(hd, keyfile) + +try: + a_result = a.key_export_secret(pattern=logrus) + b_result = b.key_export_secret(pattern=logrus) +except: + a_result = a.key_export_secret(pattern=None) + b_result = b.key_export_secret(pattern=None) + +if a_result is not None: + with open(ascfile, "wb") as f: + f.write(a_result) + os.chmod(ascfile, 0o600) +else: + pass + +if b_result is not None: + with open(gpgfile, "wb") as f: + f.write(b_result) + os.chmod(gpgfile, 0o600) +else: + pass + +

+

+ + + diff --git a/lang/python/docs/dita/howto/part03/exporting.dita b/lang/python/docs/dita/howto/part03/exporting.dita new file mode 100644 index 00000000..8c054af0 --- /dev/null +++ b/lang/python/docs/dita/howto/part03/exporting.dita @@ -0,0 +1,12 @@ + + + + + Exporting Keys + +

Exporting keys remains a reasonably simple task, but has been separated into three + different functions for the OpenPGP cryptographic engine. Two of those functions are for + exporting public keys and the third is for exporting secret keys.

+ +
+
diff --git a/lang/python/docs/dita/howto/part03/importing.dita b/lang/python/docs/dita/howto/part03/importing.dita new file mode 100644 index 00000000..267eb94e --- /dev/null +++ b/lang/python/docs/dita/howto/part03/importing.dita @@ -0,0 +1,67 @@ + + + + + Importing keys + +

Importing keys is possible with the key_import() method and takes one + argument which is a bytes literal object containing either the binary or ASCII armoured key + data for one or more keys.

+

The following example retrieves one or more keys from the SKS keyservers via the web using + the requests module. Since requests returns the content as a bytes literal object, we can + then use that directly to import the resulting data into our keybox.

+

+ import gpg +import os.path +import requests + +c = gpg.Context() +url = "https://sks-keyservers.net/pks/lookup" +pattern = input("Enter the pattern to search for key or user IDs: ") +payload = { "op": "get", "search": pattern } + +r = requests.get(url, verify=True, params=payload) +result = c.key_import(r.content) + +if result is not None and hasattr(result, "considered") is False: + print(result) +elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: {0} + + Number of keys revoked: {1} + Number of new signatures: {2} + Number of new subkeys: {3} + Number of new user IDs: {4} +Number of new secret keys: {5} + Number of unchanged keys: {6} + +The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") +else: + pass + +

+

+ When searching for a key ID of any length or a fingerprint (without spaces), the SKS + servers require the the leading 0x indicative of hexadecimal be included. + Also note that the old short key IDs (e.g. 0xDEADBEEF) should no longer + be used due to the relative ease by which such key IDs can be reproduced, as demonstrated + by the Evil32 + Project in 2014 (which was subsequently exploited in 2016). +

+

+ + + diff --git a/lang/python/docs/dita/howto/part04/decryption.dita b/lang/python/docs/dita/howto/part04/decryption.dita index 41a4650e..bb8c3680 100644 --- a/lang/python/docs/dita/howto/part04/decryption.dita +++ b/lang/python/docs/dita/howto/part04/decryption.dita @@ -30,9 +30,20 @@ else: pass

-

The data available in plaintext in this example is the decrypted content - as a byte object, the recipient key IDs and algorithms in result and the - results of verifying any signatures of the data in verify_result.

+

The data available in plaintext following a successful decryption in this + example is the decrypted content as a byte object, the recipient key IDs and algorithms in + result and the results of verifying any signatures of the data in + verify_result.

+

+ The graceful handling of GPGMEError with the try/except statement is + to handle the decryption error message produced if the file ciphertext, + and thus cfile, are encrypted with deprecated and insecure methods. + Particularly without MDC integrity checks or utilising deprecated encryption algorithms. + Messages and files encrypted with these are not decrypted with GPGME at all and any user + requiring archival access will need to access it manually with pre-GnuPG 2.3 versions of + the software which meets the requirements of the specific use case. +

+

diff --git a/lang/python/docs/dita/howto/part06/group-lines.dita b/lang/python/docs/dita/howto/part06/group-lines.dita index f4aca74c..1dbfc97a 100644 --- a/lang/python/docs/dita/howto/part06/group-lines.dita +++ b/lang/python/docs/dita/howto/part06/group-lines.dita @@ -44,6 +44,10 @@ for i in range(len(group_lists)):

from groups import group_lists

+

A demonstration of using the groups.py module is also available in + the form of the executable mutt-groups.py script. This second script + reads all the group entries in a user's gpg.conf file and converts them + into crypt-hooks suitable for use with the Mutt and Neomutt mail clients.

diff --git a/lang/python/docs/dita/howto/version-info.dita b/lang/python/docs/dita/howto/version-info.dita index 67768204..f9bb42aa 100644 --- a/lang/python/docs/dita/howto/version-info.dita +++ b/lang/python/docs/dita/howto/version-info.dita @@ -4,10 +4,12 @@ Documentation Version -

Version: 0.1.1

+

Version: 0.1.2-DRAFT

Author: Ben McGinnes <ben@gnupg.org>

-

Author GPG Key ID: DB4724E6FA4286C92B4E55C4321E4E2373590E5D

+

Author GPG Key ID: + DB4724E6FA4286C92B4E55C4321E4E2373590E5D

Language: Australian English, British English