Files
einkaufsapp/tools/vapid-keys.py
2026-08-08 20:31:58 +02:00

72 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Erzeugt das VAPID-Schlüsselpaar für Web Push.
python3 tools/vapid-keys.py
VAPID ("Voluntary Application Server Identification") ist die Art, wie
sich dieser Server gegenüber den Push-Diensten der Browserhersteller
ausweist. Das Schlüsselpaar gehört dir - es ist kein Konto bei Google
oder Apple nötig und es fließen keine Daten an Dritte, außer der
verschlüsselten Nachricht selbst.
Der öffentliche Schlüssel geht an den Browser, der private bleibt auf dem
Server. Wird der private Schlüssel getauscht, verlieren alle bestehenden
Anmeldungen ihre Gültigkeit und müssen erneuert werden - deshalb einmal
erzeugen und dann in Ruhe lassen.
Läuft ohne Zusatzpakete: `cryptography` ist bereits eine Abhängigkeit
des Backends.
"""
import base64
import sys
try:
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
except ImportError:
print(
"Das Paket 'cryptography' fehlt. Entweder im Container ausführen:\n"
" docker compose exec api python3 tools/vapid-keys.py\n"
"oder lokal installieren: pip install cryptography",
file=sys.stderr,
)
raise SystemExit(1)
def b64(data: bytes) -> str:
"""base64url ohne Auffüllzeichen - so verlangen es Web Push und JWT."""
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def main() -> int:
key = ec.generate_private_key(ec.SECP256R1())
# Privater Schlüssel: die 32 Byte des Skalars, nicht das PEM-Format.
# py_vapid nimmt diese Form direkt an.
private_value = key.private_numbers().private_value
private_bytes = private_value.to_bytes(32, "big")
# Öffentlicher Schlüssel: unkomprimierter Punkt, 65 Byte (0x04 + X + Y).
# Genau diese Form erwartet der Browser als applicationServerKey.
public_bytes = key.public_key().public_bytes(
encoding=serialization.Encoding.X962,
format=serialization.PublicFormat.UncompressedPoint,
)
print("# In die .env übernehmen. Vorhandene Zeilen ERSETZEN, nicht")
print("# ergänzen: Bei doppelten Einträgen nimmt Docker Compose den")
print("# letzten - und das ist oft die leere Vorlagenzeile.\n")
print(f"VAPID_PRIVATE_KEY={b64(private_bytes)}")
print(f"VAPID_PUBLIC_KEY={b64(public_bytes)}")
print("# Kontaktadresse für die Push-Dienste - bei Problemen melden sie sich dort.")
print("VAPID_SUBJECT=mailto:admin@example.de")
print()
print("# Der private Schlüssel gehört NICHT ins Versionsverwaltungssystem.")
print("# Ein Wechsel entwertet alle bestehenden Anmeldungen.")
return 0
if __name__ == "__main__":
raise SystemExit(main())