Release 1.1.3 with game window and Gamescope lifecycle fixes
This commit is contained in:
139
tools/sign_release_artifact.py
Normal file
139
tools/sign_release_artifact.py
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create and verify an atomic OpenSSL signature for one release artifact."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Sequence
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
_BOOTSTRAP_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BOOTSTRAP_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BOOTSTRAP_ROOT))
|
||||
|
||||
from scripts.release_lib import ( # noqa: E402
|
||||
ReleaseToolError,
|
||||
_safe_signing_key_path,
|
||||
)
|
||||
|
||||
|
||||
class ReleaseSigningError(RuntimeError):
|
||||
"""Raised when a release artifact cannot be signed safely."""
|
||||
|
||||
|
||||
def sign_release_artifact(
|
||||
artifact: Path,
|
||||
*,
|
||||
signing_key: Path,
|
||||
public_key: Path,
|
||||
) -> Path:
|
||||
"""Sign an artifact and verify the result with the published public key."""
|
||||
|
||||
source = _regular_file(artifact, "release artifact")
|
||||
try:
|
||||
private = _safe_signing_key_path(signing_key)
|
||||
except ReleaseToolError as exc:
|
||||
raise ReleaseSigningError(str(exc)) from exc
|
||||
public = _regular_file(public_key, "public key")
|
||||
openssl = shutil.which("openssl")
|
||||
if openssl is None:
|
||||
raise ReleaseSigningError("OpenSSL is required to sign release artifacts")
|
||||
signature = Path(f"{source}.sig")
|
||||
if signature.is_dir() and not signature.is_symlink():
|
||||
raise ReleaseSigningError(f"signature path is a directory: {signature}")
|
||||
signature.unlink(missing_ok=True)
|
||||
temporary = signature.with_name(f".{signature.name}.{uuid4().hex}.tmp")
|
||||
try:
|
||||
_sign(openssl, source, temporary, private)
|
||||
_verify(openssl, source, temporary, public)
|
||||
temporary.chmod(0o644)
|
||||
temporary.replace(signature)
|
||||
except BaseException:
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise
|
||||
return signature
|
||||
|
||||
|
||||
def _regular_file(path: Path, label: str) -> Path:
|
||||
value = path.expanduser()
|
||||
if value.is_symlink() or not value.is_file():
|
||||
raise ReleaseSigningError(f"{label} is missing or not a regular file: {value}")
|
||||
return value.resolve()
|
||||
|
||||
|
||||
def _sign(openssl: str, artifact: Path, signature: Path, private_key: Path) -> None:
|
||||
_run_openssl(
|
||||
[
|
||||
openssl,
|
||||
"dgst",
|
||||
"-sha256",
|
||||
"-sign",
|
||||
str(private_key),
|
||||
"-out",
|
||||
str(signature),
|
||||
str(artifact),
|
||||
],
|
||||
"sign the release artifact",
|
||||
)
|
||||
|
||||
|
||||
def _verify(openssl: str, artifact: Path, signature: Path, public_key: Path) -> None:
|
||||
_run_openssl(
|
||||
[
|
||||
openssl,
|
||||
"dgst",
|
||||
"-sha256",
|
||||
"-verify",
|
||||
str(public_key),
|
||||
"-signature",
|
||||
str(signature),
|
||||
str(artifact),
|
||||
],
|
||||
"verify the release artifact signature",
|
||||
)
|
||||
|
||||
|
||||
def _run_openssl(command: list[str], action: str) -> None:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||
raise ReleaseSigningError(f"OpenSSL could not {action}: {exc}") from exc
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or result.stdout.strip() or "unknown error"
|
||||
raise ReleaseSigningError(f"OpenSSL could not {action}: {detail}")
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("artifact", type=Path)
|
||||
parser.add_argument("--signing-key", type=Path, required=True)
|
||||
parser.add_argument("--public-key", type=Path, required=True)
|
||||
arguments = parser.parse_args(argv)
|
||||
try:
|
||||
signature = sign_release_artifact(
|
||||
arguments.artifact,
|
||||
signing_key=arguments.signing_key,
|
||||
public_key=arguments.public_key,
|
||||
)
|
||||
except (OSError, ReleaseSigningError) as exc:
|
||||
print(f"Could not sign release artifact: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
print(signature)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user