51
53
SIGNATURE_EXPIRED = 4
56
class GpgNotInstalled(errors.DependencyNotPresent):
58
_fmt = 'python-gpg is not installed, it is needed to verify signatures'
60
def __init__(self, error):
61
errors.DependencyNotPresent.__init__(self, 'gpg', error)
64
class SigningFailed(errors.BzrError):
66
_fmt = 'Failed to GPG sign data: "%(error)s"'
68
def __init__(self, error):
69
errors.BzrError.__init__(self, error=error)
72
class SignatureVerificationFailed(errors.BzrError):
74
_fmt = 'Failed to verify GPG signature data with error "%(error)s"'
76
def __init__(self, error):
77
errors.BzrError.__init__(self, error=error)
54
80
def bulk_verify_signatures(repository, revids, strategy,
55
81
process_events_callback=None):
56
82
"""Do verifications on a set of revisions
162
188
def __init__(self, config_stack):
163
189
self._config_stack = config_stack
166
self.context = gpgme.Context()
192
self.context = gpg.Context()
167
193
except ImportError as error:
168
194
pass # can't use verify()
196
self.context.signers = self._get_signing_keys()
198
def _get_signing_keys(self):
200
keyname = self._config_stack.get('gpg_signing_key')
203
return [self.context.get_key(keyname)]
204
except gpg.errors.KeyNotFound:
207
if keyname is None or keyname == 'default':
208
# 'default' or not setting gpg_signing_key at all means we should
209
# use the user email address
210
keyname = config.extract_email_address(self._config_stack.get('email'))
211
possible_keys = self.context.keylist(keyname, secret=True)
213
return [possible_keys.next()]
214
except StopIteration:
171
218
def verify_signatures_available():
175
222
:return: boolean if this strategy can verify signatures
180
227
except ImportError as error:
183
def _command_line(self):
184
key = self._config_stack.get('gpg_signing_key')
185
if key is None or key == 'default':
186
# 'default' or not setting gpg_signing_key at all means we should
187
# use the user email address
188
key = config.extract_email_address(self._config_stack.get('email'))
189
return [self._config_stack.get('gpg_signing_command'), '--clearsign',
192
230
def sign(self, content):
193
232
if isinstance(content, unicode):
194
233
raise errors.BzrBadParameterUnicode('content')
195
ui.ui_factory.clear_term()
197
preexec_fn = _set_gpg_tty
198
if sys.platform == 'win32':
199
# Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
235
plain_text = gpg.Data(content)
202
process = subprocess.Popen(self._command_line(),
203
stdin=subprocess.PIPE,
204
stdout=subprocess.PIPE,
205
preexec_fn=preexec_fn)
207
result = process.communicate(content)[0]
208
if process.returncode is None:
210
if process.returncode != 0:
211
raise errors.SigningFailed(self._command_line())
214
if e.errno == errno.EPIPE:
215
raise errors.SigningFailed(self._command_line())
219
# bad subprocess parameters, should never happen.
222
if e.errno == errno.ENOENT:
223
# gpg is not installed
224
raise errors.SigningFailed(self._command_line())
237
output, result = self.context.sign(
238
plain_text, mode=gpg.constants.sig.mode.CLEAR)
239
except gpg.errors.GPGMEError as error:
240
raise SigningFailed(str(error))
228
244
def verify(self, content, testament):
229
245
"""Check content has a valid signature.
234
250
:return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
238
254
except ImportError as error:
239
raise errors.GpgmeNotInstalled(error)
255
raise errors.GpgNotInstalled(error)
241
signature = BytesIO(content)
242
plain_output = BytesIO()
257
signature = gpg.Data(content)
244
result = self.context.verify(signature, None, plain_output)
245
except gpgme.GpgmeError as error:
246
raise errors.SignatureVerificationFailed(error[2])
260
plain_output, result = self.context.verify(signature)
261
except gpg.errors.BadSignatures as error:
262
fingerprint = error.result.signatures[0].fpr
263
if error.result.signatures[0].summary & gpg.constants.SIGSUM_KEY_EXPIRED:
264
expires = self.context.get_key(error.result.signatures[0].fpr).subkeys[0].expires
265
if expires > error.result.signatures[0].timestamp:
266
# The expired key was not expired at time of signing.
267
# test_verify_expired_but_valid()
268
return SIGNATURE_EXPIRED, fingerprint[-8:]
270
# I can't work out how to create a test where the signature
271
# was expired at the time of signing.
272
return SIGNATURE_NOT_VALID, None
274
# GPG does not know this key.
275
# test_verify_unknown_key()
276
if error.result.signatures[0].summary & gpg.constants.SIGSUM_KEY_MISSING:
277
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
279
return SIGNATURE_NOT_VALID, None
280
except gpg.errors.GPGMEError as error:
281
raise SignatureVerificationFailed(error[2])
248
283
# No result if input is invalid.
249
284
# test_verify_invalid()
285
if len(result.signatures) == 0:
251
286
return SIGNATURE_NOT_VALID, None
252
287
# User has specified a list of acceptable keys, check our result is in
253
288
# it. test_verify_unacceptable_key()
254
fingerprint = result[0].fpr
289
fingerprint = result.signatures[0].fpr
255
290
if self.acceptable_keys is not None:
256
291
if not fingerprint in self.acceptable_keys:
257
292
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
258
293
# Check the signature actually matches the testament.
259
294
# test_verify_bad_testament()
260
if testament != plain_output.getvalue():
295
if testament != plain_output:
261
296
return SIGNATURE_NOT_VALID, None
262
# Yay gpgme set the valid bit.
297
# Yay gpg set the valid bit.
263
298
# Can't write a test for this one as you can't set a key to be
264
# trusted using gpgme.
265
if result[0].summary & gpgme.SIGSUM_VALID:
300
if result.signatures[0].summary & gpg.constants.SIGSUM_VALID:
266
301
key = self.context.get_key(fingerprint)
267
302
name = key.uids[0].name
268
303
email = key.uids[0].email
269
304
return SIGNATURE_VALID, name + " <" + email + ">"
270
305
# Sigsum_red indicates a problem, unfortunatly I have not been able
271
306
# to write any tests which actually set this.
272
if result[0].summary & gpgme.SIGSUM_RED:
307
if result.signatures[0].summary & gpg.constants.SIGSUM_RED:
273
308
return SIGNATURE_NOT_VALID, None
274
# GPG does not know this key.
275
# test_verify_unknown_key()
276
if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
277
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
278
309
# Summary isn't set if sig is valid but key is untrusted but if user
279
310
# has explicity set the key as acceptable we can validate it.
280
if result[0].summary == 0 and self.acceptable_keys is not None:
311
if result.signatures[0].summary == 0 and self.acceptable_keys is not None:
281
312
if fingerprint in self.acceptable_keys:
282
313
# test_verify_untrusted_but_accepted()
283
314
return SIGNATURE_VALID, None
284
315
# test_verify_valid_but_untrusted()
285
if result[0].summary == 0 and self.acceptable_keys is None:
286
return SIGNATURE_NOT_VALID, None
287
if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
288
expires = self.context.get_key(result[0].fpr).subkeys[0].expires
289
if expires > result[0].timestamp:
290
# The expired key was not expired at time of signing.
291
# test_verify_expired_but_valid()
292
return SIGNATURE_EXPIRED, fingerprint[-8:]
294
# I can't work out how to create a test where the signature
295
# was expired at the time of signing.
296
return SIGNATURE_NOT_VALID, None
297
# A signature from a revoked key gets this.
298
# test_verify_revoked_signature()
299
if ((result[0].summary & gpgme.SIGSUM_SYS_ERROR
300
or result[0].status.strerror == 'Certificate revoked')):
316
if result.signatures[0].summary == 0 and self.acceptable_keys is None:
301
317
return SIGNATURE_NOT_VALID, None
302
318
# Other error types such as revoked keys should (I think) be caught by
303
319
# SIGSUM_RED so anything else means something is buggy.
304
raise errors.SignatureVerificationFailed(
320
raise SignatureVerificationFailed(
305
321
"Unknown GnuPG key verification result")
307
323
def set_acceptable_keys(self, command_line_input):