Skip to main content

License Generation Deep Dive

Overview

This comprehensive guide covers the license generation system in the Ink platform, including key generation algorithms, validation logic, expiration handling, activation/deactivation workflows, license renewal processes, and error scenarios.

Target Audience: Backend developers working with licensing
Prerequisites: Understanding of cryptography basics, Service Layer Architecture
Estimated Time: 50-60 minutes

Prerequisites

License Generation Architecture

Installation Steps

The Installation Steps section provides the foundational setup required to implement license generation and management in the Ink platform. These steps prepare your application with the necessary cryptographic libraries, JWT support for token-based licenses, and configuration for license policies and key generation parameters.

What You'll Set Up:

  1. License Dependencies - External libraries for cryptography and JWT handling
  2. License Configuration - Application settings for license policies and key generation

Why This Matters:

  • Enables secure license key generation using industry-standard algorithms
  • Provides flexible configuration for different license types (Trial, Perpetual, Subscription)
  • Establishes consistent license policies across your application
  • Allows environment-specific customization of license behavior

When to Use:

  • Initial application setup for license management
  • When adding licensing capabilities to a new microservice
  • During infrastructure provisioning for new environments
  • When upgrading license generation algorithms or policies

1. License Dependencies

What Are License Dependencies?

License dependencies are specialized cryptographic and encoding libraries required for secure license key generation, validation, and JWT-based licensing. These include Apache Commons Codec for Base64 encoding and JJWT (Java JWT) for creating signed, tamper-proof license tokens.

Why Are They Needed?

  • Cryptographic Security: Generate cryptographically secure license keys using HMAC algorithms
  • Key Encoding: Encode binary hash values into human-readable license key formats
  • JWT Support: Create self-contained, signed license tokens with embedded claims
  • Token Validation: Verify license authenticity and detect tampering
  • Standardization: Use industry-standard libraries for security operations

How/When Are They Used?

These dependencies are utilized throughout the license lifecycle:

  • License Generation: Creating unique, secure license keys using HMAC-SHA256
  • Key Validation: Verifying license key format and structure
  • JWT Licenses: Generating token-based licenses with embedded metadata
  • Signature Verification: Validating JWT license signatures to prevent forgery
  • Encoding/Decoding: Converting between binary and string representations

Dependency Relationships

Code Example

<!-- filepath: /Users/jetstart/dev/jetrev/ink/pom.xml -->
<!-- ...existing code... -->
<dependencies>
<!-- Apache Commons Codec for encoding -->
<!-- Provides Base64 and Hex encoding utilities -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>

<!-- Java JWT for token-based licenses -->
<!-- API interfaces for JWT operations -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>

<!-- JWT implementation (runtime only) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>

<!-- JWT Jackson integration for JSON serialization -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<!-- ...existing code... -->

2. License Configuration

What Is License Configuration?

License configuration defines the business rules, security parameters, and default behaviors for license generation, validation, and lifecycle management. It includes cryptographic settings, license type policies, and activation limits.

Why Is It Needed?

  • Security Control: Configure cryptographic algorithms and secret keys
  • Business Rules: Define trial periods, activation limits, and grace periods
  • Flexibility: Adjust license policies without code changes
  • Environment Separation: Different settings for dev/staging/production
  • Key Generation: Control license key format and structure
  • Validation Policies: Define how strictly to validate licenses

How/When Is It Used?

Configuration is referenced throughout license operations:

  • Key Generation: Determines algorithm, secret key, and key format
  • License Creation: Sets default trial periods and activation limits
  • Validation: Defines grace period and validation strictness
  • Expiration Handling: Controls how expired licenses are treated
  • Activation Limits: Enforces maximum machine activations

Configuration Flow

Code Example

# filepath: /Users/jetstart/dev/jetrev/ink/src/main/resources/application.yml
# ...existing code...
license:
# License key generation configuration
key:
# Secret key for HMAC signing (CRITICAL: Use vault in production!)
secret: ${LICENSE_SECRET_KEY:changeme-use-vault-in-production}
algorithm: HmacSHA256 # HMAC algorithm for key generation
format: BASE64 # Encoding format for license keys
segmentLength: 4 # Characters per segment (e.g., ABCD-EFGH-IJKL)
segmentCount: 6 # Number of segments in license key

# Trial license configuration
trial:
defaultDurationDays: 30 # Default trial period length
maxActivations: 1 # Maximum machines for trial licenses
allowMultipleTrials: false # Prevent users from getting multiple trials

# Perpetual license configuration
perpetual:
maxActivations: 5 # Default activation limit for perpetual licenses
allowTransfer: true # Allow license transfer between machines

# Subscription license configuration
subscription:
gracePeriodDays: 7 # Days to allow usage after expiration
autoRenewal: true # Automatically renew if subscription active

# Validation policy configuration
validation:
allowExpiredGracePeriod: true # Allow grace period for expired licenses
strictMachineIdCheck: true # Enforce strict machine ID matching
cacheValidationResults: true # Cache validation for performance
cacheExpirationMinutes: 5 # How long to cache validation results
enableOfflineValidation: false # Support offline validation mode
# ...existing code...

Configuration

License Entity

What Is the License Entity?

The License entity is the core JPA entity representing a software license in the database. It stores all license information including the unique license key, associated user and product, license type, status, activation tracking, expiration dates, and metadata.

Why Is It Needed?

  • Data Persistence: Store license information in the database
  • State Management: Track license status (Active, Expired, Revoked, Suspended)
  • Activation Tracking: Monitor which machines have activated the license
  • Expiration Control: Manage trial periods and subscription expiration
  • Audit Trail: Record license lifecycle events and changes
  • Relationship Management: Link licenses to users and products

How/When Is It Used?

The entity is central to all license operations:

  • License Generation: Creating new license records
  • Activation: Recording machine activations
  • Validation: Checking license status and expiration
  • Revocation: Administratively disabling licenses
  • Renewal: Extending license expiration dates
  • Reporting: Analyzing license usage and compliance

Entity Relationships

Code Example

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/entity/License.java
@Entity
@Table(name = "licenses", indexes = {
@Index(name = "idx_license_key", columnList = "license_key"),
@Index(name = "idx_user_id", columnList = "user_id"),
@Index(name = "idx_product_id", columnList = "product_id"),
@Index(name = "idx_status", columnList = "status"),
@Index(name = "idx_expires_at", columnList = "expires_at")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(of = "id")
public class License implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "license_key", nullable = false, unique = true, length = 255)
private String licenseKey;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

@Enumerated(EnumType.STRING)
@Column(name = "license_type", nullable = false, length = 20)
private LicenseType licenseType;

@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private LicenseStatus status = LicenseStatus.ACTIVE;

@Column(name = "max_activations")
private Integer maxActivations = 1;

@Column(name = "current_activations")
private Integer currentActivations = 0;

@Column(name = "expires_at")
private Instant expiresAt;

@Column(name = "issued_at", nullable = false)
private Instant issuedAt;

@Column(name = "activated_at")
private Instant activatedAt;

@Column(name = "revoked_at")
private Instant revokedAt;

@Column(name = "revoke_reason", length = 500)
private String revokeReason;

@Column(name = "metadata", columnDefinition = "jsonb")
@JdbcTypeCode(SqlTypes.JSON)
private Map<String, Object> metadata = new HashMap<>();

@OneToMany(mappedBy = "license", cascade = CascadeType.ALL, orphanRemoval = true)
@BatchSize(size = 25)
private List<LicenseActivation> activations = new ArrayList<>();

@Column(name = "created_at", nullable = false, updatable = false)
private Instant createdAt;

@Column(name = "updated_at", nullable = false)
private Instant updatedAt;

@Version
@Column(name = "version")
private Integer version;

@PrePersist
protected void onCreate() {
createdAt = Instant.now();
updatedAt = Instant.now();
if (issuedAt == null) {
issuedAt = Instant.now();
}
}

@PreUpdate
protected void onUpdate() {
updatedAt = Instant.now();
}

// Business logic methods

public boolean isExpired() {
return expiresAt != null && Instant.now().isAfter(expiresAt);
}

public boolean canActivate() {
return status == LicenseStatus.ACTIVE &&
!isExpired() &&
currentActivations < maxActivations;
}

public void addActivation(LicenseActivation activation) {
activations.add(activation);
activation.setLicense(this);
currentActivations = activations.size();
}

public void removeActivation(LicenseActivation activation) {
activations.remove(activation);
activation.setLicense(null);
currentActivations = activations.size();
}
}

@Getter
public enum LicenseType {
TRIAL("Trial License"),
PERPETUAL("Perpetual License"),
SUBSCRIPTION("Subscription License");

private final String displayName;

LicenseType(String displayName) {
this.displayName = displayName;
}
}

@Getter
public enum LicenseStatus {
ACTIVE("Active"),
EXPIRED("Expired"),
REVOKED("Revoked"),
SUSPENDED("Suspended");

private final String displayName;

LicenseStatus(String displayName) {
this.displayName = displayName;
}
}

License Key Generator

What Is the License Key Generator?

The License Key Generator is a specialized service responsible for creating cryptographically secure, unique license keys using HMAC algorithms. It supports both traditional segmented license keys (e.g., XXXX-XXXX-XXXX) and JWT-based token licenses.

Why Is It Needed?

  • Uniqueness: Generate globally unique license keys
  • Security: Use cryptographic algorithms to prevent forgery
  • Format Consistency: Produce consistently formatted license keys
  • Validation Support: Enable format validation without database lookup
  • Token Support: Create self-contained JWT licenses with embedded claims
  • Tamper Resistance: Prevent license key manipulation

How/When Is It Used?

The generator is invoked during:

  • License Creation: Generating keys for new licenses
  • Key Validation: Verifying license key format and structure
  • JWT Generation: Creating token-based licenses
  • Batch Generation: Creating multiple licenses for distribution
  • Key Rotation: Regenerating keys when security is compromised

Key Generation Flow

Code Example

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/service/license/LicenseKeyGenerator.java
@Service
@Slf4j
public class LicenseKeyGenerator {

@Value("${license.key.secret}")
private String secretKey;

@Value("${license.key.algorithm:HmacSHA256}")
private String algorithm;

@Value("${license.key.segmentLength:4}")
private int segmentLength;

@Value("${license.key.segmentCount:6}")
private int segmentCount;

private final SecureRandom secureRandom = new SecureRandom();

/**
* Generate a unique license key using HMAC-based algorithm
* Format: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (configurable)
*/
public String generateLicenseKey(Long userId, Long productId, LicenseType licenseType) {
try {
// Create unique seed from user, product, timestamp, and random value
String seed = String.format("%d-%d-%s-%d-%s",
userId,
productId,
licenseType,
Instant.now().toEpochMilli(),
UUID.randomUUID()
);

// Generate HMAC signature
Mac mac = Mac.getInstance(algorithm);
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8),
algorithm
);
mac.init(secretKeySpec);

byte[] hash = mac.doFinal(seed.getBytes(StandardCharsets.UTF_8));

// Encode to Base64 and format
String base64 = Base64.getEncoder().encodeToString(hash);
String cleaned = base64.replaceAll("[^A-Z0-9]", "")
.substring(0, segmentLength * segmentCount);

// Format into segments
return formatIntoSegments(cleaned);

} catch (Exception e) {
log.error("Failed to generate license key", e);
throw new LicenseGenerationException("License key generation failed", e);
}
}

/**
* Generate a JWT-based license key (alternative approach)
* Self-contained token with embedded claims
*/
public String generateJwtLicenseKey(License license) {
try {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", license.getUser().getId());
claims.put("productId", license.getProduct().getId());
claims.put("licenseType", license.getLicenseType().name());
claims.put("maxActivations", license.getMaxActivations());

JwtBuilder builder = Jwts.builder()
.claims(claims)
.id(UUID.randomUUID().toString())
.issuedAt(Date.from(license.getIssuedAt()))
.signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)));

if (license.getExpiresAt() != null) {
builder.expiration(Date.from(license.getExpiresAt()));
}

return builder.compact();

} catch (Exception e) {
log.error("Failed to generate JWT license key", e);
throw new LicenseGenerationException("JWT license key generation failed", e);
}
}

/**
* Validate license key format
*/
public boolean isValidFormat(String licenseKey) {
if (licenseKey == null || licenseKey.isBlank()) {
return false;
}

String pattern = String.format("^([A-Z0-9]{%d}-){%d}[A-Z0-9]{%d}$",
segmentLength, segmentCount - 1, segmentLength);

return licenseKey.matches(pattern);
}

/**
* Validate JWT license key
*/
public boolean isValidJwtLicenseKey(String jwtKey) {
try {
Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)))
.build()
.parseSignedClaims(jwtKey);
return true;
} catch (Exception e) {
log.debug("Invalid JWT license key", e);
return false;
}
}

private String formatIntoSegments(String key) {
StringBuilder formatted = new StringBuilder();

for (int i = 0; i < segmentCount; i++) {
int start = i * segmentLength;
int end = Math.min(start + segmentLength, key.length());

formatted.append(key, start, end);

if (i < segmentCount - 1) {
formatted.append("-");
}
}

return formatted.toString();
}
}

License Service Implementation

What Is the License Service?

The License Service is the core business logic layer that orchestrates all license operations including generation, activation, validation, renewal, and revocation. It coordinates between the database, key generator, event publisher, and implements all license management workflows.

Why Is It Needed?

  • Business Logic: Encapsulate license management rules and policies
  • Transaction Management: Ensure data consistency across operations
  • Workflow Coordination: Orchestrate multi-step license processes
  • Event Publishing: Notify other services of license state changes
  • Validation: Enforce business rules and constraints
  • Error Handling: Manage failures and edge cases gracefully

How/When Is It Used?

The service is invoked for all license operations:

  • Generation: Creating new licenses for users
  • Activation: Registering licenses on machines
  • Validation: Checking license validity in real-time
  • Deactivation: Removing license from machines
  • Renewal: Extending license expiration dates
  • Revocation: Administratively disabling licenses

Service Operation Flow

Code Example

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/service/license/LicenseService.java
// ...existing code...

Usage Examples

License Generation Scenarios

What Are License Generation Scenarios?

License generation scenarios demonstrate how to create different types of licenses (Trial, Perpetual, Subscription) with various configurations including custom activation limits, expiration dates, and metadata.

When Are They Used?

  • User Purchases: Creating licenses after payment
  • Trial Signups: Generating trial licenses for new users
  • Bulk Distribution: Creating multiple licenses for enterprises
  • Promotions: Issuing promotional or extended trial licenses

Generation Decision Flow

Code Example

// ...existing code...

License Activation Flow

What Is License Activation?

License activation is the process of registering a license on a specific machine, creating an activation record that tracks the machine ID, name, IP address, and activation timestamp. This enforces licensing limits and prevents unauthorized usage.

When Is It Used?

  • Software Installation: When users install your software
  • Device Registration: Registering license on new devices
  • License Transfer: Moving license from one machine to another
  • Cloud Instances: Activating licenses on cloud VMs

Activation Process Flow

Code Example

// ...existing code...

Verification

License Service Tests

What Are License Service Tests?

License service tests are comprehensive unit and integration tests that verify all license operations including generation, activation, validation, renewal, and revocation work correctly and handle edge cases appropriately.

Why Are They Important?

  • Correctness: Verify license operations produce correct results
  • Edge Cases: Test boundary conditions and unusual scenarios
  • Security: Ensure cryptographic operations are secure
  • Business Rules: Validate policy enforcement
  • Regression Prevention: Catch bugs before production

Test Coverage Map

Code Example

// filepath: /Users/jetstart/dev/jetrev/ink/src/test/java/com/jetrev/ink/service/license/LicenseServiceTest.java
// ...existing code...

Best Practices

  1. Secure Key Storage: Never hardcode secrets, use vault/secrets manager
  2. Key Rotation: Implement key rotation strategy
  3. Validation Caching: Cache validation results for performance
  4. Audit Logging: Log all license operations
  5. Grace Periods: Implement grace periods for expired subscriptions
  6. Offline Validation: Support offline license validation
  7. Anti-Tampering: Implement license file integrity checks
  8. Rate Limiting: Limit validation API calls
  9. Monitoring: Track license usage and activation patterns
  10. Backup Keys: Maintain secure backup of license keys

Next Steps: Explore Subscription Management Deep Dive for subscription licensing workflows.