diff options
author | Ben McGinnes <[email protected]> | 2018-09-23 09:36:54 +0000 |
---|---|---|
committer | Ben McGinnes <[email protected]> | 2018-09-23 09:36:54 +0000 |
commit | b12b2cc99621fe32a2d698ce7f091f3225f35bd0 (patch) | |
tree | 1d1e41c25dc2ad00f58017a72f268e7dc084d19c /doc | |
parent | examples: python bindings and hkp4py updates (diff) | |
download | gpgme-b12b2cc99621fe32a2d698ce7f091f3225f35bd0.tar.gz gpgme-b12b2cc99621fe32a2d698ce7f091f3225f35bd0.zip |
docs and examples: python bindings howto
* Added more comprehensive examples using hkp4py and added a couple
more example scripts for protonmail.
Tested-by: Ben McGinnes <[email protected]>
Signed-off-by: Ben McGinnes <[email protected]>
Diffstat (limited to 'doc')
-rw-r--r-- | doc/gpgme-python-howto.texi | 428 |
1 files changed, 375 insertions, 53 deletions
diff --git a/doc/gpgme-python-howto.texi b/doc/gpgme-python-howto.texi index 7b8b79d1..2e305970 100644 --- a/doc/gpgme-python-howto.texi +++ b/doc/gpgme-python-howto.texi @@ -86,6 +86,12 @@ Key selection * Counting keys:: +Importing keys + +* Working with ProtonMail:: +* Importing with HKP for Python:: +* Importing from ProtonMail with HKP for Python:: + Exporting keys * Exporting public keys:: @@ -832,7 +838,125 @@ relative ease by which such key IDs can be reproduced, as demonstrated by the Evil32 Project in 2014 (which was subsequently exploited in 2016). -Performing the same task with the @uref{https://github.com/Selfnet/hkp4py, hkp4py module} (available via PyPI) +@menu +* Working with ProtonMail:: +* Importing with HKP for Python:: +* Importing from ProtonMail with HKP for Python:: +@end menu + +@node Working with ProtonMail +@subsection Working with ProtonMail + +Here is a variation on the example above which checks the constrained +ProtonMail keyserver for ProtonMail public keys. + +@example +import gpg +import requests +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. +""") + +c = gpg.Context(armor=True) +url = "https://api.protonmail.ch/pks/lookup" +ksearch = [] + +if len(sys.argv) >= 2: + keyterm = sys.argv[1] +else: + keyterm = input("Enter the key ID, UID or search string: ") + +if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) +elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) +elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) +elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) +elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) +else: + ksearch.append(keyterm) + +for k in ksearch: + payload = @{"op": "get", "search": k@} + try: + r = requests.get(url, verify=True, params=payload) + if r.ok is True: + result = c.key_import(r.content) + elif r.ok is False: + result = r.content + except Exception as e: + result = None + + if result is not None and hasattr(result, "considered") is False: + print("@{0@} for @{1@}".format(result.decode(), k)) + 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@} + +With UIDs wholely or partially matching the following string: + + @{1@} + + Number of keys revoked: @{2@} + Number of new signatures: @{3@} + Number of new subkeys: @{4@} + Number of new user IDs: @{5@} +Number of new secret keys: @{6@} + Number of unchanged keys: @{7@} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + print(e) +@end example + +Both the above example, @uref{../examples/howto/pmkey-import.py, pmkey-import.py}, and a version which prompts +for an alternative GnuPG home directory, @uref{../examples/howto/pmkey-import-alt.py, pmkey-import-alt.py}, are +available with the other examples and are executable scripts. + +Note that while the ProtonMail servers are based on the SKS servers, +their server is related more to their API and is not feature complete +by comparison to the servers in the SKS pool. One notable difference +being that the ProtonMail server does not permit non ProtonMail users +to update their own keys, which could be a vector for attacking +ProtonMail users who may not receive a key's revocation if it had been +compromised. + +@node Importing with HKP for Python +@subsection Importing with HKP for Python + +Performing the same tasks with the @uref{https://github.com/Selfnet/hkp4py, hkp4py module} (available via PyPI) is not too much different, but does provide a number of options of benefit to end users. Not least of which being the ability to perform some checks on a key before importing it or not. For instance it may @@ -905,69 +1029,278 @@ The key IDs for all considered keys were: @end example Since the hkp4py module handles multiple keys just as effectively as -one (@samp{keys} is a list of responses per matching key), thie above -example is able to do a little bit more with the returned data. +one (@samp{keys} is a list of responses per matching key), the example +above is able to do a little bit more with the returned data before +anything is actually imported. + +@node Importing from ProtonMail with HKP for Python +@subsection Importing from ProtonMail with HKP for Python + +Though this can provide certain benefits even when working with +ProtonMail, the scope is somewhat constrained there due to the +limitations of the ProtonMail keyserver. + +For instance, searching the SKS keyserver pool for the term "gnupg" +produces hundreds of results from any time the word appears in any +part of a user ID. Performing the same search on the ProtonMail +keyserver returns zero results, even though there are at least two +test accounts which include it as part of the username. + +The cause of this discrepancy is the deliberate configuration of that +server by ProtonMail to require an exact match of the full email +address of the ProtonMail user whose key is being requested. +Presumably this is intended to reduce breaches of privacy of their +users as an email address must already be known before a key for that +address can be obtained. -Here is a variation on the first example above which checks the -constrained ProtonMail keyserver for ProtonMail public keys. +@enumerate +@item +Import from ProtonMail via HKP for Python Example no. 1 + + +The following script is avalable with the rest of the examples under +the somewhat less than original name, @samp{pmkey-import-hkp.py}. @example import gpg -import requests +import hkp4py +import os.path import sys print(""" This script searches the ProtonMail key server for the specified key and imports it. + +Usage: pmkey-import-hkp.py [search strings] """) c = gpg.Context(armor=True) -url = "https://api.protonmail.ch/pks/lookup" +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None -if len(sys.argv) >= 2: +if len(sys.argv) > 2: + keyterms = sys.argv[1:] +elif len(sys.argv) == 2: keyterm = sys.argv[1] + keyterms.append(keyterm) +else: + key_term = input("Enter the key ID, UID or search string: ") + keyterms = key_term.split() + +for keyterm in keyterms: + if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) + elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) + elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + else: + ksearch.append(keyterm) + +for k in ksearch: + print("Checking for key for: @{0@}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print("@{0@} for @{1@}".format(result.decode(), k)) + 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@} + +With UIDs wholely or partially matching the following string: + + @{1@} + + Number of keys revoked: @{2@} + Number of new signatures: @{3@} + Number of new subkeys: @{4@} + Number of new user IDs: @{5@} +Number of new secret keys: @{6@} + Number of unchanged keys: @{7@} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass +@end example + +@item +Import from ProtonMail via HKP for Python Example no. 2 + + +Like its counterpart above, this script can also be found with the +rest of the examples, by the name pmkey-import-hkp-alt.py. + +With this script a modicum of effort has been made to treat anything +passed as a @samp{homedir} which either does not exist or which is not a +directory, as also being a pssible user ID to check for. It's not +guaranteed to pick up on all such cases, but it should cover most of +them. + +@example +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. Optionally enables specifying a different GnuPG home directory. + +Usage: pmkey-import-hkp.py [homedir] [search string] + or: pmkey-import-hkp.py [search string] +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] +ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None + +if len(sys.argv) > 3: + homedir = sys.argv[1] + keyterms = sys.argv[2:] +elif len(sys.argv) == 3: + homedir = sys.argv[1] + keyterm = sys.argv[2] + keyterms.append(keyterm) +elif len(sys.argv) == 2: + homedir = "" + keyterm = sys.argv[1] + keyterms.append(keyterm) else: keyterm = input("Enter the key ID, UID or search string: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + keyterms.append(keyterm) -if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: - ksearch.append(keyterm[1:]) - ksearch.append(keyterm[1:]) - ksearch.append(keyterm[1:]) -elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: - ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) - ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) - ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) -elif keyterm.count("@@") == 0: - ksearch.append("@{0@}@@protonmail.com".format(keyterm)) - ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) - ksearch.append("@{0@}@@pm.me".format(keyterm)) -elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: - uidlist = keyterm.split("@@") - for uid in uidlist: - ksearch.append("@{0@}@@protonmail.com".format(uid)) - ksearch.append("@{0@}@@protonmail.ch".format(uid)) - ksearch.append("@{0@}@@pm.me".format(uid)) -elif keyterm.count("@@") > 2: - uidlist = keyterm.split("@@") - for uid in uidlist: - ksearch.append("@{0@}@@protonmail.com".format(uid)) - ksearch.append("@{0@}@@protonmail.ch".format(uid)) - ksearch.append("@{0@}@@pm.me".format(uid)) +if len(homedir) == 0: + homedir = None + homeless = False + +if homedir is not None: + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + if os.path.isdir(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.realpath(os.path.expanduser(homedir)) + else: + homeless = True + else: + homeless = True + elif os.path.exists(os.path.realpath(homedir)) is True: + if os.path.isdir(os.path.realpath(homedir)) is True: + c.home_dir = os.path.realpath(homedir) + else: + homeless = True + else: + homeless = True + +# First check to see if the homedir really is a homedir and if not, treat it as +# a search string. +if homeless is True: + keyterms.append(homedir) + c.home_dir = None else: - ksearch.append(keyterm) + pass + +for keyterm in keyterms: + if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) + elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) + elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + else: + ksearch.append(keyterm) for k in ksearch: - payload = @{"op": "get", "search": k@} + print("Checking for key for: @{0@}".format(k)) try: - r = requests.get(url, verify=True, params=payload) - if r.ok is True: - result = c.key_import(r.content) - elif r.ok is False: - result = r.content + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None except Exception as e: - result = None + import_result = None + results.append(import_result) +for result in results: if result is not None and hasattr(result, "considered") is False: print("@{0@} for @{1@}".format(result.decode(), k)) elif result is not None and hasattr(result, "considered") is True: @@ -999,20 +1332,9 @@ The key IDs for all considered keys were: print(result.imports[i].fpr) print("") elif result is None: - print(e) + pass @end example - -Both the above example, @uref{../examples/howto/pmkey-import.py, pmkey-import.py}, and a version which prompts -for an alternative GnuPG home directory, @uref{../examples/howto/pmkey-import-alt.py, pmkey-import-alt.py}, are -available with the other examples and are executable scripts. - -Note that while the ProtonMail servers are based on the SKS servers, -their server is related more to their API and is not feature complete -by comparison to the servers in the SKS pool. One notable difference -being that the ProtonMail server does not permit non ProtonMail users -to update their own keys, which could be a vector for attacking -ProtonMail users who may not receive a key's revocation if it had been -compromised. +@end enumerate @node Exporting keys @section Exporting keys |