diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c103e3f..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -version: 2.1 -jobs: - tests: - docker: - - image: debian:buster - steps: - - run: apt-get update && apt-get install -y sudo - - run: - name: Install Debian packaging dependencies - command: | - sudo apt install -y python3 git rpm git-lfs - - - run: - name: Clone the repository with LFS - command: git clone --depth=1 -b ${CIRCLE_BRANCH} https://github.com/freedomofpress/securedrop-yum-prod - - run: - name: Verify the signatures of all rpm artifacts - command: | - cd securedrop-yum-prod - git-lfs install - ./scripts/check.py --verify --all - -workflows: - build-packages: - jobs: - - tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..28b51f7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI +on: [push, pull_request] +defaults: + run: + shell: bash + +jobs: + verify: + runs-on: ubuntu-latest + container: debian:bookworm + steps: + - name: Install dependencies + run: | + apt-get update && apt-get install --yes python3 rpm gpg git git-lfs wget + - name: Checkout + uses: actions/checkout@v3 + with: + lfs: true + fetch-depth: 0 + - name: Verify rpms are signed by release key + run: | + git config --global --add safe.directory '*' + wget https://github.com/freedomofpress/securedrop-debian-packaging/raw/main/securedrop-keyring/securedrop-keyring.gpg + gpg --import securedrop-keyring.gpg && gpg --armor --export > securedrop-keyring.asc + ./tools/check-signed securedrop-keyring.asc diff --git a/pubkeys/prod-legacy.key b/pubkeys/prod-legacy.key deleted file mode 100644 index 1912a58..0000000 --- a/pubkeys/prod-legacy.key +++ /dev/null @@ -1,43 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBFgIrN8BEACnXQFtRVzlePZL/4wfdsAI0FohKj+v17c9U/JNOOwax2DapJe+ -pQ7jZ8G2kUDLTNTMgZLze/gmOJF28olplMi9sLwBynKbN2xOq6MybxE9NLLeE7/y -ZfMZrgMSwgHW40udRZEEpe9IYKZP2QXLGFOmRiqiQ0HNp9WKFNTfi03Yx3XEUeeZ -kap9i0+1sktYBrlnUzXUTJHiUjJTEiI9NX23Vey0NtaveJLzdEQmsYQWaMbX4ECD -Hz+UNRNrjXv303bJgSGBm53tsvQzd6Lyzk4RGOKKifm4A2RRXf6zZCpRmOJUD5dH -8eLNeNyJpRY13rzcqp6Sk05n/RJOH9QbClzBT4rAbTtDKIKsutGnPxL+8fwKoaut -xZjcZLNh712nfiMl07rmgD4by0rp8xe29MIUNkjqg01pckfvUXknRhuo7ZmrAphz -ZZKLSApWXbB32ug5WNoGaQmq+hye1i40zu3fx8MRYefkpSSatNuIbrwLLnq0NR+k -qXcP1SPgtoy/EnW0oa/NDiT/rSh1PuAjG7oOpiNdQdmnA+xIYGreeNoPtuh7gJRc -XYrtWI5zzsGwrFE0LMMPw6SVGONfM5M4Efc+oUn3cIn7gQITm31JNTbRpnwT7bMo -Hy+MrILJITj6Rwi8EGyeTBVolM/L0W3WpjJuj6yhcRZURkBMA01aSUG3yQARAQAB -tEVTZWN1cmVEcm9wIFJlbGVhc2UgU2lnbmluZyBLZXkgPHNlY3VyZWRyb3AtcmVs -ZWFzZS1rZXlAZnJlZWRvbS5wcmVzcz6JAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsF -FgIDAQACHgECF4AWIQQiJFyB47rrQTizYGExD1YSAPStdwUCXRqEkwUJBvMLKgAK -CRAxD1YSAPStd3YjD/4hT5+Q1ZVobUh9Psuv1XYaHTnqJvVxXjheXns9SGqSsvFC -+2O1RVfse+fKaY9lRaG179toKEOEcoyydCpdInlCkhx8Ny9O+pyiH5TawnaKVpRW -j/9JJGW+Ceaipr1rawOzuG67MplButBFGmA1jPkeH38wcvep+PIUU5ZJ+aXbdrKT -uWBwKjzjiF2LMsh9Pnn9XN/T5Ph39WR6utsd/wdbb8xdpq4tivUDWV7W7ztG1No9 -exYfftnn6nLF74dLayhHxESE/yUilxR/XDQxvYbcjNAS9OZVKnkrq8o+8bLBKLV1 -le4168rdyVBxrhLCG3wXaWqO4AaECMHSfZR2Lvb/d1wIyMtEcWbRlDwmTDFOQ1XU -RCR0coeemYeAzt2hF6/tIrrCGmCKllQNN+JegH2MbXG7SjnCbWwWxAWtccf6L7Ht -BYDe3RWK0VyMwsHVuTakMKzIoH++e8XnmEKf3JFMz27RcgXRFN1Wo4/iRIq/zM+i -l/wTfN9l3yzojKmwZQvvICITCkeh/1sEspEkzmg74inJVpTEHQCWQ41c5ugPqjHd -kvpjxZML4B0+9nN9WQvqhRgmjCKnN+PvYw/mBaEfgA36E8pkcyNwnw+VrFgQyQ1R -FH0yg6P2Y6zaSKLEHkpjzWaCc3sOA/qMFuTw5aUkPj7Go1DMEV/z/xl6tDlM2bQe -U2VjdXJlRHJvcCBSZWxlYXNlIFNpZ25pbmcgS2V5iQJXBBMBCgBBAhsDBQsJCAcD -BRUKCQgLBRYCAwEAAh4BAheABQkG8wsqFiEEIiRcgeO660E4s2BhMQ9WEgD0rXcF -Al0ah20CGQEACgkQMQ9WEgD0rXcqpA//ZD481Wytd1ZXiXIee8I4ekIGpq0UVJuL -g8Bh0hhH2LTqIMuMVIVQM7/k/xxHBd+kxpAv/sUhJKrY16XBkGzz7v1Rcl29uWUR -GSPiLl2OehlT8Ahf60Dv4czhlvBdT3lWtYwM2zciOe4Y5mPwqzEgkrxRD2V9XnmO -8X3giZyaTDz/iiTQ+WMSvjIgVNGBe38tzoofSCSxNk8KfAWtchZhZgR0ZsYRWlUa -7dT4Syi0KutEXjRfZFneNPWnqfhQZlxsjw5gzTgV792MPDbZAm/1eziGCvPgX01W -f2eadxSYuJRLtmOBggwo/vC04MWWQbmYgJfOjL8DDWS17cdfLa8IjUYV8MDStWY6 -PDg1gaA5s3UroFh/nOCipoGvq51iSUF/GYd2OJAUd20SjMR+TQgK3lPuX4hMtVId -4x/xrkoY4q0MZJmrB6ysbpeHhl/HA+ofwScNtyKL3iQHN6oQ8llBoMuF9xFm4xX8 -fn8WHrd+hD0S8hnBkTJ2ckSqJDxzGFu4+6NBhEWtcigzn9iD7HUWljXAUkEfN39I -jdgaxjrwE3FagE+RCEbdRXDpHYWlyo91YYqFedcT2v/l73twyFw7p3zYskW8pjRZ -F0Lqvn7fiOzxNi98tVYHqs4L17BOWFQt8MhQr9f590jtGQ/+ufhAb33/E9JFQXUg -cIYqWzBX7dw= -=ZsUE ------END PGP PUBLIC KEY BLOCK----- diff --git a/pubkeys/prod.key b/pubkeys/prod.key deleted file mode 100644 index 8a6d181..0000000 --- a/pubkeys/prod.key +++ /dev/null @@ -1,53 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -Zbk= -=5n1q ------END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/check.py b/scripts/check.py deleted file mode 100755 index e4e79e3..0000000 --- a/scripts/check.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import subprocess -import sys -import json - -PROD_SIGNING_KEY_PATH = "pubkeys/prod.key" -PROD_SIGNING_KEY_PATH_LEGACY = "pubkeys/prod-legacy.key" - -RPM_DIR = "workstation" - - -def verify_sig_rpm(path): - - for key_path in [PROD_SIGNING_KEY_PATH, PROD_SIGNING_KEY_PATH_LEGACY]: - 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(): - for root, dirs, files in os.walk(RPM_DIR): - for name in files: - verify_sig_rpm(os.path.join(root, name)) - - -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("--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() - else: - for package in args.packages: - assert os.path.exists(package) - verify_sig_rpm(package) - - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/tools/check-signed b/tools/check-signed new file mode 100755 index 0000000..a7a7a49 --- /dev/null +++ b/tools/check-signed @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import argparse +import subprocess +import sys +from pathlib import Path + + +def verify_sig_rpm(path: Path): + output = subprocess.check_output(["rpm", "--checksig", path], text=True).strip() + # rpm --checksig returns 0 if there is *no* signature, so we have + # to parse stdout + print(output) + expected = "{}: digests signatures OK".format(path) + if output != expected: + print(f"Signature verification failed for {path}") + sys.exit(1) + + +def remove_keys_in_rpm_keyring(): + # Returns non-zero if no keys are installed + result = subprocess.run(["rpm", "-q", "gpg-pubkey"], stdout=subprocess.PIPE) + if result.returncode == 0: + # If a key is in the keyring, delete it + subprocess.check_call( + ["rpm", "--erase", "--allmatches", "gpg-pubkey"], stderr=subprocess.PIPE + ) + + +def main(): + parser = argparse.ArgumentParser(description="Verify all .rpm files are signed") + parser.add_argument("keyring", type=Path) + args = parser.parse_args() + if not args.keyring.exists(): + raise RuntimeError(f"{args.keyring} doesn't exist!") + # Since we can't specify with which key to check sigs, clear the keyring + # and just import our signing key + remove_keys_in_rpm_keyring() + subprocess.check_call(["rpmkeys", "--import", str(args.keyring)]) + for rpm in Path("workstation").glob("**/*.rpm"): + verify_sig_rpm(rpm) + + +if __name__ == "__main__": + main()