@purecore/one-jwt-4-all 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,827 @@
1
+ /**
2
+ * Signal Protocol End-to-End Encryption para Sistema de Agentes
3
+ *
4
+ * Implementação do Double Ratchet Algorithm do Signal Protocol para
5
+ * comunicação end-to-end encriptada entre agentes.
6
+ *
7
+ * Características:
8
+ * - X3DH (Extended Triple Diffie-Hellman) para key agreement inicial
9
+ * - Double Ratchet para forward secrecy e break-in recovery
10
+ * - Cada mensagem usa uma chave única derivada
11
+ * - Perfect Forward Secrecy (PFS)
12
+ * - Post-Compromise Security (PCS)
13
+ *
14
+ * @see https://signal.org/docs/specifications/doubleratchet/
15
+ * @see https://signal.org/docs/specifications/x3dh/
16
+ */
17
+
18
+ import { SignJWT, jwtVerify, generateKeyPair } from '../src/index';
19
+ import * as crypto from 'node:crypto';
20
+ import { EventEmitter } from 'node:events';
21
+
22
+ // ============================================================================
23
+ // Constantes e Tipos
24
+ // ============================================================================
25
+
26
+ const SYMMETRIC_KEY_LENGTH = 32; // 256 bits
27
+ const MAX_SKIP = 1000; // Máximo de message keys puladas para guardar
28
+
29
+ interface KeyBundle {
30
+ identityKey: Buffer; // Chave de identidade de longo prazo (pública)
31
+ signedPreKey: Buffer; // Pre-key assinada (pública)
32
+ signedPreKeySignature: Buffer;
33
+ oneTimePreKey?: Buffer; // Pre-key única (pública, opcional)
34
+ ephemeralKey?: Buffer; // Chave efêmera do remetente
35
+ }
36
+
37
+ interface X3DHKeyPair {
38
+ publicKey: Buffer;
39
+ privateKey: Buffer;
40
+ }
41
+
42
+ interface RatchetState {
43
+ DHs: X3DHKeyPair; // Ratchet key pair próprio
44
+ DHr: Buffer | null; // Ratchet public key do peer
45
+ RK: Buffer; // Root key
46
+ CKs: Buffer | null; // Sending chain key
47
+ CKr: Buffer | null; // Receiving chain key
48
+ Ns: number; // Número de mensagens enviadas na chain atual
49
+ Nr: number; // Número de mensagens recebidas na chain atual
50
+ PN: number; // Número de mensagens da chain anterior
51
+ MKSKIPPED: Map<string, Buffer>; // Skipped message keys
52
+ }
53
+
54
+ interface SignalMessage {
55
+ from: string;
56
+ to: string;
57
+ messageId: string;
58
+ timestamp: number;
59
+ header: {
60
+ dh: string; // Ratchet public key (hex)
61
+ pn: number; // Previous chain length
62
+ n: number; // Message number
63
+ };
64
+ ciphertext: string; // Encrypted content (hex)
65
+ nonce: string; // Nonce usado (hex)
66
+ jwt?: string; // JWT opcional para contexto
67
+ }
68
+
69
+ // ============================================================================
70
+ // Funções Criptográficas
71
+ // ============================================================================
72
+
73
+ /**
74
+ * Gera par de chaves X25519 para Diffie-Hellman
75
+ */
76
+ function generateX25519KeyPair(): X3DHKeyPair {
77
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('x25519');
78
+ return {
79
+ publicKey: Buffer.from(publicKey.export({ type: 'spki', format: 'der' }).slice(-32)),
80
+ privateKey: Buffer.from(privateKey.export({ type: 'pkcs8', format: 'der' }).slice(-32))
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Computa Diffie-Hellman shared secret
86
+ */
87
+ function computeDH(privateKey: Buffer, publicKey: Buffer): Buffer {
88
+ const privKeyObj = crypto.createPrivateKey({
89
+ key: Buffer.concat([
90
+ Buffer.from('302e020100300506032b656e04220420', 'hex'),
91
+ privateKey
92
+ ]),
93
+ format: 'der',
94
+ type: 'pkcs8'
95
+ });
96
+
97
+ const pubKeyObj = crypto.createPublicKey({
98
+ key: Buffer.concat([
99
+ Buffer.from('302a300506032b656e032100', 'hex'),
100
+ publicKey
101
+ ]),
102
+ format: 'der',
103
+ type: 'spki'
104
+ });
105
+
106
+ return crypto.diffieHellman({
107
+ privateKey: privKeyObj,
108
+ publicKey: pubKeyObj
109
+ });
110
+ }
111
+
112
+ /**
113
+ * HKDF - Hash-based Key Derivation Function
114
+ */
115
+ function hkdf(
116
+ inputKeyMaterial: Buffer,
117
+ salt: Buffer,
118
+ info: Buffer,
119
+ length: number
120
+ ): Buffer {
121
+ return crypto.hkdfSync('sha256', inputKeyMaterial, salt, info, length);
122
+ }
123
+
124
+ /**
125
+ * KDF para Root Chain - deriva novo RK e Chain Key
126
+ */
127
+ function kdfRK(rk: Buffer, dhOut: Buffer): { rootKey: Buffer; chainKey: Buffer } {
128
+ const output = hkdf(dhOut, rk, Buffer.from('SignalRootRatchet'), 64);
129
+ return {
130
+ rootKey: output.slice(0, 32),
131
+ chainKey: output.slice(32, 64)
132
+ };
133
+ }
134
+
135
+ /**
136
+ * KDF para Chain Key - deriva novo CK e Message Key
137
+ */
138
+ function kdfCK(ck: Buffer): { chainKey: Buffer; messageKey: Buffer } {
139
+ const chainKey = crypto.createHmac('sha256', ck).update(Buffer.from([0x01])).digest();
140
+ const messageKey = crypto.createHmac('sha256', ck).update(Buffer.from([0x02])).digest();
141
+ return { chainKey, messageKey };
142
+ }
143
+
144
+ /**
145
+ * Encripta mensagem usando AES-256-GCM
146
+ */
147
+ function encrypt(plaintext: string, key: Buffer): { ciphertext: Buffer; nonce: Buffer } {
148
+ const nonce = crypto.randomBytes(12);
149
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
150
+ const encrypted = Buffer.concat([
151
+ cipher.update(plaintext, 'utf8'),
152
+ cipher.final()
153
+ ]);
154
+ const authTag = cipher.getAuthTag();
155
+ return {
156
+ ciphertext: Buffer.concat([encrypted, authTag]),
157
+ nonce
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Decripta mensagem usando AES-256-GCM
163
+ */
164
+ function decrypt(ciphertext: Buffer, key: Buffer, nonce: Buffer): string {
165
+ const authTag = ciphertext.slice(-16);
166
+ const encrypted = ciphertext.slice(0, -16);
167
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
168
+ decipher.setAuthTag(authTag);
169
+ return decipher.update(encrypted) + decipher.final('utf8');
170
+ }
171
+
172
+ // ============================================================================
173
+ // X3DH Key Agreement Protocol
174
+ // ============================================================================
175
+
176
+ class X3DHKeyBundle {
177
+ readonly identityKey: X3DHKeyPair;
178
+ readonly signedPreKey: X3DHKeyPair;
179
+ readonly signedPreKeySignature: Buffer;
180
+ readonly oneTimePreKeys: X3DHKeyPair[];
181
+ private readonly signingKey: crypto.KeyObject;
182
+
183
+ constructor() {
184
+ // Gerar Identity Key (longo prazo)
185
+ this.identityKey = generateX25519KeyPair();
186
+
187
+ // Gerar Signed Pre-Key
188
+ this.signedPreKey = generateX25519KeyPair();
189
+
190
+ // Assinar a Pre-Key com Ed25519
191
+ const { publicKey: edPub, privateKey: edPriv } = crypto.generateKeyPairSync('ed25519');
192
+ this.signingKey = edPriv;
193
+ this.signedPreKeySignature = crypto.sign(null, this.signedPreKey.publicKey, edPriv);
194
+
195
+ // Gerar One-Time Pre-Keys
196
+ this.oneTimePreKeys = Array.from({ length: 10 }, () => generateX25519KeyPair());
197
+ }
198
+
199
+ getPublicBundle(): KeyBundle {
200
+ const oneTimePreKey = this.oneTimePreKeys.shift();
201
+ return {
202
+ identityKey: this.identityKey.publicKey,
203
+ signedPreKey: this.signedPreKey.publicKey,
204
+ signedPreKeySignature: this.signedPreKeySignature,
205
+ oneTimePreKey: oneTimePreKey?.publicKey
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Executa X3DH como receptor (Bob)
211
+ */
212
+ performX3DHAsReceiver(
213
+ ephemeralKey: Buffer,
214
+ senderIdentityKey: Buffer,
215
+ usedOneTimePreKey: boolean
216
+ ): Buffer {
217
+ // DH1: IKa, SPKb
218
+ const dh1 = computeDH(this.signedPreKey.privateKey, senderIdentityKey);
219
+
220
+ // DH2: EKa, IKb
221
+ const dh2 = computeDH(this.identityKey.privateKey, ephemeralKey);
222
+
223
+ // DH3: EKa, SPKb
224
+ const dh3 = computeDH(this.signedPreKey.privateKey, ephemeralKey);
225
+
226
+ let masterSecret: Buffer;
227
+ if (usedOneTimePreKey && this.oneTimePreKeys.length > 0) {
228
+ // DH4: EKa, OPKb (se one-time pre-key foi usada)
229
+ const otpk = this.oneTimePreKeys[0];
230
+ const dh4 = computeDH(otpk.privateKey, ephemeralKey);
231
+ masterSecret = Buffer.concat([dh1, dh2, dh3, dh4]);
232
+ } else {
233
+ masterSecret = Buffer.concat([dh1, dh2, dh3]);
234
+ }
235
+
236
+ return hkdf(masterSecret, Buffer.alloc(32), Buffer.from('X3DH'), 32);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Executa X3DH como iniciador (Alice)
242
+ */
243
+ function performX3DHAsInitiator(
244
+ identityKey: X3DHKeyPair,
245
+ ephemeralKey: X3DHKeyPair,
246
+ receiverBundle: KeyBundle
247
+ ): Buffer {
248
+ // DH1: IKa, SPKb
249
+ const dh1 = computeDH(identityKey.privateKey, receiverBundle.signedPreKey);
250
+
251
+ // DH2: EKa, IKb
252
+ const dh2 = computeDH(ephemeralKey.privateKey, receiverBundle.identityKey);
253
+
254
+ // DH3: EKa, SPKb
255
+ const dh3 = computeDH(ephemeralKey.privateKey, receiverBundle.signedPreKey);
256
+
257
+ let masterSecret: Buffer;
258
+ if (receiverBundle.oneTimePreKey) {
259
+ // DH4: EKa, OPKb
260
+ const dh4 = computeDH(ephemeralKey.privateKey, receiverBundle.oneTimePreKey);
261
+ masterSecret = Buffer.concat([dh1, dh2, dh3, dh4]);
262
+ } else {
263
+ masterSecret = Buffer.concat([dh1, dh2, dh3]);
264
+ }
265
+
266
+ return hkdf(masterSecret, Buffer.alloc(32), Buffer.from('X3DH'), 32);
267
+ }
268
+
269
+ // ============================================================================
270
+ // Double Ratchet State Machine
271
+ // ============================================================================
272
+
273
+ class DoubleRatchet {
274
+ private state: RatchetState;
275
+
276
+ constructor() {
277
+ this.state = {
278
+ DHs: generateX25519KeyPair(),
279
+ DHr: null,
280
+ RK: Buffer.alloc(32),
281
+ CKs: null,
282
+ CKr: null,
283
+ Ns: 0,
284
+ Nr: 0,
285
+ PN: 0,
286
+ MKSKIPPED: new Map()
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Inicializa como Alice (iniciadora da sessão)
292
+ */
293
+ initializeAsAlice(sharedSecret: Buffer, bobRatchetPublicKey: Buffer): void {
294
+ this.state.DHs = generateX25519KeyPair();
295
+ this.state.DHr = bobRatchetPublicKey;
296
+
297
+ // Deriva root key e sending chain key iniciais
298
+ const dhOutput = computeDH(this.state.DHs.privateKey, this.state.DHr);
299
+ const { rootKey, chainKey } = kdfRK(sharedSecret, dhOutput);
300
+
301
+ this.state.RK = rootKey;
302
+ this.state.CKs = chainKey;
303
+ this.state.CKr = null;
304
+ this.state.Ns = 0;
305
+ this.state.Nr = 0;
306
+ this.state.PN = 0;
307
+ }
308
+
309
+ /**
310
+ * Inicializa como Bob (receptor da sessão)
311
+ */
312
+ initializeAsBob(sharedSecret: Buffer): void {
313
+ this.state.DHs = generateX25519KeyPair();
314
+ this.state.DHr = null;
315
+ this.state.RK = sharedSecret;
316
+ this.state.CKs = null;
317
+ this.state.CKr = null;
318
+ this.state.Ns = 0;
319
+ this.state.Nr = 0;
320
+ this.state.PN = 0;
321
+ }
322
+
323
+ /**
324
+ * Encripta uma mensagem
325
+ */
326
+ ratchetEncrypt(plaintext: string): { header: SignalMessage['header']; ciphertext: Buffer; nonce: Buffer } {
327
+ if (!this.state.CKs) {
328
+ throw new Error('Sending chain não inicializada');
329
+ }
330
+
331
+ const { chainKey, messageKey } = kdfCK(this.state.CKs);
332
+ this.state.CKs = chainKey;
333
+
334
+ const header = {
335
+ dh: this.state.DHs.publicKey.toString('hex'),
336
+ pn: this.state.PN,
337
+ n: this.state.Ns
338
+ };
339
+
340
+ this.state.Ns++;
341
+
342
+ const { ciphertext, nonce } = encrypt(plaintext, messageKey);
343
+
344
+ // Limpa message key da memória
345
+ messageKey.fill(0);
346
+
347
+ return { header, ciphertext, nonce };
348
+ }
349
+
350
+ /**
351
+ * Decripta uma mensagem
352
+ */
353
+ ratchetDecrypt(header: SignalMessage['header'], ciphertext: Buffer, nonce: Buffer): string {
354
+ const dhPublicKey = Buffer.from(header.dh, 'hex');
355
+
356
+ // Tenta chaves puladas primeiro
357
+ const skippedKey = this.trySkippedMessageKeys(header, dhPublicKey);
358
+ if (skippedKey) {
359
+ const plaintext = decrypt(ciphertext, skippedKey, nonce);
360
+ skippedKey.fill(0);
361
+ return plaintext;
362
+ }
363
+
364
+ // Verifica se precisamos fazer DH ratchet
365
+ if (!this.state.DHr || !dhPublicKey.equals(this.state.DHr)) {
366
+ this.skipMessageKeys(header.pn);
367
+ this.dhRatchet(dhPublicKey);
368
+ }
369
+
370
+ this.skipMessageKeys(header.n);
371
+
372
+ if (!this.state.CKr) {
373
+ throw new Error('Receiving chain não inicializada');
374
+ }
375
+
376
+ const { chainKey, messageKey } = kdfCK(this.state.CKr);
377
+ this.state.CKr = chainKey;
378
+ this.state.Nr++;
379
+
380
+ const plaintext = decrypt(ciphertext, messageKey, nonce);
381
+ messageKey.fill(0);
382
+
383
+ return plaintext;
384
+ }
385
+
386
+ /**
387
+ * Realiza DH Ratchet step
388
+ */
389
+ private dhRatchet(dhPublicKey: Buffer): void {
390
+ this.state.PN = this.state.Ns;
391
+ this.state.Ns = 0;
392
+ this.state.Nr = 0;
393
+ this.state.DHr = dhPublicKey;
394
+
395
+ // Deriva receiving chain
396
+ const dhOutput1 = computeDH(this.state.DHs.privateKey, this.state.DHr);
397
+ const { rootKey: rk1, chainKey: ckr } = kdfRK(this.state.RK, dhOutput1);
398
+ this.state.RK = rk1;
399
+ this.state.CKr = ckr;
400
+
401
+ // Gera novo DH key pair e deriva sending chain
402
+ this.state.DHs = generateX25519KeyPair();
403
+ const dhOutput2 = computeDH(this.state.DHs.privateKey, this.state.DHr);
404
+ const { rootKey: rk2, chainKey: cks } = kdfRK(this.state.RK, dhOutput2);
405
+ this.state.RK = rk2;
406
+ this.state.CKs = cks;
407
+ }
408
+
409
+ /**
410
+ * Armazena message keys puladas
411
+ */
412
+ private skipMessageKeys(until: number): void {
413
+ if (!this.state.CKr) return;
414
+
415
+ if (this.state.Nr + MAX_SKIP < until) {
416
+ throw new Error('Muitas mensagens puladas');
417
+ }
418
+
419
+ while (this.state.Nr < until) {
420
+ const { chainKey, messageKey } = kdfCK(this.state.CKr);
421
+ this.state.CKr = chainKey;
422
+
423
+ const key = `${this.state.DHr?.toString('hex')}-${this.state.Nr}`;
424
+ this.state.MKSKIPPED.set(key, messageKey);
425
+
426
+ this.state.Nr++;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Tenta usar uma message key pulada
432
+ */
433
+ private trySkippedMessageKeys(header: SignalMessage['header'], dhPublicKey: Buffer): Buffer | null {
434
+ const key = `${dhPublicKey.toString('hex')}-${header.n}`;
435
+ const messageKey = this.state.MKSKIPPED.get(key);
436
+
437
+ if (messageKey) {
438
+ this.state.MKSKIPPED.delete(key);
439
+ return messageKey;
440
+ }
441
+
442
+ return null;
443
+ }
444
+
445
+ /**
446
+ * Retorna public key atual para inicialização do peer
447
+ */
448
+ getPublicKey(): Buffer {
449
+ return this.state.DHs.publicKey;
450
+ }
451
+ }
452
+
453
+ // ============================================================================
454
+ // Token Authority (integração com JWT)
455
+ // ============================================================================
456
+
457
+ class TokenAuthority {
458
+ private privateKey: crypto.KeyObject;
459
+ public publicKey: crypto.KeyObject;
460
+ private issuer = 'urn:agentic-system:authority';
461
+ private audience = 'urn:agentic-system:agents';
462
+
463
+ constructor() {
464
+ const keys = generateKeyPair();
465
+ this.privateKey = crypto.createPrivateKey(keys.privateKey);
466
+ this.publicKey = crypto.createPublicKey(keys.publicKey);
467
+ }
468
+
469
+ async issueAgentToken(
470
+ agentId: string,
471
+ conversationId: string,
472
+ capabilities: string[] = []
473
+ ): Promise<string> {
474
+ return await new SignJWT({
475
+ agentId,
476
+ conversationId,
477
+ capabilities,
478
+ encryptionProtocol: 'signal-e2ee',
479
+ issuedAt: Date.now()
480
+ })
481
+ .setProtectedHeader({ alg: 'EdDSA', typ: 'JWT' })
482
+ .setIssuedAt()
483
+ .setIssuer(this.issuer)
484
+ .setAudience(this.audience)
485
+ .setSubject(agentId)
486
+ .setExpirationTime('5m')
487
+ .sign(this.privateKey);
488
+ }
489
+
490
+ async verifyToken(token: string): Promise<any> {
491
+ const { payload } = await jwtVerify(token, this.publicKey, {
492
+ issuer: this.issuer,
493
+ audience: this.audience
494
+ });
495
+ return payload;
496
+ }
497
+ }
498
+
499
+ // ============================================================================
500
+ // Agente com Signal E2EE
501
+ // ============================================================================
502
+
503
+ class SignalE2EEAgent extends EventEmitter {
504
+ readonly agentId: string;
505
+ private keyBundle: X3DHKeyBundle;
506
+ private sessions: Map<string, DoubleRatchet> = new Map();
507
+ private messageHistory: SignalMessage[] = [];
508
+ private token: string | null = null;
509
+ private authority: TokenAuthority;
510
+ private conversationId: string;
511
+ private peerPublicBundles: Map<string, KeyBundle> = new Map();
512
+ private identityKey: X3DHKeyPair;
513
+
514
+ constructor(
515
+ agentId: string,
516
+ authority: TokenAuthority,
517
+ capabilities: string[] = []
518
+ ) {
519
+ super();
520
+ this.agentId = agentId;
521
+ this.authority = authority;
522
+ this.keyBundle = new X3DHKeyBundle();
523
+ this.identityKey = generateX25519KeyPair();
524
+ this.conversationId = `conv-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
525
+ }
526
+
527
+ async initialize(): Promise<void> {
528
+ this.token = await this.authority.issueAgentToken(
529
+ this.agentId,
530
+ this.conversationId
531
+ );
532
+ console.log(`🔐 [${this.agentId}] Agente Signal E2EE inicializado`);
533
+ }
534
+
535
+ /**
536
+ * Retorna bundle público para troca de chaves
537
+ */
538
+ getPublicKeyBundle(): KeyBundle {
539
+ return this.keyBundle.getPublicBundle();
540
+ }
541
+
542
+ /**
543
+ * Armazena bundle público de um peer
544
+ */
545
+ registerPeerBundle(peerId: string, bundle: KeyBundle): void {
546
+ this.peerPublicBundles.set(peerId, bundle);
547
+ console.log(`📋 [${this.agentId}] Bundle de ${peerId} registrado`);
548
+ }
549
+
550
+ /**
551
+ * Estabelece sessão segura com peer (como iniciador)
552
+ */
553
+ async establishSession(peerId: string): Promise<void> {
554
+ const peerBundle = this.peerPublicBundles.get(peerId);
555
+ if (!peerBundle) {
556
+ throw new Error(`Bundle de ${peerId} não encontrado`);
557
+ }
558
+
559
+ // Gerar chave efêmera para X3DH
560
+ const ephemeralKey = generateX25519KeyPair();
561
+
562
+ // Executar X3DH como Alice
563
+ const sharedSecret = performX3DHAsInitiator(
564
+ this.identityKey,
565
+ ephemeralKey,
566
+ peerBundle
567
+ );
568
+
569
+ // Inicializar Double Ratchet
570
+ const ratchet = new DoubleRatchet();
571
+ ratchet.initializeAsAlice(sharedSecret, peerBundle.signedPreKey);
572
+
573
+ this.sessions.set(peerId, ratchet);
574
+
575
+ // Limpar chaves temporárias
576
+ ephemeralKey.privateKey.fill(0);
577
+
578
+ console.log(`🔗 [${this.agentId}] Sessão E2EE estabelecida com ${peerId}`);
579
+ }
580
+
581
+ /**
582
+ * Aceita sessão segura (como receptor)
583
+ */
584
+ async acceptSession(
585
+ peerId: string,
586
+ senderIdentityKey: Buffer,
587
+ senderEphemeralKey: Buffer
588
+ ): Promise<Buffer> {
589
+ // Executar X3DH como Bob
590
+ const sharedSecret = this.keyBundle.performX3DHAsReceiver(
591
+ senderEphemeralKey,
592
+ senderIdentityKey,
593
+ true
594
+ );
595
+
596
+ // Inicializar Double Ratchet
597
+ const ratchet = new DoubleRatchet();
598
+ ratchet.initializeAsBob(sharedSecret);
599
+
600
+ this.sessions.set(peerId, ratchet);
601
+
602
+ console.log(`🔗 [${this.agentId}] Sessão E2EE aceita de ${peerId}`);
603
+
604
+ return ratchet.getPublicKey();
605
+ }
606
+
607
+ /**
608
+ * Envia mensagem encriptada
609
+ */
610
+ async sendMessage(peerId: string, content: string): Promise<SignalMessage> {
611
+ const session = this.sessions.get(peerId);
612
+ if (!session) {
613
+ throw new Error(`Sessão com ${peerId} não estabelecida`);
614
+ }
615
+
616
+ const { header, ciphertext, nonce } = session.ratchetEncrypt(content);
617
+
618
+ const message: SignalMessage = {
619
+ from: this.agentId,
620
+ to: peerId,
621
+ messageId: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
622
+ timestamp: Date.now(),
623
+ header,
624
+ ciphertext: ciphertext.toString('hex'),
625
+ nonce: nonce.toString('hex'),
626
+ jwt: this.token || undefined
627
+ };
628
+
629
+ this.messageHistory.push(message);
630
+ console.log(`📤 [${this.agentId}] → [${peerId}] (E2EE): [${content.length} chars encrypted]`);
631
+
632
+ return message;
633
+ }
634
+
635
+ /**
636
+ * Recebe e decripta mensagem
637
+ */
638
+ async receiveMessage(message: SignalMessage): Promise<string> {
639
+ // Verificar JWT se presente
640
+ if (message.jwt) {
641
+ try {
642
+ await this.authority.verifyToken(message.jwt);
643
+ } catch (error) {
644
+ console.warn(`⚠️ [${this.agentId}] JWT inválido de ${message.from}`);
645
+ }
646
+ }
647
+
648
+ const session = this.sessions.get(message.from);
649
+ if (!session) {
650
+ throw new Error(`Sessão com ${message.from} não encontrada`);
651
+ }
652
+
653
+ const ciphertext = Buffer.from(message.ciphertext, 'hex');
654
+ const nonce = Buffer.from(message.nonce, 'hex');
655
+
656
+ const plaintext = session.ratchetDecrypt(message.header, ciphertext, nonce);
657
+
658
+ this.messageHistory.push(message);
659
+ console.log(`📥 [${this.agentId}] ← [${message.from}] (E2EE): ${plaintext}`);
660
+
661
+ this.emit('message', { from: message.from, content: plaintext, message });
662
+
663
+ return plaintext;
664
+ }
665
+
666
+ getMessageHistory(): SignalMessage[] {
667
+ return [...this.messageHistory];
668
+ }
669
+
670
+ getIdentityPublicKey(): Buffer {
671
+ return this.identityKey.publicKey;
672
+ }
673
+ }
674
+
675
+ // ============================================================================
676
+ // Demonstração
677
+ // ============================================================================
678
+
679
+ async function demonstrateSignalE2EE() {
680
+ console.log('🚀 Demonstração de Signal Protocol E2EE para Agentes\n');
681
+ console.log('═'.repeat(60));
682
+
683
+ // 1. Criar autoridade de tokens
684
+ const tokenAuthority = new TokenAuthority();
685
+ console.log('✅ Token Authority criada\n');
686
+
687
+ // 2. Criar agentes
688
+ const agentAlpha = new SignalE2EEAgent('agent-alpha', tokenAuthority, ['reasoning']);
689
+ const agentBeta = new SignalE2EEAgent('agent-beta', tokenAuthority, ['analysis']);
690
+
691
+ await agentAlpha.initialize();
692
+ await agentBeta.initialize();
693
+ console.log('');
694
+
695
+ // 3. Trocar bundles públicos (simulando um servidor de key distribution)
696
+ console.log('📦 Trocando bundles de chaves públicas...');
697
+ const alphaBundle = agentAlpha.getPublicKeyBundle();
698
+ const betaBundle = agentBeta.getPublicKeyBundle();
699
+
700
+ agentAlpha.registerPeerBundle('agent-beta', betaBundle);
701
+ agentBeta.registerPeerBundle('agent-alpha', alphaBundle);
702
+ console.log('');
703
+
704
+ // 4. Alpha estabelece sessão com Beta
705
+ console.log('🔐 Estabelecendo sessão E2EE...');
706
+ await agentAlpha.establishSession('agent-beta');
707
+
708
+ // Beta aceita a sessão (em produção, isso seria feito via primeira mensagem)
709
+ await agentBeta.acceptSession(
710
+ 'agent-alpha',
711
+ agentAlpha.getIdentityPublicKey(),
712
+ agentAlpha.getPublicKeyBundle().signedPreKey
713
+ );
714
+ console.log('');
715
+
716
+ // 5. Trocar mensagens encriptadas
717
+ console.log('💬 Iniciando conversa E2EE...\n');
718
+ console.log('─'.repeat(60));
719
+
720
+ // Alpha envia primeira mensagem
721
+ const msg1 = await agentAlpha.sendMessage(
722
+ 'agent-beta',
723
+ 'Olá Beta! Esta mensagem está encriptada com Signal Protocol.'
724
+ );
725
+ await agentBeta.receiveMessage(msg1);
726
+
727
+ console.log('');
728
+
729
+ // Beta responde
730
+ const msg2 = await agentBeta.sendMessage(
731
+ 'agent-alpha',
732
+ 'Olá Alpha! Recebi sua mensagem com Perfect Forward Secrecy!'
733
+ );
734
+ await agentAlpha.receiveMessage(msg2);
735
+
736
+ console.log('');
737
+
738
+ // Alpha envia outra mensagem
739
+ const msg3 = await agentAlpha.sendMessage(
740
+ 'agent-beta',
741
+ 'O Double Ratchet garante que cada mensagem usa uma chave única.'
742
+ );
743
+ await agentBeta.receiveMessage(msg3);
744
+
745
+ console.log('');
746
+
747
+ // Beta responde novamente
748
+ const msg4 = await agentBeta.sendMessage(
749
+ 'agent-alpha',
750
+ 'Exatamente! E temos Post-Compromise Security também.'
751
+ );
752
+ await agentAlpha.receiveMessage(msg4);
753
+
754
+ console.log('');
755
+ console.log('─'.repeat(60));
756
+
757
+ // 6. Mostrar resumo
758
+ console.log('\n📊 Resumo da Demonstração:\n');
759
+ console.log('🔒 Propriedades de Segurança:');
760
+ console.log(' • Perfect Forward Secrecy (PFS)');
761
+ console.log(' • Post-Compromise Security (PCS)');
762
+ console.log(' • Chaves únicas por mensagem');
763
+ console.log(' • Deniability (negabilidade)');
764
+ console.log('');
765
+ console.log('🔧 Algoritmos Utilizados:');
766
+ console.log(' • X3DH para key agreement inicial');
767
+ console.log(' • X25519 para Diffie-Hellman');
768
+ console.log(' • HKDF-SHA256 para derivação de chaves');
769
+ console.log(' • AES-256-GCM para encriptação');
770
+ console.log(' • Double Ratchet para gestão de chaves');
771
+ console.log('');
772
+ console.log(`📨 Total de mensagens trocadas: ${agentAlpha.getMessageHistory().length * 2}`);
773
+ console.log('');
774
+ console.log('✅ Demonstração concluída!');
775
+ }
776
+
777
+ // ============================================================================
778
+ // Combinação com mTLS (demonstração conceitual)
779
+ // ============================================================================
780
+
781
+ async function demonstrateCombinedSecurity() {
782
+ console.log('\n');
783
+ console.log('═'.repeat(60));
784
+ console.log('🔐 Signal E2EE + mTLS: Defesa em Profundidade');
785
+ console.log('═'.repeat(60));
786
+ console.log('');
787
+ console.log('Quando combinados, você obtém:');
788
+ console.log('');
789
+ console.log('┌─────────────────────────────────────────────────────────┐');
790
+ console.log('│ CAMADA │ PROTOCOLO │ PROTEÇÃO │');
791
+ console.log('├─────────────────────────────────────────────────────────┤');
792
+ console.log('│ Transporte │ mTLS │ Autenticação mútua │');
793
+ console.log('│ │ │ Canal seguro │');
794
+ console.log('│ │ │ Anti-MITM │');
795
+ console.log('├─────────────────────────────────────────────────────────┤');
796
+ console.log('│ Aplicação │ Signal E2E │ Forward Secrecy │');
797
+ console.log('│ │ │ Post-Compromise Sec │');
798
+ console.log('│ │ │ Deniability │');
799
+ console.log('├─────────────────────────────────────────────────────────┤');
800
+ console.log('│ Contexto │ JWT │ Identity claims │');
801
+ console.log('│ │ │ Authorization │');
802
+ console.log('│ │ │ Expiration │');
803
+ console.log('└─────────────────────────────────────────────────────────┘');
804
+ console.log('');
805
+ console.log('💡 Benefícios da combinação:');
806
+ console.log(' 1. Mesmo que mTLS seja comprometido, E2EE protege o conteúdo');
807
+ console.log(' 2. Mesmo que E2EE seja comprometido, mTLS protege o canal');
808
+ console.log(' 3. JWT adiciona contexto e autorização independente');
809
+ console.log(' 4. Defesa em profundidade reduz superfície de ataque');
810
+ console.log('');
811
+ }
812
+
813
+ // Executar demonstrações
814
+ if (import.meta.url === `file://${process.argv[1]}`) {
815
+ demonstrateSignalE2EE()
816
+ .then(demonstrateCombinedSecurity)
817
+ .catch(console.error);
818
+ }
819
+
820
+ export {
821
+ SignalE2EEAgent,
822
+ DoubleRatchet,
823
+ X3DHKeyBundle,
824
+ TokenAuthority,
825
+ SignalMessage,
826
+ KeyBundle
827
+ };