refactor(server): decrypt license with provided aes key (#12570)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for a new AES key for license management, improving license encryption and decryption processes.

- **Bug Fixes**
  - Improved error messages and handling when activating expired or invalid licenses.

- **Refactor**
  - Updated license decryption logic to use a fixed AES key instead of deriving one from the workspace ID.
  - Added validation for environment variable values to prevent invalid configurations.

- **Tests**
  - Enhanced license-related tests to cover new key usage and updated error messages.
  - Updated environment variable validation tests with clearer error messages.

- **Chores**
  - Updated environment variable handling for improved consistency.
  - Set production environment variable explicitly in build configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-05-27 11:54:28 +00:00
parent 7175019a0a
commit dc7cd0487b
13 changed files with 74 additions and 19 deletions
@@ -485,11 +485,7 @@ export class LicenseService {
// we use workspace id as aes key hash plain text content
// verify signature to make sure the payload or signature is not forged
const {
payload: payloadStr,
signature,
iv,
} = this.decryptLicense(workspaceId, buf);
const { payload: payloadStr, signature, iv } = this.decryptLicense(buf);
const verifier = createVerify('rsa-sha256');
verifier.update(iv);
@@ -515,6 +511,13 @@ export class LicenseService {
});
}
if (new Date(parseResult.data.expiresAt) < new Date()) {
throw new InvalidLicenseToActivate({
reason:
'License file has expired. Please contact with Affine support to fetch a latest one.',
});
}
if (parseResult.data.data.workspaceId !== workspaceId) {
throw new InvalidLicenseToActivate({
reason: 'Workspace mismatched with license.',
@@ -524,7 +527,13 @@ export class LicenseService {
return parseResult.data;
}
private decryptLicense(workspaceId: string, buf: Buffer) {
private decryptLicense(buf: Buffer) {
if (!this.crypto.AFFiNEProLicenseAESKey) {
throw new InternalServerError(
'License AES key is not loaded. Please contact with Affine support.'
);
}
if (buf.length < 2) {
throw new InvalidLicenseToActivate({
reason: 'Invalid license file.',
@@ -539,12 +548,14 @@ export class LicenseService {
const tag = buf.subarray(2 + ivLength, 2 + ivLength + authTagLength);
const payload = buf.subarray(2 + ivLength + authTagLength);
const aesKey = this.crypto.sha256(
`WORKSPACE_PAYLOAD_AES_KEY:${workspaceId}`
const decipher = createDecipheriv(
'aes-256-gcm',
this.crypto.AFFiNEProLicenseAESKey,
iv,
{
authTagLength,
}
);
const decipher = createDecipheriv('aes-256-gcm', aesKey, iv, {
authTagLength,
});
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
@@ -564,7 +575,7 @@ export class LicenseService {
} catch {
// we use workspace id as aes key hash plain text content
throw new InvalidLicenseToActivate({
reason: 'Workspace mismatched with license.',
reason: 'Failed to verify the license.',
});
}
}