From e2013670747a54abd373493fa776f4eb3fa37d01 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 | 26 +++++++++++++ pubkeys/prod.key | 43 ++++++++++++++++++++ pubkeys/test.key | 30 ++++++++++++++ scripts/check.py | 93 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 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..4b38e09 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +--- +version: 2.1 +jobs: + tests: + docker: + - image: circleci/python:3.7-buster + steps: + - run: + name: Install Debian packaging dependencies + command: | + sudo apt install rpm git-lfs + + - run: + name: Clone the repository with LFS + command: git clone --depth=1 -b ${CIRCLE_BRANCH} https://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 + git-lfs install + ./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..1912a58 --- /dev/null +++ b/pubkeys/prod.key @@ -0,0 +1,43 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +cIYqWzBX7dw= +=ZsUE +-----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..d099ead --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +import argparse +import os +import subprocess +import sys +import json + +PROD_SIGNING_KEY_PATH = "pubkeys/prod.key" +DEV_SIGNING_KEY_PATH = "pubkeys/test.key" +RPM_DIR = "workstation" + + +def verify_sig_rpm(path, dev=False): + + key_path = "" + if dev: + key_path = DEV_SIGNING_KEY_PATH + else: + key_path = PROD_SIGNING_KEY_PATH + 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 + line = output.decode("utf-8").rstrip() + print(line) + expected_output = "{}: digests signatures OK".format(path) + if line != expected_output: + fail("Signture verification failed for {}:{}".format(expected_output, line)) + 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(): + rpm_keys_exist = False + try: + # Returns non-zero if no keys are installed + subprocess.check_call(["rpm", "-q", "gpg-pubkey"]) + rpm_keys_exist = True + except subprocess.CalledProcessError: + rpm_keys_exist = False + + # If a key is in the keyring, delete it + if rpm_keys_exist: + try: + subprocess.check_call(["rpm", "--erase", "--allmatches", "gpg-pubkey"]) + except subprocess.CalledProcessError as e: + fail("Failed to delete key: {}".format(str(e))) + + +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() + + # Fail if no package is specified or not using '--all' + if not args.all and not args.packages: + fail("Please specify a rpm package or --all") + # 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()