-
-
Notifications
You must be signed in to change notification settings - Fork 131
proposal: secure sessions via per-device key pairs #2243
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
base: master
Are you sure you want to change the base?
proposal: secure sessions via per-device key pairs #2243
Conversation
9838d28
to
bfdf487
Compare
bfdf487
to
3da82a8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far! Left some comments.
I also think it needs more details about:
- key exchange between client and server
- what exactly do you sign in a request (only body or also headers?)
- which requests are signed (only requests to /api/graphql?)
An example flow can be: | ||
``` | ||
1. Device generates key pair | ||
2. On login, server creates an ECDH key pair |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why generate a new key pair for every user on the server?
|
||
ECDSA, much like the ECDH route above, can be used to generate a key pair on the first visit and share its public key with the server, to bind it to the user we're importing from `stacker.news`. | ||
|
||
ECDSA can be used to sign every request or JWTs, the server will then verify the signature and accept the request if the signature is valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would signing JWTs help when they get stolen?
4. On each requests, client signs a message with private key | ||
5. Server verifies the JWT and the signature, proving possession |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to include the timestamp in the request payload and sign it, else the attacker can replay signed requests.
The server also needs to verify that the timestamp is not too old.
This means that client and server clock must be in sync though, but I think we can live with that.
|
||
As a form of authentication, we can use ECDH shared secrets to either | ||
- sign requests for the GraphQL endpoint | ||
- encrypt JWT payloads |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does encrypting the JWTs help with authentication?
You mentioned in the example flow below that the server encrypts the JWT and the device decrypts it.
But the device still sends the JWT to the server on each request (encrypted or decrypted, doesn't really matter). So the attacker can still steal and use them, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmh, you're right I got off track on the ECDH route. I was thinking that the shared secret should be used to create an HMAC to sign each request, the server will then verify the HMAC and check its timestamp.
``` | ||
1. Device generates key pair | ||
2. On login, server creates an ECDH key pair | ||
3. Client and server exchanges public keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How?
A few questions:
Assuming (2) is correct, it seems like our fundamental problem is that browsers trust the domain, which leads me to ask:
I feel like we're jumping pretty fast to solutions without enumerating and understanding the problem still. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- is that true, do browsers always trust the domain? tls certs can change, etc, and browser local storage is usable by the client and cookies and sessions are still sent to the domain?
I'll start here because (2) is correct
AFAIK browsers only check the domain, they don't care for TLS changes, and everything that we may store there, can be stolen. Tokens are as secure as where they are stored.
- are there situations or browser features/storage don't trust the domain and trust the tls cert instead?
Not so long ago, browsers used to somewhat support Token Binding, the idea was to move access tokens to the TLS layer: the token would be cryptographically bound to the TLS connection, making it impossible to replay tokens across different connections.
However this extension was deprecated and removed from browsers (it was neat though).
I mean, there's also mTLS but it's also the worst UX we can get.
- is there something we can do cryptographically that makes a browser trusting the domain irrelvant?
I think that we can't do much about this, as we established that anything client-side can be stolen.
device+domain-specific pubkeys on the server and require clients sign requests with the private key?
If we ignore the fact that the private key can be stolen, this is where ECDSA is a great fit, as we can use the private key to sign requests and the public key to verify them. We could apply mitigations such as dns polling, to automatically revoke the key if it's compromised.
The observation that @ekzyis made on this is correct:
You need to include the timestamp in the request payload and sign it, else the attacker can replay signed requests.
Even if the keys are non-extractable, the attacker could theoretically sign the requests anyway without extracting them... although by the time the attacker can pull this off, we probably have catched it via DNS polling.
There is also the refresh token fallback plan, implementing short-lived session tokens with refresh tokens stored on stacker.news. We would still need mitigations, like auto-revocation if DNS polling detects the wrong CNAME.
*just to be clear, inextractable crypto keys in the browser can't be stolen/read, even by the domain owner, but they can be used to sign things. |
If we are reliant on DNS polling, not just at a point in time but continuously, then this security model is very probabilistic and we need to figure out what the failure conditions of dns polling look like and how probable those failure conditions are before we decide that it's worth proceeding. |
Provided that we need DNS polling in one form or another to ensure that we don't ever let an attacker get away with it, we'd want to do it good. Our intention for DNS polling shouldn't be to treat it as the source of truth, verifying every request, rather a mechanism to revoke access.
Agree, some of the things we can do are:
|
Again, we should not be okay with guessing about the effectiveness of dns polling ... This solution is insecure (we've all agreed on that), and we do not know how insecure exactly because it relies on dns always being timely and accurate and we know dns is not always timely and accurate, but do not know to what extent dns is untimely and inaccurate. Until we know such things, the crux of our security here is a big 5 minute guess, and a big 5 minute guess leaves our system too vulnerable for our threat level imo. Because if we're guessing, I'm going to guess we are vulnerable to the worst case: this solution lets an attacker steal customer funds for 5 minutes. |
Description
Proposal for securing sessions using per-device key pairs.
Cross-domain JWT sessions, as-is, are vulnerable to malicious custom domain owners, this proposal explores ECDH and ECDSA routes to avoid session token replays.
Additional Context
Document in draft subject to change, contains only high-level flow