Example #1
0
 def import_clean_key(self, key):
     """Import the clean key we exported in export_clean_key() to our temp
 keyring."""
     # Import the export of our public key and the given public key
     for x in [self.signer, key]:
         PiusUtil.debug("importing %s" % x)
         import_opts = ["import-minimal"]
         if x == self.signer:
             import_opts.append("keep-ownertrust")
         path = self._tmpfile_path("%s.asc" % x)
         cmd = ([self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS + [
             "--keyring",
             self.tmp_keyring,
             "--import-options",
             ",".join(import_opts),
             "--import",
             path,
         ])
         try:
             self._run_and_check_status(cmd)
         except GpgUnknownError as e:
             if x == self.signer:
                 print("\n\nERROR: Didn't find the signing key on keyring")
                 sys.exit(1)
             raise e
Example #2
0
 def export_clean_key(self, key):
     """Export clean key from the users' KeyID."""
     # Export our public key and the given public key
     for x in [self.signer, key]:
         PiusUtil.debug("exporting %s" % x)
         path = self._tmpfile_path("%s.asc" % x)
         self._export_key(self.keyring, [x], path)
Example #3
0
    def _check_gpg2(self):
        cmd = [self.gpg, "--version"]
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=self.null,
            stdout=subprocess.PIPE,
            stderr=self.null,
            text=True,
        )

        v = None
        for line in gpg.stdout:
            # On Linux this looks like:
            #   gpg (GnuPG) 2.1.8
            # On Mac this looks like:
            #   gpg (GnuPG/MacGPG2) 2.0.28
            m = re.match(r"^gpg \(GnuPG.*\) ([0-9\.]+)$", line)
            if m:
                v = m.group(1)

        if not v:
            print("ERROR: Could not determine gpg version\n")
            sys.exit(1)

        if not v.startswith("2."):
            print("ERROR: PIUS requires GnuPG 2.x or later\n")
            sys.exit(1)
Example #4
0
 def gpg_wait_for_string(self, fd, string):
     """Look for a specific string on the status-fd."""
     line = ""
     while line not in (string, ):
         PiusUtil.debug("Waiting for line %s" % string)
         raw_line = fd.readline()
         if not raw_line:
             raise GpgUnknownError("gpg output unexpectedly ended")
         line = raw_line.strip()
         PiusUtil.debug("got line %s" % line)
Example #5
0
File: state.py Project: rasa/pius
 def write_file(self, data):
     """Separated out for easier unittesting"""
     if not os.path.exists(PiusUtil.statedir()):
         os.mkdir(PiusUtil.statedir(), 0o750)
     if not os.path.isdir(PiusUtil.statedir()):
         print("WARNING: There is a %s which is not a directory."
               " Not storing state." % PiusUtil.statedir())
         return
     if os.path.exists(self.signed_keys_db):
         shutil.copy(self.signed_keys_db, self.signed_keys_db + ".save")
     with open(self.signed_keys_db, "w") as fp:
         fp.write(json.dumps(data))
Example #6
0
    def check_fingerprint(self, key):
        """Prompt the user to see if they have verified this fingerprint."""
        cmd = ([self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS +
               ["--keyring", self.keyring, "--fingerprint", key])
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=self.null,
            stdout=subprocess.PIPE,
            stderr=self.null,
            close_fds=True,
            text=True,
        )
        output = gpg.stdout.read()
        output = output.strip()
        retval = gpg.wait()
        if retval != 0:
            print("WARNING: Keyid %s not valid, skipping." % key)
            return False

        print(output)

        while True:
            ans = input(
                "\nHave you verified this user/key, and if so, what level"
                " do you want to sign at?\n  0-3, Show again, Next, Help,"
                " or Quit? [0|1|2|3|s|n|h|q] (default: n) ")
            print()

            if ans == "y":
                print(
                    "'Yes' is no longer a valid answer, please specify a level "
                    "to sign at.")
            elif ans in ("n", "N", ""):
                return False
            elif ans in ("s", "S"):
                print(output)
            elif ans in ("0", "1", "2", "3"):
                return ans
            elif ans in ("?", "h", "H"):
                self._print_cert_levels()
            elif ans in ("q", "Q"):
                print("Dying at user request")
                sys.exit(1)
Example #7
0
 def _run_and_check_status(self, cmd, shell=False):
     """Helper function for running a gpg call that requires no input
 but that we want to make sure succeeded."""
     PiusUtil.logcmd(cmd)
     gpg = subprocess.Popen(
         cmd,
         stdin=subprocess.PIPE,
         stdout=self.null,
         stderr=self.null,
         shell=shell,
         close_fds=(not shell),
     )
     retval = gpg.wait()
     if retval != 0:
         # We don't catch this, but that's fine, if this errors, a stack
         # trace is what we want
         raise GpgUnknownError(
             "'%s' exited with %d" %
             (" ".join(cmd) if isinstance(cmd, list) else cmd, retval))
Example #8
0
File: state.py Project: rasa/pius
    def load_signed_keys(self):
        PiusUtil.handle_path_migration(
            self.signed_keys_db,
            [
                os.path.join(x, self.kSIGNED_KEYS_DB_NAME)
                for x in PiusUtil.previous_statedirs()
            ],
        )

        if not os.path.exists(self.signed_keys_db):
            return dict()
        with open(self.signed_keys_db, "r") as fp:
            data = fp.read()
            # We have had multiple versions of the state file.
            #
            # v1 was just one key per line, each key was "signed"
            #
            # v2 was a hash taking the form:
            #   key: (SIGNED | WILL_NOTSIGN | NOT_SIGNED)
            #
            # v3 was the first one with a version identifier in it. It includes
            # a `meta` entry for the version and any other future metadata.
            # The data itself takes the format of:
            #   key: {
            #     'OUTBOUND': (SIGNED | WILL_NOTSIGN | NOT_SIGNED | None),
            #     'OUTBOUND': (None | Ignore),
            #   }
            try:
                signstate = json.loads(data)
                if "_meta" in signstate and signstate["_meta"]["version"] == 3:
                    PiusUtil.debug("Loading v3 PIUS statefile")
                    del (signstate["_meta"])
                elif "_meta" not in signstate:
                    PiusUtil.debug("Loading v2 PIUS statefile")
                    signstate = self.convert_from_v2(signstate)
            except ValueError:
                PiusUtil.debug("Loading v1 PIUS statefile")
                signstate = self.convert_from_v1(data)
        return signstate
Example #9
0
    def get_all_keyids(self):
        """Given a keyring, get all the KeyIDs from it."""
        PiusUtil.debug("extracting all keyids from keyring")
        cmd = ([self.gpg] + GPG_BASE_OPTS + [
            "--keyring",
            self.keyring,
            "--no-options",
            "--with-colons",
            "--fingerprint",
            "--fixed-list-mode",
        ])
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=self.null,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
        )
        # We use 'pub' instead of 'fpr' to support old crufty keys too...
        pub_re = re.compile("^pub:")
        uid_re = re.compile("^uid:")
        key_tuples = []
        name = keyid = None
        for line in gpg.stdout:
            if pub_re.match(line):
                lineparts = line.split(":")
                keyid = lineparts[4]
            elif keyid and uid_re.match(line):
                lineparts = line.split(":")
                name = lineparts[9]
                PiusUtil.debug("Got id %s for %s" % (keyid, name))
                key_tuples.append((name, keyid))
                name = keyid = None

        # sort the list
        if self.sort_keyring:
            keyids = [i[1] for i in sorted(key_tuples)]
        else:
            keyids = [i[1] for i in key_tuples]
        return keyids
Example #10
0
    def encrypt_and_sign_file(self, infile, outfile, keyid):
        """Encrypt and sign a file.

    Used for PGP/Mime email generation."""
        cmd = ([self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS + GPG_FD_OPTS + [
            "--keyring",
            self.tmp_keyring,
            "--no-options",
            "--always-trust",
            "-u",
            self.force_signer,
            "-aes",
            "-r",
            keyid,
            "-r",
            self.signer,
            "--output",
            outfile,
            infile,
        ])
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=self.null,
            close_fds=True,
            text=True,
        )

        # For some reason when using agent an initial enter is needed
        gpg.stdin.write("\n")

        skippable = [
            PiusSigner.GPG_USERID,
            PiusSigner.GPG_NEED_PASS,
            PiusSigner.GPG_GOOD_PASS,
            PiusSigner.GPG_SIG_BEG,
            PiusSigner.GPG_SIG_CREATED,
            PiusSigner.GPG_PROGRESS,
            PiusSigner.GPG_PINENTRY_LAUNCHED,
            PiusSigner.GPG_WARN_VERSION,
        ]

        while True:
            PiusUtil.debug("Waiting for response")
            line = gpg.stdout.readline().strip()
            PiusUtil.debug("Got %s" % line)
            if PiusSigner.GPG_ENC_BEG in line:
                PiusUtil.debug("Got GPG_ENC_BEG")
                continue
            elif PiusSigner.GPG_ENC_END in line:
                PiusUtil.debug("Got GPG_ENC_END")
                break
            elif PiusSigner.GPG_ENC_INV in line:
                PiusUtil.debug("Got GPG_ENC_INV")
                raise EncryptionKeyError
            elif PiusSigner.GPG_KEY_CONSIDERED in line:
                PiusUtil.debug("Got KEY_CONSIDERED")
                continue
            elif (PiusSigner.GPG_KEY_EXP in line
                  or PiusSigner.GPG_SIG_EXP in line):
                # These just mean we passed a given key/sig that's expired,
                # there may be ones left that are good. We cannot report an
                # error until we get a ENC_INV.
                PiusUtil.debug("Got GPG_KEY/SIG_EXP")
                continue
            elif any([s in line for s in skippable]):
                PiusUtil.debug("Got skippable stuff")
                continue
            else:
                raise EncryptionUnknownError(line)

        retval = gpg.wait()
        if retval != 0:
            raise EncryptionUnknownError("Return code was %s" % retval)
Example #11
0
    def sign_uid(self, key, index, level):
        """Sign a single UID of a key."""
        cmd = (
            [self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS + GPG_FD_OPTS +
            ["--keyring", self.tmp_keyring, "-u", self.force_signer] +
            self.policy_opts() + [
                "--default-cert-level",
                level,
                "--no-ask-cert-level",
                "--edit-key",
                key,
            ])  # NB: keep the `--edit-key <key>` at the very end of this list!
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=self.null,
            close_fds=True,
            text=True,
            bufsize=1,
        )

        PiusUtil.debug("Waiting for prompt")
        self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT)
        PiusUtil.debug("Selecting UID %d" % index)
        gpg.stdin.write("%s\n" % str(index))
        PiusUtil.debug("Waiting for ack")
        self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK)

        PiusUtil.debug("Running sign subcommand")
        self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT)
        PiusUtil.debug("Sending sign command")
        gpg.stdin.write("sign\n")
        self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK)

        while True:
            PiusUtil.debug("Waiting for response")
            line = gpg.stdout.readline()
            PiusUtil.debug("Got %s" % line)
            if PiusSigner.GPG_ALREADY_SIGNED in line:
                print("  UID already signed")
                gpg.stdin.write("quit\n")
                return False
            elif PiusSigner.GPG_KEY_CONSIDERED in line:
                PiusUtil.debug("Got KEY_CONSIDERED")
                continue
            elif (PiusSigner.GPG_KEY_EXP in line
                  or PiusSigner.GPG_SIG_EXP in line):
                # The user has an expired signing or encryption key, keep going
                PiusUtil.debug("Got GPG_KEY/SIG_EXP")
                continue
            elif PiusSigner.GPG_PROMPT in line:
                # Unfortunately PGP doesn't give us anything parsable in this
                # case. It just gives us another prompt. We give the most likely
                # problem. Best we can do.
                print(
                    "  ERROR: GnuPG won't let us sign, this probably means it"
                    " can't find a secret key, which most likely means that the"
                    " keyring you are using doesn't have _your_ _public_ key on"
                    " it.")
                gpg.stdin.write("quit\n")
                raise NoSelfKeyError
            elif PiusSigner.GPG_CONFIRM in line:
                # This is what we want
                break
            else:
                print("  ERROR: GnuPG reported an unknown error")
                gpg.stdin.write("quit\n")
                # Don't raise an exception, it's not probably just this UID...
                return False

        PiusUtil.debug("Confirming signing")
        gpg.stdin.write("Y\n")
        self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK)

        #
        # gpg-agent doesn't always work as well as we like. Of the problems:
        #  * It can't always pop up an X window reliably (pinentry problems)
        #  * It doesn't seem able to figure out the best pinetry program
        #    to use in many situations
        #  * Sometimes it silently fails in odd ways
        #
        # So this chunk of code will follow gpg through as many tries as
        # gpg-agent is willing to give and then inform the user of an error and
        # raise an exception.
        #
        # Since we're here, we also handle the highly unlikely case where the
        # verified cached passphrase doesn't work.
        #
        while True:
            line = gpg.stdout.readline()
            PiusUtil.debug("Got %s" % line)
            # gpg1 + gpgagent1 reported BAD_PASSPHRASE for both the agent the
            # wrong passphrase, and for canceling the prompt.
            #
            # gpg2.0 + gpgagent2.0 seems to do MISSING_PASSPHRASE and
            # BAD_PASSPHRASE for the respective answers
            #
            # gpg2.1 + gpgagent2.1 seems to just do ERROR
            if "ERROR" in line:
                print("  ERROR: Agent reported an error.")
                raise AgentError
            if "MISSING_PASSPHRASE" in line:
                print("  ERROR: Agent didn't provide passphrase to PGP.")
                raise AgentError
            if "BAD_PASSPHRASE" in line:
                line = gpg.stdout.readline()
                PiusUtil.debug("Got %s" % line)
                if "USERID_HINT" in line:
                    continue
                print("  ERROR: Agent reported the passphrase was incorrect.")
                raise AgentError
            if "GOOD_PASSPHRASE" in line:
                break
            if PiusSigner.GPG_PROMPT in line:
                break

        PiusUtil.debug("Saving key")
        gpg.stdin.write("save\n")

        gpg.wait()
        return True
Example #12
0
 def cleanup(self):
     """Cleanup all our temp files."""
     PiusUtil.clean_files([self.tmp_keyring, ("%s~" % self.tmp_keyring)])
Example #13
0
 def clean_clean_key(self, key):
     """Delete the "clean" unsigned key which we exported temporarily."""
     # Remove the temporary exports of the public keys
     paths = [self._tmpfile_path("%s.asc" % x) for x in [self.signer, key]]
     PiusUtil.clean_files(paths)
Example #14
0
 def export_signed_uid(self, key, filename):
     """Export the signed UID form working keyring."""
     PiusUtil.debug("exporting %s" % key)
     self._export_key(self.tmp_keyring, [key], filename)
Example #15
0
File: state.py Project: rasa/pius
 def __init__(self):
     self.signed_keys_db = os.path.join(PiusUtil.statedir(),
                                        self.kSIGNED_KEYS_DB_NAME)
     self.state = {}
     self._load()
     self.modified = False
Example #16
0
    def encrypt_signed_uid(self, key, filename):
        """Encrypt the file we exported the signed UID to."""
        (base, ext) = os.path.splitext(os.path.basename(filename))
        enc_file = "%s_ENCRYPTED%s" % (base, ext)
        enc_path = self._outfile_path(enc_file)
        if os.path.exists(enc_path):
            os.unlink(enc_path)
        cmd = ([self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS + GPG_FD_OPTS + [
            "--keyring",
            self.tmp_keyring,
            "--always-trust",
            "--armor",
            "-r",
            key,
            "--output",
            enc_path,
            "-e",
            filename,
        ])
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=self.null,
            close_fds=True,
        )

        # Must send a blank line...
        gpg.stdin.write("\n")
        while True:
            PiusUtil.debug("Waiting for response")
            line = gpg.stdout.readline().strip()
            PiusUtil.debug("Got %s" % line)
            if PiusSigner.GPG_ENC_BEG in line:
                PiusUtil.debug("Got GPG_ENC_BEG")
                continue
            elif PiusSigner.GPG_ENC_COMPLIANT_MODE in line:
                PiusUtil.debug("Got ENCRYPTION_COMPLIANCE_MODE")
                continue
            elif PiusSigner.GPG_ENC_END in line:
                PiusUtil.debug("Got GPG_ENC_END")
                break
            elif PiusSigner.GPG_ENC_INV in line:
                PiusUtil.debug("Got GPG_ENC_INV")
                raise EncryptionKeyError
            elif (PiusSigner.GPG_KEY_EXP in line
                  or PiusSigner.GPG_SIG_EXP in line):
                # These just mean we passed a given key/sig that's expired,
                # there may be ones left that are good. We cannot report an
                # error until we get a ENC_INV.
                PiusUtil.debug("Got GPG_KEY_EXP")
                continue
            elif PiusSigner.GPG_KEY_CONSIDERED in line:
                PiusUtil.debug("Got KEY_CONSIDERED")
                continue
            elif PiusSigner.GPG_PROGRESS in line:
                PiusUtil.debug("Got skippable stuff")
                continue
            else:
                raise EncryptionUnknownError(line)

        gpg.wait()
        return enc_file
Example #17
0
    def get_uids(self, key):
        """Get all UIDs on a given key."""
        cmd = ([self.gpg] + GPG_BASE_OPTS + GPG_QUIET_OPTS + GPG_FD_OPTS + [
            "--keyring",
            self.keyring,
            "--no-options",
            "--with-colons",
            "--edit-key",
            key,
        ])
        PiusUtil.logcmd(cmd)
        gpg = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            close_fds=True,
            bufsize=1,
            text=True,
        )

        gpg.stdin.write("\n")
        uids = []
        unique_files = []
        while True:
            line = gpg.stdout.readline().strip()

            # skip the things we don't care about...
            if not line:
                PiusUtil.debug("breaking, EOF")
                break
            if line == PiusSigner.GPG_PROMPT:
                PiusUtil.debug("got to command prompt")
                break

            # Parse the line...
            PiusUtil.debug("Got a line %s" % line)
            fields = line.split(":")

            if fields[0] != "uid":
                continue

            status = fields[1]
            uid = fields[9]
            index = int(fields[13].split(",")[0])

            PiusUtil.debug("Got UID %s with status %s" % (uid, status))

            # If we can we capture an email address is saved for
            # emailing off signed keys (not yet implemented), and
            # also for the ID for that UID.
            #
            # If we can't, then we grab what we can and make it the
            # id and blank out the email.
            #
            # For the normal case (have email), we'll be storing each email
            # twice but that's OK since it means that email is *always* a valid
            # email or None and id is *always* a valid identifier
            match = re.search(".* <(.*)>", uid)
            if match:
                email = match.group(1)
                PiusUtil.debug("got email %s" % email)
                filename = re.sub("@", "_at_", email)
                filename = "%s__%s" % (key, filename)
                uid = email
            else:
                # but if it doesn't have an email, do the right thing
                email = None
                PiusUtil.debug("no email")
                uid = re.sub(" ", "_", uid)
                uid = re.sub("'", "", uid)
                filename = "%s__%s" % (key, uid)
            # Append the UID we're signing with in case people sign with 2
            # keys in succession:
            filename = "%s__%s" % (filename, self.signer)

            if filename in unique_files:
                PiusUtil.debug("Filename is a duplicate")
                count = 2
                while True:
                    test = "%s_%s" % (filename, count)
                    PiusUtil.debug("Trying %s" % test)
                    if test not in unique_files:
                        PiusUtil.debug("%s worked!" % test)
                        filename = test
                        break
                    else:
                        count += 1
            else:
                PiusUtil.debug("%s isn't in %s" %
                               (filename, repr(unique_files)))

            # NOTE: Make sure to append the file BEFORE adding the extension
            #       since that's what we test against above!
            unique_files.append(filename)
            filename += ".asc"
            uids.append({
                "email": email,
                "file": self._outfile_path(filename),
                "status": status,
                "id": uid,
                "index": index,
            })

        # sometimes it wants a save here. I don't know why. We can quit and
        # check for a save prompt, and then hit no, but we have to make sure
        # it's still running or we'll hang. It's just easier to issue a 'save'
        # instead of a quit. Also, write() + wait() causes a hang in py3 even
        # though there's nothing in any of the pipes, so we use communicate()
        # here which does the right thing
        gpg.stdin.write("save\n")
        PiusUtil.debug("waiting")
        gpg.wait()

        return uids