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 @@ -1,90 +1,98 @@ GPGME Python Bindings HOWTO Ben McGinnes + + + + + + + + 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 @@ -1,6 +1,7 @@ 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,453 +1,917 @@ - + - + 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 DITAMAP 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 false ${cfd} ${cfd}/out/html5 ${cfd}/temp/html5 html5 false true true -Xmx384m false false true gpgme-python-howto (HTML5) false false DITAMAP true true false false false false false false false DITA-OT false false ${cfd} ${cfd}/out/webhelp-responsive-${ddt} ${cfd}/temp/webhelp-responsive-${ddt} webhelp-responsive false true true ${configured.ditaot.dir}/plugins/com.oxygenxml.webhelp.responsive/templates/oxygen oxygen-tiles.opt false force-unique true false 4 true false args.xhtml.classattr yes yes 4 yes no -Xmx384m false false true gpgme-python-howto (WebHelp Responsive) false false DITAMAP true true false false false false false false false DITA-OT \ No newline at end of file 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 @@ -1,29 +1,30 @@ Documentation Source Files

Unlike all other documentation in the GnuPG Project, including the initial version of this HOWTO, this version was not written in Emacs Org-Mode. Nor was it written in LaTeX, Texinfo or even directly in HTML. Instead it was written using the Darwin Information Typing Architecture (DITA) XML.

This was done for two main reasons:

  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 @@ -1,38 +1,49 @@ Decryption

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.

import gpg 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: try: plaintext, result, verify_result = gpg.Context().decrypt(cfile) except gpg.errors.GPGMEError as e: plaintext = None print(e) if plaintext is not None: with open(newfile, "wb") as nfile: nfile.write(plaintext) 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 @@ -1,49 +1,53 @@ Group Lines

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.

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 = [] group_lists = [] for i in range(len(groups)): group_lines.append(groups[i].split("=")) group_lists.append(groups[i].split("=")) for i in range(len(group_lists)): group_lists[i][1] = group_lists[i][1].split()

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.

To use this code as a module use:

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 @@ -1,14 +1,16 @@ 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