import { NfcAntiTamperService, NfcSecurityAuditLog, } from '../../../src/modules/nfc/service/anti-tamper'; describe('Point 15: 实体 NFC 卡片动态防伪验签算法 (anti-tamper)', () => { let service: NfcAntiTamperService; const mockUid = '04A1B2C3D4E5F6'; const mockCardId = 1001; const mockSecret = 'k9#mZ$8vP!xL2@qR5*wY8^uN1(bC4)eT'; beforeEach(() => { service = new NfcAntiTamperService(); }); describe('1. generateDeviceToken (生成动态防伪验签令牌)', () => { it('应成功生成包含时间戳与 HMAC-SHA256 签名的动态 Token', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); expect(typeof token).toBe('string'); expect(token).toContain('.'); const [tsStr, signature] = token.split('.'); const ts = Number(tsStr); expect(Number.isSafeInteger(ts)).toBe(true); expect(Math.abs(Date.now() - ts)).toBeLessThan(2000); expect(signature).toHaveLength(64); // SHA-256 16进制长度为 64 expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true); }); it('支持显式指定 timestamp 生成 Token', () => { const customTs = 1710000000000; const token = service.generateDeviceToken( mockUid, mockCardId, mockSecret, customTs ); const [tsStr] = token.split('.'); expect(Number(tsStr)).toBe(customTs); }); it('入参缺失时应抛出明确的异常保护', () => { expect(() => service.generateDeviceToken('', mockCardId, mockSecret) ).toThrow('缺少必要参数'); expect(() => service.generateDeviceToken(mockUid, 0, mockSecret) ).toThrow('缺少必要参数'); expect(() => service.generateDeviceToken(mockUid, -1, mockSecret) ).toThrow('缺少必要参数'); expect(() => service.generateDeviceToken(mockUid, mockCardId, '') ).toThrow('缺少必要参数'); }); }); describe('2. verifyDeviceToken - 合法令牌验证', () => { it('合法未过期的令牌应验证通过', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); const result = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(result.valid).toBe(true); expect(result.reason).toBeUndefined(); }); }); describe('3. verifyDeviceToken - 过期超时拦截', () => { it('超过默认 5 分钟 (300,000ms) 的令牌应被拦截', () => { const expiredTs = Date.now() - 5 * 60 * 1000 - 1000; // 5分1秒前 const token = service.generateDeviceToken( mockUid, mockCardId, mockSecret, expiredTs ); const result = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/过期|expired/i); }); it('支持自定义 maxAgeMs 过期时间阈值', () => { const pastTs = Date.now() - 10 * 1000; // 10秒前 const token = service.generateDeviceToken( mockUid, mockCardId, mockSecret, pastTs ); // 阈值 5 秒 -> 应当判定为过期 const resExpired = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret, 5000 ); expect(resExpired.valid).toBe(false); expect(resExpired.reason).toMatch(/过期|expired/i); // 阈值 20 秒 -> 应当判定为合法 const resValid = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret, 20000 ); expect(resValid.valid).toBe(true); }); }); describe('4. verifyDeviceToken - 恶意参数篡改拦截', () => { it('篡改 UID 时应拦截', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); const tamperedUid = '04A1B2C3D4E5F9'; const result = service.verifyDeviceToken( tamperedUid, mockCardId, token, mockSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/签名无效|篡改|invalid signature/i); }); it('篡改 cardId 时应拦截', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); const tamperedCardId = 9999; const result = service.verifyDeviceToken( mockUid, tamperedCardId, token, mockSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/签名无效|篡改|invalid signature/i); }); it('篡改签名内容时应拦截', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); const [ts, sig] = token.split('.'); const tamperedSig = sig.substring(0, sig.length - 1) + (sig.endsWith('0') ? '1' : '0'); const tamperedToken = `${ts}.${tamperedSig}`; const result = service.verifyDeviceToken( mockUid, mockCardId, tamperedToken, mockSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/签名无效|篡改|invalid signature/i); }); it('使用错误的密钥验证时应拦截', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); const wrongSecret = 'different_secret_key_1234567890'; const result = service.verifyDeviceToken( mockUid, mockCardId, token, wrongSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/签名无效|篡改|invalid signature/i); }); }); describe('5. verifyDeviceToken - 空参与畸形输入保护', () => { it('传入空参数时不应崩溃,应安全返回无效原因', () => { const validToken = service.generateDeviceToken( mockUid, mockCardId, mockSecret ); expect( service.verifyDeviceToken('', mockCardId, validToken, mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, 0, validToken, mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, -10, validToken, mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, mockCardId, '', mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, mockCardId, validToken, '').valid ).toBe(false); }); it('传入畸形或格式错误的 Token 应安全拦截', () => { expect( service.verifyDeviceToken(mockUid, mockCardId, 'malformedToken', mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, mockCardId, 'abc.def', mockSecret).valid ).toBe(false); expect( service.verifyDeviceToken(mockUid, mockCardId, `${Date.now()}.short`, mockSecret).valid ).toBe(false); }); }); describe('6. verifyDeviceToken - 时钟容差保护', () => { it('时间稍微超前的令牌(在容差范围内)应被允许', () => { const futureTs = Date.now() + 30 * 1000; // 30秒未来时间(网络或机器微小偏差) const token = service.generateDeviceToken( mockUid, mockCardId, mockSecret, futureTs ); const result = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(result.valid).toBe(true); }); it('异常超前(超过容差阈值)的未来令牌应被拒绝', () => { const futureTs = Date.now() + 10 * 60 * 1000; // 10分钟后 const token = service.generateDeviceToken( mockUid, mockCardId, mockSecret, futureTs ); const result = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(result.valid).toBe(false); expect(result.reason).toMatch(/未来|时钟偏差|future/i); }); }); describe('7. verifyDeviceToken - 防重放保护 (Replay Attack)', () => { it('同一合法令牌不可重复验签使用', () => { const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); // 首次验签通过 const firstResult = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(firstResult.valid).toBe(true); // 再次使用同一令牌验签,触发防重放拦截 const secondResult = service.verifyDeviceToken( mockUid, mockCardId, token, mockSecret ); expect(secondResult.valid).toBe(false); expect(secondResult.reason).toMatch(/重放|已使用|replay/i); }); }); describe('8. 安全审计日志留痕', () => { it('拦截到恶意篡改、重放或非法请求时,应记录审计日志', () => { service.clearAuditLogs(); // 1. 触发一次篡改 const token = service.generateDeviceToken(mockUid, mockCardId, mockSecret); service.verifyDeviceToken(mockUid, 9999, token, mockSecret); // 2. 触发一次重放 const validToken = service.generateDeviceToken(mockUid, mockCardId, mockSecret); service.verifyDeviceToken(mockUid, mockCardId, validToken, mockSecret); service.verifyDeviceToken(mockUid, mockCardId, validToken, mockSecret); const logs: NfcSecurityAuditLog[] = service.getAuditLogs(); expect(logs.length).toBeGreaterThanOrEqual(2); const tamperLog = logs.find(l => l.cardId === 9999); expect(tamperLog).toBeDefined(); expect(tamperLog?.reason).toMatch(/签名无效|篡改/); const replayLog = logs.find(l => l.token === validToken && /重放|已使用/.test(l.reason)); expect(replayLog).toBeDefined(); }); }); });