python bindings: importing from keyservers with hkp4py

* added a new example script to search the keyservers and import the
  results, this time using Marcel Fest's hkp4py module.
* Updated the key importing section to match this addition.
* Tested with the current version of hkp4py from github.

Tested-by: Ben McGinnes <ben@adversary.org>
Signed-off-by: Ben McGinnes <ben@adversary.org>
This commit is contained in:
Ben McGinnes 2018-09-23 08:18:44 +10:00
parent 3622576105
commit 6ed9a77c92
3 changed files with 263 additions and 6 deletions

View File

@ -38,6 +38,7 @@ Introduction
* Python 2 versus Python 3:: * Python 2 versus Python 3::
* Examples:: * Examples::
* Unofficial Drafts::
GPGME Concepts GPGME Concepts
@ -167,6 +168,7 @@ Python bindings to programmatically leverage the GPGME library.
@menu @menu
* Python 2 versus Python 3:: * Python 2 versus Python 3::
* Examples:: * Examples::
* Unofficial Drafts::
@end menu @end menu
@node Python 2 versus Python 3 @node Python 2 versus Python 3
@ -198,6 +200,14 @@ types with which GPGME deals considerably easier.
All of the examples found in this document can be found as Python 3 All of the examples found in this document can be found as Python 3
scripts in the @samp{lang/python/examples/howto} directory. scripts in the @samp{lang/python/examples/howto} directory.
@node Unofficial Drafts
@section Unofficial Drafts
In addition to shipping with each release of GPGME, there is a section
on locations to read or download @ref{Draft Editions of this HOWTO, , draft editions} of this document from
at the end of it. These are unofficial versions produced in between
major releases.
@node GPGME Concepts @node GPGME Concepts
@chapter GPGME Concepts @chapter GPGME Concepts
@ -780,7 +790,7 @@ import requests
c = gpg.Context() c = gpg.Context()
url = "https://sks-keyservers.net/pks/lookup" url = "https://sks-keyservers.net/pks/lookup"
pattern = input("Enter the pattern to search for key or user IDs: ") pattern = input("Enter the pattern to search for key or user IDs: ")
payload = @{ "op": "get", "search": pattern @} payload = @{"op": "get", "search": pattern@}
r = requests.get(url, verify=True, params=payload) r = requests.get(url, verify=True, params=payload)
result = c.key_import(r.content) result = c.key_import(r.content)
@ -822,8 +832,77 @@ relative ease by which such key IDs can be reproduced, as demonstrated
by the Evil32 Project in 2014 (which was subsequently exploited in by the Evil32 Project in 2014 (which was subsequently exploited in
2016). 2016).
Here is a variation on the above which checks the constrained Performing the same task with the @uref{https://github.com/Selfnet/hkp4py, hkp4py module} (available via PyPI)
ProtonMail keyserver for ProtonMail public keys. 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
be the policy of a site or project to only import keys which have not
been revoked. The hkp4py module permits such checks prior to the
importing of the keys found.
@example
import gpg
import hkp4py
c = gpg.Context()
server = hkp4py.KeyServer("https://hkps.pool.sks-keyservers.net")
pattern = input("Enter the pattern to search for keys or user IDs: ")
results = []
try:
keys = server.search(pattern)
print("Found @{0@} key(s).".format(len(keys)))
except Exception as e:
keys = []
for logrus in pattern.split():
if logrus.startswith("0x") is True:
key = server.search(logrus)
else:
key = server.search("0x@{0@}".format(logrus))
keys.append(key[0])
print("Found @{0@} key(s).".format(len(keys)))
for key in keys:
import_result = c.key_import(key.key_blob)
results.append(import_result)
for result in results:
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
@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.
Here is a variation on the first example above which checks the
constrained ProtonMail keyserver for ProtonMail public keys.
@example @example
import gpg import gpg

View File

@ -61,6 +61,17 @@ All of the examples found in this document can be found as Python 3
scripts in the =lang/python/examples/howto= directory. scripts in the =lang/python/examples/howto= directory.
** Unofficial Drafts
:PROPERTIES:
:CUSTOM_ID: unofficial-drafts
:END:
In addition to shipping with each release of GPGME, there is a section
on locations to read or download [[#draft-editions][draft editions]] of this document from
at the end of it. These are unofficial versions produced in between
major releases.
* GPGME Concepts * GPGME Concepts
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: gpgme-concepts :CUSTOM_ID: gpgme-concepts
@ -654,7 +665,7 @@ import requests
c = gpg.Context() c = gpg.Context()
url = "https://sks-keyservers.net/pks/lookup" url = "https://sks-keyservers.net/pks/lookup"
pattern = input("Enter the pattern to search for key or user IDs: ") pattern = input("Enter the pattern to search for key or user IDs: ")
payload = { "op": "get", "search": pattern } payload = {"op": "get", "search": pattern}
r = requests.get(url, verify=True, params=payload) r = requests.get(url, verify=True, params=payload)
result = c.key_import(r.content) result = c.key_import(r.content)
@ -696,8 +707,84 @@ relative ease by which such key IDs can be reproduced, as demonstrated
by the Evil32 Project in 2014 (which was subsequently exploited in by the Evil32 Project in 2014 (which was subsequently exploited in
2016). 2016).
Here is a variation on the above which checks the constrained Performing the same task with the [[https://github.com/Selfnet/hkp4py][hkp4py module]] (available via PyPI)
ProtonMail keyserver for ProtonMail public keys. 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
be the policy of a site or project to only import keys which have not
been revoked. The hkp4py module permits such checks prior to the
importing of the keys found.
#+BEGIN_SRC python -i
import gpg
import hkp4py
import sys
c = gpg.Context()
server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
results = []
if len(sys.argv) > 2:
pattern = " ".join(sys.argv[1:])
elif len(sys.argv) == 2:
pattern = sys.argv[1]
else:
pattern = input("Enter the pattern to search for keys or user IDs: ")
try:
keys = server.search(pattern)
print("Found {0} key(s).".format(len(keys)))
except Exception as e:
keys = []
for logrus in pattern.split():
if logrus.startswith("0x") is True:
key = server.search(logrus)
else:
key = server.search("0x{0}".format(logrus))
keys.append(key[0])
print("Found {0} key(s).".format(len(keys)))
for key in keys:
import_result = c.key_import(key.key_blob)
results.append(import_result)
for result in results:
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
#+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.
Here is a variation on the first example above which checks the
constrained ProtonMail keyserver for ProtonMail public keys.
#+BEGIN_SRC python -i #+BEGIN_SRC python -i
import gpg import gpg

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import gpg
import hkp4py
import sys
# Copyright (C) 2018 Ben McGinnes <ben@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 imports one or more public keys from the SKS keyservers.
""")
c = gpg.Context()
server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
results = []
if len(sys.argv) > 2:
pattern = " ".join(sys.argv[1:])
elif len(sys.argv) == 2:
pattern = sys.argv[1]
else:
pattern = input("Enter the pattern to search for keys or user IDs: ")
try:
keys = server.search(pattern)
print("Found {0} key(s).".format(len(keys)))
except Exception as e:
keys = []
for logrus in pattern.split():
if logrus.startswith("0x") is True:
key = server.search(logrus)
else:
key = server.search("0x{0}".format(logrus))
keys.append(key[0])
print("Found {0} key(s).".format(len(keys)))
for key in keys:
import_result = c.key_import(key.key_blob)
results.append(import_result)
for result in results:
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