Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Storing Private Keys in the Browser Securely (github.com/jwally)
10 points by jwally 7 months ago | hide | past | favorite | 14 comments
So the main purpose here is to show _a_ way that session-token theft can be mitigated. Clearly, this isn't NSA proof or something you'd use to secure a BL5 containment facility, but to prevent session-jacking; if feels like it could help a lot, and would be pretty quick and easy to roll out if an IDP wanted to implement it.



so I don't fully understand what you're preventing

    const db = await openDatabase();
    const keyPair = await getKeyPair(db);
    await crypto.subtle.exportKey("jwk", keyPair.privateKey)
exports the private key if I have a XSS vuln.

The recommendation for IP address in the JWT is good, but I don't understand your last recommendation of 1) sending the JWT, 2) additionally sending the base64 JWT in a header 3) sending the signature in the header. The crypto.subtle api only works on https domains so you're not defending against mitm attacks on unsecure networks either. And if we can't trust TLS what can we trust on the web?


> The recommendation for IP address in the JWT is good, but I don't understand your last recommendation of 1) sending the JWT, 2) additionally sending the base64 JWT in a header 3) sending the signature in the header. The crypto.subtle api only works on https domains so you're not defending against mitm attacks on unsecure networks either. And if we can't trust TLS what can we trust on the web?

So here I'm basically trying to describe what changes you would make to the way JWT validation is currently done. IIRC, the JWT goes over in the headers. I'm suggesting adding 2 headers - one with the base64 of the json of the JWT, the other header is the ecdsa signature of that.

This way, you know whoever it is that logged in and provided their signed public key, has to be sending this request because they signed it with the same key - which is locked to _this_ browser.


I'm confident then that you can skip the base64 encoded header and just have the server use the jwt passed in the bearer token and the new signature you propose. (As the base64 encoded version can be reconstructed from the JWT itself)

But I think ideally I would use a wrapped JWT with `"alg": "ES256"` and just pass it as normal in a bearer token[0] as JWTs natively support signed primitives.

[0]: https://jwt.io/#debugger-io?token=eyJhbGciOiJFUzI1NiIsInR5cC...


tbh, I haven't worked with JWT's a _ton_, so apologies if there's an _obvious_ better way to do something, lol.

I think you're right. Just sign the JWT that's going over as a header (as its a string), and add a signature from the webcrypto pieces - and BAM! you can verify that the jwt came from who it was originally assigned to...unless I'm missing something.


Rather than generating key data on the client in the open, and storing it in IDB, I would recommend the Credential Management API[0]. Hand off the responsibility to proper generation and storage to the user agent. Then do your signing of the JWT with them instead.

[0]: https://developer.mozilla.org/en-US/docs/Web/API/Credential_...


I'll have to check that out, thanks!

If it can store live objects - its perfect.

IDB is neat because it can store a PrivateKey Object whose `extractable` attribute has been set to false. So when you try to see the crypto data, you cannot.


When you `.get` a credential you can provide a challenge that it signs which you can make the JWT. With an added bonus that this passkey can exist on your phone or password manager which you can use to authenticate on a different device while still feeling confident in it's security.


TBH, I'm not an expert here.

What you're describing looks like webauthn which is used to verify the identity of a user by creating a private key on their HSM/TPM when the user signs up, and usually requires biometrics or a PIN iirc. This is used for future authentication events - which usually return a JWT.

This JWT that says "My name is Justin. I am logged in. I am an admin".

What I'm trying to solve for is "Make it so that the JWT doesn't work, except with the computer it was issued to".

In the setup I'm proposing, the JWT your server creates has your client's webcrypto Public Key in it (Naturally you verify it before putting it in there).

Now, whoever steals your JWT needs to be able to sign things with the private key that's locked on your browser - which is hard if you set it to inextractable.


Sounds like you are trying to prevent replay attacks.

How do you imagine JWTs are being stolen in the first place though? XSS sneaky websites or someone over the shoulder.

Just seems that if the attacker is all up in your browser extensions can't they just inject email and password text elements into the dom and see what gets filled by the browser saved logins?


Its not so much replay attacks I'm trying to solve for here ( although putting the instantiating user's IP address in the JWT seems like it would do a lot to thwart that )

I think the main thing here is preventing anyone from using my JWT who isn't on my browser.

Even if I'm on a site that leaks data via xss, and have several plugins that broadcast my cookies, localstorage, etc - and my live JWT and refresh tokens make it into the hands of bad guys; its worthless in the setup I'm proposing - I think...


OH FFS!!!!

Serves me right having ChatGPT add commentary and me not double checking.

This is what it should be:

          const keyPair = await crypto.subtle.generateKey(
            { name: "ECDSA", namedCurve: "P-256" },
            false, // this makes it not extractable
            ["sign", "verify"]
          );
Run that in HTTPS (here if you want) and try to extract the private key - I don't think you can, but could be wrong.


Yeah that does it for new keys generated, any old keys in IDB obviously still are exposed.


Hat off, You made it! After reading and skimming Show HN: Device-Bound Session Tokens in JavaScript ( https://news.ycombinator.com/item?id=40052684 ) I had "same" idea to explore PoC, but never done. Thanks!


Its an extremely solid idea, fast, and able to be rolled out without any modifications to the browser.

I'd love to see IDPs adopt the concept. Coupled with passkeys; it seems like it would be extremely difficult to break into someone else's account (not impossible, but this definitely adds an order or 2 of difficulty)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: