plugin XEP-0473: Decrypt items events when received:
The items events need to be decrypted too for plugin to handle correctly items updated or retracted. When an event is decrypted, and `encrypted` attribute is added to the decrypted <item> elements. rel 475
This commit is contained in:
parent
c21ac3af16
commit
0bc56134ad
1 changed files with 91 additions and 35 deletions
|
|
@ -28,7 +28,7 @@ from twisted.internet import defer
|
|||
from twisted.words.protocols.jabber import jid, xmlstream
|
||||
from twisted.words.xish import domish
|
||||
from wokkel import disco, iwokkel
|
||||
from wokkel import rsm
|
||||
from wokkel import rsm, pubsub
|
||||
from zope.interface import implementer
|
||||
|
||||
from libervia.backend.core import exceptions
|
||||
|
|
@ -37,6 +37,7 @@ from libervia.backend.core.core_types import SatXMPPEntity
|
|||
from libervia.backend.core.i18n import _
|
||||
from libervia.backend.core.log import getLogger
|
||||
from libervia.backend.memory import persistent
|
||||
from libervia.backend.plugins.plugin_xep_0373.exceptions import DecryptionFailed
|
||||
from libervia.backend.tools import utils
|
||||
from libervia.backend.tools import xml_tools
|
||||
from libervia.backend.tools.common import data_format
|
||||
|
|
@ -44,7 +45,6 @@ from libervia.backend.tools.common import uri
|
|||
|
||||
from .plugin_xep_0373 import NS_OX, get_gpg_provider
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
IMPORT_NAME = "XEP-0473"
|
||||
|
|
@ -573,7 +573,7 @@ class XEP_0473:
|
|||
revoked = C.bool(shared_secret_elt.getAttribute("revoked", C.BOOL_FALSE))
|
||||
except (KeyError, RuntimeError, ValueError) as e:
|
||||
log.warning(
|
||||
f"ignoring invalid <shared-secret> element: "
|
||||
"ignoring invalid <shared-secret> element: "
|
||||
f"{e}\n{shared_secret_elt.toXml()}"
|
||||
)
|
||||
return
|
||||
|
|
@ -682,49 +682,103 @@ class XEP_0473:
|
|||
return True
|
||||
if service is None:
|
||||
service = client.jid.userhostJID()
|
||||
shared_secrets = None
|
||||
for item in items:
|
||||
payload = item.firstChildElement()
|
||||
node_uri = uri.build_xmpp_uri("pubsub", path=service.full(), node=node)
|
||||
shared_secrets = await self.load_secrets(client, node_uri)
|
||||
if shared_secrets is None:
|
||||
log.warning(f"No known shared secret for {node_uri}, can't decrypt")
|
||||
return True
|
||||
for item_elt in items:
|
||||
payload = item_elt.firstChildElement()
|
||||
if (
|
||||
payload is not None
|
||||
and payload.name == "encrypted"
|
||||
and payload.uri == NS_OXPS
|
||||
):
|
||||
encrypted_elt = payload
|
||||
secret_id = encrypted_elt.getAttribute("key")
|
||||
if not secret_id:
|
||||
log.warning(
|
||||
f'"key" attribute is missing from encrypted item: {item.toXml()}'
|
||||
)
|
||||
continue
|
||||
if shared_secrets is None:
|
||||
node_uri = uri.build_xmpp_uri(
|
||||
"pubsub", path=service.full(), node=node
|
||||
)
|
||||
shared_secrets = await self.load_secrets(client, node_uri)
|
||||
if shared_secrets is None:
|
||||
log.warning(
|
||||
f"No known shared secret for {node_uri}, can't decrypt"
|
||||
)
|
||||
return True
|
||||
try:
|
||||
shared_secret = shared_secrets[secret_id]
|
||||
except KeyError:
|
||||
log.warning(
|
||||
f"No key known for encrypted item {item['id']!r} (shared secret "
|
||||
f"id: {secret_id!r})"
|
||||
decrypted_elt = self.decrypt_payload(
|
||||
encrypted_elt, shared_secrets, item_elt["id"]
|
||||
)
|
||||
except DecryptionFailed as e:
|
||||
log.warning(f"Can't decrypt item: {e}\n{item_elt.toXml()}")
|
||||
continue
|
||||
log.debug(f"decrypting item {item.getAttribute('id', '')}")
|
||||
decrypted = self.gpg_provider.decrypt_symmetrically(
|
||||
base64.b64decode(str(encrypted_elt)), shared_secret.key
|
||||
)
|
||||
decrypted_elt = xml_tools.parse(decrypted)
|
||||
item.children.clear()
|
||||
item.addChild(decrypted_elt)
|
||||
extra.setdefault("encrypted", {})[item["id"]] = {"type": NS_OXPS}
|
||||
item_elt.children.clear()
|
||||
item_elt.addChild(decrypted_elt)
|
||||
extra.setdefault("encrypted", {})[item_elt["id"]] = {"type": NS_OXPS}
|
||||
return True
|
||||
|
||||
def decrypt_payload(
|
||||
self,
|
||||
encrypted_elt: domish.Element,
|
||||
shared_secrets: dict[str, SharedSecret],
|
||||
item_id: str | None = None,
|
||||
) -> domish.Element:
|
||||
"""Decrypt an encrypted element payload.
|
||||
|
||||
@param encrypted_elt: the <encrypted/> element to decrypt
|
||||
@param secret_id: ID of the shared secret to use for decryption
|
||||
@param shared_secrets: shared secrets dictionary
|
||||
@param item_id: optional ID of the item being decrypted (for logging)
|
||||
@return: decrypted DOM element
|
||||
@raise DecryptionFailed: Can't decrypt the payload due to missing or incorrect
|
||||
data.
|
||||
"""
|
||||
secret_id = encrypted_elt.getAttribute("key")
|
||||
if not secret_id:
|
||||
raise DecryptionFailed(
|
||||
f'"key" attribute is missing from encrypted item {item_id!r}'
|
||||
)
|
||||
try:
|
||||
shared_secret = shared_secrets[secret_id]
|
||||
except KeyError:
|
||||
raise DecryptionFailed(
|
||||
f"No key known for encrypted item {item_id!r} (shared secret "
|
||||
f"id: {secret_id!r})"
|
||||
)
|
||||
log.debug(f"decrypting item {item_id}")
|
||||
decrypted = self.gpg_provider.decrypt_symmetrically(
|
||||
base64.b64decode(str(encrypted_elt)), shared_secret.key
|
||||
)
|
||||
return xml_tools.parse(decrypted)
|
||||
|
||||
async def decrypt_pubsub_event(
|
||||
self, client: SatXMPPEntity, pubsub_service: jid.JID, message_elt: domish.Element
|
||||
) -> None:
|
||||
"""Decrypt pubsub events in place."""
|
||||
event_elt = next(message_elt.elements(pubsub.NS_PUBSUB_EVENT, "event"), None)
|
||||
if event_elt is None:
|
||||
return
|
||||
items_elt = next(event_elt.elements(pubsub.NS_PUBSUB_EVENT, "items"), None)
|
||||
if items_elt is None:
|
||||
return
|
||||
node = items_elt.getAttribute("node")
|
||||
if not node:
|
||||
log.warning(
|
||||
f"Invalid event's items element, missing node: {items_elt.toXml()}"
|
||||
)
|
||||
return
|
||||
node_uri = uri.build_xmpp_uri("pubsub", path=pubsub_service.full(), node=node)
|
||||
shared_secrets = await self.load_secrets(client, node_uri)
|
||||
if shared_secrets is None:
|
||||
log.warning(f"No known shared secret for {node_uri}, can't decrypt")
|
||||
return
|
||||
for item_elt in items_elt.elements(pubsub.NS_PUBSUB_EVENT, "item"):
|
||||
encrypted_elt = next(item_elt.elements(NS_OXPS, "encrypted"), None)
|
||||
if encrypted_elt is None:
|
||||
continue
|
||||
try:
|
||||
decrypted_elt = self.decrypt_payload(
|
||||
encrypted_elt, shared_secrets, item_elt["id"]
|
||||
)
|
||||
except DecryptionFailed as e:
|
||||
log.warning(f"Can't decrypt event: {e}\n{event_elt.toXml()}")
|
||||
continue
|
||||
item_elt.children.clear()
|
||||
item_elt.addChild(decrypted_elt)
|
||||
# It's not great to create an attribute on "item_elt", but we have not many
|
||||
# options to indicate that the item was encrypted.
|
||||
item_elt.encrypted = {"type": NS_OXPS}
|
||||
|
||||
async def _message_received_trigger(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
|
|
@ -732,6 +786,8 @@ class XEP_0473:
|
|||
post_treat: defer.Deferred,
|
||||
) -> bool:
|
||||
sender = jid.JID(message_elt["from"]).userhostJID()
|
||||
await self.decrypt_pubsub_event(client, sender, message_elt)
|
||||
|
||||
# there may be an openpgp element if OXIM is not activate, in this case we have to
|
||||
# decrypt it here
|
||||
openpgp_elt = next(message_elt.elements(NS_OX, "openpgp"), None)
|
||||
|
|
|
|||
Loading…
Reference in a new issue