1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00

Fix: eth_sign does not validate input (#12679)

* Fix #5039

* Converted function into async

* Added more explicit explanation of why the number of bits for EcSign

* eth_sign and eth_personalSign now report errors correctly back to the user

* Added leeway to unsigned message byte check

* Fix lint
This commit is contained in:
Olaf Tomalka 2021-11-19 17:05:24 +01:00 committed by GitHub
parent fb6375472e
commit 3f3479bf6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 43 deletions

View File

@ -79,9 +79,9 @@ export default class MessageManager extends EventEmitter {
* @returns {promise} after signature has been
*
*/
addUnapprovedMessageAsync(msgParams, req) {
return new Promise((resolve, reject) => {
async addUnapprovedMessageAsync(msgParams, req) {
const msgId = this.addUnapprovedMessage(msgParams, req);
return await new Promise((resolve, reject) => {
// await finished
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
@ -93,6 +93,10 @@ export default class MessageManager extends EventEmitter {
'MetaMask Message Signature: User denied message signature.',
),
);
case 'errored':
return reject(
new Error(`MetaMask Message Signature: ${data.error}`),
);
default:
return reject(
new Error(
@ -233,6 +237,19 @@ export default class MessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected');
}
/**
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId - The id of the Message to error
*
*/
errorMessage(msgId, error) {
const msg = this.getMsg(msgId);
msg.error = error;
this._updateMsg(msg);
this._setMsgStatus(msgId, 'errored');
}
/**
* Clears all unapproved messages from memory.
*/
@ -304,7 +321,7 @@ export default class MessageManager extends EventEmitter {
* @returns {string} A hex string conversion of the buffer data
*
*/
function normalizeMsgData(data) {
export function normalizeMsgData(data) {
if (data.slice(0, 2) === '0x') {
// data is already hex
return data;

View File

@ -107,6 +107,9 @@ export default class PersonalMessageManager extends EventEmitter {
),
);
return;
case 'errored':
reject(new Error(`MetaMask Message Signature: ${data.error}`));
return;
default:
reject(
new Error(
@ -254,6 +257,19 @@ export default class PersonalMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected');
}
/**
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId - The id of the Message to error
*
*/
errorMessage(msgId, error) {
const msg = this.getMsg(msgId);
msg.error = error;
this._updateMsg(msg);
this._setMsgStatus(msgId, 'errored');
}
/**
* Clears all unapproved messages from memory.
*/

View File

@ -17,6 +17,7 @@ import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
import LatticeKeyring from 'eth-lattice-keyring';
import EthQuery from 'eth-query';
import nanoid from 'nanoid';
import { ethErrors } from 'eth-rpc-errors';
import { captureException } from '@sentry/browser';
import {
AddressBookController,
@ -61,7 +62,7 @@ import AlertController from './controllers/alert';
import OnboardingController from './controllers/onboarding';
import ThreeBoxController from './controllers/threebox';
import IncomingTransactionsController from './controllers/incoming-transactions';
import MessageManager from './lib/message-manager';
import MessageManager, { normalizeMsgData } from './lib/message-manager';
import DecryptMessageManager from './lib/decrypt-message-manager';
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager';
import PersonalMessageManager from './lib/personal-message-manager';
@ -1843,14 +1844,22 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params passed to eth_sign.
* @param {Function} cb - The callback function called with the signature.
*/
newUnsignedMessage(msgParams, req) {
const promise = this.messageManager.addUnapprovedMessageAsync(
msgParams,
req,
);
async newUnsignedMessage(msgParams, req) {
const data = normalizeMsgData(msgParams.data);
let promise;
// 64 hex + "0x" at the beginning
// This is needed because Ethereum's EcSign works only on 32 byte numbers
// For 67 length see: https://github.com/MetaMask/metamask-extension/pull/12679/files#r749479607
if (data.length === 66 || data.length === 67) {
promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req);
this.sendUpdate();
this.opts.showUserConfirmation();
return promise;
} else {
throw ethErrors.rpc.invalidParams(
'eth_sign requires 32 byte message hash',
);
}
return await promise;
}
/**
@ -1859,24 +1868,23 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params passed to eth_call.
* @returns {Promise<Object>} Full state update.
*/
signMessage(msgParams) {
async signMessage(msgParams) {
log.info('MetaMaskController - signMessage');
const msgId = msgParams.metamaskId;
try {
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.messageManager
.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams);
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
const cleanMsgParams = await this.messageManager.approveMessage(
msgParams,
);
const rawSig = await this.keyringController.signMessage(cleanMsgParams);
this.messageManager.setMsgStatusSigned(msgId, rawSig);
return this.getState();
});
} catch (error) {
log.info('MetaMaskController - eth_sign failed', error);
this.messageManager.errorMessage(msgId, error);
throw error;
}
}
/**
@ -1923,23 +1931,27 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @returns {Promise<Object>} A full state update.
*/
signPersonalMessage(msgParams) {
async signPersonalMessage(msgParams) {
log.info('MetaMaskController - signPersonalMessage');
const msgId = msgParams.metamaskId;
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.personalMessageManager
.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signPersonalMessage(cleanMsgParams);
})
.then((rawSig) => {
try {
const cleanMsgParams = await this.personalMessageManager.approveMessage(
msgParams,
);
const rawSig = await this.keyringController.signPersonalMessage(
cleanMsgParams,
);
// tells the listener that the message has been signed
// and can be returned to the dapp
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig);
return this.getState();
});
} catch (error) {
log.info('MetaMaskController - eth_personalSign failed', error);
this.personalMessageManager.errorMessage(msgId, error);
throw error;
}
}
/**

View File

@ -835,7 +835,8 @@ describe('MetaMaskController', function () {
let msgParams, metamaskMsgs, messages, msgId;
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
const data = '0x43727970746f6b697474696573';
const data =
'0x0000000000000000000000000000000000000043727970746f6b697474696573';
beforeEach(async function () {
sandbox.stub(metamaskController, 'getBalance');
@ -885,6 +886,19 @@ describe('MetaMaskController', function () {
assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED);
});
it('checks message length', async function () {
msgParams = {
from: address,
data: '0xDEADBEEF',
};
try {
await metamaskController.newUnsignedMessage(msgParams);
} catch (error) {
assert.equal(error.message, 'eth_sign requires 32 byte message hash');
}
});
it('errors when signing a message', async function () {
try {
await metamaskController.signMessage(messages[0].msgParams);