mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00
dev_scripts: Sign our assets and calculate their hashes
Add a new script called `sign-assets.py`, which produces the hash of all the Dangerzone assets for a release (Windows/macOS installers, container image), and signs them individually. Also update our RELEASE.md document, to incorporate this script into our release workflow.
This commit is contained in:
parent
f6a39ec140
commit
83c165ae33
2 changed files with 134 additions and 1 deletions
|
@ -395,6 +395,9 @@ repo.
|
||||||
To publish the release:
|
To publish the release:
|
||||||
|
|
||||||
- [ ] Run container scan on the produced container images (some time may have passed since the artifacts were built)
|
- [ ] Run container scan on the produced container images (some time may have passed since the artifacts were built)
|
||||||
|
- [ ] Collect the assets in a single directory, calculate their SHA-256 hashes, and sign them.
|
||||||
|
* You can use `./dev_scripts/sign-assets.py`, if you want to automate this
|
||||||
|
task.
|
||||||
- [ ] Create a new **draft** release on GitHub and upload the macOS and Windows installers.
|
- [ ] Create a new **draft** release on GitHub and upload the macOS and Windows installers.
|
||||||
* Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
|
* Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
|
||||||
* You can use `./dev_scripts/upload-asset.py`, if you want to upload an asset
|
* You can use `./dev_scripts/upload-asset.py`, if you want to upload an asset
|
||||||
|
@ -404,7 +407,8 @@ To publish the release:
|
||||||
**Important:** Make sure that it's the same container image as the ones that
|
**Important:** Make sure that it's the same container image as the ones that
|
||||||
are shipped in other platforms (see our [Pre-release](#Pre-release) section)
|
are shipped in other platforms (see our [Pre-release](#Pre-release) section)
|
||||||
|
|
||||||
- [ ] Update the [Dangerzone website](https://github.com/freedomofpress/dangerzone.rocks) to link to the new installers
|
- [ ] Upload the detached signatures (.asc) and checksum file.
|
||||||
|
- [ ] Update the [Dangerzone website](https://github.com/freedomofpress/dangerzone.rocks) to link to the new installers and signatures
|
||||||
- [ ] Update the brew cask release of Dangerzone with a [PR like this one](https://github.com/Homebrew/homebrew-cask/pull/116319)
|
- [ ] Update the brew cask release of Dangerzone with a [PR like this one](https://github.com/Homebrew/homebrew-cask/pull/116319)
|
||||||
- [ ] Update version and download links in `README.md`
|
- [ ] Update version and download links in `README.md`
|
||||||
|
|
||||||
|
|
129
dev_scripts/sign-assets.py
Executable file
129
dev_scripts/sign-assets.py
Executable file
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
DZ_ASSETS = [
|
||||||
|
"container.tar.gz",
|
||||||
|
"Dangerzone-{version}.msi",
|
||||||
|
"Dangerzone-{version}-arm64.dmg",
|
||||||
|
"Dangerzone-{version}-i686.dmg",
|
||||||
|
]
|
||||||
|
DZ_SIGNING_PUBKEY = "DE28AB241FA48260FAC9B8BAA7C9B38522604281"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_asset(asset, detached=True):
|
||||||
|
"""Sign a single Dangerzone asset using GPG.
|
||||||
|
|
||||||
|
By default, ask GPG to create a detached signature. Alternatively, ask it to include
|
||||||
|
the signature with the contents of the file.
|
||||||
|
"""
|
||||||
|
_sign_opt = "--detach-sig" if detached else "--clearsign"
|
||||||
|
cmd = [
|
||||||
|
"gpg",
|
||||||
|
"--batch",
|
||||||
|
"--yes",
|
||||||
|
"--armor",
|
||||||
|
_sign_opt,
|
||||||
|
"-u",
|
||||||
|
DZ_SIGNING_PUBKEY,
|
||||||
|
str(asset),
|
||||||
|
]
|
||||||
|
log.info(f"Signing '{asset}'")
|
||||||
|
log.debug(f"GPG command: {' '.join(cmd)}")
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def hash_assets(assets):
|
||||||
|
"""Create a list of hashes for all the assets, mimicking the output of `sha256sum`.
|
||||||
|
|
||||||
|
Compute the SHA-256 hash of every asset, and create a line for each asset that
|
||||||
|
follows the format of `sha256sum`. From `man sha256sum`:
|
||||||
|
|
||||||
|
The sums are computed as described in FIPS-180-2. When checking, the input
|
||||||
|
should be a former output of this program. The default mode is to print a
|
||||||
|
line with: checksum, a space, a character indicating input mode ('*' for
|
||||||
|
binary, ' ' for text or where binary is insignificant), and name for each
|
||||||
|
FILE.
|
||||||
|
"""
|
||||||
|
checksums = []
|
||||||
|
for asset in assets:
|
||||||
|
log.info(f"Hashing '{asset}'")
|
||||||
|
with open(asset, "rb") as f:
|
||||||
|
hexdigest = hashlib.file_digest(f, "sha256").hexdigest()
|
||||||
|
checksums.append(f"{hexdigest} {asset.name}")
|
||||||
|
return "\n".join(checksums)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_assets_exist(assets):
|
||||||
|
"""Ensure that assets dir exists, and that the assets are all there."""
|
||||||
|
dir = assets[0].parent
|
||||||
|
if not dir.exists():
|
||||||
|
raise ValueError(f"Path '{dir}' does not exist")
|
||||||
|
if not dir.is_dir():
|
||||||
|
raise ValueError(f"Path '{dir}' is not a directory")
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
if not asset.exists():
|
||||||
|
raise ValueError(
|
||||||
|
f"Expected asset with name '{asset}', but it does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=sys.argv[0],
|
||||||
|
description="Dev script for signing Dangerzone assets",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
required=True,
|
||||||
|
help=f"look for assets with this Dangerzone version",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"dir",
|
||||||
|
help=f"look for assets in this directory",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
# Ensure that all the necessary assets exist in the provided directory.
|
||||||
|
log.info("> Ensuring that the required assets exist")
|
||||||
|
dir = pathlib.Path(args.dir)
|
||||||
|
assets = [dir / asset.format(version=args.version) for asset in DZ_ASSETS]
|
||||||
|
ensure_assets_exist(assets)
|
||||||
|
|
||||||
|
# Create a file that holds the SHA-256 hashes of the assets.
|
||||||
|
log.info("> Create a checksums file for our assets")
|
||||||
|
checksums = hash_assets(assets)
|
||||||
|
checksums_file = dir / f"checksums-{args.version}.txt"
|
||||||
|
with open(checksums_file, "w+") as f:
|
||||||
|
f.write(checksums)
|
||||||
|
|
||||||
|
# Sign every asset and create a detached signature (.asc) for each one of them. The
|
||||||
|
# sole exception is the checksums file, which embeds its signature within the
|
||||||
|
# file, and retains its original name.
|
||||||
|
log.info("> Sign all of our assets")
|
||||||
|
for asset in assets:
|
||||||
|
sign_asset(asset)
|
||||||
|
sign_asset(checksums_file, detached=False)
|
||||||
|
(dir / f"checksums-{args.version}.txt.asc").rename(checksums_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
Loading…
Reference in a new issue