signatures.lore.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] Another test with patatt-0.2.0
@ 2021-05-11 14:35 Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 1/4] Add manpages and prepare for 0.1.0 release Konstantin Ryabitsev
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-11 14:35 UTC (permalink / raw)
  To: signatures

Get ready for a 0.2.0 release in the near future.

Konstantin Ryabitsev (4):
  Add manpages and prepare for 0.1.0 release
  Return a list of all sigs, not just goodsigs
  Return better result than just pass/fail
  Support other git dirs as sources

 MANIFEST.in        |   2 +
 README.rst         |  22 +++----
 man/patatt.5       | 112 +++++++++++++++++++++++++++++++++
 man/patatt.5.rst   |  61 ++++++++++++++++++
 patatt/__init__.py | 153 +++++++++++++++++++++++++++------------------
 setup.py           |   3 +-
 6 files changed, 281 insertions(+), 72 deletions(-)
 create mode 100644 man/patatt.5
 create mode 100644 man/patatt.5.rst

-- 
2.31.1


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

* [PATCH 1/4] Add manpages and prepare for 0.1.0 release
  2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
@ 2021-05-11 14:35 ` Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 2/4] Return a list of all sigs, not just goodsigs Konstantin Ryabitsev
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-11 14:35 UTC (permalink / raw)
  To: signatures

It's time to cut the first release, I think.

Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
---
 MANIFEST.in      |   2 +
 man/patatt.5     | 112 +++++++++++++++++++++++++++++++++++++++++++++++
 man/patatt.5.rst |  61 ++++++++++++++++++++++++++
 setup.py         |   3 +-
 4 files changed, 177 insertions(+), 1 deletion(-)
 create mode 100644 man/patatt.5
 create mode 100644 man/patatt.5.rst

diff --git a/MANIFEST.in b/MANIFEST.in
index e72662c..b62d6b7 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,4 @@
 include COPYING
+include DCO
 include README.rst
+include man/*.rst
diff --git a/man/patatt.5 b/man/patatt.5
new file mode 100644
index 0000000..5e97753
--- /dev/null
+++ b/man/patatt.5
@@ -0,0 +1,112 @@
+.\" Man page generated from reStructuredText.
+.
+.TH PATATT 5 "2021-05-07" "0.1.0" ""
+.SH NAME
+PATATT \- DKIM-like cryptographic patch attestation
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.SH SYNOPSIS
+.sp
+patatt {sign,validate,genkey} [options]
+.SH DESCRIPTION
+.sp
+This tools allows cryptographically signing patches sent via email
+by using DKIM\-like message headers. This approach is both effective and
+doesn\(aqt interfere with other code review tools the way inline or
+detached PGP signatures do. For a full overview of core concepts and
+considerations, please see README.
+.sp
+If you already have a PGP key configured for signing git tags or
+commits, then you should be able to use patatt without any additional
+configuration. Try running the following in any git repository:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+git format\-patch \-1 \-\-stdout | patatt sign
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+If patatt is not finding your PGP key, try adding the following to your
+~/.gitconfig:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+[user]
+    signingkey = [yourkeyid]
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+To find out your keyid, run \fBgpg \-\-list\-secret\-keys\fP\&. If you want to
+use a specific subkey, you can specify the subkey ID with a \fB!\fP at the
+end.
+.SH USING AS A GIT HOOK
+.sp
+If you use \fBgit\-send\-email\fP for sending patches, then you can get
+them automatically signed via the \fBsendemail\-validate\fP hook:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+$ echo \(aqpatatt sign \-\-hook "${1}"\(aq >> .git/hooks/sendemail\-validate
+$ chmod a+x .git/hooks/sendemail\-validate
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.SH SUBCOMMANDS
+.INDENT 0.0
+.IP \(bu 2
+\fIpatatt sign\fP: sign stdin or RFC2822 files passed as arguments
+.IP \(bu 2
+\fIpatatt validate\fP: basic validation for signed messages
+.IP \(bu 2
+\fIpatatt genkey\fP: generate a new ed25519 keypair
+.UNINDENT
+.sp
+You can run \fBpatatt [subcommand] \-\-help\fP to see a summary of flags for
+each subcommand.
+.SH SUPPORT
+.sp
+Please email \fI\%tools@linux.kernel.org\fP with support requests.
+.SH AUTHOR
+mricon@kernel.org
+
+License: MIT-0
+.SH COPYRIGHT
+The Linux Foundation and contributors
+.\" Generated by docutils manpage writer.
+.
diff --git a/man/patatt.5.rst b/man/patatt.5.rst
new file mode 100644
index 0000000..f607ed9
--- /dev/null
+++ b/man/patatt.5.rst
@@ -0,0 +1,61 @@
+PATATT
+======
+-----------------------------------------
+DKIM-like cryptographic patch attestation
+-----------------------------------------
+
+:Author:    mricon@kernel.org
+:Date:      2021-05-07
+:Copyright: The Linux Foundation and contributors
+:License:   MIT-0
+:Version:   0.1.0
+:Manual section: 5
+
+SYNOPSIS
+--------
+patatt {sign,validate,genkey} [options]
+
+DESCRIPTION
+-----------
+This tools allows cryptographically signing patches sent via email
+by using DKIM-like message headers. This approach is both effective and
+doesn't interfere with other code review tools the way inline or
+detached PGP signatures do. For a full overview of core concepts and
+considerations, please see README.
+
+If you already have a PGP key configured for signing git tags or
+commits, then you should be able to use patatt without any additional
+configuration. Try running the following in any git repository::
+
+    git format-patch -1 --stdout | patatt sign
+
+If patatt is not finding your PGP key, try adding the following to your
+~/.gitconfig::
+
+    [user]
+        signingkey = [yourkeyid]
+
+To find out your keyid, run ``gpg --list-secret-keys``. If you want to
+use a specific subkey, you can specify the subkey ID with a ``!`` at the
+end.
+
+USING AS A GIT HOOK
+-------------------
+If you use ``git-send-email`` for sending patches, then you can get
+them automatically signed via the ``sendemail-validate`` hook::
+
+    $ echo 'patatt sign --hook "${1}"' >> .git/hooks/sendemail-validate
+    $ chmod a+x .git/hooks/sendemail-validate
+
+SUBCOMMANDS
+-----------
+* *patatt sign*: sign stdin or RFC2822 files passed as arguments
+* *patatt validate*: basic validation for signed messages
+* *patatt genkey*: generate a new ed25519 keypair
+
+You can run ``patatt [subcommand] --help`` to see a summary of flags for
+each subcommand.
+
+SUPPORT
+-------
+Please email tools@linux.kernel.org with support requests.
diff --git a/setup.py b/setup.py
index 3eb5a8f..7fa6809 100644
--- a/setup.py
+++ b/setup.py
@@ -33,8 +33,9 @@ setup(
     author_email='mricon@kernel.org',
     packages=['patatt'],
     license='MIT-0',
-    long_description=read('README'),
+    long_description=read('README.rst'),
     long_description_content_type='text/x-rst',
+    data_files = [('share/man/man5', ['man/patatt.5'])],
     keywords=['git', 'patches', 'attestation'],
     install_requires=[
         'pynacl',
-- 
2.31.1


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

* [PATCH 2/4] Return a list of all sigs, not just goodsigs
  2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 1/4] Add manpages and prepare for 0.1.0 release Konstantin Ryabitsev
@ 2021-05-11 14:35 ` Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 3/4] Return better result than just pass/fail Konstantin Ryabitsev
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-11 14:35 UTC (permalink / raw)
  To: signatures

Instead of returning a list of goodsigs and throwing a ValidationError
if even one of them has failed, return a list of all sigs -- failed or
otherwise, and let the caller decide what to do with it.

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

diff --git a/patatt/__init__.py b/patatt/__init__.py
index 2f380e0..9ceaa29 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -42,7 +42,7 @@ DEFAULT_CONFIG = {
 KEYCACHE = dict()
 
 # My version
-__VERSION__ = '0.1.0'
+__VERSION__ = '0.2.0-dev'
 MAX_SUPPORTED_FORMAT_VERSION = 1
 
 
@@ -841,19 +841,19 @@ def cmd_sign(cmdargs, config: dict) -> None:
 
 
 def validate_message(msgdata: bytes, sources: list, trim_body: bool = False) -> list:
-    errors = list()
-    goodsigs = list()
-    success = False
+    attestations = list()
     pm = PatattMessage(msgdata)
     if not pm.signed:
-        errors.append('message is not signed')
-        raise ValidationError('message is not signed', errors)
+        logger.debug('message is not signed')
+        return attestations
 
     # Find all identities for which we have public keys
     for ds in pm.get_sigs():
+        errors = list()
         a = ds.get_field('a', decode=True)
         i = ds.get_field('i', decode=True)
         s = ds.get_field('s', decode=True)
+        t = ds.get_field('t', decode=True)
         if not s:
             s = 'default'
         if a.startswith('ed25519'):
@@ -862,6 +862,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))
             continue
 
         pkey = keysrc = None
@@ -874,21 +875,17 @@ 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))
             continue
 
         try:
             signtime = pm.validate(i, pkey, trim_body=trim_body)
-            success = True
+            attestations.append((True, i, signtime, keysrc, algo, errors))
         except ValidationError:
             errors.append('failed to validate using %s' % keysrc)
-            continue
-
-        goodsigs.append((i, signtime, keysrc, algo))
+            attestations.append((False, i, t, keysrc, algo, errors))
 
-    if not success:
-        raise ValidationError('Failed to validate message', errors)
-
-    return goodsigs
+    return attestations
 
 
 def cmd_validate(cmdargs, config: dict):
@@ -922,19 +919,20 @@ def cmd_validate(cmdargs, config: dict):
     allgood = True
     for fn, msgdata in messages.items():
         try:
-            goodsigs = validate_message(msgdata, sources, trim_body=trim_body)
-            for identity, signtime, keysrc, algo in goodsigs:
-                logger.critical('PASS | %s | %s', identity, fn)
-                if keysrc:
-                    logger.info('     | key: %s', keysrc)
+            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)
+                    if keysrc:
+                        logger.info('     | key: %s', keysrc)
+                    else:
+                        logger.info('     | key: default GnuPG keyring')
                 else:
-                    logger.info('     | key: default GnuPG keyring')
+                    allgood = False
+                    logger.critical('FAIL | %s | %s', identity, fn)
+                    for error in errors:
+                        logger.critical('     | %s', error)
 
-        except ValidationError as ex:
-            allgood = False
-            logger.critical('FAIL | %s | %s', ex, fn)
-            for error in ex.errors:
-                logger.critical('     | %s', error)
         except RuntimeError as ex:
             allgood = False
             logger.critical('ERR  | err: %s | %s', ex, fn)
-- 
2.31.1


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

* [PATCH 3/4] Return better result than just pass/fail
  2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 1/4] Add manpages and prepare for 0.1.0 release Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 2/4] Return a list of all sigs, not just goodsigs Konstantin Ryabitsev
@ 2021-05-11 14:35 ` Konstantin Ryabitsev
  2021-05-11 14:35 ` [PATCH 4/4] Support other git dirs as sources Konstantin Ryabitsev
  2021-05-20 20:59 ` [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-11 14:35 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.31.1


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

* [PATCH 4/4] Support other git dirs as sources
  2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
                   ` (2 preceding siblings ...)
  2021-05-11 14:35 ` [PATCH 3/4] Return better result than just pass/fail Konstantin Ryabitsev
@ 2021-05-11 14:35 ` Konstantin Ryabitsev
  2021-05-20 20:59 ` [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-11 14:35 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.31.1


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

* Re: [PATCH 0/4] Another test with patatt-0.2.0
  2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
                   ` (3 preceding siblings ...)
  2021-05-11 14:35 ` [PATCH 4/4] Support other git dirs as sources Konstantin Ryabitsev
@ 2021-05-20 20:59 ` Konstantin Ryabitsev
  4 siblings, 0 replies; 6+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-20 20:59 UTC (permalink / raw)
  To: signatures

On Tue, May 11, 2021 at 10:35:32AM -0400, Konstantin Ryabitsev wrote:
> Get ready for a 0.2.0 release in the near future.

Obsoleted-by: https://lore.kernel.org/r/20210512151912.4099-1-konstantin@linuxfoundation.org

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

end of thread, other threads:[~2021-05-20 20:59 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-11 14:35 [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev
2021-05-11 14:35 ` [PATCH 1/4] Add manpages and prepare for 0.1.0 release Konstantin Ryabitsev
2021-05-11 14:35 ` [PATCH 2/4] Return a list of all sigs, not just goodsigs Konstantin Ryabitsev
2021-05-11 14:35 ` [PATCH 3/4] Return better result than just pass/fail Konstantin Ryabitsev
2021-05-11 14:35 ` [PATCH 4/4] Support other git dirs as sources Konstantin Ryabitsev
2021-05-20 20:59 ` [PATCH 0/4] Another test with patatt-0.2.0 Konstantin Ryabitsev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).