1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-21 17:37:01 +01:00

Integrating ppom-validator with extension (#19511)

This commit is contained in:
Jyoti Puri 2023-07-12 19:50:55 +05:30 committed by GitHub
parent 177ea83f20
commit 73a203f106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 374 additions and 11 deletions

View File

@ -1,5 +1,5 @@
{
"content_security_policy": "frame-ancestors 'none'; script-src 'self'; object-src 'self'",
"content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
"externally_connectable": {
"matches": ["https://metamask.io/*"],
"ids": ["*"]

View File

@ -0,0 +1,93 @@
import 'fake-indexeddb/auto';
import { IndexedDBPPOMStorage } from './indexed-db-backend';
Object.defineProperty(globalThis, 'crypto', {
value: {
subtle: {
digest: () => new ArrayBuffer(12),
},
},
});
const enc = new TextEncoder();
const dec = new TextDecoder('utf-8');
describe('IndexedDBPPOMStorage', () => {
it('should be able to initialise correctly', () => {
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
expect(indexDBBackend).toBeDefined();
});
it('should be able to write and read file data if checksum matches', async () => {
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
await indexDBBackend.write(
{ name: 'fake_name', chainId: '5' },
enc.encode('fake_data'),
'000000000000000000000000',
);
const file = await indexDBBackend.read(
{ name: 'fake_name', chainId: '5' },
'000000000000000000000000',
);
expect(dec.decode(file)).toStrictEqual('fake_data');
});
it('should fail to write if checksum does not match', async () => {
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
await expect(async () => {
await indexDBBackend.write(
{ name: 'fake_name', chainId: '5' },
enc.encode('fake_data'),
'XXX',
);
}).rejects.toThrow('Checksum mismatch');
});
it('should fail to read if checksum does not match', async () => {
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
await expect(async () => {
await indexDBBackend.write(
{ name: 'fake_name', chainId: '5' },
enc.encode('fake_data'),
'000000000000000000000000',
);
await indexDBBackend.read({ name: 'fake_name', chainId: '5' }, 'XXX');
}).rejects.toThrow('Checksum mismatch');
});
it('should delete a file when delete method is called', async () => {
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
await indexDBBackend.write(
{ name: 'fake_name', chainId: '5' },
enc.encode('fake_data'),
'000000000000000000000000',
);
await indexDBBackend.delete({ name: 'fake_name', chainId: '5' });
const result = await indexDBBackend.read(
{ name: 'fake_name', chainId: '5' },
'000000000000000000000000',
);
expect(result).toBeUndefined();
});
it('should list all keys when dir is called', async () => {
const keys = [
{ chainId: '5', name: 'fake_name_1' },
{ chainId: '1', name: 'fake_name_2' },
];
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
await indexDBBackend.write(
keys[0],
enc.encode('fake_data_1'),
'000000000000000000000000',
);
await indexDBBackend.write(
keys[1],
enc.encode('fake_data_2'),
'000000000000000000000000',
);
const result = await indexDBBackend.dir();
expect(result).toStrictEqual(keys);
});
});

View File

@ -0,0 +1,127 @@
import { StorageBackend } from '@metamask/ppom-validator';
type StorageKey = {
name: string;
chainId: string;
};
const validateChecksum = async (
key: StorageKey,
data: ArrayBuffer,
checksum: string,
) => {
const hash = await crypto.subtle.digest('SHA-256', data);
const hashString = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
if (hashString !== checksum) {
throw new Error(`Checksum mismatch for key ${key}`);
}
};
export class IndexedDBPPOMStorage implements StorageBackend {
private storeName: string;
private dbVersion: number;
constructor(storeName: string, dbVersion: number) {
this.storeName = storeName;
this.dbVersion = dbVersion;
}
#getObjectStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.storeName, this.dbVersion);
request.onerror = (event: Event) => {
reject(
new Error(
`Failed to open database ${this.storeName}: ${
(event.target as any)?.error
}`,
),
);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, {
keyPath: ['name', 'chainId'],
});
}
};
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
const transaction = db.transaction([this.storeName], mode);
const objectStore = transaction.objectStore(this.storeName);
resolve(objectStore);
};
});
}
private async objectStoreAction(
method: 'get' | 'delete' | 'put' | 'getAllKeys',
args?: any,
mode: IDBTransactionMode = 'readonly',
): Promise<any> {
return new Promise<Event>((resolve, reject) => {
this.#getObjectStore(mode)
.then((objectStore) => {
const request = objectStore[method](args);
request.onsuccess = async (event) => {
resolve(event);
};
request.onerror = (event) => {
reject(
new Error(
`Error in indexDB operation ${method}: ${
(event.target as any)?.error
}`,
),
);
};
})
.catch((error) => {
reject(error);
});
});
}
async read(key: StorageKey, checksum: string): Promise<ArrayBuffer> {
const event = await this.objectStoreAction('get', [key.name, key.chainId]);
const data = (event.target as any)?.result?.data;
await validateChecksum(key, data, checksum);
return data;
}
async write(
key: StorageKey,
data: ArrayBuffer,
checksum: string,
): Promise<void> {
await validateChecksum(key, data, checksum);
await this.objectStoreAction('put', { ...key, data }, 'readwrite');
}
async delete(key: StorageKey): Promise<void> {
await this.objectStoreAction(
'delete',
[key.name, key.chainId],
'readwrite',
);
}
async dir(): Promise<StorageKey[]> {
const event = await this.objectStoreAction('getAllKeys');
return (event.target as any)?.result.map(([name, chainId]: string[]) => ({
name,
chainId,
}));
}
}

View File

@ -76,6 +76,9 @@ import { CustodyController } from '@metamask-institutional/custody-controller';
import { TransactionUpdateController } from '@metamask-institutional/transaction-update';
///: END:ONLY_INCLUDE_IN
import { SignatureController } from '@metamask/signature-controller';
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
import { PPOMController, createPPOMMiddleware } from '@metamask/ppom-validator';
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(desktop)
// eslint-disable-next-line import/order
@ -210,6 +213,9 @@ import {
} from './controllers/permissions';
import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware';
import { securityProviderCheck } from './lib/security-provider-helpers';
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
import { IndexedDBPPOMStorage } from './lib/indexed-db-backend';
///: END:ONLY_INCLUDE_IN
import { updateCurrentLocale } from './translate';
export const METAMASK_CONTROLLER_EVENTS = {
@ -630,6 +636,22 @@ export default class MetamaskController extends EventEmitter {
this.phishingController.setStalelistRefreshInterval(30 * SECOND);
}
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
this.ppomController = new PPOMController({
messenger: this.controllerMessenger.getRestricted({
name: 'PPOMController',
}),
storageBackend: new IndexedDBPPOMStorage('PPOMDB', 1),
provider: this.provider,
state: initState.PPOMController,
chainId: this.networkController.state.providerConfig.chainId,
onNetworkChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
});
///: END:ONLY_INCLUDE_IN
const announcementMessenger = this.controllerMessenger.getRestricted({
name: 'AnnouncementController',
});
@ -1529,6 +1551,9 @@ export default class MetamaskController extends EventEmitter {
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
PPOMController: this.ppomController,
///: END:ONLY_INCLUDE_IN
};
this.store.updateStructure({
@ -1633,6 +1658,9 @@ export default class MetamaskController extends EventEmitter {
this.swapsController.resetState,
this.ensController.resetState,
this.approvalController.clear.bind(this.approvalController),
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
this.ppomController.clear.bind(this.ppomController),
///: END:ONLY_INCLUDE_IN
// WE SHOULD ADD TokenListController.resetState here too. But it's not implemented yet.
];
@ -3910,6 +3938,10 @@ export default class MetamaskController extends EventEmitter {
engine.push(createLoggerMiddleware({ origin }));
engine.push(this.permissionLogController.createMiddleware());
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
engine.push(createPPOMMiddleware(this.ppomController));
///: END:ONLY_INCLUDE_IN
engine.push(
createRPCMethodTrackingMiddleware({
trackEvent: this.metaMetricsController.trackEvent.bind(

View File

@ -116,6 +116,7 @@ features:
- DISABLE_WEB_SOCKET_ENCRYPTION: false
- SKIP_OTP_PAIRING_FLOW: false
- WEB_SOCKET_PORT: null
blockaid:
###
# Build Type code extensions. Things like different support links, warning pages, banners

View File

@ -22,19 +22,20 @@ module.exports = function createStaticAssetTasks({
const copyTargetsProds = {};
const copyTargetsDevs = {};
const buildConfig = loadBuildTypesConfig();
const activeFeatures = buildConfig.buildTypes[buildType].features ?? [];
browserPlatforms.forEach((browser) => {
const [copyTargetsProd, copyTargetsDev] = getCopyTargets(
shouldIncludeLockdown,
shouldIncludeSnow,
activeFeatures,
);
copyTargetsProds[browser] = copyTargetsProd;
copyTargetsDevs[browser] = copyTargetsDev;
});
const buildConfig = loadBuildTypesConfig();
const activeFeatures = buildConfig.buildTypes[buildType].features ?? [];
const additionalAssets = activeFeatures.flatMap(
(feature) =>
buildConfig.features[feature].assets?.filter(
@ -108,7 +109,11 @@ module.exports = function createStaticAssetTasks({
}
};
function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) {
function getCopyTargets(
shouldIncludeLockdown,
shouldIncludeSnow,
activeFeatures,
) {
const allCopyTargets = [
{
src: `./app/_locales/`,
@ -198,6 +203,14 @@ function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) {
},
];
if (activeFeatures.includes('blockaid')) {
allCopyTargets.push({
src: getPathInsideNodeModules('@metamask/ppom-validator', 'dist/'),
pattern: '*.wasm',
dest: '',
});
}
const languageTags = new Set();
for (const locale of locales) {
const { code } = locale;

View File

@ -253,6 +253,7 @@
"@metamask/permission-controller": "^4.0.0",
"@metamask/phishing-controller": "^3.0.0",
"@metamask/post-message-stream": "^6.0.0",
"@metamask/ppom-validator": "^0.0.1",
"@metamask/providers": "^11.1.0",
"@metamask/rate-limit-controller": "^3.0.0",
"@metamask/rpc-methods": "^1.0.0-prerelease.1",
@ -464,6 +465,7 @@
"eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-storybook": "^0.6.12",
"fake-indexeddb": "^4.0.1",
"fancy-log": "^1.3.3",
"fast-glob": "^3.2.2",
"fs-extra": "^8.1.0",

105
yarn.lock
View File

@ -1619,6 +1619,13 @@ __metadata:
languageName: node
linkType: hard
"@blockaid/ppom-mock@npm:^1.0.0":
version: 1.0.0
resolution: "@blockaid/ppom-mock@npm:1.0.0"
checksum: 297efc29210aae5fb258bbecefcd742645966041bd9af6f256aa80c671920d5e7d9e669c4d1e34795f8556997663abc42422bfafc511ab8379134ce1c8ac324e
languageName: node
linkType: hard
"@chainsafe/as-sha256@npm:^0.3.1":
version: 0.3.1
resolution: "@chainsafe/as-sha256@npm:0.3.1"
@ -4551,6 +4558,18 @@ __metadata:
languageName: node
linkType: hard
"@metamask/ppom-validator@npm:^0.0.1":
version: 0.0.1
resolution: "@metamask/ppom-validator@npm:0.0.1"
dependencies:
"@blockaid/ppom-mock": ^1.0.0
"@metamask/base-controller": ^3.0.0
"@metamask/controller-utils": ^4.0.0
await-semaphore: ^0.1.3
checksum: a94edcd618f670b392a84caa236bbc951a6a99100d8a5fa7bd89b78747c3b06b289738b42aee433659b647441eab0a8741e1951a0e29ef6aa98ffa10a3f33f5b
languageName: node
linkType: hard
"@metamask/preferences-controller@npm:^4.1.0":
version: 4.1.0
resolution: "@metamask/preferences-controller@npm:4.1.0"
@ -10703,6 +10722,13 @@ __metadata:
languageName: node
linkType: hard
"base64-arraybuffer-es6@npm:^0.7.0":
version: 0.7.0
resolution: "base64-arraybuffer-es6@npm:0.7.0"
checksum: 6d2fd114df49201b476cea5d470504e5d4e8c4cd42544152b312c9bdcb824313086fe83f1ffc34262e9e276b82d46aefc6e63bb85553f016932061137b355cdf
languageName: node
linkType: hard
"base64-arraybuffer@npm:^0.1.5":
version: 0.1.5
resolution: "base64-arraybuffer@npm:0.1.5"
@ -14689,10 +14715,12 @@ __metadata:
languageName: node
linkType: hard
"domexception@npm:^1.0.0":
version: 1.0.0
resolution: "domexception@npm:1.0.0"
checksum: a580e233689e9dcd5e5322f4b58da618bdbf9c4b96532bd11065903b43e81acbe6ec936ceacc57ce2014b695f778ac6368eb079fe3efb1276073a363d59500a8
"domexception@npm:^1.0.0, domexception@npm:^1.0.1":
version: 1.0.1
resolution: "domexception@npm:1.0.1"
dependencies:
webidl-conversions: ^4.0.2
checksum: f564a9c0915dcb83ceefea49df14aaed106b1468fbe505119e8bcb0b77e242534f3aba861978537c0fc9dc6f35b176d0ffc77b3e342820fb27a8f215e7ae4d52
languageName: node
linkType: hard
@ -17083,6 +17111,15 @@ __metadata:
languageName: node
linkType: hard
"fake-indexeddb@npm:^4.0.1":
version: 4.0.1
resolution: "fake-indexeddb@npm:4.0.1"
dependencies:
realistic-structured-clone: ^3.0.0
checksum: dd1c82111e3b97c262a647a29dc012209f8c3bed0fbe7ae9630927772842fe8d3276794ff196d0021a5e60563a25a4323eca622a6a7bc6575b62e074328a0c90
languageName: node
linkType: hard
"fake-merkle-patricia-tree@npm:^1.0.1":
version: 1.0.1
resolution: "fake-merkle-patricia-tree@npm:1.0.1"
@ -23714,7 +23751,7 @@ __metadata:
languageName: node
linkType: hard
"lodash@npm:^4.13.1, lodash@npm:^4.16.4, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4":
"lodash@npm:^4.13.1, lodash@npm:^4.16.4, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7
@ -24631,6 +24668,7 @@ __metadata:
"@metamask/phishing-controller": ^3.0.0
"@metamask/phishing-warning": ^2.1.0
"@metamask/post-message-stream": ^6.0.0
"@metamask/ppom-validator": ^0.0.1
"@metamask/providers": ^11.1.0
"@metamask/rate-limit-controller": ^3.0.0
"@metamask/rpc-methods": ^1.0.0-prerelease.1
@ -24769,6 +24807,7 @@ __metadata:
ethjs-contract: ^0.2.3
ethjs-query: ^0.3.4
extension-port-stream: ^2.0.0
fake-indexeddb: ^4.0.1
fancy-log: ^1.3.3
fast-glob: ^3.2.2
fast-json-patch: ^3.1.1
@ -29794,6 +29833,17 @@ __metadata:
languageName: node
linkType: hard
"realistic-structured-clone@npm:^3.0.0":
version: 3.0.0
resolution: "realistic-structured-clone@npm:3.0.0"
dependencies:
domexception: ^1.0.1
typeson: ^6.1.0
typeson-registry: ^1.0.0-alpha.20
checksum: b4521b299c8dc320a5e3ef44678f80a92b0f1837901a5fbd1c7be06808110fb0b591b417114306ec55b44ef47fd17968aacca079afc9665afbe1c528026295ec
languageName: node
linkType: hard
"recast@npm:^0.21.0":
version: 0.21.5
resolution: "recast@npm:0.21.5"
@ -33580,6 +33630,15 @@ __metadata:
languageName: node
linkType: hard
"tr46@npm:^2.1.0":
version: 2.1.0
resolution: "tr46@npm:2.1.0"
dependencies:
punycode: ^2.1.1
checksum: ffe6049b9dca3ae329b059aada7f515b0f0064c611b39b51ff6b53897e954650f6f63d9319c6c008d36ead477c7b55e5f64c9dc60588ddc91ff720d64eb710b3
languageName: node
linkType: hard
"tr46@npm:^3.0.0":
version: 3.0.0
resolution: "tr46@npm:3.0.0"
@ -34052,6 +34111,24 @@ __metadata:
languageName: node
linkType: hard
"typeson-registry@npm:^1.0.0-alpha.20":
version: 1.0.0-alpha.39
resolution: "typeson-registry@npm:1.0.0-alpha.39"
dependencies:
base64-arraybuffer-es6: ^0.7.0
typeson: ^6.0.0
whatwg-url: ^8.4.0
checksum: c6b629697acf4652aecfff7be760356d764600afc9beca253278bbfc44fae0fe635b7619201b83e497cdc30645cbce7614d12a04b5726d9b8b505f73e6a3fc2a
languageName: node
linkType: hard
"typeson@npm:^6.0.0, typeson@npm:^6.1.0":
version: 6.1.0
resolution: "typeson@npm:6.1.0"
checksum: 00a77b03ac8f704acb103307bad9295fe47d6b304c386297f078ec3be63875c0b81e022a4815edb9dc2c7da0a72a431345411d35c755a8510af4a420e9e46cdc
languageName: node
linkType: hard
"uglify-js@npm:^3.1.4":
version: 3.17.0
resolution: "uglify-js@npm:3.17.0"
@ -35404,6 +35481,13 @@ __metadata:
languageName: node
linkType: hard
"webidl-conversions@npm:^6.1.0":
version: 6.1.0
resolution: "webidl-conversions@npm:6.1.0"
checksum: 1f526507aa491f972a0c1409d07f8444e1d28778dfa269a9971f2e157182f3d496dc33296e4ed45b157fdb3bf535bb90c90bf10c50dcf1dd6caacb2a34cc84fb
languageName: node
linkType: hard
"webidl-conversions@npm:^7.0.0":
version: 7.0.0
resolution: "webidl-conversions@npm:7.0.0"
@ -35578,6 +35662,17 @@ __metadata:
languageName: node
linkType: hard
"whatwg-url@npm:^8.4.0":
version: 8.7.0
resolution: "whatwg-url@npm:8.7.0"
dependencies:
lodash: ^4.7.0
tr46: ^2.1.0
webidl-conversions: ^6.1.0
checksum: a87abcc6cefcece5311eb642858c8fdb234e51ec74196bfacf8def2edae1bfbffdf6acb251646ed6301f8cee44262642d8769c707256125a91387e33f405dd1e
languageName: node
linkType: hard
"which-boxed-primitive@npm:^1.0.2":
version: 1.0.2
resolution: "which-boxed-primitive@npm:1.0.2"