aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKonstantin Ryabitsev <konstantin@linuxfoundation.org>2021-05-05 15:12:58 -0400
committerKonstantin Ryabitsev <konstantin@linuxfoundation.org>2021-05-05 15:12:58 -0400
commit14f9971ff8dae049f7b17d1b36e06c824ba7585b (patch)
tree7d77ec8443c66a91d1fbe3fd35e3b7ca92744581
parent275685dd0adfad175cbbd2e10937014fe3505a95 (diff)
downloadpatatt-14f9971ff8dae049f7b17d1b36e06c824ba7585b.tar.gz
First documentation part
Document installing and getting started as contributor. Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
-rw-r--r--README1
-rw-r--r--README.rst201
-rw-r--r--patatt/__init__.py32
-rwxr-xr-xsendemail-validate-hook2
4 files changed, 218 insertions, 18 deletions
diff --git a/README b/README
deleted file mode 100644
index fced116..0000000
--- a/README
+++ /dev/null
@@ -1 +0,0 @@
-Coming next.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..f9307c3
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,201 @@
+patatt: cryptographic patch attestation for the masses
+======================================================
+
+This utility allows an easy way to add end-to-end cryptographic
+attestation to patches sent via mail. It does so by adapting the DKIM
+email signature standard to include cryptographic developer signatures
+via a separate X-Developer-Signature email header.
+
+Basic concepts
+--------------
+DKIM is a widely adopted standard for domain-level attestation of email
+messages. It works by hashing the message body and certain individual
+headers, and then creating a cryptographic signature of the resulting
+hash. The receiving side then downloads the public key of the sending
+domain from its DNS record and checks the signature and header/body
+hashes. If the signature verifies and the resulting hashes are
+identical, then there is a high degree of assurance that neither the
+body of the message nor any of the signed headers were modified in
+transit.
+
+This utility uses the exact same DKIM standard to hash the headers and
+the body of the patch message, but uses a different set of fields and
+canonicalization routines:
+
+ - the d= field is not used (no domain signatures involved)
+ - the q= field is not used (key lookup is handled differently)
+ - the c= field is not used (see below for canonicalization)
+ - the i= field is optional, but MUST be the canonical email address of
+ the sender, if not the same as the From: field
+
+Canonicalization
+~~~~~~~~~~~~~~~~
+Patatt uses the "relaxed/simple" canonicalization as defined by the DKIM
+standard, but the message is first parsed by "git-mailinfo" in order to
+achieve the following:
+
+ - normalize any content-transfer-encoding modifications (convert back
+ from base64/quoted-printable/etc into 8-bit)
+ - use any encountered in-body From: and Subject: headers to
+ rewrite the outer message headers
+ - perform any subject-line normalization in order to strip content not
+ considered by git-am when applying the patch
+
+To achieve this, the message is passed through git-mailinfo with the
+following flags::
+
+ cat orig.msg | git mailinfo --encoding=utf-8 m p > i
+
+Patatt then uses the data found in "i" to replace the From: and Subject:
+headers of the original message, and concatenates "m" and "p" back
+together to form the body of the message, which is then normalized using
+CRLF line endings and the DKIM "simple" body canonicalization (any
+trailing blank lines are removed).
+
+Any other headers included in signing are canonicalized using the
+"relaxed" header canonicalization routines defined in the DKIM standard.
+
+In other words, the body and some of the headers are normalized and
+reconstituted using the "git-mailinfo" command, and then canonicalized
+using DKIM's relaxed/simple standard.
+
+Supported Signature Algorithms
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+DKIM standard mostly relies on RSA signatures, though RFC 8463 extends
+it to support ED25519 keys as well. While it is possible to use any of
+the DKIM-defined algorithms, patatt only supports the following
+two signing/hashing schemes:
+
+ - ed25519-sha256: exactly as defined in RFC8463
+ - openpgp-sha256: uses OpenPGP to create the signature
+
+X-Developer-Key header
+~~~~~~~~~~~~~~~~~~~~~~
+Patatt adds a separate ``X-Developer-Key:`` header with public key
+information. It is merely informational and ISN'T and SHOULDN'T be used
+for performing any kind of message validation (for obvious reasons). It
+is included to make it easier for maintainers to obtain the
+contributor's public key for performing whatever necessary
+verification steps prior to including it into their individual or
+project-wide keyrings.
+
+Getting started as contributor
+------------------------------
+It is very easy to start signing your patches with patatt.
+
+Installing
+~~~~~~~~~~
+You can install from pip::
+
+ pip install --user patatt
+
+Make sure your PATH includes $HOME/.local/bin.
+
+Alternatively, you can clone the repository and symlink patatt.sh into
+your path::
+
+ cd bin
+ ln -s ~/path/to/patatt/patatt.sh patatt
+
+After this, you should be able to run ``patatt --help`` without
+specifying the full path to the repository.
+
+Using PGP
+~~~~~~~~~
+If you already have a PGP key, you can simply start using it to sign
+patches. Add the following to your ~/.gitconfig::
+
+ [patatt]
+ signingkey = openpgp:KEYID
+
+The KEYID should be the 16-character identifier of your key, for
+example::
+
+ [patatt]
+ signingkey = openpgp:E63EDCA9329DD07E
+
+Using ed25519
+~~~~~~~~~~~~~
+If you don't already have a PGP key, you can opt to generate and use an
+ed25519 key instead (see below for some considerations on pros and cons
+of PGP vs ed25519 keys).
+
+To generate a new keypair, run::
+
+ patatt genkey
+
+You will see an output similar to the following::
+
+ Generating a new ed25519 keypair
+ Wrote: /home/user/.local/share/patatt/private/20210505.key
+ Wrote: /home/user/.local/share/patatt/public/20210505.pub
+ Wrote: /home/user/.local/share/patatt/public/ed25519/example.org/user/default
+ Add the following to your .git/config (or global ~/.gitconfig):
+ ---
+ [patatt]
+ signingkey = ed25519:20210505
+ ---
+ Next, communicate the contents of the following file to the
+ repository keyring maintainers for inclusion into the project:
+ /home/user/.local/share/patatt/public/20210505.pub
+
+Please make sure to back up your private key, located in ``~/.local/share/patatt/private``.
+It is short enough to simply print out.
+
+Next, just do as instructions say. If the project to which you are
+contributing patches already uses patatt attestation, please work with
+the project maintainers to add your public key to the repository. If
+they aren't yet using patatt, just start signing your patches and
+hopefully the project will start keeping its own keyring in the future.
+
+Testing if it's working
+~~~~~~~~~~~~~~~~~~~~~~~
+To test if it's working::
+
+ $ git format-patch -1 --stdout | patatt sign > /tmp/test
+
+If you didn't get an error message, then the process was successful. You
+can review /tmp/test to see that X-Developer-Signature and
+X-Developer-Key headers were successfully added.
+
+You can now validate your own message::
+
+ $ patatt validate /tmp/test
+
+Automatic signing via the sendemail-validate hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If everything is working well, you can start automatically signing all
+outgoing patches sent via git-send-email::
+
+ $ echo 'patatt sign --hook "{$1}"' > .git/hooks/sendemail-validate
+ $ chmod a+x .git/hooks/sendemail-validate
+
+PGP vs ed25519 keys
+~~~~~~~~~~~~~~~~~~~
+If you don't already have a PGP key, you may wonder whether it makes
+sense to create a new PGP key or start using standalone ed25519 keys.
+
+Reasons to choose PGP:
+
+- you can protect the PGP private key with a passphrase (gpg-agent will
+ manage it for you)
+- you can move your PGP key to an OpenPGP-compliant smartcard to further
+ protect your key from being leaked/stolen
+- you can use your PGP keys to sign git tags/commits, not just patches
+
+If you choose to create a new PGP key, you can follow the following
+guide:
+https://github.com/lfit/itpol/blob/master/protecting-code-integrity.md
+
+Reasons to choose standalone ed25519 keys:
+
+- much smaller signatures, especially compared to PGP RSA keys
+- implements the DKIM ed25519 signing standard
+- faster crypto
+
+If you choose ed25519 keys, you will need to make sure that PyNaCl is
+installed (pip install should have already taken care of it for you).
+
+Getting started as git repository maintainer
+--------------------------------------------
+Coming.
diff --git a/patatt/__init__.py b/patatt/__init__.py
index 2cf283f..ed7ab7f 100644
--- a/patatt/__init__.py
+++ b/patatt/__init__.py
@@ -927,7 +927,7 @@ def cmd_validate(cmdargs, config: dict):
sys.exit(1)
-def cmd_gen(cmdargs, config: dict) -> None:
+def cmd_genkey(cmdargs, config: dict) -> None:
try:
from nacl.signing import SigningKey
except ModuleNotFoundError:
@@ -958,7 +958,7 @@ def cmd_gen(cmdargs, config: dict) -> None:
logger.critical('Use a different -n or pass -f to overwrite it')
raise RuntimeError('Key already exists')
- logger.info('Generating a new ed25519 keypair')
+ logger.critical('Generating a new ed25519 keypair')
newkey = SigningKey.generate()
# Make sure we write it as 0600
@@ -967,11 +967,11 @@ def cmd_gen(cmdargs, config: dict) -> None:
with open(skey, 'wb', opener=priv_opener) as fh:
fh.write(base64.b64encode(bytes(newkey)))
- logger.info('Wrote: %s', skey)
+ logger.critical('Wrote: %s', skey)
with open(pkey, 'wb') as fh:
fh.write(base64.b64encode(newkey.verify_key.encode()))
- logger.info('Wrote: %s', pkey)
+ logger.critical('Wrote: %s', pkey)
# Also copy it into our local keyring
dpkey = os.path.join(pdir, make_pkey_path('ed25519', config.get('identity'), 'default'))
@@ -979,24 +979,24 @@ def cmd_gen(cmdargs, config: dict) -> None:
if not os.path.exists(dpkey):
with open(dpkey, 'wb') as fh:
fh.write(base64.b64encode(newkey.verify_key.encode()))
- logger.info('Wrote: %s', dpkey)
+ logger.critical('Wrote: %s', dpkey)
else:
spkey = os.path.join(pdir, make_pkey_path('ed25519', config.get('identity'), identifier))
with open(spkey, 'wb') as fh:
fh.write(base64.b64encode(newkey.verify_key.encode()))
- logger.info('Wrote: %s', spkey)
+ logger.critical('Wrote: %s', spkey)
- logger.info('Add the following to your .git/config (or global ~/.gitconfig):')
- logger.info('---')
+ logger.critical('Add the following to your .git/config (or global ~/.gitconfig):')
+ logger.critical('---')
if cmdargs.section:
- logger.info('[patatt "%s"]', cmdargs.section)
+ logger.critical('[patatt "%s"]', cmdargs.section)
else:
- logger.info('[patatt]')
- logger.info(' signingkey = ed25519:%s', identifier)
- logger.info('---')
- logger.info('Next, communicate the contents of the following file to the')
- logger.info('repository keyring maintainers for inclusion into the project:')
- logger.info(pkey)
+ logger.critical('[patatt]')
+ logger.critical(' signingkey = ed25519:%s', identifier)
+ logger.critical('---')
+ logger.critical('Next, communicate the contents of the following file to the')
+ logger.critical('repository keyring maintainers for inclusion into the project:')
+ logger.critical(pkey)
def command() -> None:
@@ -1031,7 +1031,7 @@ def command() -> None:
help='Name to use for the key, e.g. "workstation", or "default"')
sp_gen.add_argument('-f', '--force', action='store_true', default=False,
help='Overwrite any existing keys, if found')
- sp_gen.set_defaults(func=cmd_gen)
+ sp_gen.set_defaults(func=cmd_genkey)
_args = parser.parse_args()
diff --git a/sendemail-validate-hook b/sendemail-validate-hook
index 3ee80e5..9096388 100755
--- a/sendemail-validate-hook
+++ b/sendemail-validate-hook
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
if which patatt>/dev/null 2>&1; then
# We have it in path, so just execute it
- patatt -q sign --hook "${1}"
+ patatt sign --hook "${1}"
else
# Assume we're symlinked into a git checkout
REAL_SCRIPT=$(realpath -e ${BASH_SOURCE[0]})