Skip to main content

License Activation

The Muonroi License Server issues keys in the MRR-... format and manages the activation lifecycle. This guide covers both Online Mode (default, internet-connected) and Offline Mode (air-gapped environments).

Overview

License activation converts a license key into a signed ActivationProof containing tier, features, expiry, and heartbeat nonce. The proof can be verified locally without contacting the license server on every request.

Key Points:

  • License keys: MRR-{base64url} format
  • Signed by server (RSA-2048)
  • Grace period: 24 hours (revocation)
  • Heartbeat: every 4 hours (configurable)
  • Fallback tier: Free (on expiry)

License Server Endpoints

EndpointMethodAuthDescription
/api/v1/activatePOSTPublicActivate license with machine fingerprint
/api/v1/heartbeatPOSTPublicPeriodic heartbeat (nonce rotation)
/api/v1/validatePOSTPublicValidate current license status
/api/v1/keys/generatePOSTAdminGenerate new license key
/api/v1/keys/{key}GETAdminRetrieve license record
/api/v1/keys/revokePOSTAdminRevoke an existing key
/healthGETPublicHealth check

Base URL: https://license.truyentm.xyz


Online Mode (Default)

Online mode connects to the license server during activation and runs periodic heartbeats. Recommended for most deployments.

Activation Flow

sequenceDiagram
participant App as Application
participant LS as License Server
participant File as License File

App->>App: Read license key from file or env var
App->>LS: POST /api/v1/activate (key + fingerprint)
LS->>LS: Validate key & generate nonce
LS-->>App: ActivationProof (signed JSON)
App->>File: Save proof to licenses/activation_proof.json
App->>App: Start heartbeat service (4h interval)
App->>LS: POST /api/v1/heartbeat (periodic)
LS->>LS: Verify chain, rotate nonce
LS-->>App: Updated proof with new nonce

Step 1: Configure Online Mode

appsettings.json:

{
"LicenseConfigs": {
"Mode": "Online",
"LicenseFilePath": "licenses/license.key",
"ActivationProofPath": "licenses/activation_proof.json",
"FallbackToOnlineActivation": true,
"Online": {
"Endpoint": "https://license.truyentm.xyz",
"EnableHeartbeat": true,
"HeartbeatIntervalMinutes": 240,
"RevocationGraceHours": 24,
"TimeoutSeconds": 10
}
}
}
Config KeyTypeDefaultDescription
ModestringOnlineOnline or Offline
LicenseFilePathstringlicenses/license.keyPath to license key file
ActivationProofPathstringlicenses/activation_proof.jsonPath to store proof
FallbackToOnlineActivationbooltrueRetry online if offline fails
Online.Endpointstringhttps://license.truyentm.xyzLicense server URL
Online.EnableHeartbeatbooltrueEnable periodic heartbeat
Online.HeartbeatIntervalMinutesint240Heartbeat interval (4h)
Online.RevocationGraceHoursint24Grace period after revocation
Online.TimeoutSecondsint10HTTP request timeout

Step 2: Create License File

licenses/license.key:

{
"LicenseKey": "MRR-YWJjZGVmZ2hpamtsbW5vcA=="
}

Or set via environment variable:

export MUONROI_LICENSE_KEY="MRR-YWJjZGVmZ2hpamtsbW5vcA=="

Step 3: Automatic Activation

On startup, LicenseActivator will:

  1. Read license key from file or env var
  2. POST to /api/v1/activate with machine fingerprint
  3. Receive signed ActivationProof
  4. Save proof to licenses/activation_proof.json
  5. Start HeartbeatBackgroundService (runs every 4h)

Example Activation Request:

curl -X POST https://license.truyentm.xyz/api/v1/activate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "MRR-YWJjZGVmZ2hpamtsbW5vcA==",
"machineFingerprint": "abc123def456ghi789"
}'

Example Response:

{
"tier": "Enterprise",
"features": [
"rule-engine",
"multi-tenant",
"advanced-auth",
"audit-trail"
],
"expiresAt": "2025-12-31T23:59:59Z",
"activatedAt": "2024-12-20T10:30:00Z",
"heartbeatNonce": "nonce-uuid-v4-here",
"signedPayload": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"activationProof": {
"licenseKey": "MRR-...",
"tier": "Enterprise",
"features": [...],
"expiresAt": "2025-12-31T23:59:59Z",
"signature": "base64-hmac-signature"
}
}

Step 4: Heartbeat Lifecycle

The heartbeat service runs automatically in the background:

Example Heartbeat Request:

curl -X POST https://license.truyentm.xyz/api/v1/heartbeat \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "MRR-YWJjZGVmZ2hpamtsbW5vcA==",
"currentNonce": "nonce-uuid-v4-from-proof",
"machineFingerprint": "abc123def456ghi789"
}'

Heartbeat Behavior:

  • Runs every 4 hours (configurable via HeartbeatIntervalMinutes)
  • Nonce rotates on each successful heartbeat
  • Grace period: 24 hours after revocation (downgrades to Free tier)
  • Failure handling: log warning, try again at next interval
  • Network timeout: 10 seconds (configurable)

Offline Mode

Offline mode is for air-gapped or restricted-internet environments. The activation proof is generated on an internet-connected machine and transferred to the target host.

Activation Flow

sequenceDiagram
participant Admin as Admin (Connected)
participant LS as License Server
participant Target as Target Host (Offline)

Admin->>Admin: Generate activation request
Admin->>LS: POST /api/v1/activate (request)
LS->>LS: Validate & generate proof
LS-->>Admin: ActivationProof (signed JSON)
Admin->>Admin: Transfer proof file to target
Target->>Target: Load proof from file
Target->>Target: Verify signature locally
Note over Target: No network calls required

Step 1: Configure Offline Mode

appsettings.json:

{
"LicenseConfigs": {
"Mode": "Offline",
"LicenseFilePath": "licenses/license.key",
"ActivationProofPath": "licenses/activation_proof.json",
"FallbackToOnlineActivation": false,
"Offline": {
"PublicKeyPath": "licenses/license_server_public.pem"
}
}
}

Step 2: Generate Activation on Connected Machine

On a machine with internet access:

curl -X POST https://license.truyentm.xyz/api/v1/activate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "MRR-YWJjZGVmZ2hpamtsbW5vcA==",
"machineFingerprint": "target-host-fingerprint",
"offline": true
}' > activation_proof.json

Save the response to activation_proof.json.

Step 3: Transfer Proof to Target Host

Transfer activation_proof.json to the target host:

scp activation_proof.json user@target-host:/app/licenses/

Step 4: Load Proof on Target Host

The runtime loads the proof from disk on startup:

  1. Read license key from LicenseFilePath
  2. Read proof from ActivationProofPath
  3. Verify signature using public key from PublicKeyPath
  4. Validate tier and features
  5. No network calls made

No heartbeat service runs in Offline mode.


HMAC Chain Verification

Each heartbeat updates the HMAC chain to prevent tampering and detect revocation.

Chain Structure

HMAC Key:
SHA256(licenseSignature + projectSeed + salt + serverNonce)

HMAC Data:
{previous}|{sequence}|{tenantId}|{action}|{hash}|{timestamp}

Verification:
HMAC_SHA256(key, data) == received_signature
{
"sequence": 1,
"tenantId": "tenant-123",
"action": "heartbeat",
"hash": "abc123def456",
"timestamp": "2024-12-20T10:30:00Z",
"signature": "hmac-sha256-signature",
"previousSignature": "previous-link-signature"
}

Verification happens automatically:

  • On each heartbeat
  • Before granting access to rule-engine features
  • Fail-closed: denies access if chain is broken

Program.cs Registration

Register license services in your application startup:

using Muonroi.Governance.Enterprise;

var services = new ServiceCollection();

// Register license protection (all modes)
services.AddLicenseProtection(configuration);

// Register anti-tamper detection (Enterprise only)
services.AddMEnterpriseGovernance(configuration);

// Register ILicenseGuard for feature checks
services.AddScoped<ILicenseGuard>(sp =>
sp.GetRequiredService<LicenseGuard>());

Runtime Feature Checks

public class MyRuleService(ILicenseGuard guard)
{
public async Task ExecuteRuleAsync(string ruleName)
{
// Throws if feature not allowed
guard.EnsureValid("rule-engine");

// Check tier
if (guard.Tier != LicenseTier.Enterprise)
throw new UnauthorizedAccessException("Enterprise required");

// Check specific feature
bool hasMultiTenant = guard.HasFeature("multi-tenant");

// Proceed with execution
await ExecuteAsync(ruleName);
}
}

Error Handling

StatusScenarioHandling
200SuccessStore proof, continue
400Invalid keyCheck key format, retry with correct key
401UnauthorizedCheck admin credentials
403Revoked keyUse fallback tier (Free) for grace period
404Key not foundGenerate new key via admin API
500Server errorRetry with FallbackToOnlineActivation=true
TimeoutNetwork timeoutRetry at next heartbeat interval

Monitoring

Check license status programmatically:

curl -X POST https://license.truyentm.xyz/api/v1/validate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "MRR-YWJjZGVmZ2hpamtsbW5vcA=="
}'

Response:

{
"isValid": true,
"tier": "Enterprise",
"expiresAt": "2025-12-31T23:59:59Z",
"heartbeatNonce": "nonce-uuid-v4-here",
"gracePeriodRemaining": "24h",
"features": [...]
}

See Also