Files

146 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
Plugin HMAC Signer
Generates HMAC-SHA256 signatures for plugins and updates manifests.
Usage:
python scripts/plugin_hmac_sign.py pipeline/plugins/community/example.py
DOCTOR_PLUGIN_HMAC_KEY="secret" python scripts/plugin_hmac_sign.py example.py
Exit codes:
0 = Success
1 = Error
"""
import argparse
import getpass
import hashlib
import hmac
import json
import os
import sys
from pathlib import Path
# Minimum key length (32 chars = 256 bits)
MIN_KEY_LENGTH = 32
def get_hmac_key() -> str:
"""Get HMAC key from environment or prompt."""
# Try environment variable first
key = os.environ.get('DOCTOR_PLUGIN_HMAC_KEY', '')
if key:
print("Reading key from environment: DOCTOR_PLUGIN_HMAC_KEY")
else:
print("DOCTOR_PLUGIN_HMAC_KEY not set in environment")
key = getpass.getpass("Enter HMAC key (will not echo): ")
return key
def validate_key(key: str) -> tuple[bool, str]:
"""Validate HMAC key strength."""
if len(key) < MIN_KEY_LENGTH:
return False, f"Key too short: {len(key)} chars (min: {MIN_KEY_LENGTH})"
return True, "OK"
def compute_hmac(file_path: Path, key: str) -> str:
"""Compute HMAC-SHA256 of file."""
file_bytes = file_path.read_bytes()
signature = hmac.new(key.encode('utf-8'), file_bytes, hashlib.sha256).hexdigest()
return signature
def update_manifest(manifest_path: Path, signature: str, algorithm: str = "hmac-sha256"):
"""Update manifest with signature fields."""
# Load existing manifest
with manifest_path.open('r', encoding='utf-8') as f:
manifest = json.load(f)
# Add signature fields
manifest['signature'] = signature
manifest['signature_alg'] = algorithm
# Write back
with manifest_path.open('w', encoding='utf-8') as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
f.write('\n')
def process_plugin(plugin_path: Path, key: str) -> bool:
"""Process a single plugin file."""
print(f"\nHMAC Plugin HMAC Signer\n")
# Validate key
valid, message = validate_key(key)
if not valid:
print(f"FAIL {message}")
return False
print(f"Key length: {len(key)} chars OK\n")
# Check plugin exists
if not plugin_path.exists():
print(f"FAIL Plugin not found: {plugin_path}")
return False
# Check manifest exists
manifest_path = plugin_path.with_suffix('.json')
if not manifest_path.exists():
print(f"FAIL Manifest not found: {manifest_path}")
print("Run plugin_manifest.py first to create manifest")
return False
print(f"Processing: {plugin_path}")
# Compute SHA256 (for verification)
sha256 = hashlib.sha256(plugin_path.read_bytes()).hexdigest()
print(f" SHA256: {sha256}")
# Compute HMAC
signature = compute_hmac(plugin_path, key)
print(f" HMAC-SHA256: {signature}\n")
# Update manifest
print(f"Updating manifest: {manifest_path}")
update_manifest(manifest_path, signature)
print(f" Added: signature")
print(f" Added: signature_alg\n")
print("PASS Manifest updated successfully")
return True
def main():
parser = argparse.ArgumentParser(description="Plugin HMAC Signer")
parser.add_argument("plugin", type=Path, help="Plugin file to sign")
args = parser.parse_args()
# Get HMAC key
key = get_hmac_key()
if not key:
print("FAIL No HMAC key provided")
print("\nSet DOCTOR_PLUGIN_HMAC_KEY environment variable or enter interactively")
return 1
# Process plugin
try:
success = process_plugin(args.plugin, key)
return 0 if success else 1
except Exception as e:
print(f"FAIL Error: {e}")
return 1
finally:
# Clear key from memory
key = None
if __name__ == '__main__':
sys.exit(main())