0
# Keyserver Operations
1
2
Keyserver interactions for publishing, retrieving, and searching public keys across distributed keyserver networks for key distribution and discovery.
3
4
## Capabilities
5
6
### Key Retrieval
7
8
Retrieve public keys from keyservers using key IDs, fingerprints, or email addresses with support for multiple keyserver protocols.
9
10
```python { .api }
11
def recv_keys(self, keyserver, *keyids, **kwargs):
12
"""
13
Retrieve one or more keys from a keyserver.
14
15
Parameters:
16
- keyserver (str): Keyserver URL or hostname
17
- *keyids: Variable number of key IDs, fingerprints, or email addresses
18
- extra_args (list): Additional GPG arguments
19
- passphrase (str): Passphrase if needed for key import
20
21
Returns:
22
ImportResult: Result with imported key information and statistics
23
"""
24
25
def auto_locate_key(self, email, mechanisms=None, **kwargs):
26
"""
27
Automatically locate a public key by email address using various mechanisms.
28
29
Parameters:
30
- email (str): Email address to locate key for
31
- mechanisms (list): Location mechanisms ('keyserver', 'pka', 'cert', 'dane', 'wkd', 'ldap')
32
- extra_args (list): Additional GPG arguments
33
34
Returns:
35
AutoLocateKey: Result with located key information
36
"""
37
```
38
39
### Key Publishing
40
41
Publish public keys to keyservers to make them available for others to discover and retrieve.
42
43
```python { .api }
44
def send_keys(self, keyserver, *keyids, **kwargs):
45
"""
46
Send one or more keys to a keyserver for public distribution.
47
48
Parameters:
49
- keyserver (str): Keyserver URL or hostname to send keys to
50
- *keyids: Variable number of key IDs, fingerprints, or email addresses to send
51
- extra_args (list): Additional GPG arguments
52
53
Returns:
54
SendResult: Result with send operation status
55
"""
56
```
57
58
### Key Search
59
60
Search keyservers for keys matching specific criteria such as names, email addresses, or key IDs.
61
62
```python { .api }
63
def search_keys(self, query, keyserver='pgp.mit.edu', extra_args=None):
64
"""
65
Search a keyserver for keys matching the query.
66
67
Parameters:
68
- query (str): Search query (name, email, or key ID)
69
- keyserver (str): Keyserver to search (default: 'pgp.mit.edu')
70
- extra_args (list): Additional GPG arguments
71
72
Returns:
73
SearchKeys: List of matching keys with metadata
74
"""
75
```
76
77
## Result Types
78
79
```python { .api }
80
class ImportResult(StatusHandler):
81
count: int # Total keys processed
82
imported: int # Keys successfully imported
83
not_imported: int # Keys that failed to import
84
unchanged: int # Keys already in keyring unchanged
85
new_user_ids: int # New user IDs added to existing keys
86
new_signatures: int # New signatures added
87
new_subkeys: int # New subkeys added
88
secret_imported: int # Secret keys imported
89
secret_unchanged: int # Secret keys unchanged
90
fingerprints: list # List of processed key fingerprints
91
92
def summary(self):
93
"""Return human-readable import summary."""
94
95
class SendResult(StatusHandler):
96
# Status information for key sending operations
97
pass
98
99
class AutoLocateKey(StatusHandler):
100
email: str # Email address that was searched
101
display_name: str # Display name from located key
102
created_at: datetime # Key creation timestamp
103
key_length: int # Key length in bits
104
fingerprint: str # Key fingerprint
105
106
class SearchKeys(list):
107
# List of key dictionaries containing:
108
# - keyid: Key ID
109
# - algo: Algorithm number
110
# - length: Key length in bits
111
# - date: Creation date
112
# - expires: Expiration date (if any)
113
# - uids: List of user IDs
114
# - revoked: Whether key is revoked
115
# - disabled: Whether key is disabled
116
# - invalid: Whether key is invalid
117
pass
118
```
119
120
## Usage Examples
121
122
### Key Retrieval from Keyservers
123
124
```python
125
import gnupg
126
127
gpg = gnupg.GPG()
128
129
# Retrieve key by email address
130
result = gpg.recv_keys('keyserver.ubuntu.com', 'alice@example.com')
131
132
if result.imported > 0:
133
print(f"Successfully imported {result.imported} keys")
134
print(f"Key fingerprints: {result.fingerprints}")
135
else:
136
print(f"No keys imported: {result.status}")
137
138
# Retrieve multiple keys by ID
139
key_ids = ['DEADBEEF', 'CAFEBABE', '12345678']
140
result = gpg.recv_keys('pgp.mit.edu', *key_ids)
141
142
print(f"Processed {result.count} keys")
143
print(f"Imported: {result.imported}")
144
print(f"Already had: {result.unchanged}")
145
```
146
147
### Different Keyserver URLs
148
149
```python
150
# Various keyserver formats
151
keyservers = [
152
'keyserver.ubuntu.com',
153
'pgp.mit.edu',
154
'hkp://pool.sks-keyservers.net',
155
'hkps://hkps.pool.sks-keyservers.net',
156
'ldap://keyserver.pgp.com'
157
]
158
159
# Try multiple keyservers for reliability
160
def retrieve_key_with_fallback(gpg_instance, key_identifier, keyservers):
161
for keyserver in keyservers:
162
try:
163
result = gpg_instance.recv_keys(keyserver, key_identifier)
164
if result.imported > 0:
165
print(f"Retrieved key from {keyserver}")
166
return result
167
except Exception as e:
168
print(f"Failed to retrieve from {keyserver}: {e}")
169
continue
170
171
print("Failed to retrieve key from all keyservers")
172
return None
173
174
# Usage
175
result = retrieve_key_with_fallback(gpg, 'user@example.com', keyservers)
176
```
177
178
### Auto-locate Keys
179
180
```python
181
# Auto-locate key using multiple mechanisms
182
result = gpg.auto_locate_key(
183
'colleague@company.com',
184
mechanisms=['wkd', 'keyserver', 'pka'] # Web Key Directory, keyserver, Public Key Association
185
)
186
187
if result.fingerprint:
188
print(f"Found key for {result.email}")
189
print(f"Display name: {result.display_name}")
190
print(f"Fingerprint: {result.fingerprint}")
191
print(f"Key length: {result.key_length} bits")
192
print(f"Created: {result.created_at}")
193
else:
194
print(f"Could not locate key for {result.email}")
195
196
# Auto-locate with specific mechanisms
197
wkd_result = gpg.auto_locate_key('user@example.org', mechanisms=['wkd'])
198
keyserver_result = gpg.auto_locate_key('user@example.org', mechanisms=['keyserver'])
199
```
200
201
### Key Publishing
202
203
```python
204
# Send public key to keyserver
205
result = gpg.send_keys('keyserver.ubuntu.com', 'alice@example.com')
206
207
if result.status == 'key sent':
208
print("Key successfully published to keyserver")
209
else:
210
print(f"Failed to send key: {result.status}")
211
212
# Send multiple keys
213
my_keys = ['alice@example.com', 'work@alice.org']
214
result = gpg.send_keys('pgp.mit.edu', *my_keys)
215
216
# Send by fingerprint
217
result = gpg.send_keys('hkp://pool.sks-keyservers.net', 'DEADBEEFCAFEBABE12345678')
218
```
219
220
### Key Search Operations
221
222
```python
223
# Search by name
224
results = gpg.search_keys('John Smith', keyserver='keyserver.ubuntu.com')
225
226
print(f"Found {len(results)} matching keys:")
227
for key in results:
228
print(f"Key ID: {key['keyid']}")
229
print(f"Algorithm: {key['algo']}")
230
print(f"Length: {key['length']} bits")
231
print(f"Created: {key['date']}")
232
print(f"User IDs: {', '.join(key['uids'])}")
233
if key['expires']:
234
print(f"Expires: {key['expires']}")
235
if key['revoked']:
236
print("WARNING: Key is revoked")
237
print("---")
238
239
# Search by email
240
email_results = gpg.search_keys('alice@example.com')
241
242
# Search by partial key ID
243
keyid_results = gpg.search_keys('DEADBEEF')
244
```
245
246
### Advanced Search and Selection
247
248
```python
249
def find_best_key(gpg_instance, search_query, keyserver='keyserver.ubuntu.com'):
250
"""Find the best key from search results based on various criteria."""
251
252
results = gpg_instance.search_keys(search_query, keyserver=keyserver)
253
254
if not results:
255
return None
256
257
# Filter and rank keys
258
valid_keys = []
259
260
for key in results:
261
# Skip revoked, disabled, or invalid keys
262
if key.get('revoked') or key.get('disabled') or key.get('invalid'):
263
continue
264
265
# Skip expired keys
266
if key.get('expires'):
267
from datetime import datetime
268
try:
269
expires = datetime.strptime(key['expires'], '%Y-%m-%d')
270
if expires < datetime.now():
271
continue
272
except:
273
pass # Skip date parsing errors
274
275
# Prefer longer keys
276
key_score = key.get('length', 0)
277
278
# Prefer more recent keys
279
try:
280
created = datetime.strptime(key['date'], '%Y-%m-%d')
281
days_old = (datetime.now() - created).days
282
key_score += max(0, 3650 - days_old) # Bonus for newer keys (up to 10 years)
283
except:
284
pass
285
286
valid_keys.append((key_score, key))
287
288
if not valid_keys:
289
return None
290
291
# Return highest scoring key
292
valid_keys.sort(key=lambda x: x[0], reverse=True)
293
return valid_keys[0][1]
294
295
# Usage
296
best_key = find_best_key(gpg, 'alice@example.com')
297
if best_key:
298
print(f"Best key: {best_key['keyid']}")
299
print(f"Length: {best_key['length']} bits")
300
print(f"User IDs: {', '.join(best_key['uids'])}")
301
302
# Import the selected key
303
import_result = gpg.recv_keys('keyserver.ubuntu.com', best_key['keyid'])
304
if import_result.imported > 0:
305
print("Key imported successfully")
306
```
307
308
### Keyserver Management and Configuration
309
310
```python
311
# Configure default keyserver options
312
def setup_keyserver_options(gpg_instance):
313
"""Configure GPG instance with keyserver preferences."""
314
315
# Set keyserver options for reliability
316
keyserver_options = [
317
'--keyserver-options', 'timeout=30', # 30 second timeout
318
'--keyserver-options', 'import-clean', # Clean imported keys
319
'--keyserver-options', 'export-clean', # Clean exported keys
320
'--keyserver-options', 'auto-key-retrieve', # Auto-retrieve missing keys
321
]
322
323
if gpg_instance.options:
324
gpg_instance.options.extend(keyserver_options)
325
else:
326
gpg_instance.options = keyserver_options
327
328
# Create GPG instance with keyserver configuration
329
gpg = gnupg.GPG(options=[
330
'--keyserver', 'hkps://keys.openpgp.org',
331
'--keyserver-options', 'timeout=30'
332
])
333
334
# Test keyserver connectivity
335
def test_keyserver_connectivity(gpg_instance, keyserver):
336
"""Test if a keyserver is accessible."""
337
338
try:
339
# Try to search for a common key (GPG itself)
340
results = gpg_instance.search_keys('gnupg', keyserver=keyserver)
341
return len(results) > 0
342
except Exception as e:
343
print(f"Keyserver {keyserver} not accessible: {e}")
344
return False
345
346
# Test multiple keyservers
347
keyservers_to_test = [
348
'keys.openpgp.org',
349
'keyserver.ubuntu.com',
350
'pgp.mit.edu'
351
]
352
353
working_keyservers = []
354
for ks in keyservers_to_test:
355
if test_keyserver_connectivity(gpg, ks):
356
working_keyservers.append(ks)
357
print(f"✓ {ks} is accessible")
358
else:
359
print(f"✗ {ks} is not accessible")
360
361
print(f"Working keyservers: {working_keyservers}")
362
```
363
364
### Error Handling and Reliability
365
366
```python
367
def reliable_key_operation(operation_func, *args, max_retries=3, **kwargs):
368
"""Wrapper for reliable keyserver operations with retry logic."""
369
370
for attempt in range(max_retries):
371
try:
372
result = operation_func(*args, **kwargs)
373
374
# Check if operation was successful
375
if hasattr(result, 'imported') and result.imported > 0:
376
return result
377
elif hasattr(result, 'status') and 'success' in result.status.lower():
378
return result
379
elif not hasattr(result, 'imported'): # Search operations
380
return result
381
382
print(f"Attempt {attempt + 1} had no results, retrying...")
383
384
except Exception as e:
385
print(f"Attempt {attempt + 1} failed: {e}")
386
if attempt == max_retries - 1:
387
raise
388
389
# Wait before retry (exponential backoff)
390
import time
391
time.sleep(2 ** attempt)
392
393
return None
394
395
# Usage with retry logic
396
result = reliable_key_operation(
397
gpg.recv_keys,
398
'keyserver.ubuntu.com',
399
'user@example.com',
400
max_retries=3
401
)
402
403
if result and result.imported > 0:
404
print("Key retrieved successfully with retry logic")
405
```