AntelopeIO / leap

C++ implementation of the Antelope protocol
Other
113 stars 69 forks source link

Custom permissions are severely limited by linked auth requirements #1131

Open theblockstalk opened 1 year ago

theblockstalk commented 1 year ago

Problem

Except for the "active" and "owner" permission, every other permission is considered a custom permission.

"active" and "owner" are able to be freely used in smart contracts as authorisation checks with

require_auth(account)
# or
require_auth2(account, {account, "owner"_n})

Custom permissions have several additional requirements and limitations:

require_auth2(account, {account, custom_permission})
  1. require_auth2() will only succeed with a custom permission if that custom permission has been linked to the action which it is being executed in. e.g. If I want to sign an action examplecontract::exampleaction() with my custom permission custperm then I would need to 1st link custperm to examplecontract::exampleaction() by calling eosio::linkauth(account, "examplecontract"_n, "exampleaction"_n, custperm)
  2. (I think) Each customer permission can only be linked to one action at a time

See discussion on Telegram: https://t.me/antelopedevs/320746

Desired behaviour

Smart contracts can use require_auth2(account, {account, custom_permission}) or something equivalent without these additional requirements.

Suggestions

Linking should be an optional feature. Or there should be a new require_auth3 mechanism that does not check linking.

To be clear, allowing multiple linking per custom permissions is not a great solution for these use cases. As we see it, using linking with custom permissions should be removed or optional

Use cases

At the Tonomy Foundation, we Already have several use cases which would take advantage of "liberated" custom permissions. Here are two very simplified versions of these below, to illustrate how custom permissions can be leveraged for a high flexibility of usability in wallets and applications while retaining full sovereignty and security of accounts.

There are several other use cases such as proveable multifactor authentication and more but I think the below are enough.

Use case 1: Single sign on

Consider an account with the following hypothetical permission structure

See video walkthrough here: https://www.youtube.com/watch?v=b81Ju7r7T1M

At the TF, we are building a single sign on wallet. This means you can sign into multiple applications from one identity wallet. e.g. you can sign into https://pomelo.io from your EOS ID wallet. During this sign in process

  1. a private key is generated inside the https://pomelo.io web app (and stored in local storage)
  2. a request is made to the EOS ID wallet to sign into https://pomelo.io
  3. the user in the EOS ID wallet, confirms they want to sign in. At this point in time, a new permission, "pomelo" is added to the account as seen above (which is authorised by the "active" key)
  4. now the user who is signed into the https://pomelo.io app can use their "pomelo" permission to sign actions to do with the pomelo app. e.g. they can call pomelocontract::creategrantproposal() which has been programmed to except actions assigned by the pomelo custom permission. This happens directly from the https://pomelo.io web app ( as opposed to having to go back to the EOS ID wallet every time). This is a massive usability improvement.
  5. A user can sign into another app, e.g. https://upland.me and would get a new custom permission put on the watching that can be used by upland smart contracts. It is important to note that the security of each of the custom permissions is contained because the upland smart contracts would only accept actions signed by the "upland" permission and not the "pomelo" permission, Hence retaining a scoped access level of the security key, much like how this is done in existing single sign-on systems.

Use case 2: recovery

Consider an account with the following hypothetical permission structure (neglecting a few other protocol violations just to make the use case easy to follow)

With this account structure, if the user loses their active key, then they can use their hardware device (with a key on it) to recover it. Even if they lose their hardware device they can go and ask their three social recovery contacts, and two out of three of them need to sign with their keys on their accounts which would recover this users keys.

Workarounds

There are a few workarounds that we are considering to tackle this problem. Having the client-side code determine whether a custom commission needs to be linked or relinked with a new action before calling the desired action is the best solution IMO. However, this takes ups unnecessary CPU and RAM resources and adds a considerable complexity that does not seem necessary.

aaroncox commented 1 year ago

I don't really understand why Use Case 1 is an outstanding issue, or why you'd need the smart contract to use any sort of require_auth call when you could just setup permissions with those linked authorities using what we have today.

Following your Pomelo example, a 1-of-1 permission could be setup as a child of active, and then linked to the contracts/actions required to interact with the Pomelo application/contracts. None of the contracts would need to be modified because the permission system already scopes that permission to only perform those specific actions. The systems we have today already allow for this.

As for the idea of "generating a scoped private key and saving in localstorage for in-app signing", I'd agree that's a great idea, one of which we described in the "Request for Permission" protocol in the Wallet+ Blue Paper. The blue paper itself doesn't cover all the details, but serves as a brief overview of the proposal and its various integration points into other projects (the SDKs, aka wharfkit.com and the Application Registry, which doesn't exist yet).

Currently we expect that the Request for Permission proposal will be integrated into Wharf as a series of LoginPlugin, WalletPlugin and TransactPlugin extensions to the SDK framework. This will allow application developers to specify which contract/actions they'd like their app to automatically sign for (in-app), understand how to relay an updateauth action to the users wallet (either pre-login or post-login) to configure the permission, and then route requests appropriately to one or more signers (a localstorage signer for in-app signing and routing to external wallets for "elevated" signing not included in the scoped permission).

All of this should work within the current permission system we have in Antelope without any modifications to any existing smart contracts, simply by leveraging to the existing permissions system.

arhag commented 1 year ago

First, a little bit of background:

The goal behind the Antelope native permission system was to allow the user to be in control of how the keys (more generally authorities) they selected map to particular actions of contracts that can be authorized by their account.

The addition of the host function require_auth2 unfortunately goes against these goals but it was determined to be a quick way prior to the launch of the EOS network to solve the problem of enabling the multisig contract to accomplish its objective.

Unfortunately since require_auth2 was also made available to non-privileged contracts, developers have used it and have long wanted to use require_auth2 in further ways that undermine the original purpose of the Antelope native permission system, i.e. to take away control from the user for how they organize and map their permissions for their accounts. For example, imagine a contract for a game called CoinQuest (I just picked an arbitrary name ChatGPT gave me) that demands the user authorize actions of its contract with a custom permission named coin. Then imagine there is another contract for a mobile payment application called CoinWallet that demands the user authorize actions of its contract with a custom permission also named coin. If the user has no choice to customize which custom permission name each contract expects, they would be forced to use the same keys to authorize financially significant payment transactions as the ones they use to play a low-stakes game or fun.

Now with the background out of the way:

It seems like there is demand by developers to allow contracts to drive some of the organization of permissions. I do see value in these use cases. But I would want to ensure that any protocol or system supported mechanism is designed in a way that prevents conflicts like the one I described above from arising and causing restrictions or frustrations for users.

I do believe it is flawed approach to attempt to satisfy the goals of allowing contracts to have more control over how permissions are organized by forcing it on top of the existing Antelope native permission system which was explicitly design to not enable that goal. Unfortunately, it is the only approach available by the Antelope protocol today. And as you have acknowledged and Aaron has further pointed out, workarounds to enable these use cases do exist with the exiting protocol. For example, for use case 1, it requires one linkauth of the <contract account name>::* pattern to the custom permission meant for that contract in addition to creating the custom permission in the first place. For use case 2, you may require a smart contract to have exclusive ownership control of the owner permission to enable the account recovery process you outlined.

I would not worry too much about the RAM costs for those two workarounds at least. From my experience, I believe a better designed authorization system to enable this type of flexibility is not going to be able to squeeze much more benefit in overall RAM efficiency. I would rather acknowledge that legitimate concerns about RAM are a sign that more fundamental changes are needed in the Leap implementation and, if necessary, Antelope protocol to significantly increase the supply of contract storage available and thus reduce state storage costs overall not just for this use case but for all use cases. Those types of things are something we think about and continue to explore.

Thanks for sharing the use cases you presented here. If there are any other materially different use cases you see value in as well, please include them here as well.

Use cases like this, as well as other use cases like the ones Aaron has shared before, e.g. automatically expiring scope permissions, are helpful in considering what a significant overhaul of the account permission system may have to look like to address all of these use cases (to be clear I am not saying for people to expect such an overhaul to be coming). More likely than an overhaul may be some small primitives that can be added to the protocol to provide developers the tools to build efficient alternative authorization system for their applications. For example, I am thinking about things like the require_key host function idea that was considered a few years back but never went anywhere. But again there is more to explore even with that before being able to commit to such an approach.

theblockstalk commented 1 year ago

Thanks, @aaroncox and @arhag, for chiming in here.

I don't really understand why Use Case 1 is an outstanding issue, or why you'd need the smart contract to use any sort of require_auth call when you could just setup permissions with those linked authorities using what we have today.

Point 1. What bothers me about this situation is that it seems unnecessary to link the authorization. What extra security benefit does it bring at the smart contract level to force a custom mission to be linked before it can be used in a smart contract authorisation? I understand @arhag That there is a security concern in which one custom permission may accidentally provide a mechanism for authority using another app's permission, but in the example case, because the private keys control in the storage of the app, aka https:///pomelo.io is actually the client-side code running in https:///pomelo.io that decides what smart contract call it makes with the private key, not the user, which means that the security risk only exists if the user's client is compromised which opens up a whole bunch of even worse risks anyway.

Use case 3: Proveable multi-factor authentication

I want to outline one last use case to show where linked or becomes in the way.

Consider an account with the following hypothetical permission structure (neglecting a few other protocol violations just to make the use case easy to follow)

In the above example, a mobile wallet key is stored in a secure on-clave in the mobile wallet (We have built a POC of this with Tonomy ID). It creates a Randomly generated private/public key pair which is stored behind different challenges: a PIN screen, a biometric screen (FaceID or fingerprint) or a third which can be accessed with either PIN or biometric. A fourth key, "nochallenge", is also created, which does not require a challenge by just the user to confirm.

With these four standardised permissions, apps and their respective smart contracts have a variety of levels of factors of authentication that they can request from the wallet and can have proof that the challenge was met by having the signature come from specific permission. This gives apps a flexible way to choose between security and usability for different actions in their software, e.g. an in-game NFT may be minted with a "nochallenge" but only transferred with a "pinorbiometric".

Point 2. Use case 3 Clearly shows where linked auth hinders developing high-usability wallets and applications. In this case, there is no knowledge about which contract actions or contracts will use the different permissions. The only workaround for this is that each time the wallet signs a smart contract, it must unlink and relink to that action/contract.

General thoughts

The security issue you point out @arhag (with the coin account) seems valid to me. However, this, to me, seems very difficult to solve at the protocol level of Antelope. This can easily be solved at the wallet level by managing permissions correctly. For example, in use case1, the wallet is ensuring that the key pair that is assigned to the https://pomelo.io app is given an account permission, "pomelo", or in some way a recognisable name that can be associated with the app - what we actually do as we correlate a domain name with an Antelope account and make the commission name on the Tonomy ID account equal to that accompanied. e.g.

https://pomelo.io = "pomeloio" account (we do this through DNS verification)

I'm also looking forward to what features Warfkit will have for this. Having this work for all different Antelope chains will be a challenge.

There sounds like there are some internals of Antelope, which seem quite strange to me. From the outside the require_auth2 hints that there is a possibility that a smart contract can just request a signature from whichever commission wants, and this has given rise to developers like me finding out it is not so simple and requesting this. In theory, to me at least, this sounds like a simple authorisation system, but I leave it to you experts as to how feasible this would be.

require_key would be great to have as well. However, it does not have quite the same flexibility as account permission is because it doesn't support dynamic account allegations. In use case 2, the socialrecovery permission is a great example of where require_key could not handle this situation.

I would also like to support having expiring keys. This would be a great feature that can be useful; I especially think this would be great for enterprise-grade security applications.

theblockstalk commented 3 months ago

We have now developed the workaround for this issue (as explained in the original comment). This involves the web app requesting that the user's wallet sign the transaction to linkauth() the permission allocated to the web app to the active permission of the user's wallet. It works, except if the user takes their mobile offline and then can no longer use the web app, which is a counterintuitive experience for the user.

Removing the linkauth() Requirement for this use case would not open any security vulnerabilities AFAIKT.

While this workaround is kind of working, there is a broader application of this concept not just to people's personal wallets but also to DAO transactions, however, in this case, it is impossible to create the workaround because there is no central Wallet of the DAO that can sign the linkauth() transaction.