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
- Cryptography and hashing concepts
- Understanding of license types (Trial, Perpetual, Subscription)
- Completed Service Layer Architecture
- Familiarity with JPA Best Practices
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:
- License Dependencies - External libraries for cryptography and JWT handling
- 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
- Secure Key Storage: Never hardcode secrets, use vault/secrets manager
- Key Rotation: Implement key rotation strategy
- Validation Caching: Cache validation results for performance
- Audit Logging: Log all license operations
- Grace Periods: Implement grace periods for expired subscriptions
- Offline Validation: Support offline license validation
- Anti-Tampering: Implement license file integrity checks
- Rate Limiting: Limit validation API calls
- Monitoring: Track license usage and activation patterns
- Backup Keys: Maintain secure backup of license keys
Related Documentation
Next Steps: Explore Subscription Management Deep Dive for subscription licensing workflows.