コード例 #1
0
    def archive_secrets(self):
        """
        Archive secrets

        Save all secrets to the archive file.
        """
        self.logger.log("Archive secrets.")
        try:
            import yaml
        except ImportError:
            self.logger.error(
                'archive feature requires yaml, which is not available.')

        # Loop through accounts saving passwords and questions
        all_secrets = {}
        for account_id in self.all_accounts():
            questions = []
            account = self.get_account(account_id, quiet=True)
            password = self.generate_password(account)
            self.logger.debug("    Saving password.")
            for question in account.get_security_questions():
                # convert the result to a list rather than leaving it a tuple
                # because tuples are formatted oddly in yaml
                questions += [list(self.generate_answer(question, account))]
                self.logger.debug(
                    "    Saving question (%s) and its answer." % question)
            all_secrets[account_id] = {
                'password': password,
                'questions': questions
            }

        # Convert results to yaml archive
        unencrypted_secrets = yaml.dump(all_secrets)

        # Encrypt and save yaml archive
        gpg_id = self.accounts.get_gpg_id()
        encrypted_secrets = self.gpg.encrypt(unencrypted_secrets, gpg_id)
        filename = expand_path(self.accounts.get_archive_file())
        try:
            with open(filename, 'w') as f:
                f.write(str(encrypted_secrets))
            os.chmod(filename, 0o600)
        except IOError as err:
            self.logger.error('%s: %s.' % (err.filename, err.strerror))
コード例 #2
0
ファイル: logger.py プロジェクト: KenKundert/abraxas
    def _terminate(self):
        if not self.logfile:
            return
        contents = '\n'.join(self.cache) + '\n'
        filename = expand_path(self.logfile)

        if get_extension(filename) in ['gpg', 'asc']:
            encrypted = self.gpg.encrypt(
                contents.encode('utf8', 'ignore'),
                self.gpg_id, always_trust=True, armor=True
            )
            if not encrypted.ok:
                sys.stderr.write(
                    "%s: unable to encrypt.\n%s" % (filename, encrypted.stderr)
                )
            contents = str(encrypted)
        try:
            with open(filename, 'w') as file:
                file.write(contents)
            os.chmod(filename, 0o600)
        except IOError as err:
            sys.stderr.write('%s: %s.\n' % (err.filename, err.strerror))
コード例 #3
0
    def __init__(
        self, settings_dir=None, init=None, logger=None, gpg_home=None,
        stateless=False
    ):
        """
        Arguments:
        settings_dir (string)
               Path to the settings directory. Generally only specified when
               testing.
        init (string)
               User's GPG ID. When present, the settings directory is assumed
               not to exist and so is created using the specified ID.
        logger (object)
            Instance of class that provides display(), log(), debug(),
            error(), terminate() and set_logfile() methods:

            display(msg) is called when a message is to be sent to the user.
            log(msg) is called when a message is only to be logged.
            debug(msg) is called for debugging messages.
            error(msg) is called when an error has occurred, should not return.
            terminate() is called to indicate program has terminated normally.
            set_logfile(logfile, gpg, gpg_id) is called to specify
                information about the logfile, in particular, the path to
                the logfile, a gnupg encryption object, and the GPG ID.
                The last two must be specified if the logfile has an
                encryption extension (.gpg or .asc).

        gpg_home (string)
            Path to desired home directory for gpg.
        stateless (bool)
            Boolean that indicates that Abraxas should operate without 
            accessing the user's master password and accounts files.
        """

        if not settings_dir:
            settings_dir = DEFAULT_SETTINGS_DIR
        self.settings_dir = expand_path(settings_dir)
        if not logger:
            logger = Logging()
        self.logger = logger
        self.stateless = stateless
        self.accounts_path = make_path(
            self.settings_dir, DEFAULT_ACCOUNTS_FILENAME)

        # Get the dictionary
        self.dictionary = Dictionary(
            DICTIONARY_FILENAME, self.settings_dir, logger)

        # Activate GPG
        gpg_args = {'gpgbinary': GPG_BINARY}
        if gpg_home:
            gpg_args.update({'gnupghome': gpg_home})
        self.gpg = gnupg.GPG(**gpg_args)

        # Process master password file
        self.master_password_path = make_path(
            self.settings_dir, MASTER_PASSWORD_FILENAME)
        if init:
            self._create_initial_settings_files(gpg_id=init)
        self.master_password = _MasterPassword(
            self.master_password_path,
            self.dictionary,
            self.gpg,
            self.logger,
            stateless)
        try:
            path = self.master_password.data['accounts']
            if path:
                self.accounts_path = make_path(self.settings_dir, path)
        except KeyError:
            pass
コード例 #4
0
    def print_changed_secrets(self):
        """
        Identify updated secrets

        Inform the user of any secrets that have changed since they have been
        archived.
        """
        self.logger.log("Print changed secrets.")
        try:
            import yaml
        except ImportError:
            self.logger.error(
                'archive feature requires yaml, which is not available.')

        filename = expand_path(self.accounts.get_archive_file())
        try:
            with open(filename, 'rb') as f:
                encrypted_secrets = f.read()
        except IOError as err:
            self.logger.error('%s: %s.' % (err.filename, err.strerror))

        unencrypted_secrets = str(self.gpg.decrypt(encrypted_secrets))
        archived_secrets = yaml.load(unencrypted_secrets)

        # Look for changes in the accounts
        archived_ids = set(archived_secrets.keys())
        current_ids = set(list(self.all_accounts()))
        new_ids = current_ids - archived_ids
        deleted_ids = archived_ids - current_ids
        if new_ids:
            self.logger.display(
                "NEW ACCOUNTS:\n    %s" % '\n    '.join(new_ids))
        else:
            self.logger.log("No new accounts.")
        if deleted_ids:
            self.logger.display(
                "DELETED ACCOUNTS:\n    %s" % '\n    '.join(deleted_ids))
        else:
            self.logger.log("No deleted accounts.")

        # Loop through the accounts, and compare the secrets
        accounts_with_password_diffs = []
        accounts_with_question_diffs = []
        for account_id in self.all_accounts():
            questions = []
            account = self.get_account(account_id, quiet=True)
            password = self.generate_password(account)
            for i in account.get_security_questions():
                questions += [list(self.generate_answer(i, account))]
            if account_id in archived_secrets:
                # check that password is unchanged
                if password != archived_secrets[account_id]['password']:
                    accounts_with_password_diffs += [account_id]
                    self.logger.display("PASSWORD DIFFERS: %s" % account_id)
                else:
                    self.logger.debug("    Password matches.")

                # check that number of questions is unchanged
                archived_questions = archived_secrets[account_id]['questions']
                if len(questions) != len(archived_questions):
                    accounts_with_question_diffs += [account_id]
                    self.logger.display(
                        ' '.join([
                            "NUMBER OF SECURITY QUESTIONS CHANGED:",
                            "%s (was %d, is now %d)" % (
                                account_id,
                                len(archived_questions), len(questions))]))
                else:
                    self.logger.debug(
                        "    Number of questions match (%d)." % (
                            len(questions)))

                    # check that questions and answers are unchanged
                    pairs = zip(archived_questions, questions)
                    for i, (archived, new) in enumerate(pairs):
                        if archived[0] != new[0]:
                            self.logger.display(
                                "QUESTION %d DIFFERS: %s (%s -> %s)." % (
                                    i, account_id, archived[0], new[0]))
                        else:
                            self.logger.debug(
                                "    Question %d matches (%s)." % (i, new[0]))
                        if archived[1] != new[1]:
                            self.logger.display(
                                "ANSWER TO QUESTION %d DIFFERS: %s (%s)." % (
                                    i, account_id, new[0]))
                        else:
                            self.logger.debug(
                                "    Answer %d matches (%s)." % (i, new[0]))
        if accounts_with_password_diffs:
            self.logger.log(
                "Accounts with changed passwords:\n    %s" % ',\n    '.join(
                    accounts_with_password_diffs))
        else:
            self.logger.log("No accounts with changed passwords")
        if accounts_with_question_diffs:
            self.logger.log(
                "Accounts with changed questions:\n    %s" % ',\n    '.join(
                    accounts_with_question_diffs))
        else:
            self.logger.log("No accounts with changed questions")
コード例 #5
0
    def _create_initial_settings_files(self, gpg_id):
        """
        Create initial version of settings files for the user (PRIVATE)

        Will create initial versions of the master password file and the 
        accounts file, but only if they do not already exist. The master 
        password file is encrypted with the GPG ID given on the command line, 
        which should be the users.

        Arguments:
        Requires user's GPG ID (string) as the only argument.
        """

        def create_file(filename, contents, encrypt=False):
            if encrypt:
                encrypted = self.gpg.encrypt(
                    contents, gpg_id, always_trust=True, armor=True
                )
                if not encrypted.ok:
                    self.logger.error(
                        "%s: unable to encrypt.\n%s" % (
                            filename, encrypted.stderr))
                contents = str(encrypted)
            if is_file(filename):
                self.logger.display("%s: already exists." % filename)
            else:
                try:
                    with open(filename, 'w') as file:
                        file.write(contents)
                    os.chmod(filename, 0o600)
                    self.logger.display("%s: created." % filename)
                except IOError as err:
                    self.logger.error('%s: %s.' % (err.filename, err.strerror))

        def generate_random_string():
            # Generate a random long string to act as the default password

            from string import ascii_letters, digits, punctuation
            import random
            # Create alphabet from letters, digits, and punctuation, but 
            # replace double quote with a space so password can be safely 
            # represented as a double-quoted string.
            alphabet = (ascii_letters + digits + punctuation).replace('"', ' ')

            rand = random.SystemRandom()
            password = ''
            for i in range(64):
                password += rand.choice(alphabet)
            return password

        mkdir(self.settings_dir)
        default_password = generate_random_string()
        if self.settings_dir != expand_path(DEFAULT_SETTINGS_DIR):
            # If settings_dir is not the DEFAULT_SETTINGS_DIR, then this is
            # probably a test, in which case we do not want to use a
            # random password as it would cause the test results to vary.
            # Still want to generate the random string so that code gets
            # tested. It has been the source of trouble in the past.
            default_password = '******'
        create_file(
            self.master_password_path,
            MASTER_PASSWORD_FILE_INITIAL_CONTENTS % (
                self.dictionary.hash, SECRETS_SHA1, CHARSETS_SHA1,
                DEFAULT_ACCOUNTS_FILENAME, default_password),
            encrypt=True)
        create_file(
            self.accounts_path,
            ACCOUNTS_FILE_INITIAL_CONTENTS % (
                make_path(self.settings_dir, DEFAULT_LOG_FILENAME),
                make_path(self.settings_dir, DEFAULT_ARCHIVE_FILENAME),
                gpg_id),
            encrypt=(get_extension(self.accounts_path) in ['gpg', 'asc']))