Proposal: App (Gaia) Sessions


#1

Background

From a conversation on here quite some time ago we were talking about security when one loses their Blockstack account or lost their phone with their Blockstack / dApps logged in. I think a solution for this would be quite important, and while a session manager for Browser is something more akin to making another type of browser (which I’m working on), a session manager for Apps could certainly be created with what we have already.

The biggest thing that Blockstack gives apps is a private key to use for Gaia among other things. If this is able to be revoked, in a sense, then the app is unable to access the user’s data anymore. However, these private keys are used for many things within the app, and this would be taking a step backwards; forwards would be allowing the app to specify whatever private key they want, not changing it on the fly.

However, with the addition of Association Tokens in the Gaia v1(.3) authentication scheme, we are now able to track what user ID the app is tied to, along with an extra token to put information in that which the App never touches.

Proposal

My proposal would be to add an app-sessions.json to the root gaia hub of a particular user (in the same “folder” as the profile.json). This file would be of the following schema:

[
  {
    childToAssociate: "compressed public key in hex of the app", 
    exp: 1549745395,
    sid: "UUIDv4"
  }
]

(this is basically the association token but using a session id (sid) instead of a salt (why do we have salt in there?), along with the lack of an iss since that is simply the root bucket address)

The sid field would have to be added to the association token. Optionally, the token (and schema) could also include a (short) friendly name such as the app domain in a desc or tag field, so when users need to reject certain sessions they know which ones they are dealing with.

The browser would update this file every time an app session is added, or a session is removed (or periodically cleaned via the expiration date). The Gaia Hub, if it detects an association token with a sid, could then check the app-sessions.json file in the issuer’s root bucket to make sure the session hasn’t been revoked.

Summary & Takeaway:

Problems that would be solved:

  • Apps would no longer be able to access the user’s data if their session is revoked
    • this curbs rogue apps and those that are logged in on devices which are lost

Changes that would occur:

  • Add a session ID (sid) to the Gaia Association Token
  • Add an app-sessions.json file to the user’s root Gaia bucket
  • Make the Gaia Hub check the Association Tokens which have a sid against the issuer’s app-sessions.json file to make sure it’s not allowing access to a revoked app session
  • Make the browser handle sessions in its UI and authentication steps

Recommended but optional items:

  • Apps should check to see why they were revoked from the Gaia Hub, and if they no longer have an active session they should “log out” by themselves. Perhaps this could be done by the error response sent by the Gaia Hub when it detects they have a bad session?

Discussion items:

  • If two browsers are authenticating apps at the same time, one will override the other in the session.json “POST race” to Gaia Hub. This isn’t a huge issue but it’s something to consider
  • The user should also be able to specify whether or not they require apps to use an Association Token (especially with this feature).
    • I honestly don’t know how this will work outside of a full deprecation for apps that don’t use association tokens at all – and how do we force apps to use association tokens when they are optional? They have their own private/public key to use on the Gaia Hub, so if there’s no address limiting then is there any way to stop them from pretending to be “dumb” apps or even users?

Edits / Addendums

The app-sessions.json could also have the following extra metadata inside of each entry for more security and ease of use:

[
  {
    "appDomain": "app.domain.com",
    "browserId": "UUIDv4",
    "desc": "signed by blockstack.browser.org on chrome, win64",
    "signature": "jwt style signature of the signed entry"
  }
]
  • appdomain: Lists the app domain so it’s easier to see where the token belongs (or should belong)
  • browserId: Lists the browserId so if a browser session is compromised all tokens from the listed browserId could be revoked.
  • desc: A user friendly description generated by the browser
  • signature: A signature generated from the data by the browser to make sure the data isn’t tampered with

All of these additional fields would be generated by the browser and only used by the browser (would not be inside the association token at all!), except for the signature field, which could be additionally used by the GaiaHub to make sure the specific entry it is testing against hasn’t been tampered with. Technically it could also use the appDomain field if it was in “strict” mode, only allowing the token to be used from the specific domain, but that is probably too strict.


#2

How would that affect the performance?


#3

Depends on how fast the bucket is… the Gaia Hub can only really protect “writes” I think, so if the read bucket (such as the S3 backend) is fast enough it shouldn’t be too much of a delay, and hopefully rather negligent. If it’s slow then the write would be slow regardless. I suppose in formula “form” it would be
gaiaWriteSpeed = provider.writeSpeed + provider.readSpeed
which is about equal to 2*provider.readSpeed. heavily depends on the size of the file so I’m redacting that.

Protecting reads would be interesting but currently that’s only done via multiplayer storage / app-handled keys, not the Gaia Hub.


#4

There is a PR that seems to be mostly completed around revoking Gaia tokens: https://github.com/blockstack/gaia/pull/196 . Isn’t this pretty much what you’re proposing?


#5

It looks like we (zone117x and I) are trying to accomplish the same goal except have a couple core differences in implementation and features:

  • He is using issuedAt dates to expire certain tokens given before a certain time period:

That the epoch time iat (issued-at date) is greater than the bucket’s revocation date (only if such a date has been set by the bucket owner).

  • I want to revoke certain sessions at will – i.e. if I want to revoke a session for App A which was given at 2/2/2019, I don’t have to revoke a session for App B which was given at 1/1/2019.

Thanks for pointing it out but I think this proposal still stands on its own feet. Having the ability to disable or revoke individual sessions gives much more power to the end user and keeps it much more flexible for a small tradeoff.


#6

No, I think what you’re looking at is just some missing documentation he added. It’s not part of his PR. He’s implementing a new HTTP endpoint to revoke any specific token at any time. See here:


#7

I see this goal outlined by Jude, and the code implemented (which is what I drew this conclusion from) that I see in the differences section of the pull request show that he only implemented one API function POST /revoke-all/${address}, which accepts a body of { oldestValidTimestamp: Date }, and seems to do just what I said: you pass in a date, anything older than that (in the iat time, or issuedAt) gets expired.

If I’m terribly wrong please correct me, but this isn’t “any specific token at any time,” at least for #196 which you have linked both times.


#8

Hey @MichaelFedora, thanks for the proposal write-up!

The auth protocol derives the app private key deterministically as a hardened child of the Blockstack ID owner key. The reason for this is to ensure that the only data the user needs to recover their identity and app keys are (1) their seed phrase, and (2) metadata they can either memorize or safely store as plaintext. In the spirit of this design principle, the application should not be choosing keys for the user; the user should be deriving keys for their app using the above information.

To be clear, this is a side-effect of association tokens. The intent of association tokens is to make it so you can use your private Gaia hub without having to whitelist application private key addresses. The Gaia write protocol does not mandate that the authentication header include an association token – association tokens are not required for public hubs, nor for private hubs where the application addresses are whitelisted already.

I agree with the spirit of what you’re trying to do here. However, @zone117x’s PR does not address this – the PR he wrote is meant specifically for revoking authentication tokens shared with other users (i.e. I grant you write access to a portion of my app’s bucket, and I later revoke it with the code in this PR). It’s a prerequisite for implementing collections that you can share across multiple applications – you’d share a collection-specific auth token with an app to write to your collection, and later on, you can revoke a collection’s outstanding auth tokens and re-issue them on a case-by-case basis.

Regarding session management specifically, we would want to (1) minimize the number of distinct private keys for an application, and (2) make sure we can derive the latest app private key using the user’s seed phrase, ID-address, application Origin, and some other public metadata the user can easily remember (or back up somewhere). Regarding (1), we’d achieve this by only revoking sessions as a reaction to an application key compromise (i.e. when absolutely necessary). This is because changing keys is expensive – you’d need to re-encrypt all your private data with the new key, or find a way to keep the old keys around (and you can’t do the latter in the event of a compromise). Regarding (2), the approach we’d want to take is altering the application key generation by inserting some public metadata (such as a revocation date) into the derivation path. I don’t know how to do (2) reliably yet – it’s not clear where that public metadata would be stored, and ideally the user would be able to recover this data from memory (and I’d like to avoid storing it on the Gaia hub, since the act of storing it or overwriting it would tell attackers when the victim has discovered the compromise). Open to ideas though.


#9

Thanks for replying @jude!

Though I talked about it, it doesn’t have anything to do with the proposal itself (sorry!) as I was referencing this thread in a sorta underhanded way. I digress!

Gotcha. It is a very useful side effect however, and not one that should be turned down / away in my opinion.

I honestly didn’t know he had a PR until @hank brought it up - I should do more searching next time but thank you for clarifying what his is for (his PR description is rather lacking)!

This makes a lot of sense actually, thank you for this.

I suppose in order to have actual App Sessions for the Gaia Hub we’d have to re-do addresses completely, because the public-key address is derived from the private key, and if the private key is “lost”/leaked/stolen we cannot simply derive a new one, migrate data over, etc., as you said – this is a complicated action that would be too difficult to secure.

Instead it would have to be made via something completely other, such as a persistent address that does not require a persistent public/private key pair, and instead relies on actual session tokens. Instead of the app owning the bucket, the user would, and the user would issue a token to the app to use said persistent-address-child-bucket. (I do think this is a more straightforward way to do storage regardless, and I’m not sure why Gaia didn’t do this originally, but I’m sure there were reasons – at least for the standalone non-blockstack apps).

I’ll have to do some more thinking on this. Thank you again for your thoughts!


#10

Yep, you’re right, I misspoke.