feedwarrior

Slim, portable tooling for creating and distributing decentralized append logs
git clone git://git.defalsify.org/logwarrior.git
Info | Log | Files | Refs | README | LICENSE

commit 18357cb1370c41825ea139b1e8b62d515b29146f
parent bfd5f5318e94d212bfd52fb4bc69bd84c2e332e5
Author: lash <dev@holbrook.no>
Date:   Fri,  7 Nov 2025 04:31:31 +0000

Enable signing of entries

Diffstat:
Mfeedwarrior/cmd/add.py | 15++++++++++++++-
Mfeedwarrior/cmd/entry.py | 13+++++++++++++
Afeedwarrior/crypto.py | 30++++++++++++++++++++++++++++++
Mfeedwarrior/entry.py | 6++++++
Mfeedwarrior/error.py | 4++++
5 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/feedwarrior/cmd/add.py b/feedwarrior/cmd/add.py @@ -11,6 +11,7 @@ import gzip import tempfile import base64 import uuid +import getpass # local imports import feedwarrior @@ -18,6 +19,7 @@ from feedwarrior import entry as feedentry from feedwarrior.adapters import fileadapter from feedwarrior.entry import extension from feedwarrior.common import task_ids_to_uuids, check_task_uuids +from feedwarrior.crypto import PGPSigner logg = logging.getLogger() @@ -30,6 +32,7 @@ def parse_args(argparser): argparser.add_argument('--task-id', dest='task_id', type=int, action='append', help='add taskwarrior task id relations translated to uuis (cannot be used with --task-uuid') argparser.add_argument('--task-uuid', dest='task_uuid', type=str, action='append', help='add taskwarrior task uuid relations (cannot be used with --task-id') argparser.add_argument('-s', type=str, help='entry subject') + argparser.add_argument('-y', '--sign', dest='y', action='store_true', help='sign entry with pgp key') return True @@ -39,6 +42,10 @@ def check_args(args): # TODO: move logic to package to get symmetry with the show.py logic def execute(config, feed, args): + signer = None + if args.y: + signer = PGPSigner() + task_uuids = [] if args.task_id != None: task_uuids += task_ids_to_uuids(config.task_dir, args.task_id) @@ -88,8 +95,14 @@ def execute(config, feed, args): uu = str(entry.uuid) logg.debug('adding entry {}'.format(uu)) + if args.y: + passphrase = getpass.getpass('passphrase: ') + if len(passphrase) == 0: + passphrase = None + signer.sign(entry, passphrase=passphrase) + fa = fileadapter(config.data_dir, feed.uuid) fa.put(entry.uuid, entry, compress=args.z) - + feed.add(entry) return str(entry.uuid) diff --git a/feedwarrior/cmd/entry.py b/feedwarrior/cmd/entry.py @@ -5,6 +5,7 @@ import logging import uuid import json import gzip +import getpass # local imports import feedwarrior @@ -12,6 +13,7 @@ from feedwarrior import entry as feedentry from feedwarrior.adapters import fileadapter from feedwarrior.common import task_ids_to_uuids, check_task_uuids from feedwarrior.error import NotMultipartError +from feedwarrior.crypto import PGPSigner logg = logging.getLogger() @@ -20,6 +22,7 @@ def parse_args(argparser): argparser.add_argument('-z', action='store_true', help='compress entry with gzip') argparser.add_argument('--task-id', dest='task_id', type=int, action='append', help='add taskwarrior task id relations translated to uuis (cannot be used with --task-uuid') argparser.add_argument('--task-uuid', dest='task_uuid', type=str, action='append', help='add taskwarrior task uuid relations (cannot be used with --task-id') + argparser.add_argument('-y', '--sign', dest='y', action='store_true', help='sign entry with pgp key') argparser.add_argument('path', help='multipart file to use for content') return True @@ -30,6 +33,10 @@ def check_args(args): # TODO: move logic to package to get symmetry with the show.py logic def execute(config, feed, args): + signer = None + if args.y: + signer = PGPSigner() + task_uuids = [] if args.task_id != None: task_uuids += task_ids_to_uuids(config.task_dir, args.task_id) @@ -51,6 +58,12 @@ def execute(config, feed, args): uu = str(entry.uuid) logg.debug('adding entry {}'.format(uu)) + if args.y: + passphrase = getpass.getpass('passphrase: ') + if len(passphrase) == 0: + passphrase = None + signer.sign(entry, passphrase=passphrase) + fa = fileadapter(config.data_dir, feed.uuid) fa.put(entry.uuid, entry, compress=args.z) diff --git a/feedwarrior/crypto.py b/feedwarrior/crypto.py @@ -0,0 +1,30 @@ +import logging + +import gnupg + +logg = logging.getLogger(__name__) +logging.getLogger('gnupg').setLevel(logging.ERROR) +#logging.getLogger('gnupg').setLevel(logging.DEBUG) + + +class PGPSigner: + + def __init__(self, home_dir=None, default_key=None, passphrase=None, use_agent=False, skip_verify=False): + self.gpg = gnupg.GPG(gnupghome=home_dir) + self.default_key = default_key + self.passphrase = passphrase + self.use_agent = use_agent + self.sign_material = None + self.pre_buffer = [] + self.__skip_verify = skip_verify + + + def sign(self, entry, passphrase=None): # msg = IssueMessage object + if passphrase == None: + passphrase = self.passphrase + s = entry.serialize() + sig = self.gpg.sign(s['payload'], keyid=self.default_key, detach=True, passphrase=passphrase) + if sig.status != 'signature created': + logg.error('signature failure: ' + sig.status) + raise SignError() + entry.set_signature(str(sig)) diff --git a/feedwarrior/entry.py b/feedwarrior/entry.py @@ -32,6 +32,7 @@ class entry: self.uuid = uu self.message = message self.extensions = {} + self.signature = None def add_extension(self, k, v): @@ -65,9 +66,14 @@ class entry: 'uuid': str(self.uuid), 'timestamp': int(ts), 'payload': self.message.as_string(), + 'signature': repr(self.signature), } + def set_signature(self, v): + self.signature = v + + def to_multipart_file(path): #d = tempfile.TemporaryDirectory() #t = tempfile.NamedTemporaryFile(mode='w+', dir=d.name) diff --git a/feedwarrior/error.py b/feedwarrior/error.py @@ -1,2 +1,6 @@ class NotMultipartError(Exception): pass + + +class SignError(Exception): + pass