mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 09:52:37 +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:
|
||||
|
||||
- [ ] 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.
|
||||
* 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
|
||||
|
@ -404,7 +407,8 @@ To publish the release:
|
|||
**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)
|
||||
|
||||
- [ ] 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 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