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/gpgme-python-howto.texi | |
| 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 '')
| -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 | 
