270 lines
8.7 KiB
Python
270 lines
8.7 KiB
Python
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
|
#
|
|
# This file is part of the OpenPGP Gajim Plugin.
|
|
#
|
|
# OpenPGP Gajim Plugin 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; version 3 only.
|
|
#
|
|
# OpenPGP Gajim Plugin 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 OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Iterator
|
|
|
|
from nbxmpp.protocol import JID
|
|
from nbxmpp.structs import PGPKeyMetadata
|
|
|
|
from openpgp.backend.base import BasePGPBackend
|
|
from openpgp.backend.sql import ContactRow
|
|
from openpgp.backend.sql import Storage
|
|
from openpgp.modules.util import Trust
|
|
|
|
log = logging.getLogger("gajim.p.openpgp.store")
|
|
|
|
|
|
class KeyData:
|
|
"""
|
|
Holds all data related to a certain key
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
contact_data: ContactData,
|
|
fingerprint: str,
|
|
active: bool,
|
|
trust: Trust,
|
|
timestamp: float,
|
|
):
|
|
self._contact_data = contact_data
|
|
self.fingerprint = fingerprint
|
|
self.active = active
|
|
self._trust = trust
|
|
self.timestamp = timestamp
|
|
self.comment = None
|
|
self.has_pubkey = False
|
|
|
|
@property
|
|
def trust(self) -> Trust:
|
|
return self._trust
|
|
|
|
@trust.setter
|
|
def trust(self, value: Trust) -> None:
|
|
if value not in (Trust.NOT_TRUSTED, Trust.UNKNOWN, Trust.BLIND, Trust.VERIFIED):
|
|
raise ValueError("Trust value not allowed: %s" % value)
|
|
|
|
self._trust = value
|
|
self._contact_data.set_trust(self.fingerprint, self._trust)
|
|
|
|
def delete(self):
|
|
self._contact_data.delete_key(self.fingerprint)
|
|
|
|
|
|
class ContactData:
|
|
"""
|
|
Holds all data related to a contact
|
|
"""
|
|
|
|
def __init__(self, jid: JID, storage: Storage, pgp: BasePGPBackend) -> None:
|
|
self.jid = jid
|
|
self._key_store: dict[str, KeyData] = {}
|
|
self._storage = storage
|
|
self._pgp = pgp
|
|
|
|
@property
|
|
def userid(self):
|
|
assert self.jid is not None, "JID not set"
|
|
return "xmpp:%s" % self.jid
|
|
|
|
@property
|
|
def default_trust(self) -> Trust:
|
|
for key in self._key_store.values():
|
|
if key.trust in (Trust.NOT_TRUSTED, Trust.BLIND):
|
|
return Trust.UNKNOWN
|
|
return Trust.BLIND
|
|
|
|
def db_values(self) -> Iterator[tuple[JID, str, bool, Trust, float]]:
|
|
for key in self._key_store.values():
|
|
yield (
|
|
self.jid,
|
|
key.fingerprint,
|
|
key.active,
|
|
key.trust,
|
|
key.timestamp,
|
|
)
|
|
|
|
def add_from_key(self, key: PGPKeyMetadata) -> KeyData:
|
|
try:
|
|
keydata = self._key_store[key.fingerprint]
|
|
except KeyError:
|
|
keydata = KeyData(
|
|
self,
|
|
key.fingerprint,
|
|
True,
|
|
self.default_trust,
|
|
key.date,
|
|
)
|
|
self._key_store[key.fingerprint] = keydata
|
|
log.info("Add from key: %s %s", self.jid, keydata.fingerprint)
|
|
return keydata
|
|
|
|
def add_from_db(self, row: ContactRow) -> KeyData:
|
|
try:
|
|
keydata = self._key_store[row.fingerprint]
|
|
except KeyError:
|
|
keydata = KeyData(
|
|
self,
|
|
row.fingerprint,
|
|
row.active,
|
|
row.trust,
|
|
row.timestamp,
|
|
)
|
|
self._key_store[row.fingerprint] = keydata
|
|
log.info("Add from row: %s %s", self.jid, row.fingerprint)
|
|
return keydata
|
|
|
|
def process_keylist(self, keylist: list[PGPKeyMetadata] | None) -> list[str]:
|
|
log.info("Process keylist: %s %s", self.jid, keylist)
|
|
|
|
if keylist is None:
|
|
for keydata in self._key_store.values():
|
|
keydata.active = False
|
|
self._storage.save_contact(self.db_values())
|
|
return []
|
|
|
|
missing_pub_keys: list[str] = []
|
|
fingerprints = {key.fingerprint for key in keylist}
|
|
if fingerprints == self._key_store.keys():
|
|
log.info("No updates found")
|
|
for key in self._key_store.values():
|
|
if not key.has_pubkey:
|
|
missing_pub_keys.append(key.fingerprint)
|
|
return missing_pub_keys
|
|
|
|
for keydata in self._key_store.values():
|
|
keydata.active = False
|
|
|
|
for key in keylist:
|
|
try:
|
|
keydata = self._key_store[key.fingerprint]
|
|
keydata.active = True
|
|
if not keydata.has_pubkey:
|
|
missing_pub_keys.append(keydata.fingerprint)
|
|
except KeyError:
|
|
keydata = self.add_from_key(key)
|
|
missing_pub_keys.append(keydata.fingerprint)
|
|
|
|
self._storage.save_contact(self.db_values())
|
|
return missing_pub_keys
|
|
|
|
def set_public_key(self, fingerprint: str) -> None:
|
|
try:
|
|
keydata = self._key_store[fingerprint]
|
|
except KeyError:
|
|
log.warning(
|
|
"Set public key on unknown fingerprint: %s %s", self.jid, fingerprint
|
|
)
|
|
else:
|
|
keydata.has_pubkey = True
|
|
log.info("Set public key: %s %s", self.jid, fingerprint)
|
|
|
|
def get_keys(self, only_trusted: bool = True) -> list[KeyData]:
|
|
keys = list(self._key_store.values())
|
|
if not only_trusted:
|
|
return keys
|
|
return [
|
|
k for k in keys if k.active and k.trust in (Trust.VERIFIED, Trust.BLIND)
|
|
]
|
|
|
|
def get_key(self, fingerprint: str) -> KeyData | None:
|
|
return self._key_store.get(fingerprint, None)
|
|
|
|
def set_trust(self, fingerprint: str, trust: Trust) -> None:
|
|
self._storage.set_trust(self.jid, fingerprint, trust)
|
|
|
|
def delete_key(self, fingerprint: str) -> None:
|
|
self._storage.delete_key(self.jid, fingerprint)
|
|
self._pgp.delete_key(fingerprint)
|
|
del self._key_store[fingerprint]
|
|
|
|
|
|
class PGPContacts:
|
|
"""
|
|
Holds all contacts available for PGP encryption
|
|
"""
|
|
|
|
def __init__(self, pgp: BasePGPBackend, storage: Storage) -> None:
|
|
self._contacts: dict[JID, ContactData] = {}
|
|
self._storage = storage
|
|
self._pgp = pgp
|
|
self._load_from_storage()
|
|
self._load_from_keyring()
|
|
|
|
def _load_from_keyring(self):
|
|
log.info("Load keys from keyring")
|
|
keyring = self._pgp.get_keys()
|
|
for key in keyring:
|
|
log.info("Found: %s %s", key.jid, key.fingerprint)
|
|
assert key.jid is not None
|
|
self.set_public_key(key.jid, key.fingerprint)
|
|
|
|
def _load_from_storage(self):
|
|
log.info("Load contacts from storage")
|
|
rows = self._storage.load_contacts()
|
|
for row in rows:
|
|
log.info("Found: %s %s", row.jid, row.fingerprint)
|
|
try:
|
|
contact_data = self._contacts[row.jid]
|
|
except KeyError:
|
|
contact_data = ContactData(row.jid, self._storage, self._pgp)
|
|
contact_data.add_from_db(row)
|
|
self._contacts[row.jid] = contact_data
|
|
else:
|
|
contact_data.add_from_db(row)
|
|
|
|
def process_keylist(
|
|
self, jid: JID, keylist: list[PGPKeyMetadata] | None
|
|
) -> list[str]:
|
|
try:
|
|
contact_data = self._contacts[jid]
|
|
except KeyError:
|
|
contact_data = ContactData(jid, self._storage, self._pgp)
|
|
missing_pub_keys = contact_data.process_keylist(keylist)
|
|
self._contacts[jid] = contact_data
|
|
else:
|
|
missing_pub_keys = contact_data.process_keylist(keylist)
|
|
|
|
return missing_pub_keys
|
|
|
|
def set_public_key(self, jid: JID, fingerprint: str) -> None:
|
|
try:
|
|
contact_data = self._contacts[jid]
|
|
except KeyError:
|
|
log.warning("ContactData not found: %s %s", jid, fingerprint)
|
|
else:
|
|
contact_data.set_public_key(fingerprint)
|
|
|
|
def get_keys(self, jid: JID, only_trusted: bool = True) -> list[KeyData]:
|
|
try:
|
|
contact_data = self._contacts[jid]
|
|
return contact_data.get_keys(only_trusted=only_trusted)
|
|
except KeyError:
|
|
return []
|
|
|
|
def get_trust(self, jid: JID, fingerprint: str) -> Trust:
|
|
contact_data = self._contacts.get(jid, None)
|
|
if contact_data is None:
|
|
return Trust.UNKNOWN
|
|
|
|
key = contact_data.get_key(fingerprint)
|
|
if key is None:
|
|
return Trust.UNKNOWN
|
|
return key.trust
|