erosolar-cli 2.1.242 → 2.1.243
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.
- package/dist/capabilities/iMessageVerificationCapability.d.ts +31 -0
- package/dist/capabilities/iMessageVerificationCapability.d.ts.map +1 -0
- package/dist/capabilities/iMessageVerificationCapability.js +56 -0
- package/dist/capabilities/iMessageVerificationCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/index.js.map +1 -1
- package/dist/core/agentOrchestrator.d.ts +31 -0
- package/dist/core/agentOrchestrator.d.ts.map +1 -1
- package/dist/core/agentOrchestrator.js +328 -0
- package/dist/core/agentOrchestrator.js.map +1 -1
- package/dist/core/iMessageVerification.d.ts +408 -0
- package/dist/core/iMessageVerification.d.ts.map +1 -0
- package/dist/core/iMessageVerification.js +883 -0
- package/dist/core/iMessageVerification.js.map +1 -0
- package/dist/core/techFraudInvestigator.d.ts +131 -0
- package/dist/core/techFraudInvestigator.d.ts.map +1 -0
- package/dist/core/techFraudInvestigator.js +992 -0
- package/dist/core/techFraudInvestigator.js.map +1 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.d.ts +3 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.js +14 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.js.map +1 -0
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +2 -0
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/tools/iMessageVerificationTools.d.ts +17 -0
- package/dist/tools/iMessageVerificationTools.d.ts.map +1 -0
- package/dist/tools/iMessageVerificationTools.js +842 -0
- package/dist/tools/iMessageVerificationTools.js.map +1 -0
- package/dist/tools/taoTools.d.ts.map +1 -1
- package/dist/tools/taoTools.js +1277 -1
- package/dist/tools/taoTools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iMessage PQ3 Verification Tools
|
|
3
|
+
*
|
|
4
|
+
* CLI tool suite for verifying Apple's iMessage implementation honesty.
|
|
5
|
+
* Provides the verification capabilities Apple doesn't offer publicly:
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - iMessageKeyMonitor: Monitor IDS key changes over time
|
|
9
|
+
* - iMessageOOBVerify: Out-of-band key verification (like Signal safety numbers)
|
|
10
|
+
* - iMessageKTAudit: Audit Apple's Key Transparency log
|
|
11
|
+
* - iMessageTrafficAnalyze: Analyze network traffic for anomalies
|
|
12
|
+
* - iMessageEvidenceExport: Export evidence for legal proceedings
|
|
13
|
+
* - iMessageClaimsAnalyze: Compare Apple's claims vs. verifiable reality
|
|
14
|
+
*/
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import * as crypto from 'node:crypto';
|
|
17
|
+
import { iMessageVerificationEngine, APPLE_PQ3_CLAIMS, APPLE_INFRASTRUCTURE, } from '../core/iMessageVerification.js';
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
+
// TOOL SUITE FACTORY
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21
|
+
export function createiMessageVerificationTools(workingDir = process.cwd()) {
|
|
22
|
+
const storageDir = path.join(workingDir, '.erosolar', 'integrity');
|
|
23
|
+
// Engine instance (lazy initialization)
|
|
24
|
+
let engineInstance = null;
|
|
25
|
+
const getEngine = async () => {
|
|
26
|
+
if (!engineInstance) {
|
|
27
|
+
engineInstance = new iMessageVerificationEngine({ storageDir });
|
|
28
|
+
await engineInstance.initialize();
|
|
29
|
+
}
|
|
30
|
+
return engineInstance;
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
id: 'imessage-verification',
|
|
34
|
+
description: 'Cryptographic verification of Apple iMessage PQ3 implementation honesty',
|
|
35
|
+
tools: [
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
37
|
+
// Key Monitoring
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
39
|
+
{
|
|
40
|
+
name: 'iMessageKeyMonitor',
|
|
41
|
+
description: `Monitor iMessage IDS public key changes over time.
|
|
42
|
+
|
|
43
|
+
This tool records observations of a user's public keys as served by Apple's
|
|
44
|
+
Identity Directory Service (IDS). By tracking changes over time, we can detect:
|
|
45
|
+
|
|
46
|
+
- Unauthorized key additions (Apple injecting their own key)
|
|
47
|
+
- Key modifications outside normal rotation
|
|
48
|
+
- Suspicious patterns indicating MITM setup
|
|
49
|
+
|
|
50
|
+
Key monitoring is essential because Apple controls the key directory.
|
|
51
|
+
If Apple substitutes keys, they can perform MITM despite E2E encryption claims.
|
|
52
|
+
|
|
53
|
+
Usage:
|
|
54
|
+
1. Capture IDS responses via network proxy (mitmproxy, Charles, etc.)
|
|
55
|
+
2. Extract public key data from responses
|
|
56
|
+
3. Record observations over time
|
|
57
|
+
4. Compare observations to detect changes
|
|
58
|
+
|
|
59
|
+
Operations:
|
|
60
|
+
- record: Record a new key observation
|
|
61
|
+
- list: List all observations for a user
|
|
62
|
+
- compare: Compare two observations for changes
|
|
63
|
+
- history: Show change history for a user`,
|
|
64
|
+
parameters: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
required: ['operation'],
|
|
67
|
+
properties: {
|
|
68
|
+
operation: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
enum: ['record', 'list', 'compare', 'history'],
|
|
71
|
+
description: 'Operation to perform',
|
|
72
|
+
},
|
|
73
|
+
userId: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'User identifier (phone number or email)',
|
|
76
|
+
},
|
|
77
|
+
keys: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
items: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
keyId: { type: 'string' },
|
|
83
|
+
deviceId: { type: 'string' },
|
|
84
|
+
keyType: { type: 'string', enum: ['encryption', 'signing', 'pq-kem'] },
|
|
85
|
+
algorithm: { type: 'string' },
|
|
86
|
+
publicKeyData: { type: 'string' },
|
|
87
|
+
expiresAt: { type: 'string' },
|
|
88
|
+
signature: { type: 'string' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
description: 'Array of public keys observed',
|
|
92
|
+
},
|
|
93
|
+
networkMetadata: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
requestTimestamp: { type: 'string' },
|
|
97
|
+
responseTimestamp: { type: 'string' },
|
|
98
|
+
latencyMs: { type: 'number' },
|
|
99
|
+
serverIp: { type: 'string' },
|
|
100
|
+
tlsVersion: { type: 'string' },
|
|
101
|
+
rawRequest: { type: 'string' },
|
|
102
|
+
rawResponse: { type: 'string' },
|
|
103
|
+
},
|
|
104
|
+
description: 'Network capture metadata',
|
|
105
|
+
},
|
|
106
|
+
observationId1: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'First observation ID (for compare)',
|
|
109
|
+
},
|
|
110
|
+
observationId2: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Second observation ID (for compare)',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
handler: async (args) => {
|
|
117
|
+
const operation = args['operation'];
|
|
118
|
+
const userId = args['userId'];
|
|
119
|
+
const engine = await getEngine();
|
|
120
|
+
switch (operation) {
|
|
121
|
+
case 'record': {
|
|
122
|
+
if (!userId) {
|
|
123
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
124
|
+
}
|
|
125
|
+
const keys = args['keys'];
|
|
126
|
+
if (!keys || keys.length === 0) {
|
|
127
|
+
return JSON.stringify({ success: false, error: 'keys array required' });
|
|
128
|
+
}
|
|
129
|
+
const networkMetadata = args['networkMetadata'] || {
|
|
130
|
+
requestTimestamp: new Date().toISOString(),
|
|
131
|
+
responseTimestamp: new Date().toISOString(),
|
|
132
|
+
latencyMs: 0,
|
|
133
|
+
};
|
|
134
|
+
const observation = await engine.recordKeyObservation({
|
|
135
|
+
userId,
|
|
136
|
+
keys,
|
|
137
|
+
networkMetadata,
|
|
138
|
+
});
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
success: true,
|
|
141
|
+
operation: 'record',
|
|
142
|
+
observation: {
|
|
143
|
+
id: observation.id,
|
|
144
|
+
timestamp: observation.timestamp,
|
|
145
|
+
userId: observation.userId,
|
|
146
|
+
keyCount: observation.keys.length,
|
|
147
|
+
changeDetected: observation.changeDetected,
|
|
148
|
+
changes: observation.changes,
|
|
149
|
+
hash: observation.hash,
|
|
150
|
+
},
|
|
151
|
+
message: observation.changeDetected
|
|
152
|
+
? `⚠️ KEY CHANGES DETECTED: ${observation.changes?.length} changes from previous observation`
|
|
153
|
+
: `Key observation recorded. ${observation.keys.length} keys observed.`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
case 'list': {
|
|
157
|
+
if (!userId) {
|
|
158
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
159
|
+
}
|
|
160
|
+
const observations = engine.getKeyObservations(userId);
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
success: true,
|
|
163
|
+
operation: 'list',
|
|
164
|
+
userId,
|
|
165
|
+
observationCount: observations.length,
|
|
166
|
+
observations: observations.map(o => ({
|
|
167
|
+
id: o.id,
|
|
168
|
+
timestamp: o.timestamp,
|
|
169
|
+
keyCount: o.keys.length,
|
|
170
|
+
changeDetected: o.changeDetected,
|
|
171
|
+
hash: o.hash,
|
|
172
|
+
})),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
case 'compare': {
|
|
176
|
+
if (!userId) {
|
|
177
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
178
|
+
}
|
|
179
|
+
const observations = engine.getKeyObservations(userId);
|
|
180
|
+
const id1 = args['observationId1'];
|
|
181
|
+
const id2 = args['observationId2'];
|
|
182
|
+
const obs1 = id1
|
|
183
|
+
? observations.find(o => o.id === id1)
|
|
184
|
+
: observations[observations.length - 2];
|
|
185
|
+
const obs2 = id2
|
|
186
|
+
? observations.find(o => o.id === id2)
|
|
187
|
+
: observations[observations.length - 1];
|
|
188
|
+
if (!obs1 || !obs2) {
|
|
189
|
+
return JSON.stringify({
|
|
190
|
+
success: false,
|
|
191
|
+
error: 'Could not find observations to compare',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Detailed comparison
|
|
195
|
+
const changes = [];
|
|
196
|
+
for (const key2 of obs2.keys) {
|
|
197
|
+
const key1 = obs1.keys.find(k => k.keyId === key2.keyId);
|
|
198
|
+
if (!key1) {
|
|
199
|
+
changes.push({
|
|
200
|
+
type: 'ADDED',
|
|
201
|
+
keyId: key2.keyId,
|
|
202
|
+
detail: `New key for device ${key2.deviceId} (${key2.keyType})`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (key1.publicKeyData !== key2.publicKeyData) {
|
|
206
|
+
changes.push({
|
|
207
|
+
type: 'MODIFIED',
|
|
208
|
+
keyId: key2.keyId,
|
|
209
|
+
detail: `Key data changed for device ${key2.deviceId}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const key1 of obs1.keys) {
|
|
214
|
+
if (!obs2.keys.find(k => k.keyId === key1.keyId)) {
|
|
215
|
+
changes.push({
|
|
216
|
+
type: 'REMOVED',
|
|
217
|
+
keyId: key1.keyId,
|
|
218
|
+
detail: `Key removed for device ${key1.deviceId}`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return JSON.stringify({
|
|
223
|
+
success: true,
|
|
224
|
+
operation: 'compare',
|
|
225
|
+
observation1: { id: obs1.id, timestamp: obs1.timestamp },
|
|
226
|
+
observation2: { id: obs2.id, timestamp: obs2.timestamp },
|
|
227
|
+
changesDetected: changes.length > 0,
|
|
228
|
+
changes,
|
|
229
|
+
implications: changes.length > 0
|
|
230
|
+
? 'Key changes detected. If these changes were not initiated by the user (e.g., adding a new device), they may indicate key substitution by Apple.'
|
|
231
|
+
: 'No changes detected between observations.',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
case 'history': {
|
|
235
|
+
if (!userId) {
|
|
236
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
237
|
+
}
|
|
238
|
+
const observations = engine.getKeyObservations(userId);
|
|
239
|
+
const history = observations
|
|
240
|
+
.filter(o => o.changeDetected && o.changes)
|
|
241
|
+
.map(o => ({
|
|
242
|
+
timestamp: o.timestamp,
|
|
243
|
+
changes: o.changes,
|
|
244
|
+
}));
|
|
245
|
+
return JSON.stringify({
|
|
246
|
+
success: true,
|
|
247
|
+
operation: 'history',
|
|
248
|
+
userId,
|
|
249
|
+
totalObservations: observations.length,
|
|
250
|
+
observationsWithChanges: history.length,
|
|
251
|
+
changeHistory: history,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
return JSON.stringify({ success: false, error: `Unknown operation: ${operation}` });
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
260
|
+
// Out-of-Band Verification
|
|
261
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
262
|
+
{
|
|
263
|
+
name: 'iMessageOOBVerify',
|
|
264
|
+
description: `Perform out-of-band key verification with contacts.
|
|
265
|
+
|
|
266
|
+
This is the ONLY way to verify that Apple hasn't substituted keys.
|
|
267
|
+
Similar to Signal's safety number verification:
|
|
268
|
+
|
|
269
|
+
1. You and your contact both enable iMessage Contact Key Verification
|
|
270
|
+
2. Each of you gets a verification code/fingerprint for the other
|
|
271
|
+
3. You compare these codes via a TRUSTED channel (in person, phone call)
|
|
272
|
+
4. If they MATCH: Keys are verified, Apple hasn't performed MITM
|
|
273
|
+
5. If they DON'T MATCH: Apple (or another attacker) has substituted keys
|
|
274
|
+
|
|
275
|
+
THIS IS CRITICAL: Without out-of-band verification, you're trusting
|
|
276
|
+
Apple's word that they haven't substituted keys.
|
|
277
|
+
|
|
278
|
+
Operations:
|
|
279
|
+
- verify: Record an out-of-band verification attempt
|
|
280
|
+
- list: List all verifications for a contact
|
|
281
|
+
- fingerprint: Generate fingerprint from a public key
|
|
282
|
+
- mismatches: List all verification mismatches (potential MITM)`,
|
|
283
|
+
parameters: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
required: ['operation'],
|
|
286
|
+
properties: {
|
|
287
|
+
operation: {
|
|
288
|
+
type: 'string',
|
|
289
|
+
enum: ['verify', 'list', 'fingerprint', 'mismatches'],
|
|
290
|
+
description: 'Operation to perform',
|
|
291
|
+
},
|
|
292
|
+
yourUserId: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'Your phone number or email',
|
|
295
|
+
},
|
|
296
|
+
contactUserId: {
|
|
297
|
+
type: 'string',
|
|
298
|
+
description: 'Contact\'s phone number or email',
|
|
299
|
+
},
|
|
300
|
+
verificationMethod: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
enum: ['in_person', 'voice_call', 'video_call', 'trusted_channel'],
|
|
303
|
+
description: 'How the verification was performed',
|
|
304
|
+
},
|
|
305
|
+
yourKeyFingerprint: {
|
|
306
|
+
type: 'string',
|
|
307
|
+
description: 'Your verification code from iMessage Contact Key Verification',
|
|
308
|
+
},
|
|
309
|
+
contactKeyFingerprint: {
|
|
310
|
+
type: 'string',
|
|
311
|
+
description: 'What YOUR device shows as the contact\'s verification code',
|
|
312
|
+
},
|
|
313
|
+
contactReportedFingerprint: {
|
|
314
|
+
type: 'string',
|
|
315
|
+
description: 'What the CONTACT told you their verification code is',
|
|
316
|
+
},
|
|
317
|
+
witnesses: {
|
|
318
|
+
type: 'array',
|
|
319
|
+
items: { type: 'string' },
|
|
320
|
+
description: 'Names of witnesses to the verification',
|
|
321
|
+
},
|
|
322
|
+
evidence: {
|
|
323
|
+
type: 'string',
|
|
324
|
+
description: 'Path to evidence (screenshot, recording)',
|
|
325
|
+
},
|
|
326
|
+
publicKeyData: {
|
|
327
|
+
type: 'string',
|
|
328
|
+
description: 'Public key data (for fingerprint operation)',
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
handler: async (args) => {
|
|
333
|
+
const operation = args['operation'];
|
|
334
|
+
const engine = await getEngine();
|
|
335
|
+
switch (operation) {
|
|
336
|
+
case 'verify': {
|
|
337
|
+
const yourUserId = args['yourUserId'];
|
|
338
|
+
const contactUserId = args['contactUserId'];
|
|
339
|
+
const verificationMethod = args['verificationMethod'];
|
|
340
|
+
const yourKeyFingerprint = args['yourKeyFingerprint'];
|
|
341
|
+
const contactKeyFingerprint = args['contactKeyFingerprint'];
|
|
342
|
+
const contactReportedFingerprint = args['contactReportedFingerprint'];
|
|
343
|
+
const witnesses = args['witnesses'];
|
|
344
|
+
const evidence = args['evidence'];
|
|
345
|
+
if (!yourUserId || !contactUserId || !verificationMethod ||
|
|
346
|
+
!contactKeyFingerprint || !contactReportedFingerprint) {
|
|
347
|
+
return JSON.stringify({
|
|
348
|
+
success: false,
|
|
349
|
+
error: 'Required: yourUserId, contactUserId, verificationMethod, contactKeyFingerprint, contactReportedFingerprint',
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const verification = await engine.recordOutOfBandVerification({
|
|
353
|
+
yourUserId,
|
|
354
|
+
contactUserId,
|
|
355
|
+
verificationMethod,
|
|
356
|
+
yourKeyFingerprint: yourKeyFingerprint || '',
|
|
357
|
+
contactKeyFingerprint,
|
|
358
|
+
contactReportedFingerprint,
|
|
359
|
+
witnesses,
|
|
360
|
+
evidence,
|
|
361
|
+
});
|
|
362
|
+
if (verification.match) {
|
|
363
|
+
return JSON.stringify({
|
|
364
|
+
success: true,
|
|
365
|
+
operation: 'verify',
|
|
366
|
+
result: 'MATCH',
|
|
367
|
+
verification: {
|
|
368
|
+
id: verification.id,
|
|
369
|
+
timestamp: verification.timestamp,
|
|
370
|
+
contactUserId: verification.contactUserId,
|
|
371
|
+
match: verification.match,
|
|
372
|
+
hash: verification.hash,
|
|
373
|
+
},
|
|
374
|
+
message: '✅ VERIFICATION PASSED: Key fingerprints match. Your communication with this contact is protected by genuine E2E encryption.',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
return JSON.stringify({
|
|
379
|
+
success: true,
|
|
380
|
+
operation: 'verify',
|
|
381
|
+
result: 'MISMATCH',
|
|
382
|
+
verification: {
|
|
383
|
+
id: verification.id,
|
|
384
|
+
timestamp: verification.timestamp,
|
|
385
|
+
contactUserId: verification.contactUserId,
|
|
386
|
+
match: verification.match,
|
|
387
|
+
discrepancy: verification.discrepancy,
|
|
388
|
+
hash: verification.hash,
|
|
389
|
+
},
|
|
390
|
+
message: `🚨 CRITICAL: KEY MISMATCH DETECTED!
|
|
391
|
+
|
|
392
|
+
Your device shows: ${contactKeyFingerprint}
|
|
393
|
+
Contact reports: ${contactReportedFingerprint}
|
|
394
|
+
|
|
395
|
+
This is evidence that Apple (or another party) has substituted encryption keys.
|
|
396
|
+
Your messages to this contact may be readable by Apple despite their E2E claims.
|
|
397
|
+
|
|
398
|
+
This evidence has been recorded and can be exported for legal proceedings.`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
case 'list': {
|
|
403
|
+
const contactUserId = args['contactUserId'];
|
|
404
|
+
if (!contactUserId) {
|
|
405
|
+
return JSON.stringify({ success: false, error: 'contactUserId required' });
|
|
406
|
+
}
|
|
407
|
+
// Access stored verifications through engine
|
|
408
|
+
// Note: Would need to add getter method to engine
|
|
409
|
+
return JSON.stringify({
|
|
410
|
+
success: true,
|
|
411
|
+
operation: 'list',
|
|
412
|
+
contactUserId,
|
|
413
|
+
message: 'Use iMessageEvidenceExport to see all verification records',
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
case 'fingerprint': {
|
|
417
|
+
const publicKeyData = args['publicKeyData'];
|
|
418
|
+
if (!publicKeyData) {
|
|
419
|
+
return JSON.stringify({ success: false, error: 'publicKeyData required' });
|
|
420
|
+
}
|
|
421
|
+
const fingerprint = engine.generateKeyFingerprint(publicKeyData);
|
|
422
|
+
return JSON.stringify({
|
|
423
|
+
success: true,
|
|
424
|
+
operation: 'fingerprint',
|
|
425
|
+
fingerprint,
|
|
426
|
+
format: 'Groups of 5 digits for easy verbal comparison',
|
|
427
|
+
usage: 'Compare this fingerprint with your contact via trusted channel',
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
case 'mismatches': {
|
|
431
|
+
const evidenceRecords = engine.getEvidenceRecords();
|
|
432
|
+
const mitmEvidence = evidenceRecords.filter(e => e.evidenceType === 'mitm_detected');
|
|
433
|
+
return JSON.stringify({
|
|
434
|
+
success: true,
|
|
435
|
+
operation: 'mismatches',
|
|
436
|
+
mismatchCount: mitmEvidence.length,
|
|
437
|
+
mismatches: mitmEvidence.map(e => ({
|
|
438
|
+
id: e.id,
|
|
439
|
+
timestamp: e.timestamp,
|
|
440
|
+
summary: e.summary,
|
|
441
|
+
severity: e.severity,
|
|
442
|
+
hash: e.hash,
|
|
443
|
+
})),
|
|
444
|
+
implications: mitmEvidence.length > 0
|
|
445
|
+
? 'One or more key mismatches detected. This is strong evidence of key substitution by Apple or another party with access to IDS.'
|
|
446
|
+
: 'No key mismatches detected in verification history.',
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
default:
|
|
450
|
+
return JSON.stringify({ success: false, error: `Unknown operation: ${operation}` });
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
455
|
+
// Key Transparency Audit
|
|
456
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
457
|
+
{
|
|
458
|
+
name: 'iMessageKTAudit',
|
|
459
|
+
description: `Audit Apple's Key Transparency (KT) log.
|
|
460
|
+
|
|
461
|
+
Apple's KT system is supposed to make key substitution detectable by:
|
|
462
|
+
- Publishing all keys to a verifiable append-only log
|
|
463
|
+
- Providing inclusion proofs that keys are in the log
|
|
464
|
+
- Providing consistency proofs that the log hasn't been forked
|
|
465
|
+
|
|
466
|
+
PROBLEM: Apple has NO public third-party auditors.
|
|
467
|
+
They do internal auditing only, which defeats the purpose.
|
|
468
|
+
|
|
469
|
+
This tool allows YOU to be the auditor:
|
|
470
|
+
1. Query the KT service for a user's keys
|
|
471
|
+
2. Verify the inclusion proof
|
|
472
|
+
3. Compare KT keys to keys you observed from IDS
|
|
473
|
+
4. Detect split-view attacks (different keys shown to different users)
|
|
474
|
+
|
|
475
|
+
Operations:
|
|
476
|
+
- audit: Perform a KT audit for a user
|
|
477
|
+
- compare: Compare KT response to local observation
|
|
478
|
+
- history: Show KT audit history
|
|
479
|
+
- inconsistencies: List all detected KT inconsistencies`,
|
|
480
|
+
parameters: {
|
|
481
|
+
type: 'object',
|
|
482
|
+
required: ['operation'],
|
|
483
|
+
properties: {
|
|
484
|
+
operation: {
|
|
485
|
+
type: 'string',
|
|
486
|
+
enum: ['audit', 'compare', 'history', 'inconsistencies'],
|
|
487
|
+
description: 'Operation to perform',
|
|
488
|
+
},
|
|
489
|
+
userId: {
|
|
490
|
+
type: 'string',
|
|
491
|
+
description: 'User identifier',
|
|
492
|
+
},
|
|
493
|
+
ktResponse: {
|
|
494
|
+
type: 'object',
|
|
495
|
+
properties: {
|
|
496
|
+
logPosition: { type: 'number' },
|
|
497
|
+
epoch: { type: 'number' },
|
|
498
|
+
inclusionProof: { type: 'string' },
|
|
499
|
+
consistencyProof: { type: 'string' },
|
|
500
|
+
vrfOutput: { type: 'string' },
|
|
501
|
+
keyData: { type: 'string' },
|
|
502
|
+
},
|
|
503
|
+
description: 'Captured KT service response',
|
|
504
|
+
},
|
|
505
|
+
localObservationId: {
|
|
506
|
+
type: 'string',
|
|
507
|
+
description: 'ID of local key observation to compare',
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
handler: async (args) => {
|
|
512
|
+
const operation = args['operation'];
|
|
513
|
+
const userId = args['userId'];
|
|
514
|
+
const engine = await getEngine();
|
|
515
|
+
switch (operation) {
|
|
516
|
+
case 'audit': {
|
|
517
|
+
if (!userId) {
|
|
518
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
519
|
+
}
|
|
520
|
+
const ktResponse = args['ktResponse'];
|
|
521
|
+
if (!ktResponse) {
|
|
522
|
+
return JSON.stringify({
|
|
523
|
+
success: false,
|
|
524
|
+
error: 'ktResponse required',
|
|
525
|
+
hint: `To capture KT responses, intercept traffic to ${APPLE_INFRASTRUCTURE.kt_servers.join(', ')}`,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
// Get latest local observation
|
|
529
|
+
const observations = engine.getKeyObservations(userId);
|
|
530
|
+
const latestObs = observations[observations.length - 1];
|
|
531
|
+
if (!latestObs) {
|
|
532
|
+
return JSON.stringify({
|
|
533
|
+
success: false,
|
|
534
|
+
error: 'No local key observations for this user. Record key observations first with iMessageKeyMonitor.',
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
const audit = await engine.auditKeyTransparency({
|
|
538
|
+
userId,
|
|
539
|
+
ktResponse,
|
|
540
|
+
localKeyObservation: latestObs,
|
|
541
|
+
});
|
|
542
|
+
return JSON.stringify({
|
|
543
|
+
success: true,
|
|
544
|
+
operation: 'audit',
|
|
545
|
+
audit: {
|
|
546
|
+
id: audit.id,
|
|
547
|
+
timestamp: audit.timestamp,
|
|
548
|
+
userId: audit.userId,
|
|
549
|
+
ktLogPosition: audit.ktLogPosition,
|
|
550
|
+
ktEpoch: audit.ktEpoch,
|
|
551
|
+
verificationResult: audit.verificationResult,
|
|
552
|
+
localKeyHash: audit.localKeyHash,
|
|
553
|
+
ktKeyHash: audit.ktKeyHash,
|
|
554
|
+
match: audit.match,
|
|
555
|
+
hash: audit.hash,
|
|
556
|
+
},
|
|
557
|
+
message: audit.match && audit.verificationResult.errors.length === 0
|
|
558
|
+
? '✅ KT audit passed: Keys match and proofs verify.'
|
|
559
|
+
: `⚠️ KT AUDIT ISSUES: ${audit.verificationResult.errors.join('; ')}`,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
case 'history': {
|
|
563
|
+
if (!userId) {
|
|
564
|
+
return JSON.stringify({ success: false, error: 'userId required' });
|
|
565
|
+
}
|
|
566
|
+
// Would need to expose ktAudits from engine
|
|
567
|
+
return JSON.stringify({
|
|
568
|
+
success: true,
|
|
569
|
+
operation: 'history',
|
|
570
|
+
userId,
|
|
571
|
+
message: 'Use iMessageEvidenceExport to see full audit history',
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
case 'inconsistencies': {
|
|
575
|
+
const evidenceRecords = engine.getEvidenceRecords();
|
|
576
|
+
const ktEvidence = evidenceRecords.filter(e => e.evidenceType === 'kt_inconsistency');
|
|
577
|
+
return JSON.stringify({
|
|
578
|
+
success: true,
|
|
579
|
+
operation: 'inconsistencies',
|
|
580
|
+
inconsistencyCount: ktEvidence.length,
|
|
581
|
+
inconsistencies: ktEvidence.map(e => ({
|
|
582
|
+
id: e.id,
|
|
583
|
+
timestamp: e.timestamp,
|
|
584
|
+
summary: e.summary,
|
|
585
|
+
severity: e.severity,
|
|
586
|
+
hash: e.hash,
|
|
587
|
+
})),
|
|
588
|
+
implications: ktEvidence.length > 0
|
|
589
|
+
? 'KT inconsistencies detected. Apple may be serving different keys to different clients (split-view attack) or the KT log has been tampered with.'
|
|
590
|
+
: 'No KT inconsistencies detected.',
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
default:
|
|
594
|
+
return JSON.stringify({ success: false, error: `Unknown operation: ${operation}` });
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
599
|
+
// Traffic Analysis
|
|
600
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
601
|
+
{
|
|
602
|
+
name: 'iMessageTrafficAnalyze',
|
|
603
|
+
description: `Analyze iMessage network traffic for anomalies.
|
|
604
|
+
|
|
605
|
+
By capturing and analyzing network traffic during iMessage operations,
|
|
606
|
+
we can detect:
|
|
607
|
+
|
|
608
|
+
- Messages routed through unexpected servers
|
|
609
|
+
- Extra network round-trips indicating MITM proxy
|
|
610
|
+
- Timing anomalies suggesting interception
|
|
611
|
+
- Traffic to non-Apple infrastructure
|
|
612
|
+
|
|
613
|
+
This provides additional evidence beyond key verification.
|
|
614
|
+
|
|
615
|
+
Usage:
|
|
616
|
+
1. Capture traffic with Wireshark, tcpdump, or similar
|
|
617
|
+
2. Export packet summaries
|
|
618
|
+
3. Analyze with this tool
|
|
619
|
+
|
|
620
|
+
Operations:
|
|
621
|
+
- analyze: Analyze a set of captured packets
|
|
622
|
+
- expectedHosts: List expected Apple hosts for reference
|
|
623
|
+
- anomalies: List all detected anomalies across analyses`,
|
|
624
|
+
parameters: {
|
|
625
|
+
type: 'object',
|
|
626
|
+
required: ['operation'],
|
|
627
|
+
properties: {
|
|
628
|
+
operation: {
|
|
629
|
+
type: 'string',
|
|
630
|
+
enum: ['analyze', 'expectedHosts', 'anomalies'],
|
|
631
|
+
description: 'Operation to perform',
|
|
632
|
+
},
|
|
633
|
+
sessionId: {
|
|
634
|
+
type: 'string',
|
|
635
|
+
description: 'Unique identifier for this capture session',
|
|
636
|
+
},
|
|
637
|
+
analysisType: {
|
|
638
|
+
type: 'string',
|
|
639
|
+
enum: ['message_routing', 'key_fetch', 'kt_query', 'full_session'],
|
|
640
|
+
description: 'Type of traffic being analyzed',
|
|
641
|
+
},
|
|
642
|
+
packets: {
|
|
643
|
+
type: 'array',
|
|
644
|
+
items: {
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: {
|
|
647
|
+
timestamp: { type: 'string' },
|
|
648
|
+
direction: { type: 'string', enum: ['outbound', 'inbound'] },
|
|
649
|
+
protocol: { type: 'string' },
|
|
650
|
+
sourceIp: { type: 'string' },
|
|
651
|
+
destIp: { type: 'string' },
|
|
652
|
+
destPort: { type: 'number' },
|
|
653
|
+
payloadSize: { type: 'number' },
|
|
654
|
+
payloadHash: { type: 'string' },
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
description: 'Array of packet summaries',
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
handler: async (args) => {
|
|
662
|
+
const operation = args['operation'];
|
|
663
|
+
const engine = await getEngine();
|
|
664
|
+
switch (operation) {
|
|
665
|
+
case 'analyze': {
|
|
666
|
+
const sessionId = args['sessionId'] || crypto.randomUUID();
|
|
667
|
+
const analysisType = args['analysisType'] || 'full_session';
|
|
668
|
+
const packets = args['packets'];
|
|
669
|
+
if (!packets || packets.length === 0) {
|
|
670
|
+
return JSON.stringify({
|
|
671
|
+
success: false,
|
|
672
|
+
error: 'packets array required',
|
|
673
|
+
hint: 'Capture packets with Wireshark/tcpdump and export as JSON',
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
const analysis = await engine.analyzeTraffic({
|
|
677
|
+
sessionId,
|
|
678
|
+
packets,
|
|
679
|
+
analysisType,
|
|
680
|
+
});
|
|
681
|
+
return JSON.stringify({
|
|
682
|
+
success: true,
|
|
683
|
+
operation: 'analyze',
|
|
684
|
+
analysis: {
|
|
685
|
+
id: analysis.id,
|
|
686
|
+
timestamp: analysis.timestamp,
|
|
687
|
+
sessionId: analysis.sessionId,
|
|
688
|
+
analysisType: analysis.analysisType,
|
|
689
|
+
summary: analysis.summary,
|
|
690
|
+
anomalyCount: analysis.anomalies.length,
|
|
691
|
+
anomalies: analysis.anomalies,
|
|
692
|
+
hash: analysis.hash,
|
|
693
|
+
},
|
|
694
|
+
message: analysis.anomalies.length > 0
|
|
695
|
+
? `⚠️ ${analysis.anomalies.length} anomalies detected in traffic analysis`
|
|
696
|
+
: '✅ No anomalies detected in traffic analysis',
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
case 'expectedHosts': {
|
|
700
|
+
return JSON.stringify({
|
|
701
|
+
success: true,
|
|
702
|
+
operation: 'expectedHosts',
|
|
703
|
+
expectedInfrastructure: APPLE_INFRASTRUCTURE,
|
|
704
|
+
note: 'iMessage traffic should only go to these hosts. Traffic to other destinations is suspicious.',
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
case 'anomalies': {
|
|
708
|
+
const evidenceRecords = engine.getEvidenceRecords();
|
|
709
|
+
const trafficEvidence = evidenceRecords.filter(e => e.evidenceType === 'mitm_detected' &&
|
|
710
|
+
e.technicalDetails.observedBehavior.includes('traffic'));
|
|
711
|
+
return JSON.stringify({
|
|
712
|
+
success: true,
|
|
713
|
+
operation: 'anomalies',
|
|
714
|
+
trafficAnomalyEvidence: trafficEvidence.map(e => ({
|
|
715
|
+
id: e.id,
|
|
716
|
+
timestamp: e.timestamp,
|
|
717
|
+
summary: e.summary,
|
|
718
|
+
severity: e.severity,
|
|
719
|
+
})),
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
default:
|
|
723
|
+
return JSON.stringify({ success: false, error: `Unknown operation: ${operation}` });
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
728
|
+
// Evidence Export
|
|
729
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
730
|
+
{
|
|
731
|
+
name: 'iMessageEvidenceExport',
|
|
732
|
+
description: `Export iMessage verification evidence for legal proceedings.
|
|
733
|
+
|
|
734
|
+
Generates a comprehensive evidence package including:
|
|
735
|
+
- All key observations with hashes
|
|
736
|
+
- Out-of-band verification records
|
|
737
|
+
- KT audit results
|
|
738
|
+
- Traffic analysis reports
|
|
739
|
+
- Dishonesty evidence records
|
|
740
|
+
- Apple's claims vs. observed behavior analysis
|
|
741
|
+
- Legal summary document
|
|
742
|
+
|
|
743
|
+
The exported evidence is cryptographically hashed for court admissibility.
|
|
744
|
+
|
|
745
|
+
Operations:
|
|
746
|
+
- export: Export all evidence to a directory
|
|
747
|
+
- report: Generate summary report
|
|
748
|
+
- claims: Analyze Apple's claims vs. reality
|
|
749
|
+
- evidence: List all dishonesty evidence records`,
|
|
750
|
+
parameters: {
|
|
751
|
+
type: 'object',
|
|
752
|
+
required: ['operation'],
|
|
753
|
+
properties: {
|
|
754
|
+
operation: {
|
|
755
|
+
type: 'string',
|
|
756
|
+
enum: ['export', 'report', 'claims', 'evidence'],
|
|
757
|
+
description: 'Operation to perform',
|
|
758
|
+
},
|
|
759
|
+
outputDir: {
|
|
760
|
+
type: 'string',
|
|
761
|
+
description: 'Directory to export evidence to',
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
handler: async (args) => {
|
|
766
|
+
const operation = args['operation'];
|
|
767
|
+
const engine = await getEngine();
|
|
768
|
+
switch (operation) {
|
|
769
|
+
case 'export': {
|
|
770
|
+
const outputDir = args['outputDir'] || path.join(process.cwd(), 'imessage-evidence');
|
|
771
|
+
const exportPath = await engine.exportForLitigation(outputDir);
|
|
772
|
+
return JSON.stringify({
|
|
773
|
+
success: true,
|
|
774
|
+
operation: 'export',
|
|
775
|
+
exportPath,
|
|
776
|
+
message: `Evidence exported to ${exportPath}. Contains all verification data, evidence records, and legal summary.`,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
case 'report': {
|
|
780
|
+
const report = await engine.generateVerificationReport();
|
|
781
|
+
return JSON.stringify({
|
|
782
|
+
success: true,
|
|
783
|
+
operation: 'report',
|
|
784
|
+
report,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
case 'claims': {
|
|
788
|
+
return JSON.stringify({
|
|
789
|
+
success: true,
|
|
790
|
+
operation: 'claims',
|
|
791
|
+
applesClaims: Object.entries(APPLE_PQ3_CLAIMS).map(([key, data]) => ({
|
|
792
|
+
claim: key,
|
|
793
|
+
statement: data.claim,
|
|
794
|
+
source: data.source,
|
|
795
|
+
verifiable: data.verifiable,
|
|
796
|
+
reason: data.reason,
|
|
797
|
+
})),
|
|
798
|
+
summary: `Apple makes ${Object.keys(APPLE_PQ3_CLAIMS).length} key security claims about iMessage PQ3. Of these:
|
|
799
|
+
- ${Object.values(APPLE_PQ3_CLAIMS).filter(c => c.verifiable === true).length} are fully verifiable
|
|
800
|
+
- ${Object.values(APPLE_PQ3_CLAIMS).filter(c => c.verifiable === 'partial').length} are partially verifiable
|
|
801
|
+
- ${Object.values(APPLE_PQ3_CLAIMS).filter(c => c.verifiable === false).length} are NOT independently verifiable
|
|
802
|
+
|
|
803
|
+
The unverifiable claims require trusting Apple, which is the entity with the capability to violate them.`,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
case 'evidence': {
|
|
807
|
+
const evidenceRecords = engine.getEvidenceRecords();
|
|
808
|
+
return JSON.stringify({
|
|
809
|
+
success: true,
|
|
810
|
+
operation: 'evidence',
|
|
811
|
+
totalRecords: evidenceRecords.length,
|
|
812
|
+
bySeverity: {
|
|
813
|
+
irrefutable: evidenceRecords.filter(e => e.severity === 'irrefutable').length,
|
|
814
|
+
confirmed: evidenceRecords.filter(e => e.severity === 'confirmed').length,
|
|
815
|
+
probable: evidenceRecords.filter(e => e.severity === 'probable').length,
|
|
816
|
+
},
|
|
817
|
+
byType: {
|
|
818
|
+
key_substitution: evidenceRecords.filter(e => e.evidenceType === 'key_substitution').length,
|
|
819
|
+
message_injection: evidenceRecords.filter(e => e.evidenceType === 'message_injection').length,
|
|
820
|
+
mitm_detected: evidenceRecords.filter(e => e.evidenceType === 'mitm_detected').length,
|
|
821
|
+
kt_inconsistency: evidenceRecords.filter(e => e.evidenceType === 'kt_inconsistency').length,
|
|
822
|
+
false_e2e_claim: evidenceRecords.filter(e => e.evidenceType === 'false_e2e_claim').length,
|
|
823
|
+
},
|
|
824
|
+
records: evidenceRecords.map(e => ({
|
|
825
|
+
id: e.id,
|
|
826
|
+
timestamp: e.timestamp,
|
|
827
|
+
evidenceType: e.evidenceType,
|
|
828
|
+
severity: e.severity,
|
|
829
|
+
summary: e.summary,
|
|
830
|
+
hash: e.hash,
|
|
831
|
+
})),
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
default:
|
|
835
|
+
return JSON.stringify({ success: false, error: `Unknown operation: ${operation}` });
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
//# sourceMappingURL=iMessageVerificationTools.js.map
|