[git] GPGME - branch, master, updated. gpgme-1.11.1-302-gb12b2cc

by Ben McGinnes cvs at cvs.gnupg.org
Sun Sep 23 11:39:04 CEST 2018


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GnuPG Made Easy".

The branch, master has been updated
       via  b12b2cc99621fe32a2d698ce7f091f3225f35bd0 (commit)
      from  ced4bdbbb239c1fe209665b4f5a7aeb9651889ed (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit b12b2cc99621fe32a2d698ce7f091f3225f35bd0
Author: Ben McGinnes <ben at adversary.org>
Date:   Sun Sep 23 19:36:54 2018 +1000

    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 <ben at adversary.org>
    Signed-off-by: Ben McGinnes <ben at adversary.org>

diff --git a/doc/gpgme-python-howto.texi b/doc/gpgme-python-howto.texi
index 7b8b79d..2e30597 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)
+ at menu
+* Working with ProtonMail::
+* Importing with HKP for Python::
+* Importing from ProtonMail with HKP for Python::
+ at end menu
+
+ at node Working with ProtonMail
+ at subsection Working with ProtonMail
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+ at 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)
+ at 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.
+
+ at node Importing with HKP for Python
+ at 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.
+
+ at node Importing from ProtonMail with HKP for Python
+ at 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.
+ at enumerate
+ at 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
+ at end example
+
+ at 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.
+
+ at 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.
+ at end enumerate
 
 @node Exporting keys
 @section Exporting keys
diff --git a/lang/python/docs/gpgme-python-howto.org b/lang/python/docs/gpgme-python-howto.org
index b5b9ed4..2f6ce73 100644
--- a/lang/python/docs/gpgme-python-howto.org
+++ b/lang/python/docs/gpgme-python-howto.org
@@ -707,7 +707,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 [[https://github.com/Selfnet/hkp4py][hkp4py module]] (available via PyPI)
+
+*** Working with ProtonMail
+    :PROPERTIES:
+    :CUSTOM_ID: import-protonmail
+    :END:
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+#+BEGIN_SRC python -i
+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_SRC
+
+Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts
+for an alternative GnuPG home directory, [[../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.
+
+
+*** Importing with HKP for Python
+    :PROPERTIES:
+    :CUSTOM_ID: import-hkp4py
+    :END:
+
+Performing the same tasks with the [[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
@@ -780,69 +898,284 @@ The key IDs for all considered keys were:
 #+END_SRC
 
 Since the hkp4py module handles multiple keys just as effectively as
-one (=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 (=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.
+
+
+*** Importing from ProtonMail with HKP for Python
+    :PROPERTIES:
+    :CUSTOM_ID: import-protonmail-hkp4py
+    :END:
+
+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.
+
+
+**** Import from ProtonMail via HKP for Python Example no. 1
+     :PROPERTIES:
+     :CUSTOM_ID: import-hkp4py-pm1
+     :END:
 
-Here is a variation on the first example above which checks the
-constrained ProtonMail keyserver for ProtonMail public keys.
+The following script is avalable with the rest of the examples under
+the somewhat less than original name, =pmkey-import-hkp.py=.
 
 #+BEGIN_SRC python -i
 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_SRC
+
+
+**** Import from ProtonMail via HKP for Python Example no. 2
+     :PROPERTIES:
+     :CUSTOM_ID: import-hkp4py-pm2
+     :END:
+
+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 =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.
+
+#+BEGIN_SRC python -i
+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:
@@ -874,21 +1207,9 @@ The key IDs for all considered keys were:
             print(result.imports[i].fpr)
         print("")
     elif result is None:
-        print(e)
+        pass
 #+END_SRC
 
-Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts
-for an alternative GnuPG home directory, [[../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.
-
 
 ** Exporting keys
    :PROPERTIES:
diff --git a/lang/python/examples/howto/pmkey-import-hkp-alt.py b/lang/python/examples/howto/pmkey-import-hkp-alt.py
new file mode 100755
index 0000000..61fcd8d
--- /dev/null
+++ b/lang/python/examples/howto/pmkey-import-hkp-alt.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben at gnupg.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+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 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:
+    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:
+    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
diff --git a/lang/python/examples/howto/pmkey-import-hkp.py b/lang/python/examples/howto/pmkey-import-hkp.py
new file mode 100755
index 0000000..66223a9
--- /dev/null
+++ b/lang/python/examples/howto/pmkey-import-hkp.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben at gnupg.org>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+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)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+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

-----------------------------------------------------------------------

Summary of changes:
 doc/gpgme-python-howto.texi                        | 428 ++++++++++++++++++---
 lang/python/docs/gpgme-python-howto.org            | 427 +++++++++++++++++---
 lang/python/examples/howto/pmkey-import-hkp-alt.py | 174 +++++++++
 .../{pmkey-import-alt.py => pmkey-import-hkp.py}   | 111 +++---
 4 files changed, 980 insertions(+), 160 deletions(-)
 create mode 100755 lang/python/examples/howto/pmkey-import-hkp-alt.py
 copy lang/python/examples/howto/{pmkey-import-alt.py => pmkey-import-hkp.py} (54%)


hooks/post-receive
-- 
GnuPG Made Easy
http://git.gnupg.org




More information about the Gnupg-commits mailing list