feat: RSA key management for ActivityPub signing

- keys.py: Generate/load RSA-2048 keypairs, sign activities
- setup_keys.py: CLI to generate keys
- Real RsaSignature2017 signing (falls back to placeholder if no keys)
- Public key included in actor profile
- Private keys gitignored

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-07 13:51:58 +00:00
parent acaf3a0ffa
commit dec5266554
6 changed files with 226 additions and 15 deletions

View File

@@ -117,13 +117,8 @@ def save_activities(activities: list):
def load_actor() -> dict:
"""Load actor data."""
path = DATA_DIR / "actor.json"
if path.exists():
with open(path) as f:
return json.load(f)
# Return default actor
return {
"""Load actor data with public key if available."""
actor = {
"id": f"https://{DOMAIN}/users/{USERNAME}",
"type": "Person",
"preferredUsername": USERNAME,
@@ -134,6 +129,17 @@ def load_actor() -> dict:
"following": f"https://{DOMAIN}/users/{USERNAME}/following",
}
# Add public key if available
from keys import has_keys, load_public_key_pem
if has_keys(DATA_DIR, USERNAME):
actor["publicKey"] = {
"id": f"https://{DOMAIN}/users/{USERNAME}#main-key",
"owner": f"https://{DOMAIN}/users/{USERNAME}",
"publicKeyPem": load_public_key_pem(DATA_DIR, USERNAME)
}
return actor
def load_followers() -> list:
"""Load followers list."""
@@ -153,15 +159,21 @@ def save_followers(followers: list):
# ============ Signing ============
from keys import has_keys, load_public_key_pem, create_signature
def sign_activity(activity: dict) -> dict:
"""Sign an activity (placeholder - real impl uses RSA)."""
# In production, use artdag.activitypub.signatures
activity["signature"] = {
"type": "RsaSignature2017",
"creator": f"https://{DOMAIN}/users/{USERNAME}#main-key",
"created": datetime.now(timezone.utc).isoformat(),
"signatureValue": "placeholder-implement-real-signing"
}
"""Sign an activity with RSA private key."""
if not has_keys(DATA_DIR, USERNAME):
# No keys - use placeholder (for testing)
activity["signature"] = {
"type": "RsaSignature2017",
"creator": f"https://{DOMAIN}/users/{USERNAME}#main-key",
"created": datetime.now(timezone.utc).isoformat(),
"signatureValue": "NO_KEYS_CONFIGURED"
}
else:
activity["signature"] = create_signature(DATA_DIR, USERNAME, DOMAIN, activity)
return activity