Skip to content
← Alle Beiträge
· Delta1 Labs KeyrightLizenzierung.NETTiefenanalyse

Clock Skew bei der Lizenzprüfung: Warum ein nagelneuer Schlüssel auf einem Laptop mit nachgehender Uhr scheitert – und welche Toleranz Ihre Verifikation braucht

Ein Kunde aktiviert um 9:00 Uhr eine Lizenz, und Ihre App lehnt sie als „noch nicht gültig“ ab. Nichts ist gefälscht, nichts ist abgelaufen – die Uhr seines Laptops geht vier Minuten gegenüber der Maschine nach, die den Schlüssel ausgestellt hat, sodass der NotBefore-Claim in seiner Zukunft liegt. Dieser Beitrag behandelt das Problem der ehrlichen Uhr (nicht den Angreifer, der die Uhr zurückdreht – das ist ein anderer Beitrag): die wenigen Minuten Uneinigkeit zwischen der Uhr des Ausstellers und der Uhr des Verifiers, die eine korrekte Lizenz im denkbar schlechtesten Moment in einen Scheinfehler verwandeln – direkt nach dem Kauf. Er liest die drei Zeitangaben einer Keyright-Lizenz (IssuedAt, NotBefore, Expiry), zeigt den naiven Vergleich, der dabei bricht, und baut die toleranzbewusste Verifikation: eine kleine symmetrische Toleranz auf NotBefore und Expiry, warum diese Toleranz in ihrer Absicht asymmetrisch ist, und warum verstrichene Zeit innerhalb einer Sitzung von einer monotonen Uhr kommen sollte – nicht von der Wanduhr, der Sie gerade das volle Vertrauen entzogen haben.

Ein Kunde kauft Ihre Software, aktiviert die Lizenz um Punkt 9:00 Uhr, und Ihre App meldet ihm, die Lizenz sei „noch nicht gültig“. Mit dem Schlüssel ist alles in Ordnung. Er ist signiert, er gehört ihm, er läuft erst in einem Jahr ab. Die Uhr seines Laptops geht lediglich vier Minuten gegenüber der Maschine nach, die den Schlüssel ausgestellt hat – also liegt der NotBefore-Zeitpunkt der Lizenz vier Minuten in seiner Zukunft. Ein strikter Zeitvergleich lehnt sie ab, und Sie haben einen erfolgreichen Verkauf in den ersten zehn Sekunden in ein Support-Ticket verwandelt.

Das ist das Problem der ehrlichen Uhr, und es ist etwas anderes als ein Angreifer, der die Uhr zurückdreht, um ein Ablaufdatum zu umgehen – das ist eine andere Verteidigung. Hier betrügt niemand; zwei Uhren sind sich schlicht uneinig. Dieser Beitrag baut die Keyright-Verifikation, die diese Uneinigkeit absorbiert, ohne eine Lücke zu öffnen.

Drei Zeitangaben und die zwei Vergleiche, auf die es ankommt

Eine Keyright-Lizenz trägt drei UTC-Zeitpunkte, und sie beantworten drei verschiedene Fragen:

public sealed class LicenseInfo
{
    public DateTime IssuedAtUtc  { get; }   // when the issuer signed it
    public DateTime? NotBeforeUtc { get; }  // not valid before this (usually == IssuedAt)
    public DateTime? ExpiryUtc    { get; }  // not valid after this (null = perpetual)
    // ...
}

IssuedAtUtc ist rein informativ – es sagt Ihnen, wie alt der Schlüssel ist. Die beiden Angaben, die über die Gültigkeit entscheiden, sind NotBeforeUtc und ExpiryUtc, und die Verifikation besteht im Kern aus zwei Vergleichen mit jetzt:

// The naive gate. Correct on paper, fragile in the field.
var now = DateTime.UtcNow;
if (info.NotBeforeUtc is { } nbf && now < nbf) return Invalid("not yet valid");
if (info.ExpiryUtc    is { } exp && now >= exp) return Invalid("expired");

Der Fehler liegt darin, dass now die Vorstellung dieser Maschine von „jetzt“ ist – und diese Maschine ist nicht diejenige, die die Lizenz gestempelt hat. Beide Vergleiche sind auf die Millisekunde exakt, sodass jede Abweichung zwischen den beiden Uhren genau auf der Grenze landet und das Ergebnis kippt.

Warum die nachgehende Uhr verliert – direkt nach dem Kauf

Endgeräte von Verbrauchern gehen weit häufiger nach als vor – ein Laptop, der im Ruhezustand war, ein Desktop, dessen NTP-Abgleich überfällig ist, eine VM, die aus einem Snapshot wiederhergestellt wurde. Stellen Sie sich den Aussteller um 9:00:00 Uhr vor und die Uhr des Kunden um 8:56:00 Uhr, vier Minuten dahinter. Die Lizenz wird mit NotBefore = 09:00:00 gestempelt. Das DateTime.UtcNow des Kunden liefert 08:56:00, was kleiner als 09:00:00 ist, also ist now < nbf wahr und das Gate sagt noch nicht gültig. Die Lizenz funktioniert erst, wenn die Uhr des Kunden über 9:00 Uhr gekrochen ist – vier Minuten, in denen ein zahlender Kunde auf eine Ablehnung starrt.

Verifier-Uhr geht 4 min nach; NotBefore = 09:0008:5609:00NotBeforeVerifier-„jetzt“Toleranz (5 min)strikt: now < NotBefore → ABLEHNEN (noch nicht gültig)Toleranz: now ≥ NotBefore − 5 min → ANNEHMENgültiger Bereich

Die Lösung: ein Toleranz-Regler, an beiden Enden angewendet

Das Heilmittel ist eine kleine Toleranz – Leeway –, die beim NotBefore-Vergleich abgezogen und beim Expiry-Vergleich hinzugefügt wird. Keyright stellt sie als eine einzige Option bereit, sodass Sie sie einmal setzen, statt magische Zahlen über Ihr Gate zu verstreuen:

var result = KeyrightClient.Validate(key, new ValidationOptions
{
    PublicKey = AppKeys.LicensePublicKey,
    ClockSkew = TimeSpan.FromMinutes(5),   // default; tune per deployment
});

Intern werden die beiden Vergleiche zu:

var now  = DateTime.UtcNow;
var skew = options.ClockSkew;              // e.g. 5 minutes

// Accept a license that looks slightly early because our clock may be slow.
if (info.NotBeforeUtc is { } nbf && now < nbf - skew)
    return Invalid("not yet valid");

// Allow a hair of grace so a license does not blink off exactly on the tick.
if (info.ExpiryUtc is { } exp && now >= exp + skew)
    return Invalid("expired");

Mit einem ClockSkew von fünf Minuten ergibt sich für den Kunden, dessen Uhr vier Minuten nachgeht, nun now < nbf - skew → 08:56 < 08:55 → falsch, also wird die Lizenz akzeptiert. Der Angreifer, der eine Uhr um mehr als fünf Minuten zurückdrehen müsste, um eine abgelaufene Lizenz am Leben zu halten, gewinnt nur genau diese fünf Minuten – Rauschen gegenüber einem Jahresabonnement.

Die Toleranz ist symmetrisch im Wert, asymmetrisch in der Absicht

Derselbe skew-Wert landet an beiden Enden, aber er erledigt zwei verschiedene Aufgaben, und es hilft, sie beim Namen zu nennen. Am NotBefore-Ende verzeiht die Toleranz einem Verifier, dessen Uhr hinter der des Ausstellers liegt – der oben beschriebene, häufige Fall des „nachgehenden Laptops“. Am Expiry-Ende ist die Toleranz ein kulantes Gnadenfenster, damit eine Lizenz auf einem Verifier, dessen Uhr leicht vorgeht, nicht auf die exakte Millisekunde des Ablaufs erlischt; ohne sie verliert ein Kunde mit vorgehender Uhr den Zugriff ein paar Minuten vor dem Ablauf, den Sie ihm tatsächlich verkauft haben.

Halten Sie beide klein. Eine große Expiry-Toleranz ist schlicht eine kostenlose Verlängerung, die Sie nie gewähren wollten, und eine große NotBefore-Toleranz weitet das Fenster, in dem sich eine zurückgedrehte Uhr einnisten könnte. Minuten, nicht Stunden. Der Standardwert von fünf Minuten ist der Sweet Spot: komfortabel größer als gewöhnliche Drift, weit kleiner als alles, wofür sich Betrug lohnen würde.

Dauern messen: Greifen Sie zur monotonen Uhr

Es gibt eine zweite, subtilere Falle, sobald Sie akzeptiert haben, dass die Wanduhr nicht vollständig vertrauenswürdig ist. Überall dort, wo Sie selbst verstrichene Zeit messen – eine Kulanzfrist, nachdem der Lizenzserver nicht erreichbar war, eine Cache-TTL für ein verifiziertes Ergebnis, „wie lange ist die letzte erfolgreiche Validierung her“ –, subtrahieren Sie nicht zwei DateTime.UtcNow-Messwerte. Die Wanduhr kann springen: NTP korrigiert sie, die Maschine erwacht aus dem Ruhezustand, ein Benutzer stellt sie um. Zwei Messwerte über einen solchen Sprung hinweg zu subtrahieren, kann Ihnen ein negatives Intervall bescheren oder ein absurd aufgeblähtes.

// Wrong: wall-clock subtraction can go backwards or jump.
var started = DateTime.UtcNow;
// ... later ...
var elapsed = DateTime.UtcNow - started;   // may be negative after an NTP step

// Right: a monotonic clock only moves forward, immune to clock edits.
var sw = Stopwatch.StartNew();
// ... later ...
var elapsed = sw.Elapsed;                    // always sane

Stopwatch stützt sich auf den monotonen Timer des Betriebssystems; sie schreitet immer nur voran, mit gleichmäßiger Rate, unabhängig davon, was die Wanduhr tut. Die Arbeitsteilung ist damit sauber: Verwenden Sie die Wanduhr nur für den Vergleich mit den absoluten NotBefore- und Expiry-Zeitpunkten der Lizenz – mit Toleranz – und Stopwatch für jede Dauer, die Sie innerhalb einer laufenden Sitzung berechnen. Die Uhr, der Sie für „wie spät ist es“ nicht ganz trauen können, ist genau die Uhr, die Sie für „wie viel Zeit ist vergangen“ nicht verwenden dürfen.

Das Fazit

Clock Skew ist kein Angriff, sondern der Normalzustand der Welt: zwei Maschinen, die ohne böse Absicht um ein paar Minuten auseinanderliegen. Eine Verifikation, die Zeitpunkte auf die Millisekunde vergleicht, lehnt ehrliche, frisch gekaufte Lizenzen ab, sobald die Uhr eines Kunden nachgeht – das teuerste denkbare False Negative. Geben Sie dem Vergleich über einen einzigen ClockSkew-Regler ein paar Minuten Toleranz an beiden Enden, NotBefore wie Expiry, halten Sie diese Toleranz im Minutenbereich, damit sie nie zur kostenlosen Verlängerung oder zur Manipulationslücke wird, und messen Sie jede verstrichene Dauer mit einer monotonen Stopwatch statt mit der Wanduhr, der Sie gerade das Misstrauen ausgesprochen haben. Das ist der Unterschied zwischen einem Lizenz-Gate, das im Labor funktioniert, und einem, das auf einem Laptop funktioniert, der gerade aufgewacht ist.

Nebula.NET testen

Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.