[openpgp] Port to Gtk4

This commit is contained in:
Philipp Hörist
2025-02-08 22:09:33 +01:00
parent 3a5816259c
commit c67e7f341b
19 changed files with 935 additions and 1111 deletions
+56 -75
View File
@@ -14,34 +14,29 @@
# 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 typing import Any
from typing import cast
import logging
from collections.abc import Sequence
from pathlib import Path
import gpg
from gpg.errors import KeyNotFound
from gpg.results import ImportResult
from nbxmpp.protocol import JID
from openpgp.backend.base import BaseKeyringItem
from openpgp.backend.base import BasePGPBackend
from openpgp.backend.gpgme_types import Key
from openpgp.backend.util import parse_uid
from openpgp.modules.key_store import KeyData
from openpgp.modules.util import DecryptionFailed
log = logging.getLogger("gajim.p.openpgp.gpgme")
class KeyringItem:
def __init__(self, key):
self._key = key
self._uid = self._get_uid()
@property
def is_xmpp_key(self) -> bool:
try:
return self.jid is not None
except Exception:
return False
def is_valid(self, jid: JID) -> bool:
if not self.is_xmpp_key:
return False
return jid == self.jid
class KeyringItem(BaseKeyringItem):
def _get_uid(self) -> str | None:
for uid in self._key.uids:
@@ -51,36 +46,22 @@ class KeyringItem:
pass
@property
def fingerprint(self):
def fingerprint(self) -> str:
return self._key.fpr
@property
def uid(self):
if self._uid is not None:
return self._uid
@property
def jid(self):
if self._uid is not None:
return JID.from_string(self._uid)
def __hash__(self):
return hash(self.fingerprint)
class GPGME:
def __init__(self, jid, gnuhome):
class GPGMe(BasePGPBackend):
def __init__(self, jid: str, gnuhome: Path) -> None:
self._jid = jid
self._context_args = {
"home_dir": str(gnuhome),
"offline": True,
"armor": False,
}
self._home_dir = str(gnuhome)
def generate_key(self):
with gpg.Context(**self._context_args) as context:
def _get_context(self) -> gpg.Context:
return gpg.Context(armor=False, offline=True, home_dir=self._home_dir)
def generate_key(self) -> None:
with self._get_context() as context:
result = context.create_key(
f"xmpp:{str(self._jid)}",
f"xmpp:{self._jid}",
algorithm="default",
expires=False,
passphrase=None,
@@ -89,11 +70,11 @@ class GPGME:
log.info("Generated new key: %s", result.fpr)
def get_key(self, fingerprint):
with gpg.Context(**self._context_args) as context:
def _get_key(self, fingerprint: str) -> Key | None:
with self._get_context() as context:
try:
key = context.get_key(fingerprint)
except gpg.errors.KeyNotFound as error:
return cast(Key, context.get_key(fingerprint))
except KeyNotFound as error:
log.warning("key not found: %s", error.keystr)
return
@@ -101,11 +82,9 @@ class GPGME:
log.warning("get_key() error: %s", error)
return
return key
def get_own_key_details(self):
with gpg.Context(**self._context_args) as context:
keys = list(context.keylist(secret=True))
def get_own_key_details(self) -> tuple[str | None, int | None]:
with self._get_context() as context:
keys = cast(list[Key], list(context.keylist(secret=True)))
if not keys:
return None, None
@@ -116,10 +95,10 @@ class GPGME:
return None, None
def get_keys(self):
keys = []
with gpg.Context(**self._context_args) as context:
for key in context.keylist():
def get_keys(self) -> Sequence[KeyringItem]:
keys: list[KeyringItem] = []
with self._get_context() as context:
for key in context.keylist(secret=False):
keyring_item = KeyringItem(key)
if not keyring_item.is_xmpp_key:
log.warning("Key not suited for xmpp: %s", key.fpr)
@@ -130,10 +109,9 @@ class GPGME:
return keys
def export_key(self, fingerprint):
with gpg.Context(**self._context_args) as context:
key = context.key_export_minimal(pattern=fingerprint)
return key
def export_key(self, fingerprint: str) -> bytes | None:
with self._get_context() as context:
return context.key_export_minimal(pattern=fingerprint)
# def encrypt_decrypt_files(self):
# c = gpg.Context()
@@ -149,9 +127,11 @@ class GPGME:
# with open('foo2.txt', 'w') as output_file:
# c.decrypt(input_file, output_file)
def encrypt(self, plaintext, keys):
recipients = []
with gpg.Context(**self._context_args) as context:
def encrypt(
self, payload: bytes, keys: list[KeyData]
) -> tuple[bytes | None, str | None]:
recipients: list[Any] = []
with self._get_context() as context:
for key in keys:
key = context.get_key(key.fingerprint)
if key is not None:
@@ -160,18 +140,18 @@ class GPGME:
if not recipients:
return None, "No keys found to encrypt to"
with gpg.Context(**self._context_args) as context:
result = context.encrypt(
str(plaintext).encode(), recipients, always_trust=True
)
with self._get_context() as context:
result = context.encrypt(payload, recipients, always_trust=True)
ciphertext, result, _sign_result = result
return ciphertext, None
ciphertext, result, _sign_result = result
return ciphertext, None
def decrypt(self, ciphertext):
with gpg.Context(**self._context_args) as context:
raise RuntimeError
def decrypt(self, payload: bytes) -> tuple[str, str]:
with self._get_context() as context:
try:
result = context.decrypt(ciphertext)
result = context.decrypt(payload)
except Exception as error:
raise DecryptionFailed("Decryption failed: %s" % error)
@@ -186,9 +166,9 @@ class GPGME:
return plaintext, fingerprints[0]
def import_key(self, data, jid):
def import_key(self, data: bytes, jid: JID) -> KeyringItem | None:
log.info("Import key from %s", jid)
with gpg.Context(**self._context_args) as context:
with self._get_context() as context:
result = context.key_import(data)
if not isinstance(result, ImportResult) or result.imported != 1:
log.error("Key import failed: %s", jid)
@@ -196,7 +176,7 @@ class GPGME:
return
fingerprint = result.imports[0].fpr
key = self.get_key(fingerprint)
key = self._get_key(fingerprint)
item = KeyringItem(key)
if not item.is_valid(jid):
log.warning("Invalid key found")
@@ -206,8 +186,9 @@ class GPGME:
return item
def delete_key(self, fingerprint):
def delete_key(self, fingerprint: str) -> None:
log.info("Delete Key: %s", fingerprint)
key = self.get_key(fingerprint)
with gpg.Context(**self._context_args) as context:
key = self._get_key(fingerprint)
assert key is not None
with self._get_context() as context:
context.op_delete(key, True)