or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

encryption-decryption.mdgpg-instance.mdindex.mdkey-discovery.mdkey-management.mdkeyserver-operations.mdsigning-verification.md

keyserver-operations.mddocs/

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

```