def _get_gpg_output_for_pubkey_file(self, pubkey_file): with TemporaryDirectory() as temp_gpg_home: gpg = GPG(gnupghome=temp_gpg_home) with open(pubkey_file, 'rb') as pubkey_fh: gpg.import_keys(pubkey_fh.read()) gpg_stdout = check_output([ 'gpg', '--homedir', temp_gpg_home, '--keyid-format', '0xlong', '--with-fingerprint', '--list-options', 'show-uid-validity', '--verify-options', 'show-uid-validity', # '--list-sigs', # Public keys for signatures over the UIDs might not be present. '--list-public-keys' ]).decode('utf-8') truncated_lines = [] in_header = True for line in gpg_stdout.split('\n'): # OpenPGP subkeys might be subject to more frequent change # and are expected to not always be updated in the keyring. # You might need to update OpenPGP subkeys from keyservers. if not in_header and not re.match(r'sub\s', line): truncated_lines.append(line) if re.match(r'^---------', line): in_header = False return '\n'.join(truncated_lines)
def get_expirations(keylist): """ This function is not implemented in GPG object class because need to operate on the whole keys """ atfork() try: temp_gpgroot = os.path.join(GLSetting.gpgroot, "-expiration_check-%s" % random.randint(0, 0xFFFF) ) os.makedirs(temp_gpgroot, mode=0700) gpexpire= GPG(gnupghome=temp_gpgroot, options="--trust-model always") except Exception as excep: log.err("Unable to setup expiration check environment: %s" % excep) raise excep try: for key in keylist: gpexpire.import_keys(key) except Exception as excep: log.err("Error in GPG import_keys: %s" % excep) raise excep try: all_keys = gpexpire.list_keys() except Exception as excep: log.err("Error in GPG list_keys: %s" % excep) raise excep expirations = {} for ak in all_keys: expirations.update({ ak['fingerprint'] : ak['date']}) return expirations
def gpg_init(tmpdir, benchmark, openpgp_keys, monkeypatch, _): pubkey = openpgp_keys[0] privkey = openpgp_keys[2] # this is PRIVATE_KEY gpg = GPG(homedir=tmpdir.dirname) gpg.import_keys(pubkey.key_data) gpg.import_keys(privkey.key_data) return gpg, pubkey, privkey
def get_expirations(keylist): """ This function is not implemented in GPG object class because need to operate on the whole keys """ try: temp_gpgroot = os.path.join( GLSetting.gpgroot, "-expiration_check-%s" % xeger(r'[A-Za-z0-9]{8}')) os.makedirs(temp_gpgroot, mode=0700) gpexpire = GPG(gnupghome=temp_gpgroot, options=['--trust-model', 'always']) except Exception as excep: log.err("Unable to setup expiration check environment: %s" % excep) raise excep try: for key in keylist: gpexpire.import_keys(key) except Exception as excep: log.err("Error in GPG import_keys: %s" % excep) raise excep try: all_keys = gpexpire.list_keys() except Exception as excep: log.err("Error in GPG list_keys: %s" % excep) raise excep expirations = {} for ak in all_keys: expirations.update({ak['fingerprint']: ak['date']}) return expirations
def _encrypt(self, plain): # test if we have public keys for all recipients available_recipients = [] keys = [] for key in Key.objects.all(): keys.append(key) available_recipients.extend(key.addresses.split(', ')) logger.debug("available_recipients: %s", available_recipients) if not all(recipient in available_recipients for recipient in self.recipients()): logger.error( "Public key not present for at least one of these recipients: %s", self.recipients()) raise GPGException( "Public key not present for at least one recipient") # encryption with TemporaryDirectory() as temp_dir: gpg = GPG(gnupghome=temp_dir) for key in keys: gpg.import_keys(key.key) res = gpg.encrypt(plain, self.recipients(), always_trust=True) if not res: handle_gpg_error(res, 'encryption') return smart_text(res)
def encrypt_file(gpg: gnupg.GPG, targer_folder: str): configuration_kwargs = utils.get_configuratation( path=os.path.join( targer_folder, 'configuration' ) ) value = _get_decryption(configuration_kwargs=configuration_kwargs) gpg.import_keys(value) keys = gpg.list_keys() gpg.trust_keys( keys.fingerprints, 'TRUST_ULTIMATE' ) decrypted_filepaths = utils.get_files( path=os.path.join( targer_folder, 'decrypted_files' ) ) encrypted_path = os.path.join( targer_folder, 'encrypted_files' ) for decryped_file in decrypted_filepaths: base_name = __set_file_name(path=decryped_file) logger.info(f'encrypting file {base_name}') output_file = os.path.join( encrypted_path, base_name ) with open(decryped_file, 'rb') as connection: status = gpg.encrypt_file( file=connection, recipients=keys.fingerprints, output=output_file, ) logger.info(f'encrypting done') if not status.ok: logger.info(f'{status.stderr}') else: logger.info(f'Status [{status.status}]')
def verify_gpg(datafile, signature_file=None) -> bool: """ verify Julia releases using GPG """ if signature_file is None: signature_file = datafile + ".asc" with open(GPG_PUBLIC_KEY_PATH) as fh: keycontent = fh.read() with TemporaryDirectory() as tmpdir: gpg = GPG(gnupghome=tmpdir) gpg.import_keys(keycontent) return bool(_verify_gpg(gpg, datafile, signature_file))
def _sign(self, data, digest_algo): gpg = GPG(homedir=settings.GPG_SIGN_DIR) if not gpg.list_keys(): # Import key if no private key key in keyring with open(settings.GPG_SIGN_KEY, 'r') as f: key = f.read() gpg.import_keys(key) signature = gpg.sign(data, passphrase=settings.GPG_SIGN_KEY_PASSPHRASE, clearsign=False, detach=True, digest_algo=digest_algo) return str(signature)
def configure_gpg(self, key: str, password: Optional[str]) -> None: """Configure the repo to sign tags with GPG.""" home = os.environ["GNUPGHOME"] = mkdtemp(prefix="tagbot_gpg_") os.chmod(home, S_IREAD | S_IWRITE | S_IEXEC) logger.debug(f"Set GNUPGHOME to {home}") gpg = GPG(gnupghome=home, use_agent=True) import_result = gpg.import_keys(self._maybe_decode_private_key(key), passphrase=password) if import_result.sec_imported != 1: logger.warning(import_result.stderr) raise Abort("Importing key failed") key_id = import_result.fingerprints[0] logger.debug(f"GPG key ID: {key_id}") if password: # Sign some dummy data to put our password into the GPG agent, # so that we don't need to supply the password when we create a tag. sign_result = gpg.sign("test", passphrase=password) if sign_result.status != "signature created": logger.warning(sign_result.stderr) raise Abort("Testing GPG key failed") # On Debian, the Git version is too old to recognize tag.gpgSign, # so the tag command will need to use --sign. self._git._gpgsign = True self._git.config("tag.gpgSign", "true") self._git.config("user.signingKey", key_id)
def send_mail(self, subject, body): if self.gpg_key: with TemporaryDirectory() as tmp_dir: gpg = GPG(homedir=tmp_dir) key = gpg.import_keys(self.gpg_key) body = str(gpg.encrypt(body, *key.fingerprints)) send_mail(subject, body, settings.EMAIL_ADDRESS, [self.email])
def clean_key(self): """ Raises ValidationError if the entered key is invalid or includes a GPG private key. """ data = self.cleaned_data['key'] with TemporaryDirectory() as temp_dir: gpg_keychain = GPG(gnupghome=temp_dir) res = gpg_keychain.import_keys(data) if not res: errors = [ forms.ValidationError(_("Invalid key."), code='invalid') ] for attr in ['status', 'stderr']: # not all fields are always present if hasattr(res, attr): errors.append( forms.ValidationError("%(name)s: %(value)s", params={ 'name': attr, 'value': getattr(res, attr) }, code=attr)) raise forms.ValidationError(errors) if len(gpg_keychain.list_keys( True)) > 0: # check existance of private keys raise forms.ValidationError(_( "Import public keys only, no private keys! " "You should consider the private key(s) compromised."), code='private_key') return data
def check_git_commits(self, repo_path='.'): with TemporaryDirectory() as temp_gpg_home: gpg = GPG(gnupghome=temp_gpg_home) for long_key_id in os.listdir(self._keyring_name): with open(os.path.join(self._keyring_name, long_key_id), 'rb') as pubkey_fh: gpg.import_keys(pubkey_fh.read()) repo = git.Git(repo_path) repo.update_environment(GNUPGHOME=temp_gpg_home) commit_count = 0 # %G?: show "G" for a Good signature, # "B" for a Bad signature, # "U" for a good, untrusted signature and # "N" for no signature # TODO 'N' is also returned for signatures made by expired subkeys. # Seems to be a bug. # Current workaround is only to check the HEAD commit. for log_line in [repo.log('--format=%H %G?').split('\n')[0]]: (commit_hash, signature_check) = log_line.split(' ') commit_count += 1 if signature_check not in ['U', 'G']: raise Exception( "OpenPGP signature of commit could not be verified." "\nAffected commit:\n{}".format( repo.log('-1', commit_hash), )) logging.info( textwrap.dedent(""" OK - All commits in the repository '{repo_path}' are signed and all public keys to verify the signatures are contained in current HEAD of this repository. """).lstrip().replace('\n', ' ').format(repo_path=repo_path, )) if commit_count <= 0: # That condition is expected to never be True because of # "returned with exit code 128" for "fatal: bad default revision 'HEAD'". # Leaving it in just to be sure (in case git becomes more # "friendly" in the future. raise Exception("Expected at least one git commit." " Found {} commits.".format(commit_count, )) else: logging.info( "OK - The repository '{repo_path}' contains at least one commit." .format(repo_path=repo_path, )) return True
def test_addresses_for_key(self): """Test email address extraction from GPG public keys.""" with TemporaryDirectory() as temp_dir: gpg_keychain = GPG(gnupghome=temp_dir) res = gpg_keychain.import_keys(self.key.key) self.assertTrue(res) self.assertEqual(len(res.results), 1) self.assertEqual(addresses_for_key(gpg_keychain, res.results[0]), ['*****@*****.**'])
def _pgp_decrypt(self, encrypted_firmware, key): ''' This does the actual decryption. ''' try: tmp_dir = tempfile.mkdtemp() gpg = GPG(gnupghome=tmp_dir) gpg.import_keys(key) decrypted_data = gpg.decrypt(encrypted_firmware) finally: try: shutil.rmtree(tmp_dir) except OSError as exc: if exc.errno != errno.ENOENT: raise return bytes(decrypted_data.data)
def gnupg_key_fingerprint(gnupg_instance: GPG, private_gpg_key: str): keys_imported = gnupg_instance.import_keys(private_gpg_key) key_fingerprint = keys_imported.fingerprints[0] yield key_fingerprint if key_fingerprint in gnupg_instance.list_keys(secret=True).fingerprints: remove_gpg_key_pair(gpg_binary=gnupg_instance.gpgbinary, fingerprint=key_fingerprint)
def clean_key(self): """ Validate the key contains an email address. """ key = self.cleaned_data["key"] gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(key) if result.count == 0: raise forms.ValidationError(_("Invalid Key")) return key
def forward_change(apps, schema_editor): Key = apps.get_model('email_extras', 'Key') Address = apps.get_model('email_extras', 'Address') for key in Key.objects.all(): addresses = Address.objects.filter(address__in=key.addresses.split(',')) addresses.update(key=key) gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(key.key) key.fingerprint = result.fingerprints[0] key.save()
def save(self, *args, **kwargs): super(Key, self).save(*args, **kwargs) gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(self.key) addresses = [] for key in result.results: addresses.extend(addresses_for_key(gpg, key)) self.addresses = ",".join(addresses) for address in addresses: address, _ = Address.objects.get_or_create(address=address) address.use_asc = self.use_asc address.save()
def forward_change(apps, schema_editor): Key = apps.get_model('email_extras', 'Key') Address = apps.get_model('email_extras', 'Address') for key in Key.objects.all(): addresses = Address.objects.filter( address__in=key.addresses.split(',')) addresses.update(key=key) gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(key.key) key.fingerprint = result.fingerprints[0] key.save()
def _get_decryption(gpg: gnupg.GPG, configuration_kwargs: Dict) -> gnupg.GPG: ssm_name = '{environment}.{name}-{company_name}'.format( environment=settings.ENVIRONMENT, name=configuration_kwargs.get('secret_name'), company_name=configuration_kwargs.get('company_name')) secret_key = [] for key in ['public', 'private']: logger.info(f'Handling {key} for decryption') ssm_name_extended = f'{ssm_name}-{key}' value = parameter_store.get_keys(profile_name='ssm', name=ssm_name_extended) secret_key.append(value) secret_keys = ''.join(secret_key) gpg.import_keys(secret_keys) keys = gpg.list_keys(True) gpg.trust_keys(keys.fingerprints, 'TRUST_ULTIMATE') return gpg
def _encrypt(self, plain): # test if we have public keys for all recipients available_recipients = [] keys = [] for key in Key.objects.all(): keys.append(key) available_recipients.extend(key.addresses.split(', ')) logger.debug("available_recipients: %s", available_recipients) if not all(recipient in available_recipients for recipient in self.recipients()): logger.error("Public key not present for at least one of these recipients: %s", self.recipients()) raise GPGException("Public key not present for at least one recipient") # encryption with TemporaryDirectory() as temp_dir: gpg = GPG(gnupghome=temp_dir) for key in keys: gpg.import_keys(key.key) res = gpg.encrypt(plain, self.recipients(), always_trust=True) if not res: handle_gpg_error(res, 'encryption') return smart_text(res)
def verify_gpg(datafile, signature_file=None) -> bool: """ verify Julia releases using GPG """ if signature_file is None: signature_file = datafile + ".asc" with open(GPG_PUBLIC_KEY_PATH) as fh: keycontent = fh.read() rst = False try: with TemporaryDirectory() as tmpdir: gpg = GPG(gnupghome=tmpdir) gpg.import_keys(keycontent) rst = bool(_verify_gpg(gpg, datafile, signature_file)) except FileNotFoundError: # issue #45 -- might reaches here during the temp dir cleanup # This might only related to VM environment # https://bugs.python.org/issue25717 pass finally: return rst
def import_key(keypath, keyring): """ Imports all keys from specified directory This function is idempotent, so importing the same key multiple times will always succeed. :param keypath: path to armored key file :param keyring: directory of the keyring """ gpg = GPG(gnupghome=keyring) try: with open(keypath, 'r') as keyfile: result = gpg.import_keys(keyfile.read()) except OSError: raise KeyImportError("Could not open '%s'" % keypath, keypath) if result.count == 0: raise KeyImportError("Could not import '%s'" % keypath, keypath)
def save(self, *args, **kwargs): with TemporaryDirectory() as temp_dir: gpg_keychain = GPG(gnupghome=temp_dir) res = gpg_keychain.import_keys(self.key) if not res: handle_gpg_error(res, 'import') if len(gpg_keychain.list_keys(True)) > 0: raise GPGException("Will not import GPG private key!") addresses = [] for key in res.results: addresses.extend(addresses_for_key(gpg_keychain, key)) self.addresses = ', '.join(addresses) operation = "Updating" if self.pk else "Creating" logger.info("%s GPG key for: %s", operation, self.addresses) super(Key, self).save(*args, **kwargs)
def save_model(self, request, obj, form, change): """ import the key and parse the addresses from it and save them and omit the super save_model call so as to never save the key instance """ gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(obj.key) if result.count == 0: raise forms.ValidationError("Invalid Key") else: addresses = [] for key in result.results: addresses.extend(addresses_for_key(gpg, key)) obj.addresses = ",".join(addresses) for address in addresses: Address.objects.get_or_create(address=address)
def save_model(self, request, obj, form, change): """ Import the key and parse the addresses from it and save them, and omit the super save_model call so as to never save the key instance. """ gpg = GPG(gnupghome=GNUPG_HOME) result = gpg.import_keys(obj.key) if result.count == 0: raise forms.ValidationError("Invalid Key") else: addresses = [] for key in result.results: addresses.extend(addresses_for_key(gpg, key)) obj.addresses = ",".join(addresses) for address in addresses: Address.objects.get_or_create(address=address)
def key_edit(key, password, path_to_user_folder): try: gpg = GPG() gpg.delete_keys(key, True, passphrase=password) gpg.delete_keys(key) key_data = open(path_to_user_folder+'/main/gpg_private_key.asc').read() new_key = gpg.import_keys(key_data) new_key = new_key.results[0]['fingerprint'] gpg.trust_keys(new_key, "TRUST_ULTIMATE") system('echo RELOADAGENT | gpg-connect-agent') copy2(path_to_user_folder+'/main/gpg_private_key.asc', path_to_user_folder+'/user_data/gpg_private_key.asc') return new_key except: msg = bot.send_message(message.chat.id, 'Произошла ошибка.', reply_markup = types.ReplyKeyboardRemove(selective=False)) del_mess(msg, bot, 6) return key
def configure_gpg(self, key: str, password: Optional[str]) -> None: """Configure the repo to sign tags with GPG.""" home = os.environ["GNUPGHOME"] = mkdtemp(prefix="tagbot_gpg_") os.chmod(home, S_IREAD | S_IWRITE | S_IEXEC) logger.debug(f"Set GNUPGHOME to {home}") gpg = GPG(gnupghome=home, use_agent=True) # For some reason, this doesn't require the password even though the CLI does. import_result = gpg.import_keys(self._maybe_b64(key)) if import_result.sec_imported != 1: logger.warning(import_result.stderr) raise Abort("Importing key failed") key_id = import_result.fingerprints[0] logger.debug(f"GPG key ID: {key_id}") if password: # Sign some dummy data to put our password into the GPG agent, # so that we don't need to supply the password when we create a tag. sign_result = gpg.sign("test", passphrase=password) if sign_result.status != "signature created": logger.warning(sign_result.stderr) raise Abort("Testing GPG key failed") self._git.config("user.signingKey", key_id) self._git.config("tag.gpgSign", "true")
def clean_key(self): """ Raises ValidationError if the entered key is invalid or includes a GPG private key. """ data = self.cleaned_data['key'] with TemporaryDirectory() as temp_dir: gpg_keychain = GPG(gnupghome=temp_dir) res = gpg_keychain.import_keys(data) if not res: errors = [forms.ValidationError(_("Invalid key."), code='invalid')] for attr in ['status', 'stderr']: # not all fields are always present if hasattr(res, attr): errors.append(forms.ValidationError("%(name)s: %(value)s", params={'name': attr, 'value': getattr(res, attr)}, code=attr)) raise forms.ValidationError(errors) if len(gpg_keychain.list_keys(True)) > 0: # check existance of private keys raise forms.ValidationError(_("Import public keys only, no private keys! " "You should consider the private key(s) compromised."), code='private_key') return data
def import_from_dump(file): """Import from an SKS dump.""" # First, load a new GPG instance. gpg = GPG(gnupghome="/tmp/skier-import") # Read data in dumpf = open(file, 'rb') data = dumpf.read() dump = pgpdump.BinaryData(data) dumpf.close() # Import the keydump into gpg try: gpg.import_keys(data) except: # Yeah, whatever. pass """ # Read in packets packets_flat = [] try: for packet in dump.packets(): packets_flat.append(packet) except Exception as e: # Some SKS data is malformed. print("SKS data in {} is malformed - truncating at packet {}".format(file, len(packets_flat)+1)) # Unflatten list into sublists, starting at each new Publickey packet. packets = [] tmpl = [] for packet in packets_flat: if isinstance(packet, pgpdump.packet.PublicKeyPacket) and not isinstance(packet, pgpdump.packet.PublicSubkeyPacket) and tmpl: tmpl.insert(0, packet) gpg_output = gpg.export_keys(keyids=[packet.fingerprint.decode()]) if gpg_output: tmpl.append(gpg_output) packets.append(tmpl) tmpl = [] continue else: tmpl.append(packet) packets.append(tmpl) """ armored = [] for key in gpg.list_keys(): tmpkey = gpg.export_keys(keyids=key['fingerprint']) if not tmpkey: print("{} is malformed - not importing".format(key['fingerprint'])) armored.append(tmpkey) print("Importing {} keys".format(len(armored))) """ for gpack in packets: keyinfo = KeyInfo.pgp_dump(None, packets=gpack) dbob = db.Key.from_keyinfo(keyinfo) db.db.session.add(dbob) db.db.session.commit() """ for key in armored: kyinfo = KeyInfo.pgp_dump(key) if not kyinfo.fingerprint: print("Key {} is malformed - cannot be imported".format(kyinfo)) else: dbob = db.Key.from_keyinfo(kyinfo) db.db.session.add(dbob) db.db.session.commit() shutil.rmtree("/tmp/skier-import")
class GLBPGP(object): """ PGP has not a dedicated class, because one of the function is called inside a transact, and I'm not quite confident on creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self): """ every time is needed, a new keyring is created here. """ try: temp_pgproot = os.path.join(GLSettings.pgproot, "%s" % rstr.xeger(r'[A-Za-z0-9]{8}')) os.makedirs(temp_pgproot, mode=0700) self.pgph = GPG(gnupghome=temp_pgproot, options=['--trust-model', 'always']) self.pgph.encoding = "UTF-8" except OSError as ose: log.err("Critical, OS error in operating with GnuPG home: %s" % ose) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key: @return: True or False, True only if a key is effectively importable and listed. """ try: import_result = self.pgph.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s" % excep) raise errors.PGPKeyInvalid if len(import_result.fingerprints) != 1: raise errors.PGPKeyInvalid fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.pgph.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s" % excep) raise errors.PGPKeyInvalid info = u"" expiration = datetime.utcfromtimestamp(0) for key in all_keys: if key['fingerprint'] == fingerprint: if key['expires']: expiration = datetime.utcfromtimestamp(int(key['expires'])) exp_date = datetime_to_day_str(expiration) else: exp_date = u'Never' info += "Key length: %s\n" % key['length'] info += "Key expiration: %s\n" % exp_date try: for uid in key['uids']: info += "\t%s\n" % uid except Exception as excep: log.err("Error in PGP key format/properties: %s" % excep) raise errors.PGPKeyInvalid break if not len(info): log.err("Key apparently imported but unable to reload it") raise errors.PGPKeyInvalid return { 'fingerprint': fingerprint, 'expiration': expiration, 'info': info } def encrypt_file(self, key_fingerprint, plainpath, filestream, output_path): """ @param pgp_key_public: @param plainpath: @return: """ encrypt_obj = self.pgph.encrypt_file(filestream, str(key_fingerprint)) if not encrypt_obj.ok: raise errors.PGPKeyInvalid log.debug("Encrypting for key %s file %s (%d bytes)" % (key_fingerprint, plainpath, len(str(encrypt_obj)))) encrypted_path = os.path.join(os.path.abspath(output_path), "pgp_encrypted-%s" % rstr.xeger(r'[A-Za-z0-9]{16}')) try: with open(encrypted_path, "w+") as f: f.write(str(encrypt_obj)) return encrypted_path, len(str(encrypt_obj)) except Exception as excep: log.err("Error in writing PGP file output: %s (%s) bytes %d" % (excep.message, encrypted_path, len(str(encrypt_obj)) )) raise errors.InternalServerError("Error in writing [%s]" % excep.message) def encrypt_message(self, key_fingerprint, plaintext): """ @param plaindata: An arbitrary long text that would be encrypted @param receiver_desc: The output of globaleaks.handlers.admin.admin_serialize_receiver() dictionary. It contain the fingerprint of the Receiver PUBKEY @return: The unicode of the encrypted output (armored) """ # This second argument may be a list of fingerprint, not just one encrypt_obj = self.pgph.encrypt(plaintext, str(key_fingerprint)) if not encrypt_obj.ok: raise errors.PGPKeyInvalid log.debug("Encrypting for key %s %d byte of plain data (%d cipher output)" % (key_fingerprint, len(plaintext), len(str(encrypt_obj)))) return str(encrypt_obj) def destroy_environment(self): try: shutil.rmtree(self.pgph.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s" % (self.pgph.gnupghome, excep))
class GLBPGP(object): """ PGP has not a dedicated class, because one of the function is called inside a transact, and I'm not quite confident on creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self): """ every time is needed, a new keyring is created here. """ try: temp_pgproot = os.path.join(GLSettings.pgproot, "%s" % generateRandomKey(8)) os.makedirs(temp_pgproot, mode=0700) self.gnupg = GPG(gnupghome=temp_pgproot, options=['--trust-model', 'always']) self.gnupg.encoding = "UTF-8" except OSError as ose: log.err("Critical, OS error in operating with GnuPG home: %s" % ose) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key: @return: True or False, True only if a key is effectively importable and listed. """ try: import_result = self.gnupg.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s" % excep) raise errors.PGPKeyInvalid if len(import_result.fingerprints) == 0: raise errors.PGPKeyInvalid fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.gnupg.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s" % excep) raise errors.PGPKeyInvalid expiration = datetime.utcfromtimestamp(0) for key in all_keys: if key['fingerprint'] == fingerprint: if key['expires']: expiration = datetime.utcfromtimestamp(int(key['expires'])) break return { 'fingerprint': fingerprint, 'expiration': expiration, } def encrypt_file(self, key_fingerprint, input_file, output_path): """ Encrypt a file with the specified PGP key """ encrypted_obj = self.gnupg.encrypt_file(input_file, str(key_fingerprint), output=output_path) if not encrypted_obj.ok: raise errors.PGPKeyInvalid return encrypted_obj, os.stat(output_path).st_size def encrypt_message(self, key_fingerprint, plaintext): """ Encrypt a text message with the specified key """ encrypted_obj = self.gnupg.encrypt(plaintext, str(key_fingerprint)) if not encrypted_obj.ok: raise errors.PGPKeyInvalid return str(encrypted_obj) def destroy_environment(self): try: shutil.rmtree(self.gnupg.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s" % (self.gnupg.gnupghome, excep))
class PGPContext(object): """ PGP does not have a dedicated class, because one of the function is called inside a transact. I'm not confident creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self, tempdirprefix=None): """ every time is needed, a new keyring is created here. """ if tempdirprefix is None: tempdir = tempfile.mkdtemp() else: tempdir = tempfile.mkdtemp(prefix=tempdirprefix) try: gpgbinary = 'gpg' if os.path.exists('/usr/bin/gpg1'): gpgbinary = 'gpg1' self.gnupg = GPG(gpgbinary=gpgbinary, gnupghome=tempdir, options=['--trust-model', 'always']) self.gnupg.encoding = "UTF-8" except OSError as excep: log.err("Critical, OS error in operating with GnuPG home: %s", excep) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key @return: a dict with the expiration date and the key fingerprint """ try: import_result = self.gnupg.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s", excep) raise errors.InputValidationError if not import_result.fingerprints: raise errors.InputValidationError fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.gnupg.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s", excep) raise errors.InputValidationError expiration = datetime.utcfromtimestamp(0) for k in all_keys: if k['fingerprint'] == fingerprint: if k['expires']: expiration = datetime.utcfromtimestamp(int(k['expires'])) break return {'fingerprint': fingerprint, 'expiration': expiration} def encrypt_file(self, key_fingerprint, input_file, output_path): """ Encrypt a file with the specified PGP key """ encrypted_obj = self.gnupg.encrypt_file(input_file, str(key_fingerprint), output=output_path) if not encrypted_obj.ok: raise errors.InputValidationError return encrypted_obj, os.stat(output_path).st_size def encrypt_message(self, key_fingerprint, plaintext): """ Encrypt a text message with the specified key """ encrypted_obj = self.gnupg.encrypt(plaintext, str(key_fingerprint)) if not encrypted_obj.ok: raise errors.InputValidationError return str(encrypted_obj) def __del__(self): try: shutil.rmtree(self.gnupg.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s", self.gnupg.gnupghome, excep)
class GLBGPG: """ GPG has not a dedicated class, because one of the function is callend inside a transact, and I'm not quite confident on creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self, receiver_desc): """ every time is needed, a new keyring is created here. """ atfork() if receiver_desc.has_key('gpg_key_status') and \ receiver_desc['gpg_key_status'] != Receiver._gpg_types[1]: # Enabled log.err("Requested GPG initialization for a receiver without GPG configured! %s" % receiver_desc['username']) raise Exception("Requested GPG init for user without GPG [%s]" % receiver_desc['username']) try: temp_gpgroot = os.path.join(GLSetting.gpgroot, "%s" % random.randint(0, 0xFFFF) ) os.makedirs(temp_gpgroot, mode=0700) self.gpgh = GPG(gnupghome=temp_gpgroot, options="--trust-model always") except Exception as excep: log.err("Unable to instance GPG object: %s" % excep) raise excep self.receiver_desc = receiver_desc log.debug("GPG object initialized for receiver %s" % receiver_desc['username']) def sanitize_gpg_string(self, received_gpgasc): """ @param received_gpgasc: A gpg armored key @return: Sanitized string or raise InvalidInputFormat This function validate the integrity of a GPG key """ lines = received_gpgasc.split("\n") sanitized = "" start = 0 if not len(lines[start]): start += 1 if lines[start] != '-----BEGIN PGP PUBLIC KEY BLOCK-----': raise errors.InvalidInputFormat("GPG invalid format") else: sanitized += lines[start] + "\n" i = 0 while i < len(lines): # the C language as left some archetypes in my code # [ITA] https://www.youtube.com/watch?v=7jI4DnRJP3k i += 1 try: if len(lines[i]) < 2: continue except IndexError: continue main_content = re.compile( r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" , re.UNICODE) base64only = main_content.findall(lines[i]) if len(base64only) == 1: sanitized += str(base64only[0]) + "\n" # this GPG/PGP format it's different from the common base64 ? dunno if len(lines[i]) == 5 and lines[i][0] == '=': sanitized += str(lines[i]) + "\n" if lines[i] == '-----END PGP PUBLIC KEY BLOCK-----': sanitized += lines[i] + "\n" return sanitized raise errors.InvalidInputFormat("Malformed PGP key block") def validate_key(self, armored_key): """ @param armored_key: @return: True or False, True only if a key is effectively importable and listed. """ # or raise InvalidInputFormat sanitized_gpgasc = self.sanitize_gpg_string(armored_key) try: self.ke = self.gpgh.import_keys(sanitized_gpgasc) except Exception as excep: log.err("Error in GPG import_keys: %s" % excep) return False # Error reported in stderr may just be warning, this is because is not raise an exception here # if self.ke.stderr: # log.err("Receiver %s in uploaded GPG key has raise and alarm:\n< %s >" % # (self.receiver_desc['username'], (self.ke.stderr.replace("\n", "\n "))[:-3])) if not (hasattr(self.ke, 'results') and len(self.ke.results) == 1 and self.ke.results[0].has_key('fingerprint')): log.err("User error: unable to import GPG key in the keyring") return False # else, the key has been loaded and we extract info about that: self.fingerprint = self.ke.results[0]['fingerprint'] # looking if the key is effectively reachable try: all_keys = self.gpgh.list_keys() except Exception as excep: log.err("Error in GPG list_keys: %s" % excep) return False self.keyinfo = u"" for key in all_keys: if key['fingerprint'] == self.fingerprint: self.keyinfo += "Key length %s" % key['length'] try: for uid in key['uids']: self.keyinfo += "\n\t%s" % uid except Exception as excep: log.err("Error in GPG key format/properties: %s" % excep) return False if not len(self.keyinfo): log.err("Key apparently imported but unable to be extracted info") return False return True def encrypt_file(self, plainpath, filestream, output_path): """ @param gpg_key_armor: @param plainpath: @return: """ if not self.validate_key(self.receiver_desc['gpg_key_armor']): raise errors.GPGKeyInvalid encrypt_obj = self.gpgh.encrypt_file(filestream, str(self.receiver_desc['gpg_key_fingerprint'])) if not encrypt_obj.ok: # continue here if is not ok log.err("Falure in encrypting file %s %s (%s)" % ( plainpath, self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint']) ) log.err(encrypt_obj.stderr) raise errors.GPGKeyInvalid log.debug("Encrypting for %s (%s) file %s (%d bytes)" % (self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'], plainpath, len(str(encrypt_obj))) ) encrypted_path = os.path.join( os.path.abspath(output_path), "gpg_encrypted-%d-%d" % (random.randint(0, 0xFFFF), random.randint(0, 0xFFFF))) if os.path.isfile(encrypted_path): log.err("Unexpected unpredictable unbelievable error! %s" % encrypted_path) raise errors.InternalServerError("File conflict in GPG encrypted output") try: with open(encrypted_path, "w+") as f: f.write(str(encrypt_obj)) return encrypted_path, len(str(encrypt_obj)) except Exception as excep: log.err("Error in writing GPG file output: %s (%s) bytes %d" % (excep.message, encrypted_path, len(str(encrypt_obj)) )) raise errors.InternalServerError("Error in writing [%s]" % excep.message ) def encrypt_message(self, plaintext): """ @param plaindata: An arbitrary long text that would be encrypted @param receiver_desc: The output of globaleaks.handlers.admin.admin_serialize_receiver() dictionary. It contain the fingerprint of the Receiver PUBKEY @return: The unicode of the encrypted output (armored) """ if not self.validate_key(self.receiver_desc['gpg_key_armor']): raise errors.GPGKeyInvalid # This second argument may be a list of fingerprint, not just one encrypt_obj = self.gpgh.encrypt(plaintext, str(self.receiver_desc['gpg_key_fingerprint']) ) if not encrypt_obj.ok: # else, is not .ok log.err("Falure in encrypting %d bytes for %s (%s)" % (len(plaintext), self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint']) ) log.err(encrypt_obj.stderr) raise errors.GPGKeyInvalid log.debug("Encrypting for %s (%s) %d byte of plain data (%d cipher output)" % (self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'], len(plaintext), len(str(encrypt_obj))) ) return str(encrypt_obj) def destroy_environment(self): try: shutil.rmtree(self.gpgh.gnupghome) except Exception as excep: log.err("Unable to clean temporary GPG environment: %s: %s" % (self.gpgh.gnupghome, excep))
class GLBPGP(object): """ PGP has not a dedicated class, because one of the function is called inside a transact, and I'm not quite confident on creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self): """ every time is needed, a new keyring is created here. """ try: temp_pgproot = os.path.join(GLSettings.pgproot, "%s" % xeger(r'[A-Za-z0-9]{8}')) os.makedirs(temp_pgproot, mode=0700) self.pgph = GPG(gnupghome=temp_pgproot, options=['--trust-model', 'always']) self.pgph.encoding = "UTF-8" except OSError as ose: log.err("Critical, OS error in operating with GnuPG home: %s" % ose) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key: @return: True or False, True only if a key is effectively importable and listed. """ try: import_result = self.pgph.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s" % excep) raise errors.PGPKeyInvalid if len(import_result.fingerprints) != 1: raise errors.PGPKeyInvalid fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.pgph.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s" % excep) raise errors.PGPKeyInvalid info = u"" expiration = datetime.utcfromtimestamp(0) for key in all_keys: if key['fingerprint'] == fingerprint: if key['expires']: expiration = datetime.utcfromtimestamp(int(key['expires'])) exp_date = datetime_to_day_str(expiration) else: exp_date = u'Never' info += "Key length: %s\n" % key['length'] info += "Key expiration: %s\n" % exp_date try: for uid in key['uids']: info += "\t%s\n" % uid except Exception as excep: log.err("Error in PGP key format/properties: %s" % excep) raise errors.PGPKeyInvalid break if not len(info): log.err("Key apparently imported but unable to reload it") raise errors.PGPKeyInvalid ret = { 'fingerprint': fingerprint, 'expiration': expiration, 'info': info } return ret def encrypt_file(self, key_fingerprint, plainpath, filestream, output_path): """ @param pgp_key_public: @param plainpath: @return: """ encrypt_obj = self.pgph.encrypt_file(filestream, str(key_fingerprint)) if not encrypt_obj.ok: raise errors.PGPKeyInvalid log.debug("Encrypting for key %s file %s (%d bytes)" % (key_fingerprint, plainpath, len(str(encrypt_obj)))) encrypted_path = os.path.join(os.path.abspath(output_path), "pgp_encrypted-%s" % xeger(r'[A-Za-z0-9]{16}')) try: with open(encrypted_path, "w+") as f: f.write(str(encrypt_obj)) return encrypted_path, len(str(encrypt_obj)) except Exception as excep: log.err("Error in writing PGP file output: %s (%s) bytes %d" % (excep.message, encrypted_path, len(str(encrypt_obj)) )) raise errors.InternalServerError("Error in writing [%s]" % excep.message) def encrypt_message(self, key_fingerprint, plaintext): """ @param plaindata: An arbitrary long text that would be encrypted @param receiver_desc: The output of globaleaks.handlers.admin.admin_serialize_receiver() dictionary. It contain the fingerprint of the Receiver PUBKEY @return: The unicode of the encrypted output (armored) """ # This second argument may be a list of fingerprint, not just one encrypt_obj = self.pgph.encrypt(plaintext, str(key_fingerprint)) if not encrypt_obj.ok: raise errors.PGPKeyInvalid log.debug("Encrypting for key %s %d byte of plain data (%d cipher output)" % (key_fingerprint, len(plaintext), len(str(encrypt_obj)))) return str(encrypt_obj) def destroy_environment(self): try: shutil.rmtree(self.pgph.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s" % (self.pgph.gnupghome, excep))
def gpg_init_only(tmpdir, benchmark, openpgp_keys, monkeypatch, num_keys): keys = openpgp_keys[0:num_keys] gpg = GPG(homedir=tmpdir.dirname) for key in keys: gpg.import_keys(key.key_data)
class GLBGPG: """ GPG has not a dedicated class, because one of the function is called inside a transact, and I'm not quite confident on creating an object that operates on the filesystem knowing that would be run also on the Storm cycle. """ def __init__(self, receiver_desc): """ every time is needed, a new keyring is created here. """ if receiver_desc.has_key('gpg_key_status') and \ receiver_desc['gpg_key_status'] != Receiver._gpg_types[1]: # Enabled log.err( "Requested GPG initialization for a receiver without GPG configured! %s" % receiver_desc['username']) raise Exception("Requested GPG init for user without GPG [%s]" % receiver_desc['username']) try: temp_gpgroot = os.path.join(GLSetting.gpgroot, "%s" % xeger(r'[A-Za-z0-9]{8}')) os.makedirs(temp_gpgroot, mode=0700) self.gpgh = GPG(gnupghome=temp_gpgroot, options=['--trust-model', 'always']) except Exception as excep: log.err("Unable to instance GPG object: %s" % excep) raise excep self.receiver_desc = receiver_desc log.debug("GPG object initialized for receiver %s" % receiver_desc['username']) def sanitize_gpg_string(self, received_gpgasc): """ @param received_gpgasc: A gpg armored key @return: Sanitized string or raise InvalidInputFormat This function validate the integrity of a GPG key """ lines = received_gpgasc.split("\n") sanitized = "" start = 0 if not len(lines[start]): start += 1 if lines[start] != '-----BEGIN PGP PUBLIC KEY BLOCK-----': raise errors.InvalidInputFormat("GPG invalid format") else: sanitized += lines[start] + "\n" i = 0 while i < len(lines): # the C language as left some archetypes in my code # [ITA] https://www.youtube.com/watch?v=7jI4DnRJP3k i += 1 try: if len(lines[i]) < 2: continue except IndexError: continue main_content = re.compile( r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", re.UNICODE) base64only = main_content.findall(lines[i]) if len(base64only) == 1: sanitized += str(base64only[0]) + "\n" # this GPG/PGP format it's different from the common base64 ? dunno if len(lines[i]) == 5 and lines[i][0] == '=': sanitized += str(lines[i]) + "\n" if lines[i] == '-----END PGP PUBLIC KEY BLOCK-----': sanitized += lines[i] + "\n" return sanitized raise errors.InvalidInputFormat("Malformed PGP key block") def validate_key(self, armored_key): """ @param armored_key: @return: True or False, True only if a key is effectively importable and listed. """ # or raise InvalidInputFormat sanitized_gpgasc = self.sanitize_gpg_string(armored_key) try: key = self.gpgh.import_keys(sanitized_gpgasc) except Exception as excep: log.err("Error in GPG import_keys: %s" % excep) return False # Error reported in stderr may just be warning, this is because is not raise an exception here # if self.ke.stderr: # log.err("Receiver %s in uploaded GPG key has raise and alarm:\n< %s >" % # (self.receiver_desc['username'], (self.ke.stderr.replace("\n", "\n "))[:-3])) if not (hasattr(key, 'results') and len(key.results) >= 1 and key.results[0].has_key('fingerprint')): log.err("User error: unable to import GPG key in the keyring") return False # else, the key has been loaded and we extract info about that: self.fingerprint = key.results[0]['fingerprint'] # looking if the key is effectively reachable try: all_keys = self.gpgh.list_keys() except Exception as excep: log.err("Error in GPG list_keys: %s" % excep) return False self.keyinfo = u"" for key in all_keys: if key['fingerprint'] == self.fingerprint: self.keyinfo += "Key length %s" % key['length'] try: for uid in key['uids']: self.keyinfo += "\n\t%s" % uid except Exception as excep: log.err("Error in GPG key format/properties: %s" % excep) return False if not len(self.keyinfo): log.err("Key apparently imported but unable to be extracted info") return False return True def encrypt_file(self, plainpath, filestream, output_path): """ @param gpg_key_armor: @param plainpath: @return: """ if not self.validate_key(self.receiver_desc['gpg_key_armor']): raise errors.GPGKeyInvalid encrypt_obj = self.gpgh.encrypt_file( filestream, str(self.receiver_desc['gpg_key_fingerprint'])) if not encrypt_obj.ok: # continue here if is not ok log.err("Falure in encrypting file %s %s (%s)" % (plainpath, self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'])) log.err(encrypt_obj.stderr) raise errors.GPGKeyInvalid log.debug("Encrypting for %s (%s) file %s (%d bytes)" % (self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'], plainpath, len(str(encrypt_obj)))) encrypted_path = os.path.join( os.path.abspath(output_path), "gpg_encrypted-%s" % xeger(r'[A-Za-z0-9]{8}')) if os.path.isfile(encrypted_path): log.err("Unexpected unpredictable unbelievable error! %s" % encrypted_path) raise errors.InternalServerError( "File conflict in GPG encrypted output") try: with open(encrypted_path, "w+") as f: f.write(str(encrypt_obj)) return encrypted_path, len(str(encrypt_obj)) except Exception as excep: log.err("Error in writing GPG file output: %s (%s) bytes %d" % (excep.message, encrypted_path, len(str(encrypt_obj)))) raise errors.InternalServerError("Error in writing [%s]" % excep.message) def encrypt_message(self, plaintext): """ @param plaindata: An arbitrary long text that would be encrypted @param receiver_desc: The output of globaleaks.handlers.admin.admin_serialize_receiver() dictionary. It contain the fingerprint of the Receiver PUBKEY @return: The unicode of the encrypted output (armored) """ if not self.validate_key(self.receiver_desc['gpg_key_armor']): raise errors.GPGKeyInvalid # This second argument may be a list of fingerprint, not just one encrypt_obj = self.gpgh.encrypt( plaintext, str(self.receiver_desc['gpg_key_fingerprint'])) if not encrypt_obj.ok: # else, is not .ok log.err("Falure in encrypting %d bytes for %s (%s)" % (len(plaintext), self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'])) log.err(encrypt_obj.stderr) raise errors.GPGKeyInvalid log.debug( "Encrypting for %s (%s) %d byte of plain data (%d cipher output)" % (self.receiver_desc['username'], self.receiver_desc['gpg_key_fingerprint'], len(plaintext), len(str(encrypt_obj)))) return str(encrypt_obj) def destroy_environment(self): try: shutil.rmtree(self.gpgh.gnupghome) except Exception as excep: log.err("Unable to clean temporary GPG environment: %s: %s" % (self.gpgh.gnupghome, excep))
def setUp(self): """ Construct environment. Here, we adopt the configuration used by the production or development installation and make the necessary changes before writing it to a new file in the etc directory. When we're finished, we'll remove it in the tearDown() function. """ if not os.path.exists(self.client_env['CONFDIR']): # Don't waste time and entropy on keys if we can't write configuration # files -- that's a very distressing experience. assert len(sys.argv) == 2 # Prepare two GPG keypairs; one for the agent, one for the client agent_gpg_dir = os.path.join(self.client_env['CONFDIR'], 'agent_gpg') agent_gpg = GPG(gnupghome=agent_gpg_dir) agent_key = agent_gpg.gen_key(agent_gpg.gen_key_input(**self.gpg_params)) client_gpg_dir = os.path.join(self.client_env['CONFDIR'], 'client_gpg') client_gpg = GPG(gnupghome=client_gpg_dir) client_key = client_gpg.gen_key(client_gpg.gen_key_input(**self.gpg_params)) # Export both public keys; import them into the opposing side agent_key_blob = agent_gpg.export_keys(agent_key.fingerprint) client_gpg.import_keys(agent_key_blob) client_gpg.sign_key(agent_key.fingerprint) client_key_blob = client_gpg.export_keys(client_key.fingerprint) agent_gpg.import_keys(client_key_blob) agent_gpg.sign_key(client_key.fingerprint) # Configure the agent to run in a development-safe configuration. # # Here, we load the base configuration we ship with the application # as the default. All other configuration will be ignored for the # purposes of the test run, since this is outside of our scope. with open(self.agent_env['CONFDIR'], 'w') as f: l = os.path.join(self.client_env['CONFDIR'], 'agent_%s.log') agent_cfg = ConfigurationFactory.get('hypernova.agent', root_dir=sys.argv[1]) agent_cfg.set('server', 'address', self.agent_addr[0]) agent_cfg.set('server', 'port', self.agent_addr[1]) agent_cfg.set('server', 'daemon', 'false') agent_cfg.set('gpg', 'key_store', agent_gpg_dir) agent_cfg.set('gpg', 'fingerprint', agent_key.fingerprint) agent_cfg.set('logging', 'main_log', l %('main')) agent_cfg.set('logging', 'request_log', l %('request')) agent_cfg.set('logging', 'error_log', l %('error')) agent_cfg.write(f) # The client has to use two different configuration files, both in # the same directory. client_cfg_dir = self.client_env['CONFDIR'] # Configure the client to use its temporary key. # # To communicate with the agent (which will be running in a limited # testing mode), we'll need to reconfigure the client with a keypair # we've imported into the agent. This keystore manipulation has # already taken place, so we know the fingerprint of our new private # key. client_cfg_file = os.path.join(client_cfg_dir, 'client.ini') with open(client_cfg_file, 'w') as f: client_cfg = configparser.SafeConfigParser() client_cfg.add_section('client') client_cfg.set('client', 'privkey', client_key.fingerprint) client_cfg.write(f) # Pair the client to the agent. # # We do this manually, since the importer requires that we write the # public key to a file before we import it. This would be a # pointless exercise and an unnecessary complication. client_srv_file = os.path.join(client_cfg_dir, 'servers.ini') with open(client_srv_file, 'w') as f: client_srv_cfg = configparser.SafeConfigParser() client_srv_cfg.add_section('local') client_srv_cfg.set('local', 'addr', ':'.join(self.agent_addr)) client_srv_cfg.set('local', 'pubkey', agent_key.fingerprint) client_srv_cfg.write(f) # TODO: instead of lazily and unreliably falling asleep on the job, # we should probably use a regular expression to check the output. # Time is of the essence, though! # No, we'll use pexpect instead, since it's now shipped as a py3k # dependency. agent_cmd = [ 'hn-agent', self.agent_env['CONFDIR'] ] self.agent_proc = subprocess.Popen(agent_cmd, env=self.agent_env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(self.agent_init_time)
def _check_openpgp_pubkey_from_file(self, pubkey_file, long_key_id): with TemporaryDirectory() as temp_gpg_home: gpg = GPG(gnupghome=temp_gpg_home) with open(pubkey_file, 'rb') as pubkey_fh: import_result = gpg.import_keys(pubkey_fh.read()) if len(import_result.results) == 0: raise Exception( "The OpenPGP file {} contains no OpenPGP keys." " Keys: {}".format( pubkey_file, import_result.results, ) ) logging.info("OK - OpenPGP file {pubkey_file} contains one or more OpenPGP key.".format( pubkey_file=pubkey_file, )) fingerprint = [x['fingerprint'] for x in import_result.results if x.get('fingerprint')][0] actual_long_key_id = fingerprint[-16:] given_long_key_id = re.sub(r'^0x', '', long_key_id) if actual_long_key_id.lower() != given_long_key_id.lower(): raise Exception( textwrap.dedent( """ The OpenPGP file {given_long_key_id} contains a different key than what the file name suggests. Key ID from file name: {given_long_key_id}, Key ID from pubkey in file: {actual_long_key_id} """ ).lstrip().format( given_long_key_id=given_long_key_id, actual_long_key_id=actual_long_key_id, ) ) logging.info( "OK - OpenPGP file {pubkey_file} contains a OpenPGP public key" " whose long key ID matching the file name.".format( pubkey_file=pubkey_file, ) ) list_key = gpg.list_keys()[0] epoch_time = int(time.time()) expires_time = int(list_key['expires']) if self._strict: if expires_time < epoch_time: raise Exception( textwrap.dedent( """ The OpenPGP file {} contains a expired OpenPGP key. Current date: {} Expiration date: {} """ ).lstrip().format( pubkey_file, datetime.fromtimestamp(epoch_time), datetime.fromtimestamp(expires_time), ) ) else: logging.info( "OK - OpenPGP public key from {pubkey_file} is not expired." " Expiration date: {expiration_date}".format( pubkey_file=pubkey_file, expiration_date=datetime.fromtimestamp(expires_time), ) ) # https://keyring.debian.org/creating-key.html if self._strict: if int(list_key['length']) < self._OPENPGP_MIN_KEY_SIZE: raise Exception( textwrap.dedent( """ The OpenPGP file {} contains a weak OpenPGP key. Current key length in bits: {} Expected at least (inclusive): {} """ ).lstrip().format( pubkey_file, list_key['length'], self._OPENPGP_MIN_KEY_SIZE, ) ) else: logging.info( "OK - The key length of the OpenPGP public key from {pubkey_file} is not considered to be weak." " Key length in bits: {key_size}".format( pubkey_file=pubkey_file, key_size=list_key['length'], ) ) return True
class PGPContext(object): def __init__(self, tempdirprefix=None): if tempdirprefix is None: tempdir = tempfile.mkdtemp() else: tempdir = tempfile.mkdtemp(prefix=tempdirprefix) try: self.gnupg = GPG(gnupghome=tempdir, options=['--trust-model', 'always']) self.gnupg.encoding = "UTF-8" except OSError as excep: log.err("Critical, OS error in operating with GnuPG home: %s", excep) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key @return: a dict with the expiration date and the key fingerprint """ try: import_result = self.gnupg.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s", excep) raise errors.InputValidationError if not import_result.fingerprints: raise errors.InputValidationError fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.gnupg.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s", excep) raise errors.InputValidationError expiration = datetime.utcfromtimestamp(0) for k in all_keys: if k['fingerprint'] == fingerprint: if k['expires']: expiration = datetime.utcfromtimestamp(int(k['expires'])) break return {'fingerprint': fingerprint, 'expiration': expiration} def encrypt_file(self, key_fingerprint, input_file, output_path): """ Encrypt a file with the specified PGP key """ encrypted_obj = self.gnupg.encrypt_file(input_file, key_fingerprint, output=output_path) if not encrypted_obj.ok: raise errors.InputValidationError return encrypted_obj, os.stat(output_path).st_size def encrypt_message(self, key_fingerprint, plaintext): """ Encrypt a text message with the specified key """ encrypted_obj = self.gnupg.encrypt(plaintext, key_fingerprint) if not encrypted_obj.ok: raise errors.InputValidationError return str(encrypted_obj) def __del__(self): try: shutil.rmtree(self.gnupg.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s", self.gnupg.gnupghome, excep)
def check_git_commits(self, repo_path='.'): with TemporaryDirectory() as temp_gpg_home: gpg = GPG(gnupghome=temp_gpg_home) for long_key_id in os.listdir(self._keyring_name): with open(os.path.join( self._keyring_name, long_key_id ), 'rb') as pubkey_fh: gpg.import_keys(pubkey_fh.read()) repo = git.Git(repo_path) repo.update_environment(GNUPGHOME=temp_gpg_home) commit_count = 0 # %G?: show "G" for a Good signature, # "B" for a Bad signature, # "U" for a good, untrusted signature and # "N" for no signature # TODO 'N' is also returned for signatures made by expired subkeys. # Seems to be a bug. # Current workaround is only to check the HEAD commit. for log_line in [repo.log('--format=%H %G?').split('\n')[0]]: (commit_hash, signature_check) = log_line.split(' ') commit_count += 1 if signature_check not in ['U', 'G']: raise Exception( "OpenPGP signature of commit could not be verified." "\nAffected commit:\n{}".format( repo.log('-1', commit_hash), ) ) logging.info( textwrap.dedent( """ OK - All commits in the repository '{repo_path}' are signed and all public keys to verify the signatures are contained in current HEAD of this repository. """ ).lstrip().replace('\n', ' ').format( repo_path=repo_path, ) ) if commit_count <= 0: # That condition is expected to never be True because of # "returned with exit code 128" for "fatal: bad default revision 'HEAD'". # Leaving it in just to be sure (in case git becomes more # "friendly" in the future. raise Exception( "Expected at least one git commit." " Found {} commits.".format( commit_count, ) ) else: logging.info( "OK - The repository '{repo_path}' contains at least one commit.".format( repo_path=repo_path, ) ) return True
class TempGPGWrapper(object): """ A context manager that wraps a temporary GPG keyring which only contains the keys given at object creation. """ def __init__(self, keys=None, gpgbinary=None): """ Create an empty temporary keyring and import any given C{keys} into it. :param keys: OpenPGP key, or list of. :type keys: OpenPGPKey or list of OpenPGPKeys :param gpgbinary: Name for GnuPG binary executable. :type gpgbinary: C{str} """ self._gpg = None self._gpgbinary = gpgbinary if not keys: keys = list() if not isinstance(keys, list): keys = [keys] self._keys = keys for key in keys: leap_assert_type(key, OpenPGPKey) def __enter__(self): """ Build and return a GPG keyring containing the keys given on object creation. :return: A GPG instance containing the keys given on object creation. :rtype: gnupg.GPG """ self._build_keyring() return self._gpg def __exit__(self, exc_type, exc_value, traceback): """ Ensure the gpg is properly destroyed. """ # TODO handle exceptions and log here self._destroy_keyring() def _build_keyring(self): """ Create a GPG keyring containing the keys given on object creation. :return: A GPG instance containing the keys given on object creation. :rtype: gnupg.GPG """ privkeys = [key for key in self._keys if key and key.private is True] publkeys = [key for key in self._keys if key and key.private is False] # here we filter out public keys that have a correspondent # private key in the list because the private key_data by # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. privfps = map(lambda privkey: privkey.fingerprint, privkeys) publkeys = filter( lambda pubkey: pubkey.fingerprint not in privfps, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) self._gpg = GPG(binary=self._gpgbinary, homedir=tempfile.mkdtemp()) leap_assert(len(listkeys()) is 0, 'Keyring not empty.') # import keys into the keyring: # concatenating ascii-armored keys, which is correctly # understood by GPG. self._gpg.import_keys("".join( [x.key_data for x in publkeys + privkeys])) # assert the number of keys in the keyring leap_assert( len(listkeys()) == len(publkeys) + len(privkeys), 'Wrong number of public keys in keyring: %d, should be %d)' % (len(listkeys()), len(publkeys) + len(privkeys))) leap_assert( len(listsecretkeys()) == len(privkeys), 'Wrong number of private keys in keyring: %d, should be %d)' % (len(listsecretkeys()), len(privkeys))) def _destroy_keyring(self): """ Securely erase the keyring. """ # TODO: implement some kind of wiping of data or a more # secure way that # does not write to disk. try: for secret in [True, False]: for key in self._gpg.list_keys(secret=secret): self._gpg.delete_keys( key['fingerprint'], secret=secret) leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') except: raise finally: leap_assert(self._gpg.homedir != os.path.expanduser('~/.gnupg'), "watch out! Tried to remove default gnupg home!") shutil.rmtree(self._gpg.homedir)
class PGPContext(object): def __init__(self, tempdirprefix=None): if tempdirprefix is None: tempdir = tempfile.mkdtemp() else: tempdir = tempfile.mkdtemp(prefix=tempdirprefix) try: gpgbinary='gpg' if os.path.exists('/usr/bin/gpg1'): gpgbinary='gpg1' self.gnupg = GPG(gpgbinary=gpgbinary, gnupghome=tempdir, options=['--trust-model', 'always']) self.gnupg.encoding = "UTF-8" except OSError as excep: log.err("Critical, OS error in operating with GnuPG home: %s", excep) raise except Exception as excep: log.err("Unable to instance PGP object: %s" % excep) raise def load_key(self, key): """ @param key @return: a dict with the expiration date and the key fingerprint """ try: import_result = self.gnupg.import_keys(key) except Exception as excep: log.err("Error in PGP import_keys: %s", excep) raise errors.InputValidationError if not import_result.fingerprints: raise errors.InputValidationError fingerprint = import_result.fingerprints[0] # looking if the key is effectively reachable try: all_keys = self.gnupg.list_keys() except Exception as excep: log.err("Error in PGP list_keys: %s", excep) raise errors.InputValidationError expiration = datetime.utcfromtimestamp(0) for k in all_keys: if k['fingerprint'] == fingerprint: if k['expires']: expiration = datetime.utcfromtimestamp(int(k['expires'])) break return { 'fingerprint': fingerprint, 'expiration': expiration } def encrypt_file(self, key_fingerprint, input_file, output_path): """ Encrypt a file with the specified PGP key """ encrypted_obj = self.gnupg.encrypt_file(input_file, str(key_fingerprint), output=output_path) if not encrypted_obj.ok: raise errors.InputValidationError return encrypted_obj, os.stat(output_path).st_size def encrypt_message(self, key_fingerprint, plaintext): """ Encrypt a text message with the specified key """ encrypted_obj = self.gnupg.encrypt(plaintext, str(key_fingerprint)) if not encrypted_obj.ok: raise errors.InputValidationError return str(encrypted_obj) def __del__(self): try: shutil.rmtree(self.gnupg.gnupghome) except Exception as excep: log.err("Unable to clean temporary PGP environment: %s: %s", self.gnupg.gnupghome, excep)