diff options
author | Konstantin Ryabitsev <konstantin@linuxfoundation.org> | 2021-05-05 15:12:58 -0400 |
---|---|---|
committer | Konstantin Ryabitsev <konstantin@linuxfoundation.org> | 2021-05-05 15:12:58 -0400 |
commit | 14f9971ff8dae049f7b17d1b36e06c824ba7585b (patch) | |
tree | 7d77ec8443c66a91d1fbe3fd35e3b7ca92744581 | |
parent | 275685dd0adfad175cbbd2e10937014fe3505a95 (diff) | |
download | patatt-14f9971ff8dae049f7b17d1b36e06c824ba7585b.tar.gz |
First documentation part
Document installing and getting started as contributor.
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | README.rst | 201 | ||||
-rw-r--r-- | patatt/__init__.py | 32 | ||||
-rwxr-xr-x | sendemail-validate-hook | 2 |
4 files changed, 218 insertions, 18 deletions
@@ -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]}) |