Use Sparkle signer for full appcasts
This commit is contained in:
parent
a84922f4f3
commit
1d45016a63
4 changed files with 63 additions and 17 deletions
|
|
@ -1,5 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sign Sparkle appcast enclosures with a Google Cloud KMS Ed25519 key."""
|
||||
"""Sign Sparkle appcast enclosures.
|
||||
|
||||
Google Cloud KMS Ed25519 signing can only sign small payloads directly. Sparkle
|
||||
requires a standard EdDSA signature over the full update archive, so normal
|
||||
release artifacts use Sparkle's signing tool with the decrypted release key.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -20,13 +25,14 @@ DEFAULT_LOCATION = "global"
|
|||
DEFAULT_KEYRING = "burrow-identity"
|
||||
DEFAULT_KEY = "sparkle-ed25519"
|
||||
DEFAULT_VERSION = "1"
|
||||
DEFAULT_KMS_MAX_INPUT_BYTES = 65536
|
||||
|
||||
|
||||
def run(command: list[str]) -> None:
|
||||
subprocess.run(command, check=True)
|
||||
def run(command: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE)
|
||||
|
||||
|
||||
def kms_sign(args: argparse.Namespace, payload: Path) -> bytes:
|
||||
def kms_sign(args: argparse.Namespace, payload: Path) -> str:
|
||||
with tempfile.TemporaryDirectory(prefix="burrow-sparkle-kms.") as tmp:
|
||||
signature_path = Path(tmp) / "signature.bin"
|
||||
command = [
|
||||
|
|
@ -49,7 +55,33 @@ def kms_sign(args: argparse.Namespace, payload: Path) -> bytes:
|
|||
if args.gcloud_project:
|
||||
command.extend(["--project", args.gcloud_project])
|
||||
run(command)
|
||||
return base64.b64decode(signature_path.read_text(encoding="utf-8").strip())
|
||||
return base64.b64encode(signature_path.read_bytes()).decode("ascii")
|
||||
|
||||
|
||||
def sign_update(args: argparse.Namespace, payload: Path) -> str:
|
||||
key_path = Path(args.ed_key_path) if args.ed_key_path else None
|
||||
if key_path is None or not key_path.is_file():
|
||||
raise FileNotFoundError(
|
||||
"Sparkle artifact is too large for direct Google KMS Ed25519 signing "
|
||||
"and SPARKLE_EDDSA_KEY_PATH does not point to a key file"
|
||||
)
|
||||
result = run([
|
||||
args.sign_update,
|
||||
"--ed-key-file",
|
||||
str(key_path),
|
||||
"-p",
|
||||
str(payload),
|
||||
])
|
||||
signature = result.stdout.strip().splitlines()[-1].strip()
|
||||
if not signature:
|
||||
raise ValueError(f"Sparkle sign_update produced no signature for {payload}")
|
||||
return signature
|
||||
|
||||
|
||||
def sparkle_signature(args: argparse.Namespace, payload: Path) -> str:
|
||||
if payload.stat().st_size <= args.kms_max_input_bytes:
|
||||
return kms_sign(args, payload)
|
||||
return sign_update(args, payload)
|
||||
|
||||
|
||||
def enclosure_path(enclosure: ET.Element, artifact_dir: Path) -> Path:
|
||||
|
|
@ -78,8 +110,7 @@ def sign_appcast(args: argparse.Namespace) -> None:
|
|||
|
||||
for enclosure in enclosures:
|
||||
artifact = enclosure_path(enclosure, artifact_dir)
|
||||
signature = kms_sign(args, artifact)
|
||||
enclosure.attrib[f"{{{SPARKLE_NS}}}edSignature"] = base64.b64encode(signature).decode("ascii")
|
||||
enclosure.attrib[f"{{{SPARKLE_NS}}}edSignature"] = sparkle_signature(args, artifact)
|
||||
|
||||
tree.write(appcast, encoding="utf-8", xml_declaration=True)
|
||||
print(f"signed {len(enclosures)} enclosure(s) in {appcast}")
|
||||
|
|
@ -94,7 +125,10 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|||
parser.add_argument("--kms-keyring", default=os.environ.get("BURROW_KMS_KEYRING", DEFAULT_KEYRING))
|
||||
parser.add_argument("--kms-key", default=os.environ.get("BURROW_SPARKLE_KMS_KEY", DEFAULT_KEY))
|
||||
parser.add_argument("--kms-version", default=os.environ.get("BURROW_SPARKLE_KMS_VERSION", DEFAULT_VERSION))
|
||||
parser.add_argument("--kms-max-input-bytes", type=int, default=DEFAULT_KMS_MAX_INPUT_BYTES)
|
||||
parser.add_argument("--gcloud", default=os.environ.get("GCLOUD", "gcloud"))
|
||||
parser.add_argument("--ed-key-path", default=os.environ.get("SPARKLE_EDDSA_KEY_PATH"))
|
||||
parser.add_argument("--sign-update", default=os.environ.get("SPARKLE_SIGN_UPDATE", "sign_update"))
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue