feat: app

This commit is contained in:
hashbro
2026-10-10 02:12:14 +08:00
parent 4f5764a2cd
commit 0ee7749262
3 changed files with 90 additions and 29 deletions
+86 -2
View File
@@ -11,6 +11,7 @@ use App\Models\WalletMnemonic;
use App\Services\EthKeystore;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use kornrunner\Keccak;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@@ -89,8 +90,7 @@ class KeystoreAdminTest extends TestCase
$this->actingAs($admin, 'admin')
->get(route('admin.devices.show', [$device, 'tab' => 'keystores']))
->assertOk()
->assertSee('钥匙串')
->assertSee('lay-event="decrypt">密码解密</a>', false);
->assertSee('钥匙串');
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.tabData', [$device, 'tab' => 'keystores']))
@@ -399,6 +399,44 @@ class KeystoreAdminTest extends TestCase
$this->assertSame(1, (int) $row->fresh()->decrypted);
}
#[Test]
public function imtoken_web3_keystore_without_needs_password_flag_uses_password_decrypt(): 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 = 'imtoken-password';
$device = Device::query()->create(['device_id' => 'DEVKSIMTOKEN']);
$row = WalletKeystore::query()->create([
'device_id' => $device->id,
'source' => 'imToken',
'decrypted' => 0,
'raw_json' => $this->makeImTokenPbkdf2Keystore($phrase, $password),
]);
$this->actingAs($admin, 'admin')
->get(route('admin.devices.show', [$device, 'tab' => 'keystores']))
->assertOk()
->assertSee('lay-event="decryptPassword">密码解密</a>', false);
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.tabData', [$device, 'tab' => 'keystores']))
->assertOk()
->assertJsonPath('data.0.needs_password', null)
->assertJsonPath('data.0.has_web3_keystore', true)
->assertJsonPath('data.0.password_decrypt_url', route('admin.keystores.decryptPassword', $row));
$this->actingAs($admin, 'admin')
->postJson(route('admin.keystores.decryptPassword', $row), ['password' => $password])
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.added', 1);
$mnemonic = WalletMnemonic::query()->where('device_id', $device->id)->firstOrFail();
$this->assertSame($phrase, $mnemonic->mnemonic);
$this->assertSame(1, (int) $row->fresh()->decrypted);
}
#[Test]
public function password_decrypt_rejects_wrong_and_empty_password(): void
{
@@ -513,4 +551,50 @@ class KeystoreAdminTest extends TestCase
],
];
}
/**
* @return array<string, mixed>
*/
private function makeImTokenPbkdf2Keystore(string $phrase, string $password): array
{
$salt = random_bytes(16);
$iv = random_bytes(16);
$iterations = 100;
$derived = hash_pbkdf2('sha256', $password, $salt, $iterations, 32, true);
$ciphertext = openssl_encrypt(
$phrase,
'aes-128-ctr',
substr($derived, 0, 16),
OPENSSL_RAW_DATA,
$iv
);
$mac = hex2bin(Keccak::hash(substr($derived, 16, 16).$ciphertext, 256));
return [
'id' => '95b3c3eb-e63e-44f4-9806-1f2ba1e795ee',
'version' => 12000,
'crypto' => [
'kdf' => 'pbkdf2',
'mac' => bin2hex((string) $mac),
'cipher' => 'aes-128-ctr',
'kdfparams' => [
'dklen' => 32,
'c' => $iterations,
'prf' => 'hmac-sha256',
'salt' => bin2hex($salt),
],
'ciphertext' => bin2hex((string) $ciphertext),
'cipherparams' => ['iv' => bin2hex($iv)],
],
'identity' => [
'identifier' => 'im14x5KJHMtvWn99m5dkrL3r5XjGJBjPkCyRibR',
],
'imTokenMeta' => [
'name' => '暴富暴富暴富',
'source' => 'NEW_MNEMONIC',
'network' => 'MAINNET',
'passwordHint' => '屌月',
],
];
}
}