Skip to main content

scripts-validate-checksums

#!/usr/bin/env python3 """

title: "Validate Checksums" component_type: script version: "1.0.0" audience: contributor status: stable summary: "validate-checksums.py - Verify SHA256 checksums against manifest or checksum file." keywords: ['checksums', 'validate'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "validate-checksums.py" language: python executable: true usage: "python3 scripts/validate-checksums.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: false

validate-checksums.py - Verify SHA256 checksums against manifest or checksum file.

This script validates that binary files match their expected checksums, detecting corruption or tampering in release artifacts.

Usage: python validate-checksums.py --manifest manifest.json --dir dist/ python validate-checksums.py --checksums SHA256SUMS --dir dist/ python validate-checksums.py --file binary --checksum abc123...

Exit Codes: 0 - All checksums valid 1 - One or more checksums invalid 2 - Missing files or configuration error """

import argparse import hashlib import json import sys from pathlib import Path from typing import Dict, List, Optional, Tuple

def calculate_sha256(file_path: Path) -> str: """Calculate SHA256 checksum of a file.""" sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): sha256_hash.update(chunk) return sha256_hash.hexdigest()

def load_manifest_checksums(manifest_path: Path) -> Dict[str, str]: """Load checksums from manifest.json file.""" with open(manifest_path, "r") as f: manifest = json.load(f)

checksums = {}
binaries = manifest.get("binaries", {})

for platform, info in binaries.items():
if "url" in info and "sha256" in info:
# Extract filename from URL
url = info["url"]
filename = url.split("/")[-1]
checksums[filename] = info["sha256"]

return checksums

def load_checksums_file(checksums_path: Path) -> Dict[str, str]: """Load checksums from SHA256SUMS format file.""" checksums = {} with open(checksums_path, "r") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue parts = line.split() if len(parts) >= 2: checksum = parts[0] # Handle both "hash filename" and "hash filename" formats filename = parts[1].lstrip("") checksums[filename] = checksum

return checksums

def verify_checksum( file_path: Path, expected_checksum: str, verbose: bool = False ) -> Tuple[bool, str]: """Verify a single file's checksum.""" if not file_path.exists(): return False, f"File not found: {file_path}"

actual_checksum = calculate_sha256(file_path)

if actual_checksum.lower() == expected_checksum.lower():
if verbose:
print(f"✓ {file_path.name}: VALID")
return True, actual_checksum
else:
if verbose:
print(f"✗ {file_path.name}: INVALID")
print(f" Expected: {expected_checksum}")
print(f" Actual: {actual_checksum}")
return False, actual_checksum

def verify_directory( checksums: Dict[str, str], directory: Path, verbose: bool = False ) -> Tuple[int, int, List[str]]: """Verify all files in a directory against checksums.""" valid_count = 0 invalid_count = 0 errors = []

for filename, expected_checksum in checksums.items():
file_path = directory / filename

# Also check for uncompressed versions
if not file_path.exists():
# Try without .tar.gz or .zip extension
base_name = filename
for ext in [".tar.gz", ".zip"]:
if filename.endswith(ext):
base_name = filename[: -len(ext)]
break
alt_path = directory / base_name
if alt_path.exists():
file_path = alt_path

if not file_path.exists():
if verbose:
print(f"⚠ {filename}: NOT FOUND")
errors.append(f"File not found: {filename}")
invalid_count += 1
continue

is_valid, actual = verify_checksum(file_path, expected_checksum, verbose)
if is_valid:
valid_count += 1
else:
invalid_count += 1
errors.append(
f"Checksum mismatch for {filename}: "
f"expected {expected_checksum}, got {actual}"
)

return valid_count, invalid_count, errors

def main(): parser = argparse.ArgumentParser( description="Verify SHA256 checksums for release artifacts", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Verify against manifest.json python validate-checksums.py --manifest dist/manifest.json --dir dist/

# Verify against SHA256SUMS file
python validate-checksums.py --checksums dist/SHA256SUMS --dir dist/

# Verify single file
python validate-checksums.py --file myapp-darwin-x64 --checksum abc123...

# Verbose output
python validate-checksums.py --manifest manifest.json --dir dist/ -v
""",
)

source_group = parser.add_mutually_exclusive_group(required=True)
source_group.add_argument(
"--manifest", type=Path, help="Path to manifest.json file"
)
source_group.add_argument(
"--checksums", type=Path, help="Path to SHA256SUMS file"
)
source_group.add_argument(
"--file", type=Path, help="Single file to verify"
)

parser.add_argument(
"--dir",
type=Path,
help="Directory containing files to verify",
)
parser.add_argument(
"--checksum",
type=str,
help="Expected checksum (required with --file)",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Verbose output"
)
parser.add_argument(
"--json", action="store_true", help="Output results as JSON"
)

args = parser.parse_args()

# Single file verification
if args.file:
if not args.checksum:
print("ERROR: --checksum is required when using --file", file=sys.stderr)
sys.exit(2)

is_valid, actual = verify_checksum(args.file, args.checksum, args.verbose)

if args.json:
result = {
"file": str(args.file),
"valid": is_valid,
"expected": args.checksum,
"actual": actual,
}
print(json.dumps(result, indent=2))
else:
if is_valid:
print(f"✓ Checksum valid for {args.file}")
else:
print(f"✗ Checksum invalid for {args.file}")
print(f" Expected: {args.checksum}")
print(f" Actual: {actual}")

sys.exit(0 if is_valid else 1)

# Directory verification
if not args.dir:
print("ERROR: --dir is required when using --manifest or --checksums", file=sys.stderr)
sys.exit(2)

if not args.dir.exists():
print(f"ERROR: Directory not found: {args.dir}", file=sys.stderr)
sys.exit(2)

# Load checksums
try:
if args.manifest:
if not args.manifest.exists():
print(f"ERROR: Manifest not found: {args.manifest}", file=sys.stderr)
sys.exit(2)
checksums = load_manifest_checksums(args.manifest)
source_name = f"manifest ({args.manifest})"
else:
if not args.checksums.exists():
print(f"ERROR: Checksums file not found: {args.checksums}", file=sys.stderr)
sys.exit(2)
checksums = load_checksums_file(args.checksums)
source_name = f"checksums ({args.checksums})"
except Exception as e:
print(f"ERROR: Failed to load checksums: {e}", file=sys.stderr)
sys.exit(2)

if not checksums:
print("ERROR: No checksums found in source file", file=sys.stderr)
sys.exit(2)

# Verify
if args.verbose:
print(f"Verifying {len(checksums)} files from {source_name}")
print(f"Directory: {args.dir}")
print("-" * 50)

valid, invalid, errors = verify_directory(checksums, args.dir, args.verbose)

# Output results
if args.json:
result = {
"source": str(args.manifest or args.checksums),
"directory": str(args.dir),
"total": valid + invalid,
"valid": valid,
"invalid": invalid,
"errors": errors,
"success": invalid == 0,
}
print(json.dumps(result, indent=2))
else:
print()
print("=" * 50)
print(f"Verification Results")
print("=" * 50)
print(f"Total files: {valid + invalid}")
print(f"Valid: {valid}")
print(f"Invalid: {invalid}")
print()

if errors:
print("Errors:")
for error in errors:
print(f" - {error}")
print()

if invalid == 0:
print("✓ All checksums valid")
else:
print("✗ Verification FAILED")

sys.exit(0 if invalid == 0 else 1)

if name == "main": main()