Source code for passpy.gpg

# passpy --  ZX2C4's pass compatible library and cli
# Copyright (C) 2016 Benedikt Rascher-Friesenhausen <benediktrascherfriesenhausen@gmail.com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os

from gnupg import GPG


[docs]def _get_gpg_recipients(path): """Get the GPG recipients for the given path. :param str path: The directory to get the GPG recipients for. :raises FileNotFoundError: if there is not valid .gpg-id file for path. :rtype: list :returns: The list of IDs of the GPG recipients for the given path. """ while True: gpg_id_path = os.path.join(path, '.gpg-id') if os.path.isfile(gpg_id_path): break; path = os.path.dirname(path) if path is None or path == '': raise FileNotFoundError( 'You must initialise the password store first!') with open(gpg_id_path) as gpg_id_file: gpg_recipients = [line.rstrip('\n') for line in gpg_id_file] return gpg_recipients
[docs]def read_key(path, gpg_bin, gpg_opts): """Read and decrypt a single key file. :param str path: The path to the key to decrypt. :param str gpg_bin: The path to the gpg binary. :param list gpg_opts: The options for gpg. :rtype: str :returns: The unencrypted content of the file at `path`. """ gpg = GPG(gpgbinary=gpg_bin, options=gpg_opts) with open(path, 'rb') as key_file: return str(gpg.decrypt_file(key_file))
[docs]def write_key(path, key_data, gpg_bin, gpg_opts): """Encrypt and write a single key file. :param str path: The path to the key to decrypt. :param str gpg_bin: The path to the gpg binary. :param list gpg_opts: The options for gpg. """ gpg = GPG(gpgbinary=gpg_bin, options=gpg_opts) gpg_recipients = _get_gpg_recipients(path) # pass always ends it's files with an endline if not key_data.endswith('\n'): key_data += '\n' key_data_enc = gpg.encrypt(key_data, gpg_recipients).data with open(path, 'wb') as key_file: key_file.write(key_data_enc)
[docs]def _reencrypt_key(path, gpg, gpg_recipients): """Reencrypt a single key. Gets called from :func:`passpy.gpg._reencrypt_path`. :param str path: The path to a gpg encrypted file. :param gpg: The gpg object. :type gpg: :class:`gnupg.GPG` :param list gpg_recipients: The list of GPG Ids to encrypt the key with. """ with open(path, 'rb') as key_file: key_data = gpg.decrypt_file(key_file).data key_data_enc = gpg.encrypt(key_data, gpg_recipients).data with open(path, 'wb') as key_file: key_file.write(key_data_enc)
[docs]def reencrypt_path(path, gpg_bin, gpg_opts): """Reencrypt a single or multiple keys. If path is a directory all keys inside that directory and it's subdirectories will be reencrypted. :param str path: The key or directory to reencrypt. If ``None`` the function will silently fail. :param str gpg_bin: The path to the gpg binary. :param list gpg_opts: The gpg options. :raises FileNotFoundError: if path does not exist. """ if path is None: return gpg = GPG(gpgbinary=gpg_bin, options=gpg_opts) if os.path.isfile(path): gpg_recipients = _get_gpg_recipients(path) _reencrypt_key(path, gpg, gpg_recipients) elif os.path.isdir(path): for root, dirs, keys in os.walk(path): gpg_recipients = _get_gpg_recipients(root) for key in keys: if not key.endswith('.gpg'): continue key_path = os.path.join(root, key) _reencrypt_key(key_path, gpg, gpg_recipients) else: raise FileNotFoundError('{0} does not exist.'.format(path))