nodejs-insta-private-api-mqt 1.3.81 → 1.3.83
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/repositories/account.repository.js +249 -142
- package/package.json +1 -1
|
@@ -45,55 +45,11 @@ class AccountRepository extends Repository {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
const headerCandidates = [
|
|
50
|
-
headers['ig-set-authorization'],
|
|
51
|
-
headers['ig-set-authorization'.toLowerCase()],
|
|
52
|
-
headers['ig-set-authorization'.toUpperCase()],
|
|
53
|
-
headers['ig-set-authorization'],
|
|
54
|
-
headers['ig-set-authorization'],
|
|
55
|
-
headers['ig-set-authorization'],
|
|
56
|
-
headers['ig-set-authorization'],
|
|
57
|
-
headers['ig-set-authorization'],
|
|
58
|
-
headers['ig-set-authorization'],
|
|
59
|
-
headers['ig-set-authorization'],
|
|
60
|
-
headers['ig-set-authorization'],
|
|
61
|
-
headers['ig-set-authorization'],
|
|
62
|
-
headers['ig-set-authorization'],
|
|
63
|
-
headers['ig-set-authorization'],
|
|
64
|
-
headers['ig-set-authorization'],
|
|
65
|
-
headers['ig-set-authorization'],
|
|
66
|
-
headers['ig-set-authorization'],
|
|
67
|
-
headers['ig-set-authorization'],
|
|
68
|
-
headers['ig-set-authorization'],
|
|
69
|
-
headers['ig-set-authorization'],
|
|
70
|
-
headers['ig-set-authorization'],
|
|
71
|
-
headers['ig-set-authorization'],
|
|
72
|
-
headers['ig-set-authorization'],
|
|
73
|
-
headers['ig-set-authorization'],
|
|
74
|
-
headers['ig-set-authorization'],
|
|
75
|
-
headers['ig-set-authorization'],
|
|
76
|
-
headers['ig-set-authorization'],
|
|
77
|
-
headers['ig-set-authorization'],
|
|
78
|
-
headers['ig-set-authorization'],
|
|
79
|
-
headers['ig-set-authorization'],
|
|
80
|
-
headers['ig-set-authorization'],
|
|
81
|
-
headers['ig-set-authorization'],
|
|
82
|
-
headers['ig-set-authorization'],
|
|
83
|
-
headers['ig-set-authorization'],
|
|
84
|
-
headers['ig-set-authorization'],
|
|
85
|
-
headers['ig-set-authorization'],
|
|
86
|
-
headers['ig-set-authorization'],
|
|
87
|
-
headers['ig-set-authorization'],
|
|
88
|
-
headers['ig-set-authorization'],
|
|
89
|
-
headers['ig-set-authorization'],
|
|
90
|
-
]; // (we'll just check the normalized header below)
|
|
91
|
-
|
|
92
|
-
// Simpler: look for any header that contains 'IG-Set-Authorization' or 'ig-set-authorization'
|
|
48
|
+
// Simpler: look for any header that contains ig-set-authorization
|
|
93
49
|
for (const key of Object.keys(headers)) {
|
|
94
50
|
const val = headers[key];
|
|
95
51
|
if (!val) continue;
|
|
96
|
-
if (key.includes('ig-set-authorization')
|
|
52
|
+
if (key.includes('ig-set-authorization')) {
|
|
97
53
|
const token = this._normalizeTokenString(val);
|
|
98
54
|
if (token) {
|
|
99
55
|
this.client.state.authorization = token;
|
|
@@ -297,7 +253,7 @@ class AccountRepository extends Repository {
|
|
|
297
253
|
const bearerPatterns = [
|
|
298
254
|
/Bearer IGT:2:[A-Za-z0-9+\/=]+/,
|
|
299
255
|
/Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
|
|
300
|
-
|
|
256
|
+
/\?"Bearer IGT:2:([A-Za-z0-9+\/=]+)\?"/,
|
|
301
257
|
/ig-set-authorization[\\"\s:]*Bearer IGT:2:([A-Za-z0-9+\/=]+)/i,
|
|
302
258
|
/"IG-Set-Authorization"\s*:\s*"(Bearer IGT:2:[A-Za-z0-9+\/=]+)"/i,
|
|
303
259
|
/IG-Set-Authorization[\s:]+(Bearer IGT:2:[A-Za-z0-9+\/=]+)/i,
|
|
@@ -310,10 +266,10 @@ class AccountRepository extends Repository {
|
|
|
310
266
|
// If capturing group present, prefer it
|
|
311
267
|
if (m[1]) {
|
|
312
268
|
const token = m[1].includes('Bearer IGT:2:') ? m[1] : `Bearer IGT:2:${m[1]}`;
|
|
313
|
-
return token.replace(
|
|
269
|
+
return token.replace(/\?"/g, '').trim();
|
|
314
270
|
}
|
|
315
271
|
// Otherwise m[0] contains the full token
|
|
316
|
-
return m[0].replace(
|
|
272
|
+
return m[0].replace(/\?"/g, '').trim();
|
|
317
273
|
}
|
|
318
274
|
}
|
|
319
275
|
|
|
@@ -347,7 +303,6 @@ class AccountRepository extends Repository {
|
|
|
347
303
|
return null;
|
|
348
304
|
}
|
|
349
305
|
|
|
350
|
-
|
|
351
306
|
async login(credentialsOrUsername, passwordArg) {
|
|
352
307
|
let username, password;
|
|
353
308
|
if (typeof credentialsOrUsername === 'object' && credentialsOrUsername !== null) {
|
|
@@ -367,18 +322,53 @@ class AccountRepository extends Repository {
|
|
|
367
322
|
|
|
368
323
|
const { encrypted, time } = this.encryptPassword(password);
|
|
369
324
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
325
|
+
// optional CSRF warmup (like app does before login)
|
|
326
|
+
await this.ensureCsrfToken();
|
|
327
|
+
|
|
328
|
+
return this.requestWithRetry(async () => {
|
|
329
|
+
// ====== client_input_params (oglindă după traficul real) ======
|
|
330
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
331
|
+
const aacInitTimestamp = nowSec - Math.floor(Math.random() * 50);
|
|
374
332
|
const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
|
|
375
|
-
const aaccs = crypto.randomBytes(32).toString('
|
|
333
|
+
const aaccs = crypto.randomBytes(32).toString('base64').replace(/=/g, '');
|
|
334
|
+
|
|
335
|
+
const androidDeviceId =
|
|
336
|
+
this.client.state.androidDeviceId ||
|
|
337
|
+
this.client.state.deviceId ||
|
|
338
|
+
`android-${crypto.randomBytes(8).toString('hex')}`;
|
|
339
|
+
|
|
340
|
+
const machineId =
|
|
341
|
+
this.client.state.mid ||
|
|
342
|
+
this.client.state.machineId ||
|
|
343
|
+
`aZ${crypto.randomBytes(8).toString('hex')}`;
|
|
344
|
+
|
|
345
|
+
const familyDeviceId =
|
|
346
|
+
this.client.state.phoneId ||
|
|
347
|
+
this.client.state.familyDeviceId ||
|
|
348
|
+
crypto.randomUUID
|
|
349
|
+
? crypto.randomUUID()
|
|
350
|
+
: require('uuid').v4();
|
|
351
|
+
|
|
352
|
+
const qeDeviceId =
|
|
353
|
+
this.client.state.deviceId ||
|
|
354
|
+
this.client.state.qeDeviceId ||
|
|
355
|
+
crypto.randomUUID
|
|
356
|
+
? crypto.randomUUID()
|
|
357
|
+
: require('uuid').v4();
|
|
358
|
+
|
|
359
|
+
const accountsList = this.client.state.accountsList || [];
|
|
360
|
+
|
|
361
|
+
const flashCallPermissionStatus = {
|
|
362
|
+
READ_PHONE_STATE: 'GRANTED',
|
|
363
|
+
READ_CALL_LOG: 'GRANTED',
|
|
364
|
+
ANSWER_PHONE_CALLS: 'GRANTED',
|
|
365
|
+
};
|
|
376
366
|
|
|
377
367
|
const clientInputParams = {
|
|
378
368
|
aac: JSON.stringify({
|
|
379
369
|
aac_init_timestamp: aacInitTimestamp,
|
|
380
|
-
aacjid
|
|
381
|
-
aaccs
|
|
370
|
+
aacjid,
|
|
371
|
+
aaccs,
|
|
382
372
|
}),
|
|
383
373
|
sim_phones: [],
|
|
384
374
|
aymh_accounts: [],
|
|
@@ -388,25 +378,77 @@ return this.requestWithRetry(async () => {
|
|
|
388
378
|
auth_secure_device_id: '',
|
|
389
379
|
has_whatsapp_installed: 1,
|
|
390
380
|
password: `#PWD_INSTAGRAM:4:${time}:${encrypted}`,
|
|
391
|
-
sso_token_map_json_string:
|
|
381
|
+
sso_token_map_json_string: JSON.stringify({
|
|
382
|
+
[this.client.state.cookieUserId || '0']: [],
|
|
383
|
+
}),
|
|
392
384
|
block_store_machine_id: '',
|
|
393
|
-
ig_vetted_device_nonces:
|
|
385
|
+
ig_vetted_device_nonces: JSON.stringify({}),
|
|
386
|
+
cloud_trust_token: null,
|
|
387
|
+
event_flow: 'login_manual',
|
|
388
|
+
password_contains_non_ascii: 'false',
|
|
389
|
+
client_known_key_hash: '',
|
|
390
|
+
encrypted_msisdn: '',
|
|
391
|
+
has_granted_read_phone_permissions: 0,
|
|
392
|
+
app_manager_id: '',
|
|
393
|
+
should_show_nested_nta_from_aymh: 0,
|
|
394
|
+
device_id: androidDeviceId,
|
|
395
|
+
zero_balance_state: '',
|
|
396
|
+
login_attempt_count: 1,
|
|
397
|
+
machine_id: machineId,
|
|
398
|
+
flash_call_permission_status: flashCallPermissionStatus,
|
|
399
|
+
accounts_list: accountsList,
|
|
400
|
+
gms_incoming_call_retriever_eligibility: 'client_not_supported',
|
|
401
|
+
family_device_id: familyDeviceId,
|
|
402
|
+
fb_ig_device_id: [],
|
|
403
|
+
device_emails: [],
|
|
404
|
+
try_num: 1,
|
|
405
|
+
lois_settings: { lois_token: '' },
|
|
406
|
+
event_step: 'home_page',
|
|
407
|
+
headers_infra_flow_id: '',
|
|
408
|
+
openid_tokens: {},
|
|
394
409
|
contact_point: username,
|
|
395
|
-
machine_id: this.client.state.mid || '',
|
|
396
|
-
login_attempt_count: '0',
|
|
397
|
-
reg_flow_taken: 'phone',
|
|
398
|
-
device_id: this.client.state.uuid,
|
|
399
|
-
phone_id: this.client.state.phoneId,
|
|
400
|
-
family_device_id: this.client.state.phoneId,
|
|
401
|
-
encryption_enabled: '1',
|
|
402
|
-
has_dbl_tap_login: 0,
|
|
403
|
-
jazoest: AccountRepository.createJazoest(this.client.state.phoneId),
|
|
404
|
-
openid_tokens: '{}',
|
|
405
410
|
};
|
|
406
411
|
|
|
412
|
+
// ====== server_params (oglindă după traficul real) ======
|
|
413
|
+
const waterfallId = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
|
|
414
|
+
const latencyMarkerId = 36707139;
|
|
415
|
+
const latencyInstanceId = Number(`${Date.now()}${Math.floor(Math.random() * 1000)}`);
|
|
416
|
+
|
|
407
417
|
const serverParams = {
|
|
418
|
+
should_trigger_override_login_2fa_action: 0,
|
|
419
|
+
is_vanilla_password_page_empty_password: 0,
|
|
420
|
+
is_from_logged_out: 0,
|
|
421
|
+
should_trigger_override_login_success_action: 0,
|
|
422
|
+
login_credential_type: 'none',
|
|
423
|
+
server_login_source: 'login',
|
|
424
|
+
waterfall_id: waterfallId,
|
|
425
|
+
two_step_login_type: 'one_step_login',
|
|
426
|
+
login_source: 'Login',
|
|
427
|
+
is_platform_login: 0,
|
|
428
|
+
INTERNAL__latency_qpl_marker_id: latencyMarkerId,
|
|
429
|
+
is_from_aymh: 0,
|
|
430
|
+
offline_experiment_group: 'caa_iteration_v3_perf_ig_4',
|
|
431
|
+
is_from_landing_page: 0,
|
|
432
|
+
left_nav_button_action: 'NONE',
|
|
433
|
+
password_text_input_id: 'z0jejq:194',
|
|
434
|
+
is_from_empty_password: 0,
|
|
435
|
+
is_from_msplit_fallback: 0,
|
|
436
|
+
ar_event_source: 'login_home_page',
|
|
437
|
+
qe_device_id: qeDeviceId,
|
|
438
|
+
username_text_input_id: 'z0jejq:193',
|
|
439
|
+
layered_homepage_experiment_group: null,
|
|
440
|
+
device_id: androidDeviceId,
|
|
441
|
+
login_surface: 'switcher',
|
|
442
|
+
INTERNAL__latency_qpl_instance_id: latencyInstanceId,
|
|
443
|
+
reg_flow_source: 'login_home_native_integration_point',
|
|
444
|
+
is_caa_perf_enabled: 1,
|
|
408
445
|
credential_type: 'password',
|
|
409
|
-
|
|
446
|
+
is_from_password_entry_page: 0,
|
|
447
|
+
caller: 'gslr',
|
|
448
|
+
family_device_id: familyDeviceId,
|
|
449
|
+
is_from_assistive_id: 0,
|
|
450
|
+
access_flow_version: 'pre_mt_behavior',
|
|
451
|
+
is_from_logged_in_switcher: 1,
|
|
410
452
|
};
|
|
411
453
|
|
|
412
454
|
const paramsJson = JSON.stringify({
|
|
@@ -416,75 +458,102 @@ return this.requestWithRetry(async () => {
|
|
|
416
458
|
|
|
417
459
|
const attestParams = AccountRepository.generateAttestParams(this.client.state);
|
|
418
460
|
|
|
461
|
+
const bkClientContext = JSON.stringify({
|
|
462
|
+
bloks_version: this.client.state.bloksVersionId,
|
|
463
|
+
styles_id: 'instagram',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ====== HEADERS – modelate după request_i.instagram.com_mlwzj8x1.txt ======
|
|
467
|
+
const lang = (this.client.state.language || 'ro_RO');
|
|
468
|
+
const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
|
|
469
|
+
|
|
470
|
+
const userAgent =
|
|
471
|
+
this.client.state.userAgent ||
|
|
472
|
+
'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
|
|
473
|
+
|
|
419
474
|
const bloksHeaders = {
|
|
420
|
-
|
|
421
|
-
'
|
|
422
|
-
'
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
'
|
|
427
|
-
'
|
|
428
|
-
'
|
|
429
|
-
'
|
|
430
|
-
'
|
|
431
|
-
'
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
'
|
|
435
|
-
'
|
|
436
|
-
'
|
|
437
|
-
'
|
|
438
|
-
'X-FB-Request-Analytics-Tags': JSON.stringify({
|
|
475
|
+
'accept-language': acceptLanguage,
|
|
476
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
477
|
+
'ig-intended-user-id': '0',
|
|
478
|
+
'priority': 'u=3',
|
|
479
|
+
|
|
480
|
+
'x-bloks-is-layout-rtl': 'false',
|
|
481
|
+
'x-bloks-prism-ax-base-colors-enabled': 'false',
|
|
482
|
+
'x-bloks-prism-button-version': 'CONTROL',
|
|
483
|
+
'x-bloks-prism-colors-enabled': 'true',
|
|
484
|
+
'x-bloks-prism-font-enabled': 'false',
|
|
485
|
+
'x-bloks-prism-indigo-link-version': '0',
|
|
486
|
+
'x-bloks-version-id': this.client.state.bloksVersionId,
|
|
487
|
+
|
|
488
|
+
'x-fb-client-ip': 'True',
|
|
489
|
+
'x-fb-connection-type': 'WIFI',
|
|
490
|
+
'x-fb-friendly-name': 'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
491
|
+
'x-fb-network-properties': 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
492
|
+
'x-fb-request-analytics-tags': JSON.stringify({
|
|
439
493
|
network_tags: {
|
|
440
|
-
product: String(this.client.state.fbAnalyticsApplicationId || ''),
|
|
494
|
+
product: String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
441
495
|
purpose: 'fetch',
|
|
442
496
|
surface: 'undefined',
|
|
443
497
|
request_category: 'api',
|
|
444
498
|
retry_attempt: '0',
|
|
445
499
|
},
|
|
446
500
|
}),
|
|
447
|
-
'
|
|
448
|
-
|
|
449
|
-
'
|
|
450
|
-
'
|
|
451
|
-
|
|
452
|
-
'
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
'
|
|
457
|
-
'
|
|
458
|
-
'
|
|
459
|
-
'
|
|
460
|
-
'
|
|
461
|
-
'
|
|
462
|
-
'
|
|
463
|
-
'
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
'
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
'
|
|
472
|
-
|
|
473
|
-
'
|
|
474
|
-
'
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
'
|
|
478
|
-
|
|
501
|
+
'x-fb-server-cluster': 'True',
|
|
502
|
+
|
|
503
|
+
'x-ig-android-id': androidDeviceId,
|
|
504
|
+
'x-ig-app-id': String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
|
|
505
|
+
'x-ig-app-locale': lang,
|
|
506
|
+
'x-ig-attest-params': JSON.stringify(attestParams),
|
|
507
|
+
'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
|
|
508
|
+
'x-ig-bandwidth-totalbytes-b': '0',
|
|
509
|
+
'x-ig-bandwidth-totaltime-ms': '0',
|
|
510
|
+
'x-ig-client-endpoint': 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
|
|
511
|
+
'x-ig-capabilities': '3brTv10=',
|
|
512
|
+
'x-ig-connection-type': 'WIFI',
|
|
513
|
+
'x-ig-device-id': qeDeviceId,
|
|
514
|
+
'x-ig-device-locale': lang,
|
|
515
|
+
'x-ig-family-device-id': familyDeviceId,
|
|
516
|
+
'x-ig-mapped-locale': lang,
|
|
517
|
+
'x-ig-nav-chain':
|
|
518
|
+
'LockoutFragment:dogfooding_lockout:1:cold_start:...,' +
|
|
519
|
+
'com.bloks.www.caa.login.aymh_single_profile_screen_entry:com.bloks.www.caa.login.aymh_single_profile_screen_entry:14:button:0:::0',
|
|
520
|
+
'x-ig-timezone-offset': String(
|
|
521
|
+
typeof this.client.state.timezoneOffset === 'number'
|
|
522
|
+
? this.client.state.timezoneOffset
|
|
523
|
+
: 7200
|
|
524
|
+
),
|
|
525
|
+
'x-ig-www-claim': this.client.state.igWWWClaim || '0',
|
|
526
|
+
|
|
527
|
+
'x-mid': machineId,
|
|
528
|
+
'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000)
|
|
529
|
+
.toString()
|
|
530
|
+
.padStart(3, '0')}`,
|
|
531
|
+
'x-pigeon-session-id':
|
|
532
|
+
this.client.state.pigeonSessionId ||
|
|
533
|
+
`UFS-${crypto.randomBytes(16).toString('hex')}-1`,
|
|
534
|
+
'x-tigon-is-retry': 'False',
|
|
535
|
+
|
|
536
|
+
// ...
|
|
537
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
538
|
+
// ...
|
|
539
|
+
'user-agent': userAgent,
|
|
540
|
+
'x-fb-conn-uuid-client': crypto.randomBytes(16).toString('hex'),
|
|
541
|
+
'x-fb-http-engine': 'MNS/TCP',
|
|
542
|
+
'x-fb-rmd': 'state=URL_ELIGIBLE',
|
|
479
543
|
};
|
|
480
544
|
|
|
481
545
|
const response = await this.client.request.send({
|
|
482
546
|
method: 'POST',
|
|
483
547
|
url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
484
|
-
form: {
|
|
548
|
+
form: {
|
|
549
|
+
params: paramsJson,
|
|
550
|
+
bk_client_context: bkClientContext,
|
|
551
|
+
bloks_versioning_id: this.client.state.bloksVersionId,
|
|
552
|
+
},
|
|
485
553
|
headers: bloksHeaders,
|
|
486
554
|
});
|
|
487
|
-
|
|
555
|
+
|
|
556
|
+
// === DEBUG LOGIN: salvăm răspunsul pentru analiză ===
|
|
488
557
|
try {
|
|
489
558
|
const fs = require('fs');
|
|
490
559
|
const path = require('path');
|
|
@@ -492,7 +561,6 @@ return this.requestWithRetry(async () => {
|
|
|
492
561
|
const debugDir = path.join(process.cwd(), 'authinfo_instagram');
|
|
493
562
|
const debugFile = path.join(debugDir, 'login-debug.json');
|
|
494
563
|
|
|
495
|
-
// asigură-te că folderul există
|
|
496
564
|
try {
|
|
497
565
|
fs.mkdirSync(debugDir, { recursive: true });
|
|
498
566
|
} catch (e) {}
|
|
@@ -508,12 +576,11 @@ return this.requestWithRetry(async () => {
|
|
|
508
576
|
} catch (e) {
|
|
509
577
|
// nu stricăm login-ul dacă nu merge debug-ul
|
|
510
578
|
}
|
|
511
|
-
// === SFÂRȘIT DEBUG LOGIN ===
|
|
579
|
+
// === SFÂRȘIT DEBUG LOGIN ===
|
|
512
580
|
|
|
513
581
|
const body = response.body;
|
|
514
582
|
|
|
515
583
|
// Immediately attempt to extract and save authorization token from the response
|
|
516
|
-
// This covers tokens returned in headers, in layout payloads, or embedded login_response headers.
|
|
517
584
|
this._extractAndSaveAuthorization(response);
|
|
518
585
|
|
|
519
586
|
if (body && body.two_factor_required) {
|
|
@@ -1071,34 +1138,74 @@ return this.requestWithRetry(async () => {
|
|
|
1071
1138
|
});
|
|
1072
1139
|
}
|
|
1073
1140
|
|
|
1074
|
-
|
|
1141
|
+
static generateAttestParams(state) {
|
|
1142
|
+
// Emulate Instagram's keystore attestation as closely as possible:
|
|
1143
|
+
// - version: 2
|
|
1144
|
+
// - type: "keystore"
|
|
1145
|
+
// - errors: [0]
|
|
1146
|
+
// - challenge_nonce: random base64url
|
|
1147
|
+
// - signed_nonce: ECDSA signature over the nonce
|
|
1148
|
+
// - key_hash: sha256(spki(publicKey))
|
|
1149
|
+
// - certificate_chain: 4 PEM certificates concatenated (leaf + 2 intermediates + root)
|
|
1150
|
+
//
|
|
1151
|
+
// NOTE: This is *not* a real hardware-backed attestation chain, but it mirrors
|
|
1152
|
+
// the structure of the official app very closely so the server sees the same
|
|
1153
|
+
// shape: a single attestation object with a 4‑certificate chain.
|
|
1075
1154
|
const challengeNonce = crypto.randomBytes(24).toString('base64url');
|
|
1076
1155
|
|
|
1077
1156
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
|
|
1078
1157
|
namedCurve: 'prime256v1',
|
|
1079
1158
|
});
|
|
1080
1159
|
|
|
1160
|
+
// Sign the challenge nonce with the private key (simulating TEE signing).
|
|
1081
1161
|
const signedData = crypto.sign(null, Buffer.from(challengeNonce), privateKey);
|
|
1082
1162
|
const signedNonce = signedData.toString('base64');
|
|
1083
1163
|
|
|
1084
1164
|
const publicKeyDer = publicKey.export({ type: 'spki', format: 'der' });
|
|
1085
1165
|
const keyHash = crypto.createHash('sha256').update(publicKeyDer).digest('hex');
|
|
1086
1166
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1167
|
+
// Build a 4‑certificate chain (leaf + 2 intermediates + root), just like
|
|
1168
|
+
// what we saw in the captured traffic from the real Instagram app.
|
|
1169
|
+
const leafCertPem = AccountRepository._generateSelfSignedCert(
|
|
1170
|
+
privateKey,
|
|
1171
|
+
publicKey,
|
|
1172
|
+
'Android Keystore Key'
|
|
1173
|
+
);
|
|
1174
|
+
const intermediate1Pem = AccountRepository._generateSelfSignedCert(
|
|
1175
|
+
privateKey,
|
|
1176
|
+
publicKey,
|
|
1177
|
+
'Android Keystore Key Attestation'
|
|
1178
|
+
);
|
|
1179
|
+
const intermediate2Pem = AccountRepository._generateSelfSignedCert(
|
|
1180
|
+
privateKey,
|
|
1181
|
+
publicKey,
|
|
1182
|
+
'Android Hardware Keystore'
|
|
1183
|
+
);
|
|
1184
|
+
const rootCertPem = AccountRepository._generateSelfSignedCert(
|
|
1185
|
+
privateKey,
|
|
1186
|
+
publicKey,
|
|
1187
|
+
'Android Keystore Root'
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
const certificateChain = [
|
|
1191
|
+
leafCertPem,
|
|
1192
|
+
intermediate1Pem,
|
|
1193
|
+
intermediate2Pem,
|
|
1194
|
+
rootCertPem,
|
|
1195
|
+
].join('\n');
|
|
1091
1196
|
|
|
1092
1197
|
return {
|
|
1093
|
-
attestation: [
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1198
|
+
attestation: [
|
|
1199
|
+
{
|
|
1200
|
+
version: 2,
|
|
1201
|
+
type: 'keystore',
|
|
1202
|
+
errors: [0],
|
|
1203
|
+
challenge_nonce: challengeNonce,
|
|
1204
|
+
signed_nonce: signedNonce,
|
|
1205
|
+
key_hash: keyHash,
|
|
1206
|
+
certificate_chain: certificateChain,
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1102
1209
|
};
|
|
1103
1210
|
}
|
|
1104
1211
|
|
|
@@ -1162,7 +1269,7 @@ return this.requestWithRetry(async () => {
|
|
|
1162
1269
|
}
|
|
1163
1270
|
|
|
1164
1271
|
static createJazoest(input) {
|
|
1165
|
-
const buf = Buffer.from(input, 'ascii');
|
|
1272
|
+
const buf = Buffer.from(input || '', 'ascii');
|
|
1166
1273
|
let sum = 0;
|
|
1167
1274
|
for (let i = 0; i < buf.byteLength; i++) {
|
|
1168
1275
|
sum += buf.readUInt8(i);
|
package/package.json
CHANGED