|
|
@@ -0,0 +1,372 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+
|
|
|
+"""
|
|
|
+Password Generator for Emergency Access Server
|
|
|
+Generates secure passwords and their hashes for HTTP Basic Authentication
|
|
|
+"""
|
|
|
+
|
|
|
+import secrets
|
|
|
+import string
|
|
|
+import hashlib
|
|
|
+import argparse
|
|
|
+import json
|
|
|
+import getpass
|
|
|
+from typing import Dict, Any
|
|
|
+
|
|
|
+
|
|
|
+def generate_secure_password(length: int = 64) -> str:
|
|
|
+ """
|
|
|
+ Generate a cryptographically secure password.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ length: Password length (default: 16)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Secure password string
|
|
|
+ """
|
|
|
+ # Use a mix of letters, numbers, and safe symbols
|
|
|
+ alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
|
|
+ return ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
|
+
|
|
|
+
|
|
|
+def generate_password_hash(password: str) -> str:
|
|
|
+ """
|
|
|
+ Generate a password hash using PBKDF2 with SHA-256.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ password: Plain text password
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Password hash in format "salt:hash"
|
|
|
+ """
|
|
|
+ salt = secrets.token_hex(16)
|
|
|
+ password_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
|
|
|
+ return f"{salt}:{password_hash.hex()}"
|
|
|
+
|
|
|
+
|
|
|
+def verify_password(password: str, password_hash: str) -> bool:
|
|
|
+ """
|
|
|
+ Verify a password against its hash.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ password: Plain text password
|
|
|
+ password_hash: Hash in format "salt:hash"
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ True if password matches, False otherwise
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ salt, stored_hash = password_hash.split(':')
|
|
|
+ computed_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
|
|
|
+ return computed_hash.hex() == stored_hash
|
|
|
+ except Exception:
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+def generate_key_credentials(key_id: str) -> Dict[str, str]:
|
|
|
+ """
|
|
|
+ Generate username and password for a key.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ key_id: Key identifier
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Dictionary with username, password, and password_hash
|
|
|
+ """
|
|
|
+ username = f"emergency_{key_id}"
|
|
|
+ password = generate_secure_password(64) # Longer for emergency keys
|
|
|
+ password_hash = generate_password_hash(password)
|
|
|
+
|
|
|
+ return {
|
|
|
+ "username": username,
|
|
|
+ "password": password,
|
|
|
+ "password_hash": password_hash
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def generate_health_credentials() -> Dict[str, str]:
|
|
|
+ """
|
|
|
+ Generate username and password for health endpoint.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Dictionary with username, password, and password_hash
|
|
|
+ """
|
|
|
+ username = "health_monitor"
|
|
|
+ password = generate_secure_password(16)
|
|
|
+ password_hash = generate_password_hash(password)
|
|
|
+
|
|
|
+ return {
|
|
|
+ "username": username,
|
|
|
+ "password": password,
|
|
|
+ "password_hash": password_hash
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def generate_full_credentials(keys: list) -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ Generate all credentials for a configuration.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ keys: List of key identifiers
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Dictionary with all credentials
|
|
|
+ """
|
|
|
+ credentials = {
|
|
|
+ "health": generate_health_credentials(),
|
|
|
+ "keys": {}
|
|
|
+ }
|
|
|
+
|
|
|
+ for key_id in keys:
|
|
|
+ credentials["keys"][key_id] = generate_key_credentials(key_id)
|
|
|
+
|
|
|
+ return credentials
|
|
|
+
|
|
|
+
|
|
|
+def update_config_with_credentials(config_path: str, credentials: Dict[str, Any]) -> None:
|
|
|
+ """
|
|
|
+ Update a configuration file with generated credentials.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ config_path: Path to configuration file
|
|
|
+ credentials: Generated credentials
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ with open(config_path, 'r') as f:
|
|
|
+ config = json.load(f)
|
|
|
+ except FileNotFoundError:
|
|
|
+ print(f"Configuration file {config_path} not found")
|
|
|
+ return
|
|
|
+ except json.JSONDecodeError:
|
|
|
+ print(f"Invalid JSON in {config_path}")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Update health credentials
|
|
|
+ if "routes" not in config:
|
|
|
+ config["routes"] = {}
|
|
|
+
|
|
|
+ config["routes"]["health_username"] = credentials["health"]["username"]
|
|
|
+ config["routes"]["health_password_hash"] = credentials["health"]["password_hash"]
|
|
|
+
|
|
|
+ # Update key credentials
|
|
|
+ if "keys" in config:
|
|
|
+ for key_id, key_creds in credentials["keys"].items():
|
|
|
+ if key_id in config["keys"]:
|
|
|
+ config["keys"][key_id]["username"] = key_creds["username"]
|
|
|
+ config["keys"][key_id]["password_hash"] = key_creds["password_hash"]
|
|
|
+
|
|
|
+ # Write updated config
|
|
|
+ with open(config_path, 'w') as f:
|
|
|
+ json.dump(config, f, indent=2)
|
|
|
+
|
|
|
+ print(f"Updated configuration file: {config_path}")
|
|
|
+
|
|
|
+
|
|
|
+def print_credentials(credentials: Dict[str, Any], show_passwords: bool = True) -> None:
|
|
|
+ """
|
|
|
+ Print generated credentials in a readable format.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ credentials: Generated credentials
|
|
|
+ show_passwords: Whether to show plain text passwords
|
|
|
+ """
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+ print("GENERATED CREDENTIALS")
|
|
|
+ print("=" * 60)
|
|
|
+
|
|
|
+ print("\nHealth Endpoint:")
|
|
|
+ print(f" Username: {credentials['health']['username']}")
|
|
|
+ if show_passwords:
|
|
|
+ print(f" Password: {credentials['health']['password']}")
|
|
|
+ print(f" Hash: {credentials['health']['password_hash']}")
|
|
|
+
|
|
|
+ print("\nKey Endpoints:")
|
|
|
+ for key_id, key_creds in credentials["keys"].items():
|
|
|
+ print(f"\n {key_id}:")
|
|
|
+ print(f" Username: {key_creds['username']}")
|
|
|
+ if show_passwords:
|
|
|
+ print(f" Password: {key_creds['password']}")
|
|
|
+ print(f" Hash: {key_creds['password_hash']}")
|
|
|
+
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+
|
|
|
+ if show_passwords:
|
|
|
+ print("⚠️ SECURITY WARNING:")
|
|
|
+ print(" Store these passwords securely!")
|
|
|
+ print(" The plain text passwords are shown only once.")
|
|
|
+ print(" Consider using a password manager.")
|
|
|
+
|
|
|
+ print("\n💡 Usage Examples:")
|
|
|
+ print(f" curl -u {credentials['health']['username']}:PASSWORD http://localhost:1127/health-check")
|
|
|
+ for key_id, key_creds in credentials["keys"].items():
|
|
|
+ print(f" curl -u {key_creds['username']}:PASSWORD http://localhost:1127/emergency-key-{key_id}")
|
|
|
+
|
|
|
+
|
|
|
+def interactive_password_setup() -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ Interactive setup for custom passwords.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Dictionary with credentials using user-provided passwords
|
|
|
+ """
|
|
|
+ print("\n" + "=" * 60)
|
|
|
+ print("INTERACTIVE PASSWORD SETUP")
|
|
|
+ print("=" * 60)
|
|
|
+
|
|
|
+ credentials = {"keys": {}}
|
|
|
+
|
|
|
+ # Health endpoint password
|
|
|
+ print("\nHealth Endpoint Setup:")
|
|
|
+ while True:
|
|
|
+ password = getpass.getpass("Enter password for health endpoint: ")
|
|
|
+ confirm = getpass.getpass("Confirm password: ")
|
|
|
+
|
|
|
+ if password == confirm and len(password) >= 8:
|
|
|
+ credentials["health"] = {
|
|
|
+ "username": "health_monitor",
|
|
|
+ "password": password,
|
|
|
+ "password_hash": generate_password_hash(password)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ elif password != confirm:
|
|
|
+ print("❌ Passwords don't match. Try again.")
|
|
|
+ else:
|
|
|
+ print("❌ Password must be at least 8 characters. Try again.")
|
|
|
+
|
|
|
+ # Key passwords
|
|
|
+ print("\nKey Endpoint Setup:")
|
|
|
+ key_count = int(input("How many keys do you want to configure? "))
|
|
|
+
|
|
|
+ for i in range(key_count):
|
|
|
+ key_id = input(f"Enter key ID for key {i+1} (e.g., 'backup', 'master'): ").strip()
|
|
|
+
|
|
|
+ while True:
|
|
|
+ password = getpass.getpass(f"Enter password for {key_id}: ")
|
|
|
+ confirm = getpass.getpass("Confirm password: ")
|
|
|
+
|
|
|
+ if password == confirm and len(password) >= 12:
|
|
|
+ credentials["keys"][key_id] = {
|
|
|
+ "username": f"emergency_{key_id}",
|
|
|
+ "password": password,
|
|
|
+ "password_hash": generate_password_hash(password)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ elif password != confirm:
|
|
|
+ print("❌ Passwords don't match. Try again.")
|
|
|
+ else:
|
|
|
+ print("❌ Key passwords must be at least 12 characters. Try again.")
|
|
|
+
|
|
|
+ return credentials
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ parser = argparse.ArgumentParser(
|
|
|
+ description="Generate secure passwords and hashes for Emergency Access Server",
|
|
|
+ formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
+ epilog="""
|
|
|
+Examples:
|
|
|
+ # Generate credentials for specific keys
|
|
|
+ python generate_passwords.py --keys backup master recovery
|
|
|
+
|
|
|
+ # Generate and update config file
|
|
|
+ python generate_passwords.py --keys backup master --update-config config.json
|
|
|
+
|
|
|
+ # Interactive setup
|
|
|
+ python generate_passwords.py --interactive
|
|
|
+
|
|
|
+ # Hash a specific password
|
|
|
+ python generate_passwords.py --hash-password "my_secure_password"
|
|
|
+
|
|
|
+ # Verify a password against a hash
|
|
|
+ python generate_passwords.py --verify-password "password" --hash "salt:hash"
|
|
|
+ """
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--keys', '-k',
|
|
|
+ nargs='+',
|
|
|
+ help='Key identifiers to generate credentials for'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--update-config', '-u',
|
|
|
+ metavar='CONFIG_FILE',
|
|
|
+ help='Update configuration file with generated credentials'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--interactive', '-i',
|
|
|
+ action='store_true',
|
|
|
+ help='Interactive password setup'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--hash-password',
|
|
|
+ metavar='PASSWORD',
|
|
|
+ help='Generate hash for a specific password'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--verify-password',
|
|
|
+ metavar='PASSWORD',
|
|
|
+ help='Verify a password against a hash (use with --hash)'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--hash',
|
|
|
+ metavar='HASH',
|
|
|
+ help='Hash to verify password against'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--hide-passwords',
|
|
|
+ action='store_true',
|
|
|
+ help='Don\'t show plain text passwords in output'
|
|
|
+ )
|
|
|
+
|
|
|
+ parser.add_argument(
|
|
|
+ '--length',
|
|
|
+ type=int,
|
|
|
+ default=16,
|
|
|
+ help='Password length for generated passwords (default: 16)'
|
|
|
+ )
|
|
|
+
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ # Handle specific operations
|
|
|
+ if args.hash_password:
|
|
|
+ password_hash = generate_password_hash(args.hash_password)
|
|
|
+ print(f"Password hash: {password_hash}")
|
|
|
+ return
|
|
|
+
|
|
|
+ if args.verify_password and args.hash:
|
|
|
+ is_valid = verify_password(args.verify_password, args.hash)
|
|
|
+ print(f"Password verification: {'✅ VALID' if is_valid else '❌ INVALID'}")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Generate credentials
|
|
|
+ if args.interactive:
|
|
|
+ credentials = interactive_password_setup()
|
|
|
+ elif args.keys:
|
|
|
+ credentials = generate_full_credentials(args.keys)
|
|
|
+ else:
|
|
|
+ # Default: generate for common keys
|
|
|
+ credentials = generate_full_credentials(['backup', 'master'])
|
|
|
+
|
|
|
+ # Print results
|
|
|
+ print_credentials(credentials, show_passwords=not args.hide_passwords)
|
|
|
+
|
|
|
+ # Update config if requested
|
|
|
+ if args.update_config:
|
|
|
+ update_config_with_credentials(args.update_config, credentials)
|
|
|
+
|
|
|
+ # Save credentials to file
|
|
|
+ credentials_file = "emergency_credentials.json"
|
|
|
+ with open(credentials_file, 'w') as f:
|
|
|
+ json.dump(credentials, f, indent=2)
|
|
|
+ print(f"\n💾 Credentials saved to: {credentials_file}")
|
|
|
+ print("⚠️ Remember to store this file securely and delete it after copying passwords!")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|