170 lines
5.8 KiB
TypeScript
170 lines
5.8 KiB
TypeScript
// apps/desktop/tests/red/crypto-license.usecase.test.ts
|
|
// Phase 11+: Ed25519 비대칭 암호화 라이센스 검증 & Reverse Trial 유스케이스 테스트
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
generateLicenseKeyPair,
|
|
issueSignedLicenseKey,
|
|
verifySignedLicenseKey,
|
|
} from '@d3ro/core/utils/crypto-license'
|
|
import { getLicenseService } from '../../src/main/services/LicenseService'
|
|
import { useRedHarness } from './harness'
|
|
|
|
useRedHarness()
|
|
|
|
describe('Ed25519 암호화 라이센스 & Reverse Trial 유스케이스', () => {
|
|
it('Ed25519 키쌍을 생성하고 유효한 라이센스를 서명/검증할 수 있다', () => {
|
|
const { publicKeyPem, privateKeyPem } = generateLicenseKeyPair()
|
|
expect(publicKeyPem).toContain('BEGIN PUBLIC KEY')
|
|
expect(privateKeyPem).toContain('BEGIN PRIVATE KEY')
|
|
|
|
const payload = {
|
|
licenseId: 'lic-1001',
|
|
tier: 'pro_plus' as const,
|
|
customerEmail: 'alice@enterprise.com',
|
|
issuedAt: Date.now(),
|
|
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
|
|
machineId: 'test-machine-id-1234',
|
|
}
|
|
|
|
const key = issueSignedLicenseKey(payload, privateKeyPem)
|
|
expect(key.startsWith('D3RO-LIC-')).toBe(true)
|
|
|
|
// 검증 성공
|
|
const verification = verifySignedLicenseKey(key, 'test-machine-id-1234', publicKeyPem, {
|
|
environment: 'production',
|
|
})
|
|
expect(verification.valid).toBe(true)
|
|
expect(verification.tier).toBe('pro_plus')
|
|
expect(verification.reason).toBe('valid')
|
|
expect(verification.payload?.customerEmail).toBe('alice@enterprise.com')
|
|
})
|
|
|
|
it('운영 환경은 레거시 접두어 키를 명시적 허용 옵션이 있어도 거부한다', () => {
|
|
const verification = verifySignedLicenseKey(
|
|
'D3RO-PRO-TEST-KEY1',
|
|
undefined,
|
|
undefined,
|
|
{ allowDevelopmentKeys: true, environment: 'production' },
|
|
)
|
|
|
|
expect(verification.valid).toBe(false)
|
|
expect(verification.tier).toBe('free')
|
|
expect(verification.reason).toBe('corrupted_token')
|
|
})
|
|
|
|
it('테스트 환경은 명시적으로 허용한 정확한 개발 fixture만 인정한다', () => {
|
|
const knownFixture = verifySignedLicenseKey(
|
|
'D3RO-PRO-TEST-KEY1',
|
|
undefined,
|
|
undefined,
|
|
{ allowDevelopmentKeys: true, environment: 'test' },
|
|
)
|
|
const forgedPrefix = verifySignedLicenseKey(
|
|
'D3RO-PRO-ATTACKER-KEY',
|
|
undefined,
|
|
undefined,
|
|
{ allowDevelopmentKeys: true, environment: 'test' },
|
|
)
|
|
|
|
expect(knownFixture.valid).toBe(true)
|
|
expect(knownFixture.tier).toBe('pro')
|
|
expect(knownFixture.reason).toBe('dev_key')
|
|
expect(forgedPrefix.valid).toBe(false)
|
|
expect(forgedPrefix.tier).toBe('free')
|
|
})
|
|
|
|
it('운영 공개키가 없으면 서명 형식이 맞아도 fail-closed 한다', () => {
|
|
const { privateKeyPem } = generateLicenseKeyPair()
|
|
const key = issueSignedLicenseKey(
|
|
{
|
|
licenseId: 'lic-no-production-key',
|
|
tier: 'enterprise',
|
|
customerEmail: 'release@example.test',
|
|
issuedAt: Date.now(),
|
|
expiresAt: null,
|
|
machineId: null,
|
|
},
|
|
privateKeyPem,
|
|
)
|
|
|
|
const verification = verifySignedLicenseKey(key, undefined, undefined, {
|
|
environment: 'production',
|
|
})
|
|
expect(verification.valid).toBe(false)
|
|
expect(verification.reason).toBe('invalid_signature')
|
|
expect(verification.message).toContain('not configured')
|
|
})
|
|
|
|
it('머신 ID가 일치하지 않으면 machine_mismatch 로 실패한다', () => {
|
|
const { publicKeyPem, privateKeyPem } = generateLicenseKeyPair()
|
|
const payload = {
|
|
licenseId: 'lic-1002',
|
|
tier: 'pro' as const,
|
|
customerEmail: 'bob@company.com',
|
|
issuedAt: Date.now(),
|
|
expiresAt: null,
|
|
machineId: 'machine-A',
|
|
}
|
|
|
|
const key = issueSignedLicenseKey(payload, privateKeyPem)
|
|
const verification = verifySignedLicenseKey(key, 'machine-B', publicKeyPem)
|
|
expect(verification.valid).toBe(false)
|
|
expect(verification.reason).toBe('machine_mismatch')
|
|
})
|
|
|
|
it('만료된 라이센스는 expired 로 실패한다', () => {
|
|
const { publicKeyPem, privateKeyPem } = generateLicenseKeyPair()
|
|
const payload = {
|
|
licenseId: 'lic-1003',
|
|
tier: 'pro' as const,
|
|
customerEmail: 'charlie@past.com',
|
|
issuedAt: Date.now() - 100000,
|
|
expiresAt: Date.now() - 1000, // 이미 만료됨
|
|
machineId: null,
|
|
}
|
|
|
|
const key = issueSignedLicenseKey(payload, privateKeyPem)
|
|
const verification = verifySignedLicenseKey(key, undefined, publicKeyPem)
|
|
expect(verification.valid).toBe(false)
|
|
expect(verification.reason).toBe('expired')
|
|
})
|
|
|
|
it('서명이 위조된 라이센스는 invalid_signature 로 실패한다', () => {
|
|
const { publicKeyPem } = generateLicenseKeyPair()
|
|
const otherKeyPair = generateLicenseKeyPair()
|
|
|
|
const payload = {
|
|
licenseId: 'lic-forged',
|
|
tier: 'pro_plus' as const,
|
|
customerEmail: 'attacker@evil.com',
|
|
issuedAt: Date.now(),
|
|
expiresAt: null,
|
|
machineId: null,
|
|
}
|
|
|
|
// 다른 키로 서명 (위조 시도)
|
|
const forgedKey = issueSignedLicenseKey(payload, otherKeyPair.privateKeyPem)
|
|
const verification = verifySignedLicenseKey(forgedKey, undefined, publicKeyPem)
|
|
expect(verification.valid).toBe(false)
|
|
expect(verification.reason).toBe('invalid_signature')
|
|
})
|
|
|
|
it('LicenseService.startTrial() 호출 시 14일 Pro+ Reverse Trial 이 활성화된다', () => {
|
|
const svc = getLicenseService()
|
|
svc.initialize()
|
|
|
|
const result = svc.startTrial('trial-tester@local')
|
|
expect(result.success).toBe(true)
|
|
expect(result.tier).toBe('pro_plus')
|
|
expect(svc.tier).toBe('pro_plus')
|
|
|
|
const info = svc.getInfo()
|
|
expect(info.isTrial).toBe(true)
|
|
expect(info.trialExpiresAt).toBeGreaterThan(Date.now())
|
|
|
|
// 중복 체험 시작은 방지된다
|
|
const retry = svc.startTrial('again@local')
|
|
expect(retry.success).toBe(false)
|
|
})
|
|
})
|