-################################################################################
-@session_wrapper
-def check_signature (sig_filename, data_filename="", keyrings=None, autofetch=None, session=None):
- """
- Check the signature of a file and return the fingerprint if the
- signature is valid or 'None' if it's not. The first argument is the
- filename whose signature should be checked. The second argument is a
- reject function and is called when an error is found. The reject()
- function must allow for two arguments: the first is the error message,
- the second is an optional prefix string. It's possible for reject()
- to be called more than once during an invocation of check_signature().
- The third argument is optional and is the name of the files the
- detached signature applies to. The fourth argument is optional and is
- a *list* of keyrings to use. 'autofetch' can either be None, True or
- False. If None, the default behaviour specified in the config will be
- used.
- """
-
- rejects = []
-
- # Ensure the filename contains no shell meta-characters or other badness
- if not re_taint_free.match(sig_filename):
- rejects.append("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
- return (None, rejects)
-
- if data_filename and not re_taint_free.match(data_filename):
- rejects.append("!!WARNING!! tainted data filename: '%s'." % (data_filename))
- return (None, rejects)
-
- if not keyrings:
- keyrings = [ x.keyring_name for x in session.query(Keyring).filter(Keyring.active == True).all() ]
-
- # Autofetch the signing key if that's enabled
- if autofetch == None:
- autofetch = Cnf.get("Dinstall::KeyAutoFetch")
- if autofetch:
- error_msg = retrieve_key(sig_filename)
- if error_msg:
- rejects.append(error_msg)
- return (None, rejects)
-
- # Build the command line
- status_read, status_write = os.pipe()
- cmd = "gpgv --status-fd %s %s %s %s" % (
- status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
-
- # Invoke gpgv on the file
- (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
-
- # Process the status-fd output
- (keywords, internal_error) = process_gpgv_output(status)
-
- # If we failed to parse the status-fd output, let's just whine and bail now
- if internal_error:
- rejects.append("internal error while performing signature check on %s." % (sig_filename))
- rejects.append(internal_error, "")
- rejects.append("Please report the above errors to the Archive maintainers by replying to this mail.", "")
- return (None, rejects)
-
- # Now check for obviously bad things in the processed output
- if keywords.has_key("KEYREVOKED"):
- rejects.append("The key used to sign %s has been revoked." % (sig_filename))
- if keywords.has_key("BADSIG"):
- rejects.append("bad signature on %s." % (sig_filename))
- if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
- rejects.append("failed to check signature on %s." % (sig_filename))
- if keywords.has_key("NO_PUBKEY"):
- args = keywords["NO_PUBKEY"]
- if len(args) >= 1:
- key = args[0]
- rejects.append("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
- if keywords.has_key("BADARMOR"):
- rejects.append("ASCII armour of signature was corrupt in %s." % (sig_filename))
- if keywords.has_key("NODATA"):
- rejects.append("no signature found in %s." % (sig_filename))
- if keywords.has_key("EXPKEYSIG"):
- args = keywords["EXPKEYSIG"]
- if len(args) >= 1:
- key = args[0]
- rejects.append("Signature made by expired key 0x%s" % (key))
- if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
- args = keywords["KEYEXPIRED"]
- expiredate=""
- if len(args) >= 1:
- timestamp = args[0]
- if timestamp.count("T") == 0:
- try:
- expiredate = time.strftime("%Y-%m-%d", time.gmtime(float(timestamp)))
- except ValueError:
- expiredate = "unknown (%s)" % (timestamp)
- else:
- expiredate = timestamp
- rejects.append("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
-
- if len(rejects) > 0:
- return (None, rejects)
-
- # Next check gpgv exited with a zero return code
- if exit_status:
- rejects.append("gpgv failed while checking %s." % (sig_filename))
- if status.strip():
- rejects.append(prefix_multi_line_string(status, " [GPG status-fd output:] "))
- else:
- rejects.append(prefix_multi_line_string(output, " [GPG output:] "))
- return (None, rejects)
-
- # Sanity check the good stuff we expect
- if not keywords.has_key("VALIDSIG"):
- rejects.append("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
- else:
- args = keywords["VALIDSIG"]
- if len(args) < 1:
- rejects.append("internal error while checking signature on %s." % (sig_filename))
- else:
- fingerprint = args[0]
- if not keywords.has_key("GOODSIG"):
- rejects.append("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
- if not keywords.has_key("SIG_ID"):
- rejects.append("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
-
- # Finally ensure there's not something we don't recognise
- known_keywords = dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
- SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
- NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="",POLICY_URL="")
-
- for keyword in keywords.keys():
- if not known_keywords.has_key(keyword):
- rejects.append("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
-
- if len(rejects) > 0:
- return (None, rejects)
- else:
- return (fingerprint, [])
-