Skip to content

Commit

Permalink
Add auto-locate-key support.
Browse files Browse the repository at this point in the history
  • Loading branch information
vsajip committed Mar 13, 2024
1 parent 2854b37 commit 3df9074
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
76 changes: 76 additions & 0 deletions gnupg.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"""

import codecs
from datetime import date, datetime
from email.utils import parseaddr
from io import StringIO
import logging
import os
Expand Down Expand Up @@ -1000,6 +1002,55 @@ def handle_status(self, key, value):
logger.debug('message ignored: %s, %s', key, value)


class AutoLocateKey(StatusHandler):
"""
This class handles status messages during key auto-locating.
fingerprint: str
key_length: int
created_at: date
email: str
email_real_name: str
"""

def __init__(self, gpg):
StatusHandler.__init__(self, gpg)
self.fingerprint = None
self.type = None
self.created_at = None
self.email = None
self.email_real_name = None

def handle_status(self, key, value):
if key == "IMPORTED":
_, email, display_name = value.split()

self.email = email
self.email_real_name = display_name[1:-1]
elif key == "KEY_CONSIDERED":
self.fingerprint = value.strip().split()[0]

def pub(self, args):
"""
Internal method to handle the 'pub' status message.
`pub` message contains the fingerprint of the public key, its type and its creation date.
"""
pass

def uid(self, args):
self.created_at = datetime.fromtimestamp(int(args[5]))
raw_email_content = args[9]
email, real_name = parseaddr(raw_email_content)
self.email = email
self.email_real_name = real_name

def sub(self, args):
self.key_length = int(args[2])

def fpr(self, args):
# Only store the first fingerprint
self.fingerprint = self.fingerprint or args[9]


VERSION_RE = re.compile(r'^cfg:version:(\d+(\.\d+)*)'.encode('ascii'))
HEX_DIGITS_RE = re.compile(r'[0-9a-f]+$', re.I)
PUBLIC_KEY_RE = re.compile(r'gpg: public key is (\w+)')
Expand Down Expand Up @@ -1029,6 +1080,7 @@ class GPG(object):
'trust': TrustResult,
'verify': Verify,
'export': ExportResult,
'auto-locate-key': AutoLocateKey,
}
"A map of GPG operations to result object types."

Expand Down Expand Up @@ -1869,6 +1921,30 @@ def search_keys(self, query, keyserver='pgp.mit.edu', extra_args=None):
getattr(result, keyword)(L)
return result

def auto_locate_key(self, email, mechanisms=None, **kwargs):
"""
Auto locate a public key by `email`.
Args:
email (str): The email address to search for.
mechanisms (list[str]): A list of mechanisms to use. Valid mechanisms can be found
here https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
under "--auto-key-locate". Default: ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']
"""
mechanisms = mechanisms or ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']

args = ['--auto-key-locate', ','.join(mechanisms), '--locate-keys', email]

result = self.result_map['auto-locate-key'](self)

if 'extra_args' in kwargs:
args.extend(kwargs['extra_args'])

process = self._open_subprocess(args)
self._collect_output(process, result, stdin=process.stdin)
self._decode_result(result)
return result

def gen_key(self, input):
"""
Generate a key; you might use `gen_key_input()` to create the input.
Expand Down
16 changes: 14 additions & 2 deletions test_gnupg.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def test_key_generation_with_escapes(self):
self.assertEqual(uid, 'Test Name (Funny chars: '
'\r\n\x0c\x0b\x00\x08) <[email protected]>')

@unittest.skipIf(os.name == 'nt', 'Test requires POSIX-style permissions')
@skipIf(os.name == 'nt', 'Test requires POSIX-style permissions')
def test_key_generation_failure(self):
if self.gpg.version < (2, 0): # pragma: no cover
raise unittest.SkipTest('gpg 1.x hangs in this test')
Expand Down Expand Up @@ -1552,6 +1552,18 @@ def test_multiple_signatures_one_invalid(self):
finally:
os.remove(fn)

@skipIf('CI' not in os.environ, "Don't test locally")
def test_auto_key_locating(self):
gpg = self.gpg

# Let's hope ProtonMail doesn't change their key anytime soon
expected_fingerprint = "90E619A84E85330A692F6D81A655882018DBFA9D"
expected_type = "rsa2048"

actual = self.gpg.auto_locate_key("[email protected]")

self.assertEqual(actual.fingerprint, expected_fingerprint)


TEST_GROUPS = {
'sign':
Expand All @@ -1574,7 +1586,7 @@ def test_multiple_signatures_one_invalid(self):
'basic':
set(['test_environment', 'test_list_keys_initial', 'test_nogpg', 'test_make_args', 'test_quote_with_shell']),
'test':
set(['test_multiple_signatures_one_invalid']),
set(['test_auto_key_locating']),
}


Expand Down

0 comments on commit 3df9074

Please sign in to comment.