Official .NET 8+ client for System Locker Nightflyer, an offline authorization lease protocol for desktop software. Nightflyer proves possession of an installation P-256 key on every request and accepts only Ed25519-signed leases, statuses, decisions, and key transitions from locally pinned keys.
dotnet add package SystemLocker.Nightflyerusing SystemLocker.Nightflyer;
var config = new NightflyerConfig {
SystemId = "abcdefghijklmnopqrst",
TrustedSigningKeys = new Dictionary<string, string> { ["YOUR_KID"] = "YOUR_BASE64URL_ED25519_KEY" },
};
using var session = NightflyerAuthorizationSession.CreateMemory(config);
var result = await session.EasyAuthorizeAsync(
new NightflyerBinding("1.0.0", "", myHwid),
requestedOfflineSeconds: 86400,
licenseKey: enteredKey); // Omit after the key has been retained.
if (result.CanProceed)
StartProtectedWork(result.Lease!);
else if (result.ShouldPromptForKey)
ShowLicenseKeyPrompt();For a Windows persistent session, use OpenPersistent with a
WindowsDpapiStateStore. It protects the installation key, lease, time
checkpoint, retained license key, accepted signing keys, and status high-water
marks together with per-user DPAPI and atomically replaces each record. The
retained key lets an inactive installation request a fresh lease without asking
the user to type it again. Nightflyer erases it only after the server returns
the definitive KEY_NOT_FOUND result. On macOS and Linux,
provide an INightflyerStateStore backed by Keychain or Secret Service; the SDK
intentionally does not silently fall back to plaintext files.
EasyAuthorizeAsync() is the recommended startup API. With a newly entered key
it requests a fresh lease. Without one, it loads protected state, renews a due
lease or calls SynchronizeStatusAsync() for a strict status check, finishes
any exact pending end request, and requests a new lease with the retained key
when the old lease is inactive, ended, or expired.
The result makes application policy explicit: CanProceed, IsOffline,
CredentialPresent, ShouldPromptForKey, RetryAfter, and a typed
RecommendedAction. A NetworkUnavailable result with CanProceed == true
means the online check did not finish but the existing signed offline lease is
still usable. The application can continue and retry after the reported delay.
If a developer unbinds a lease, the old lease is reported as revoked and
surfaces locally as Inactive; this is not a permanent ban on the installation
or key. EasyAuthorizeAsync() immediately tries to obtain a new lease with the
retained key. A successful signed response starts a new lease epoch. A signed
KEY_NOT_FOUND result clears the retained key and sets ShouldPromptForKey;
other denials retain it while still allowing the user to enter a replacement.
var startup = await session.EasyAuthorizeAsync(binding, 86400);
if (!startup.CanProceed)
Handle(startup.RecommendedAction, startup.Diagnostic, startup.RetryAfter);Alternatively, the lower-level Load(), RenewIfDueAsync(),
SynchronizeStatusAsync(), and AuthorizeWithStoredKeyAsync() operations are
available for any application that needs more control over the process.
- HTTPS-only endpoints, no redirects, and a fresh ES256 installation proof.
- Canonical request binding and access-token binding for renewal, status, and end.
- Ed25519 verification before token claims are consumed, with fixed JWS types.
- Exact lease, status, and transition profiles; a status or decision cannot be substituted for a lease.
- Request-JTI, system, installation-thumbprint, expiry, and key-transition checks.
- Persisted generation/status/keyset high-water marks, revocation enforcement, per-boot effective-time checks, and protected rollback checkpoints.
- A renewal state machine with cross-process installation coordination, terminal end tombstones, and offline-on-network-failure behavior.
A valid cached lease can be used without connectivity, including while traveling. During the same OS boot, lease time advances from verified server UTC using an elapsed-time clock that includes sleep. Time-zone changes and wall-clock corrections do not shorten or extend the signed lease lifetime.
After a reboot, the default policy permits offline use when UTC is consistent
with the protected time checkpoint and the lease has not expired. A clock
rollback that cannot be verified requires an online check; set
RequireOnlineAfterReboot to require one after every reboot instead. A
successful signed online response repairs a bad local time checkpoint but
does not extend an existing lease's signed expiry. If the OS cannot provide a
boot identifier, reopening the application uses the same conservative UTC
checks as a reboot.
Pinned server keys are a developer-provided trust root. The session accepts a new key only through a signed transition from a previously trusted key, persists the key/status/generation high-water marks, and rejects revoked keys, devices, grants, rollback, and stale leases. It uses the signed server-time anchor plus a suspend-aware monotonic clock within a boot. Nightflyer cannot immediately revoke a device that is offline, and even though it increases resilience to cracking, it is not a substitute for proper obfuscation and other protections.