All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/3] Return better result than just pass/fail
@ 2021-05-12 16:37 Konstantin Ryabitsev
  2021-05-12 16:37 ` [PATCH 2/3] Support other git dirs as sources Konstantin Ryabitsev
  2021-05-12 16:37 ` [PATCH 3/3] Release as v0.2.0 Konstantin Ryabitsev
  0 siblings, 2 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-12 16:37 UTC (permalink / raw)
  To: signatures

We want to pass some better information about why verification failed,
if only because "we don't have a key" is not nearly as bad as "we have a
key and it actively failed verification".

Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
---
 patatt/__init__.py | 50 ++++++++++++++++++++++++++++++----------------
 1 file changed, 33 insertions(+), 17 deletions(-)

diff --git a/patatt/__init__.py b/patatt/__init__.py
index 9ceaa29..be32e36 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -33,6 +33,13 @@ GPGBIN = 'gpg'
 # Hardcoded defaults
 DEVSIG_HDR = b'X-Developer-Signature'
 DEVKEY_HDR = b'X-Developer-Key'
+
+# Result and severity levels
+RES_VALID = 0
+RES_NOKEY = 8
+RES_ERROR = 16
+RES_BADSIG = 32
+
 REQ_HDRS = [b'from', b'subject']
 DEFAULT_CONFIG = {
     'keyringsrc': ['ref::.keys', 'ref::.local-keys', 'ref:refs/meta/keyring:'],
@@ -862,7 +869,7 @@ def validate_message(msgdata: bytes, sources: list, trim_body: bool = False) ->
             algo = 'openpgp'
         else:
             errors.append('%s/%s Unknown algorigthm: %s' % (i, s, a))
-            attestations.append((False, i, t, None, a, errors))
+            attestations.append((RES_ERROR, i, t, None, a, errors))
             continue
 
         pkey = keysrc = None
@@ -875,15 +882,15 @@ def validate_message(msgdata: bytes, sources: list, trim_body: bool = False) ->
 
         if not pkey and algo == 'ed25519':
             errors.append('%s/%s no matching ed25519 key found' % (i, s))
-            attestations.append((False, i, t, None, algo, errors))
+            attestations.append((RES_NOKEY, i, t, None, algo, errors))
             continue
 
         try:
             signtime = pm.validate(i, pkey, trim_body=trim_body)
-            attestations.append((True, i, signtime, keysrc, algo, errors))
+            attestations.append((RES_VALID, i, signtime, keysrc, algo, errors))
         except ValidationError:
             errors.append('failed to validate using %s' % keysrc)
-            attestations.append((False, i, t, keysrc, algo, errors))
+            attestations.append((RES_BADSIG, i, t, keysrc, algo, errors))
 
     return attestations
 
@@ -916,29 +923,38 @@ def cmd_validate(cmdargs, config: dict):
     else:
         trim_body = False
 
-    allgood = True
+    highest_err = 0
     for fn, msgdata in messages.items():
         try:
             attestations = validate_message(msgdata, sources, trim_body=trim_body)
-            for passing, identity, signtime, keysrc, algo, errors in attestations:
-                if passing:
-                    logger.critical('PASS | %s | %s', identity, fn)
+            for result, identity, signtime, keysrc, algo, errors in attestations:
+                if result > highest_err:
+                    highest_err = result
+
+                if result == RES_VALID:
+                    logger.critical('  PASS | %s, %s', identity, fn)
                     if keysrc:
-                        logger.info('     | key: %s', keysrc)
+                        logger.info('       | key: %s', keysrc)
                     else:
-                        logger.info('     | key: default GnuPG keyring')
+                        logger.info('       | key: default GnuPG keyring')
+                elif result <= RES_NOKEY:
+                    logger.critical(' NOKEY | %s, %s', identity, fn)
+                    for error in errors:
+                        logger.critical('       | %s', error)
+                elif result <= RES_ERROR:
+                    logger.critical(' ERROR | %s, %s', identity, fn)
+                    for error in errors:
+                        logger.critical('       | %s', error)
                 else:
-                    allgood = False
-                    logger.critical('FAIL | %s | %s', identity, fn)
+                    logger.critical('BADSIG | %s, %s', identity, fn)
                     for error in errors:
-                        logger.critical('     | %s', error)
+                        logger.critical('       | %s', error)
 
         except RuntimeError as ex:
-            allgood = False
-            logger.critical('ERR  | err: %s | %s', ex, fn)
+            highest_err = RES_ERROR
+            logger.critical(' ERROR | err: %s | %s', ex, fn)
 
-    if not allgood:
-        sys.exit(1)
+    sys.exit(highest_err)
 
 
 def cmd_genkey(cmdargs, config: dict) -> None:
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/3] Support other git dirs as sources
  2021-05-12 16:37 [PATCH 1/3] Return better result than just pass/fail Konstantin Ryabitsev
@ 2021-05-12 16:37 ` Konstantin Ryabitsev
  2021-05-12 16:37 ` [PATCH 3/3] Release as v0.2.0 Konstantin Ryabitsev
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-12 16:37 UTC (permalink / raw)
  To: signatures

We need to have a way to specify other git dirs as sources, so change
how our ref: locations work. Instead of:

ref:[refname]:[subpath]

we now have:

ref:[repopath]:[refname]:[subpath]

Additionally, add a way to deal with one level of symlinks.

Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
---
 README.rst         | 22 ++++++-------
 man/patatt.5       |  2 +-
 man/patatt.5.rst   |  4 +--
 patatt/__init__.py | 79 ++++++++++++++++++++++++++++------------------
 4 files changed, 63 insertions(+), 44 deletions(-)

diff --git a/README.rst b/README.rst
index e0272b8..6cde177 100644
--- a/README.rst
+++ b/README.rst
@@ -368,12 +368,12 @@ check, via the ``keyringsrc`` setting (can be specified multiple
 times and will be checked in the listed order)::
 
     [patatt]
-        # Empty ref means "use currently checked out ref"
-        keyringsrc = ref::.keys
-        # Use a dedicated ref called refs/meta/keyring
-        keyringsrc = ref:refs/meta/keyring:
-        # You can fetch other project's keyring into its own ref
-        keyringsrc = ref:refs/meta/someone-elses-keyring:
+        # Empty ref means "use currently checked out ref in this repo"
+        keyringsrc = ref:::.keys
+        # Use a dedicated ref in this repo called refs/meta/keyring
+        keyringsrc = ref::refs/meta/keyring:
+        # Use a ref in a different repo
+        keyringsrc = ref:~/path/to/another/repo:refs/heads/main:.keys
         # Use a regular dir on disk
         keyringsrc = ~/git/pgpkeys/keyring
 
@@ -385,13 +385,13 @@ Any path on disk can be used for a keyring location, and some will
 always be checked just in case. The following locations are added by
 default::
 
-    ref::.keys
-    ref::.local-keys
-    ref:refs/meta/keyring:
+    ref:::.keys
+    ref:::.local-keys
+    ref::refs/meta/keyring:
     $XDG_DATA_HOME/patatt/public
 
-The "::" means "whatever ref is currently checked out", and
-$XDG_DATA_HOME usually points at ~/.local/share.
+The ":::" means "whatever ref is checked out in the current repo",
+and $XDG_DATA_HOME usually points at $HOME/.local/share.
 
 Getting support and contributing patches
 ----------------------------------------
diff --git a/man/patatt.5 b/man/patatt.5
index 5e97753..70cea05 100644
--- a/man/patatt.5
+++ b/man/patatt.5
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH PATATT 5 "2021-05-07" "0.1.0" ""
+.TH PATATT 5 "2021-05-11" "0.2.0" ""
 .SH NAME
 PATATT \- DKIM-like cryptographic patch attestation
 .
diff --git a/man/patatt.5.rst b/man/patatt.5.rst
index f607ed9..2ab345c 100644
--- a/man/patatt.5.rst
+++ b/man/patatt.5.rst
@@ -5,10 +5,10 @@ DKIM-like cryptographic patch attestation
 -----------------------------------------
 
 :Author:    mricon@kernel.org
-:Date:      2021-05-07
+:Date:      2021-05-11
 :Copyright: The Linux Foundation and contributors
 :License:   MIT-0
-:Version:   0.1.0
+:Version:   0.2.0
 :Manual section: 5
 
 SYNOPSIS
diff --git a/patatt/__init__.py b/patatt/__init__.py
index be32e36..e547b28 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -41,9 +41,6 @@ RES_ERROR = 16
 RES_BADSIG = 32
 
 REQ_HDRS = [b'from', b'subject']
-DEFAULT_CONFIG = {
-    'keyringsrc': ['ref::.keys', 'ref::.local-keys', 'ref:refs/meta/keyring:'],
-}
 
 # Quick cache for key info
 KEYCACHE = dict()
@@ -598,9 +595,9 @@ def _run_command(cmdargs: list, stdin: bytes = None, env: Optional[dict] = None)
 def git_run_command(gitdir: Optional[str], args: list, stdin: Optional[bytes] = None,
                     env: Optional[dict] = None) -> Tuple[int, bytes, bytes]:
     if gitdir:
-        env = {'GIT_DIR': gitdir}
-
-    args = ['git', '--no-pager'] + args
+        args = ['git', '--git-dir', gitdir, '--no-pager'] + args
+    else:
+        args = ['git', '--no-pager'] + args
     return _run_command(args, stdin=stdin, env=env)
 
 
@@ -688,36 +685,55 @@ def get_public_key(source: str, keytype: str, identity: str, selector: str) -> T
     keypath = make_pkey_path(keytype, identity, selector)
     logger.debug('Looking for %s in %s', keypath, source)
 
-    if source.find('ref:') == 0:
-        gittop = get_git_toplevel()
+    # ref:refs/heads/someref:in-repo/path
+    if source.startswith('ref:'):
+        # split by :
+        parts = source.split(':', 4)
+        if len(parts) < 4:
+            raise ConfigurationError('Invalid ref, must have at least 3 colons: %s' % source)
+        gittop = parts[1]
+        gitref = parts[2]
+        gitsub = parts[3]
+        if not gittop:
+            gittop = get_git_toplevel()
         if not gittop:
             raise KeyError('Not in a git tree, so cannot use a ref: source')
-        # format is: ref:refspec:path
-        # or it could omit the refspec, meaning "whatever the current ref"
-        # but it should always have at least two ":"
-        chunks = source.split(':', 2)
-        if len(chunks) < 3:
-            logger.debug('ref: sources must have refspec and path, e.g.: ref:refs/heads/master:.keys')
-            raise ConfigurationError('Invalid ref: source: %s' % source)
+
+        gittop = os.path.expanduser(gittop)
+        if gittop.find('$') >= 0:
+            gittop = os.path.expandvars(gittop)
+        if os.path.isdir(os.path.join(gittop, '.git')):
+            gittop = os.path.join(gittop, '.git')
+
+        # it could omit the refspec, meaning "whatever the current ref"
         # grab the key from a fully ref'ed path
-        ref = chunks[1]
-        pathtop = chunks[2]
-        subpath = os.path.join(pathtop, keypath)
+        subpath = os.path.join(gitsub, keypath)
 
-        if not ref:
+        if not gitref:
             # What is our current ref?
-            cmdargs = ['git', 'symbolic-ref', 'HEAD']
-            ecode, out, err = _run_command(cmdargs)
+            cmdargs = ['symbolic-ref', 'HEAD']
+            ecode, out, err = git_run_command(gittop, cmdargs)
             if ecode == 0:
-                ref = out.decode().strip()
+                gitref = out.decode().strip()
+        if not gitref:
+            raise KeyError('Could not figure out current ref in %s' % gittop)
 
-        cmdargs = ['git']
-        keysrc = f'{ref}:{subpath}'
-        cmdargs += ['show', keysrc]
-        ecode, out, err = _run_command(cmdargs)
+        keysrc = f'{gitref}:{subpath}'
+        cmdargs = ['show', keysrc]
+        ecode, out, err = git_run_command(gittop, cmdargs)
         if ecode == 0:
+            # Handle one level of symlinks
+            if out.find(b'\n') < 0 < out.find(b'/'):
+                # Check this path as well
+                linktgt = os.path.normpath(os.path.join(os.path.dirname(subpath), out.decode()))
+                keysrc = f'{gitref}:{linktgt}'
+                cmdargs = ['show', keysrc]
+                ecode, out, err = git_run_command(gittop, cmdargs)
+                if ecode == 0:
+                    logger.debug('KEYSRC  : %s (symlinked)', keysrc)
+                    return out, 'ref:%s:%s' % (gittop, keysrc)
             logger.debug('KEYSRC  : %s', keysrc)
-            return out, keysrc
+            return out, 'ref:%s:%s' % (gittop, keysrc)
 
         # Does it exist on disk in gittop?
         fullpath = os.path.join(gittop, subpath)
@@ -726,7 +742,7 @@ def get_public_key(source: str, keytype: str, identity: str, selector: str) -> T
                 logger.debug('KEYSRC  : %s', fullpath)
                 return fh.read(), fullpath
 
-        raise KeyError('Could not find %s in %s' % (subpath, ref))
+        raise KeyError('Could not find %s in %s:%s' % (subpath, gittop, gitref))
 
     # It's a disk path, then
     # Expand ~ and env vars
@@ -1084,8 +1100,11 @@ def command() -> None:
         ch.setLevel(logging.CRITICAL)
 
     logger.addHandler(ch)
-    config = get_config_from_git(r'patatt\..*', section=_args.section,
-                                 defaults=DEFAULT_CONFIG, multivals=['keyringsrc'])
+    config = get_config_from_git(r'patatt\..*', section=_args.section, multivals=['keyringsrc'])
+    # Append some extra keyring locations
+    if 'keyringsrc' not in config:
+        config['keyringsrc'] = list()
+    config['keyringsrc'] += ['ref:::.keys', 'ref:::.local-keys', 'ref::refs/meta/keyring:']
     logger.debug('config: %s', config)
 
     if 'func' not in _args:
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 3/3] Release as v0.2.0
  2021-05-12 16:37 [PATCH 1/3] Return better result than just pass/fail Konstantin Ryabitsev
  2021-05-12 16:37 ` [PATCH 2/3] Support other git dirs as sources Konstantin Ryabitsev
@ 2021-05-12 16:37 ` Konstantin Ryabitsev
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-12 16:37 UTC (permalink / raw)
  To: signatures

Time to cut a 0.2.0, though I expect 0.3.0 won't be too far behind.

Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
---
 patatt/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patatt/__init__.py b/patatt/__init__.py
index e547b28..373392d 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -46,7 +46,7 @@ REQ_HDRS = [b'from', b'subject']
 KEYCACHE = dict()
 
 # My version
-__VERSION__ = '0.2.0-dev'
+__VERSION__ = '0.2.0'
 MAX_SUPPORTED_FORMAT_VERSION = 1
 
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-05-12 16:37 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-05-12 16:37 [PATCH 1/3] Return better result than just pass/fail Konstantin Ryabitsev
2021-05-12 16:37 ` [PATCH 2/3] Support other git dirs as sources Konstantin Ryabitsev
2021-05-12 16:37 ` [PATCH 3/3] Release as v0.2.0 Konstantin Ryabitsev

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.