TESM-Lizenzserver 2.0.0 -- Neubau

Vollstaendiger Neubau der Anwendung. Der vorherige Stand bleibt unveraendert
im Zweig SONNET5 erhalten.

Aufbau: apps/tesm-license (Anwendung), packages/tesm-core (gemeinsamer Kern),
packages/tesm-licensing (Lizenzprotokoll), deploy (Installation, systemd,
privilegierter Helfer), docs, tests. Die verwaltete Anwendung liegt in ihrem
eigenen Repository; beide Repositorien bringen die gemeinsamen Pakete mit,
damit sich jedes allein installieren laesst.

Die wichtigsten Unterschiede zum Vorgaenger:

* Keine doppelte licensing.py -- ein Paket, das beide Anwendungen
  installieren, statt zweier Dateien, die byte-identisch bleiben sollen.
* Der Webprozess laeuft unprivilegiert; alles, was Root braucht, geht ueber
  einen einzigen Helfer mit Positivlisten fuer jedes Argument.
* CSRF-Schutz ueberhaupt -- der Vorgaenger hatte keinen.
* Rechte werden serverseitig geprueft, nicht nur im Template ausgeblendet.
* Keine Lizenz ohne master_endpoint: eine Ausstellung ohne Endpunkt wird
  abgelehnt statt eine Lizenz zu erzeugen, die sich nie aktivieren kann.
* Offline-Aktivierung in beide Richtungen; die Lizenz bleibt als
  "Aktivierung offen" markiert, bis sie zurueckkommt.
* Getrennte Signaturkontexte je Nachrichtenart, Nonce gegen Wiedereinspielung,
  seq gegen das Zurueckrollen auf eine aeltere Lizenz.
* Kein Hostname im Maschinen-Fingerabdruck.
* Verschachtelte Datenbankverbindungen sind ein Fehler, kein Deadlock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 18:01:23 +02:00
co-authored by Claude Opus 5
parent c130c85b17
commit f7805a2180
211 changed files with 31099 additions and 14759 deletions
@@ -0,0 +1,32 @@
"""Fehlerklassen des Lizenzprotokolls.
Alle Fehler sind bewusst so gebaut, dass sie *keine* Geheimnisse (Schluessel,
Signaturen, Payload-Inhalte) in ihrer Textform tragen -- sie landen in Logs und
teilweise in HTTP-Antworten.
"""
from __future__ import annotations
class LicensingError(Exception):
"""Basisklasse aller Protokollfehler."""
class InvalidLicenseFile(LicensingError):
"""Lizenzdatei ist strukturell kaputt, unvollstaendig oder falsch versioniert."""
class UntrustedIssuer(LicensingError):
"""Signatur ist in sich gueltig, der Aussteller aber nicht vertrauenswuerdig (Pinning)."""
class SignatureInvalid(LicensingError):
"""Signatur passt nicht zur Payload."""
class ReplayDetected(LicensingError):
"""Nonce wurde schon gesehen oder Zeitstempel liegt ausserhalb des Frischefensters."""
class ProtocolViolation(LicensingError):
"""Anfrage/Antwort verletzt das erwartete Format oder die erwartete Aktion."""