From af92b367fd96a2aa6fcfad75eafd59badf8a88ad Mon Sep 17 00:00:00 2001 From: mickael e Date: Thu, 23 Jan 2020 15:45:55 -0500 Subject: [PATCH] Verifies signatures in CI Small scripts that iterates through files in the workstation repo and verifies their signature. Specifies `--test` in this repo as this is the dev repo signed with the test key. --- .circleci/config.yml | 31 ++++++++++++++++++ pubkeys/prod.key | 62 ++++++++++++++++++++++++++++++++++++ pubkeys/test.key | 30 ++++++++++++++++++ scripts/check.py | 75 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 pubkeys/prod.key create mode 100644 pubkeys/test.key create mode 100755 scripts/check.py diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..a3d0ff0 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +--- +version: 2.1 +jobs: + tests: + docker: + - image: circleci/python:3.7-buster + steps: + - checkout + - run: + name: Install Debian packaging dependencies + command: | + sudo apt install rpm git-lfs + git-lfs install + + - run: + name: workaround for git-lfs in circle with deploy key + command: git config --global --unset url.ssh://git@github.com.insteadof + + - run: + name: Clone the repository with LFS + command: git clone -b ${CIRCLE_BRANCH} //github.com/freedomofpress/securedrop-workstation-dev-rpm-packages-lfs + - run: + name: Verify the signatures of all rpm artifacts + command: | + cd securedrop-workstation-dev-rpm-packages-lfs + ./scripts/check.py --dev --verify --all + +workflows: + build-packages: + jobs: + - tests diff --git a/pubkeys/prod.key b/pubkeys/prod.key new file mode 100644 index 0000000..c2a374e --- /dev/null +++ b/pubkeys/prod.key @@ -0,0 +1,62 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +wX2j+Ii4ZpUH +=WaCA +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pubkeys/test.key b/pubkeys/test.key new file mode 100644 index 0000000..b6c6ef9 --- /dev/null +++ b/pubkeys/test.key @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFhPGZsBCACzn00s3+i5HdGIldDGYXxY2HKL9Qhk0DhiRrNPaQemhNijuFlC +geCeKN/smDAUyM5mfEoxmWy3V7n8SEQUpqI4dIS2AohReLkyKEKiIpTuXW7F9kO3 +vcXHgrTka+8B4ZQxDuTHNFJLmBwJnP24LrL6BzkDIUNeQFwM0EFTDOJlW1QV6qkm +9WGizo2sR0VBJJabfRWrTWd8llYOVcc+LptErVNADPaX6iqb+QnZVJ/nYmCTgABj +lD3aZ4EPZ+ioVOcOxbgBkAX76COObUUw/XahBGwj4fJ5kyzvDSBCHHlRzN39LKpM +Y+HfSc1scAOWN+Dd0N/joIa0j0U4SGHo1NdzABEBAAG0MVNlY3VyZURyb3AgVEVT +VElORyBrZXkgPHNlY3VyZWRyb3BAZnJlZWRvbS5wcmVzcz6JAU4EEwEIADgWIQRO +15zDNi19EoNwRgJKO+SpIhGwPAUCWE8ZmwIbAwULCQgHAgYVCAkKCwIEFgIDAQIe +AQIXgAAKCRBKO+SpIhGwPCb9B/9SuVoxbe3nLlU0bHDQtoq5P7adyTZK+5gKIiAo +mtAkc/EuiF6jYIDLo+DBB1GBJVjyD5igTt14XR3JpMe6nLtztD5zgGk47gYQk3y5 +6f5ydd7zRo9OxulRYDvU1mXMUc0EmqfzuSxY55HJy5KQvjeKIU0fTvwbPYXdhFCC +42iyBIkp4e4/C5oO4lNrNY2DJEZ+a8H5LHasJ4g9A78f/D5q0HWO1HutzfDeiMvq +WFwlGMD2OzTEQA2MGlVRIYvLHAG1aV9fXY8kjCFT8ri5hxlQeTkKISfbW3pFSq6s +Ow4r975zWLTPJNm+WTbBpfIOFBVAW34EHkcb/QmntlvqkNM+uQENBFhPGZsBCAC4 +VEtCQEuZ3WzCNL/0yQFih1EjT/AsS3j3++xvSOYWF+c7AjR9X0MkJFTnUZBHs6MX +PM33bbkWbBBE2ILdDCEF72Uc5HyyC2lW2DvPY9ZLVSGcMCUsKARv5rbeNdgiLVP5 +8AMkmG48q0Pxrr6UVX14M34Jm5G91c/dj9zHtVwkLg4RG/rcumQdlpQhNmMycB2X +lat48atmEkutfLEQizXIlgiCdNEpgfUBy/jZZcCOjwr8PUPmSUWjKOVMv6CSLx8K +z2cP4We7tyq4qhc0cWjJOWOmJpu5tbmi6XEEWGaIJyN+POhHEcb0tI1rTJ88nrMb +DI/NF/35kuWIIkADOb2vABEBAAGJATYEGAEIACAWIQRO15zDNi19EoNwRgJKO+Sp +IhGwPAUCWE8ZmwIbDAAKCRBKO+SpIhGwPC3fB/0TfuScS718FiEcVRI3F2wBbzTQ +VARhGzEvPSU5Z3Cur/EB8ihpWvwi39tUMeg5HTheDl/8A7f1QCjIFSVEr1slGNLh +YFF07XGWhy837z6kiihK2z6/w6Q9QJqjE+QVZCKr97aIPejvEoHoslZTU5pJ52qF +J7KQd1hEvVs00DxY6VlyK0FzXqByKYq6Arl2tzlCZ6RPEHKXV2xSP06jLEagzgYe +DylVo9Xahenj4n/Mtq7Am6tGgU9Vy9cGbWNBdUND/mFQEEZSh9RJabPeluH12sir +5/tfsDr4DGHSz7ws+5M6Zbk6oNJEwQZ4cR+81qCfXE5X5LW1KlAL8wDl7dfS +=fYUi +-----END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/check.py b/scripts/check.py new file mode 100755 index 0000000..a79aa51 --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import argparse +import os +import subprocess +import sys +import json + +PROD_SIGNING_KEY_FPR="pubkeys/prod.key" +DEV_SIGNING_KEY_FPR="pubkeys/test.key" +RPM_DIR="workstation" + +def verify_sig_rpm(path, dev=False): + + key_path = "" + if dev: + key_path = DEV_SIGNING_KEY_FPR + else: + key_path = PROD_SIGNING_KEY_FPR + try: + subprocess.check_call(["rpmkeys", "--import", key_path]) + except subprocess.CalledProcessError as e: + fail("Error importing key: {}".format(str(e))) + + # Check the signature + try: + output = subprocess.check_output(["rpm", "--checksig", path]) + # rpm --checksig returns 0 if there is *no* signature. I couldn't + # find a way other than parsing stdout + print(str(output)) + if "digests signatures OK" not in str(output): + fail("Signture verification failed for {}:{}".format(path, output)) + except subprocess.CalledProcessError as e: + fail("Error checking signature: {}".format(str(e))) + + +def verify_all_rpms(dev=False): + for root, dirs, files in os.walk(RPM_DIR): + for name in files: + verify_sig_rpm(os.path.join(root,name), dev) + +def remove_keys_in_rpm_keyring(): + try: + subprocess.check_call(["rpm", "--erase", "--allmatches", "gpg-pubkey"]) + except subprocess.CalledProcessError: + pass + +def fail(msg): + print(msg, file=sys.stderr) + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--dev', action='store_true', default=False) + parser.add_argument('--verify', action='store_true', default=True) + parser.add_argument('--all', action='store_true', default=False) + parser.add_argument('packages', type=str, nargs='*', help='Files to sign/verify') + args = parser.parse_args() + + print(args) + + # Since we can't specify with which key to check sigs, we should clear the keyring + remove_keys_in_rpm_keyring() + + if args.verify: + if args.all: + verify_all_rpms(args.dev) + else: + for package in args.packages: + assert os.path.exists(package) + verify_sig_rpm(package, args.dev) + + sys.exit(0) + +if __name__ == "__main__": + main()