signatures.lore.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/3] Sign Message-Id header if present
@ 2021-05-21 18:48 Konstantin Ryabitsev
  2021-05-21 18:48 ` [PATCH 2/3] Make header order deterministic Konstantin Ryabitsev
  2021-05-21 18:48 ` [PATCH 3/3] Release as 0.4.0 Konstantin Ryabitsev
  0 siblings, 2 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-21 18:48 UTC (permalink / raw)
  To: signatures

It is useful to sign the message-id header, because it is frequently
used as the patch identifier. Unfortunately, unless git-format-patch is
run with --thread, the message-id won't be generated until *after* the
sendemail-validate hook is invoked, so most of the time we won't end up
signing that header.

However, having this as an option is handy.

Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
---
 man/patatt.5       |  2 +-
 man/patatt.5.rst   |  4 +--
 patatt/__init__.py | 68 ++++++++++++++++++++++++++++++++--------------
 3 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/man/patatt.5 b/man/patatt.5
index 70cea05..1e46a8a 100644
--- a/man/patatt.5
+++ b/man/patatt.5
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH PATATT 5 "2021-05-11" "0.2.0" ""
+.TH PATATT 5 "2021-05-21" "0.4.0" ""
 .SH NAME
 PATATT \- DKIM-like cryptographic patch attestation
 .
diff --git a/man/patatt.5.rst b/man/patatt.5.rst
index 2ab345c..845595b 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-11
+:Date:      2021-05-21
 :Copyright: The Linux Foundation and contributors
 :License:   MIT-0
-:Version:   0.2.0
+:Version:   0.4.0
 :Manual section: 5
 
 SYNOPSIS
diff --git a/patatt/__init__.py b/patatt/__init__.py
index 92dee85..76028ae 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -41,12 +41,13 @@ RES_ERROR = 16
 RES_BADSIG = 32
 
 REQ_HDRS = [b'from', b'subject']
+OPT_HDRS = [b'message-id']
 
 # Quick cache for key info
 KEYCACHE = dict()
 
 # My version
-__VERSION__ = '0.3.0'
+__VERSION__ = '0.4.0-dev'
 MAX_SUPPORTED_FORMAT_VERSION = 1
 
 
@@ -126,29 +127,56 @@ class DevsigHeader:
         self._body_hash = base64.b64encode(hashed.digest())
 
     # do any git-mailinfo normalization prior to calling this
-    def set_headers(self, headers: list) -> None:
-        hfield = self.get_field('h')
-        if hfield:
-            # Make sure REQ_HEADERS are in this list
-            want_headers = [x.strip() for x in hfield.split(b':')]
-            for rqhdr in REQ_HDRS:
-                if rqhdr not in want_headers:
-                    raise ValidationError('Signature is missing a required header %s' % rqhdr.decode())
-        else:
-            want_headers = REQ_HDRS
-
-        self._headervals = list()
-        for header in headers:
+    def set_headers(self, headers: list, mode: str) -> None:
+        parsed = list()
+        allhdrs = set()
+        # DKIM operates on headers in reverse order
+        for header in reversed(headers):
             try:
                 left, right = header.split(b':', 1)
                 hname = left.strip().lower()
-                if hname not in want_headers:
-                    continue
+                parsed.append((hname, right))
+                allhdrs.add(hname)
             except ValueError:
                 continue
-            self._headervals.append(hname + b':' + DevsigHeader._dkim_canonicalize_header(right))
 
-        self.hdata['h'] = b':'.join(want_headers)
+        reqset = set(REQ_HDRS)
+        optset = set(OPT_HDRS)
+        self._headervals = list()
+        if mode == 'sign':
+            # Make sure REQ_HDRS is a subset of allhdrs
+            if not reqset.issubset(allhdrs):
+                raise SigningError('The following required headers not present: %s'
+                                   % (b', '.join(reqset.difference(allhdrs)).decode()))
+            # Add optional headers that are actually present
+            optpresent = allhdrs.intersection(optset)
+            signlist = list(reqset.union(optpresent))
+            self.hdata['h'] = b':'.join(signlist)
+
+        elif mode == 'validate':
+            hfield = self.get_field('h')
+            signlist = [x.strip() for x in hfield.split(b':')]
+            # Make sure REQ_HEADERS are in this set
+            if not reqset.issubset(set(signlist)):
+                raise ValidationError('The following required headers not signed: %s'
+                                      % (b', '.join(reqset.difference(set(signlist))).decode()))
+        else:
+            raise RuntimeError('Unknown set_header mode: %s' % mode)
+
+        for shname in signlist:
+            if shname not in allhdrs:
+                # Per RFC:
+                # Nonexistent header fields do not contribute to the signature computation (that is, they are
+                # treated as the null input, including the header field name, the separating colon, the header field
+                # value, and any CRLF terminator).
+                continue
+            at = 0
+            for hname, rawval in list(parsed):
+                if hname == shname:
+                    self._headervals.append(hname + b':' + DevsigHeader._dkim_canonicalize_header(rawval))
+                    parsed.pop(at)
+                    break
+                at += 1
 
     def sanity_check(self) -> None:
         if 'a' not in self.hdata:
@@ -435,7 +463,7 @@ class PatattMessage:
                 self.headers.remove(header)
         self.git_canonicalize()
         ds = DevsigHeader()
-        ds.set_headers(self.canon_headers)
+        ds.set_headers(self.canon_headers, mode='sign')
         ds.set_body(self.canon_body)
         ds.set_field('l', str(len(self.canon_body)))
         if identity and identity != self.canon_identity:
@@ -478,7 +506,7 @@ class PatattMessage:
             raise ValidationError('No signatures matching identity %s' % identity)
 
         self.git_canonicalize()
-        vds.set_headers(self.canon_headers)
+        vds.set_headers(self.canon_headers, mode='validate')
 
         if trim_body:
             lfield = vds.get_field('l')
-- 
2.31.1


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

* [PATCH 2/3] Make header order deterministic
  2021-05-21 18:48 [PATCH 1/3] Sign Message-Id header if present Konstantin Ryabitsev
@ 2021-05-21 18:48 ` Konstantin Ryabitsev
  2021-05-21 18:48 ` [PATCH 3/3] Release as 0.4.0 Konstantin Ryabitsev
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-21 18:48 UTC (permalink / raw)
  To: signatures

I know that it doesn't matter, but the OCD part of me likes seeing the
h= value in a deterministic order.

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

diff --git a/patatt/__init__.py b/patatt/__init__.py
index 76028ae..a80f63a 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -149,8 +149,8 @@ class DevsigHeader:
                 raise SigningError('The following required headers not present: %s'
                                    % (b', '.join(reqset.difference(allhdrs)).decode()))
             # Add optional headers that are actually present
-            optpresent = allhdrs.intersection(optset)
-            signlist = list(reqset.union(optpresent))
+            optpresent = list(allhdrs.intersection(optset))
+            signlist = REQ_HDRS + sorted(optpresent)
             self.hdata['h'] = b':'.join(signlist)
 
         elif mode == 'validate':
-- 
2.31.1


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

* [PATCH 3/3] Release as 0.4.0
  2021-05-21 18:48 [PATCH 1/3] Sign Message-Id header if present Konstantin Ryabitsev
  2021-05-21 18:48 ` [PATCH 2/3] Make header order deterministic Konstantin Ryabitsev
@ 2021-05-21 18:48 ` Konstantin Ryabitsev
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Ryabitsev @ 2021-05-21 18:48 UTC (permalink / raw)
  To: signatures

Let's bump to 0.4.0, so we can pull that into b4.

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 a80f63a..f15ccad 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -47,7 +47,7 @@ OPT_HDRS = [b'message-id']
 KEYCACHE = dict()
 
 # My version
-__VERSION__ = '0.4.0-dev'
+__VERSION__ = '0.4.0'
 MAX_SUPPORTED_FORMAT_VERSION = 1
 
 
-- 
2.31.1


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

end of thread, other threads:[~2021-05-21 18:48 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-21 18:48 [PATCH 1/3] Sign Message-Id header if present Konstantin Ryabitsev
2021-05-21 18:48 ` [PATCH 2/3] Make header order deterministic Konstantin Ryabitsev
2021-05-21 18:48 ` [PATCH 3/3] Release as 0.4.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).