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

MVP Recommendation: TOTP #36

Open
wparad opened this issue Apr 8, 2024 · 9 comments
Open

MVP Recommendation: TOTP #36

wparad opened this issue Apr 8, 2024 · 9 comments

Comments

@wparad
Copy link

wparad commented Apr 8, 2024

I made a comment about how to significantly reduce the complexity of the flow here: #23 (comment)

But the general response of #23 is "this is just the first step."

However the long term doesn't really align with this proposal as it is. A much better first step would be:

Use a header.
navigator.secureSession.start creates a seed for TOTP and sends that to the registration request for a session. Then on every subsequent request to the server a new TOTP code could be generated and sent as an additional header.

Over time more strategies instead of TOTP could be implemented including ones that support TPMs and signatures, but this would allow validation on the service side and use the framework on the client side without significant changes anywhere along the line.

I would strongly advise against changing the mechanism for setting cookies.

@arnar
Copy link
Collaborator

arnar commented Apr 8, 2024

I think this boils down to proposing that DBSC supports establishing a symmetric key (the TOTP seed), and issues a challenge-less signature based on the client time (or some epoch counter) to each request.

The threat model (post-login malware) mandates that the symmetric key can be protected better than stored cookies - otherwise there is no gain. That might work but means that we're still talking about TPMs or system-level protections, so there are still some significant performance considerations even for symmetric keys.

A challenge-less approach is vulnerable to transient malware: Temporary access to the signing oracle would allow malware to generate and exfiltrate future TOTP hashes at will. Granted, this is also the case in the currently proposed DBSC if servers choose to not bother with challenges.

We could explore a mode of this where Sec-Secure-Session-Challenge response header values are embedded in the signature here (it wouldn't be TOTP anymore, but that's ok), freeing the browser from having to do any round-trips for refreshes. That's an option worth exploring as an additional type of instruction from the registration/refresh endpoint, and might be a good option for servers that can afford new logic on existing endpoints.

I would strongly advise against changing the mechanism for setting cookies.

Nothing in the current DBSC proposal suggests changing any semantics of either the Set-Cookie or Cookie headers. The only cookie-related functionality it proposes is that a browser can be instructed to look for the presence of a cookie, and consult the refresh endpoint when a new one is needed. All other cookie handling, including how the refresh endpoint issues those cookies, is done via the standard headers without any modification.

@wparad
Copy link
Author

wparad commented Apr 9, 2024

I wrote a lot of other things here, but I think it would be more helpful to jump to an actual concrete proposal:

And it's important for first agree on what the long term is, we can't make sure we are picking the best short term, until there is alignment on the long term. Otherwise we'll end up building things in the short term that aren't needed but due to the fact of the "web" we'll never be able to remove them.

So I guess here is my proposal:

The whole refresh session call can all be abstracted into the browser, the server side doesn't need to care about any of this, no refresh session endpoint, etc... There are only two pieces of data the server needs to care about:

  1. A header appended to every request to the server so that every request sent to the service can be verified, in essence actually achieve Device Bound Credentials (not just Device Bound Session Credentials)
  2. Some data passed to the session creation endpoint in the service POST /session that includes the public key that will be used to verify (1)

I'm not following why the service would need to have a POST /securesession/refresh endpoint. The service already has a way to extend session credentials and issue new access tokens (which may or may not be cookies), forcing the implementation of a duplicate endpoint POST /securesession/refresh is just obnoxious. All this proposal needs to do is send these additional headers on already existing requests to the service.

For reference, this whole middle part of this diagram seems hugely complex with a lot of extra unnecessary communication between the client and the service. I'm hoping we can eliminate all of this:

image

There are for sure current limitations of current technology to support my proposal, but if there is agreement on this optimal future state of the reality, then we learn a few things:

  • We must not require services to introduce additional endpoints
  • We must not need a requirement to refresh session credential being stored by the client
  • The strategy must secure all endpoints not just the one to refresh the session credential

Again, I think it's important for agreement on the future most optimistic case, because right now the current repo proposal for DBSC dances around "well, ummm, TPM aren't that performant". Well okay, no problem, there are ways around those sorts of technical issues, for instance, why not do X or Y, but remember "here is the long term that we want".

Right now it feels very much like the ultimate goal is undefined and we are just trying to throw "something" out there. Which I do totally get, but that something includes introducing the complexity of "another endpoint on the service side" which doesn't make any sense to me. Since the future I see doesn't include a need for that endpoint, it seems wrong to require the introduction at any stage.


I'd must prefer to have that conversation first about the long term solution landscape here, and then figure out what the iterative path to get there. What seems to be happening right now is "let's introduce this thing that we think will be a stepping stone", because realistically I'm struggling to see how session bound credentials is better than the current state. As evidence for that refresh_token rotation in OAuth2.0 is a viable option and most if not all non-identity providers don't use that. And if you are using that, then automatically rotating session credentials seem to make this stepping stone almost completely unnecessary, right? And if someone isn't using rotating session credentials already, then it's hard to argue that they would move to this which basically does the exact same thing.

@el1s7
Copy link

el1s7 commented Apr 10, 2024

I agree with @wparad, after taking a look at this, it seems it's overly-complex and not clear, for something that should only serve a simple purpose: "proving the user requests are coming from his browser where he initially logged in".

I think maybe instead of the current approach (which I'm not sure I understood very well) this could be done with just some extra security headers. Here's my proposal:


  1. When the server initially sets the cookies for the first time on the client, such as when it's starting a user login session or identifying the visitor, it can additionally ask the browser to start a secure session by returning a special header on the response alongside the "Set-Cookie". Also, it can return a header for specifying when the browser should return the secure session public/symmetric key information, for example:

Set-Cookie: user_session_id_bla_bla=...
Sec-Secure-Session-Start: 1
Sec-Secure-Session-Start-Url: /example/user/logged_in/welcome

  1. The browser then creates a new secure session.
    And on the next client request that matches the provided URL, the browser adds the headers for informing the server that a secure session has started and "this is the public/symmetric key" (the key is only sent once when a session starts). No additional API requests made, all communication is done easily through headers.

Example headers returned only once on the initial session start at the specified URL:

Sec-Secure-Session-Key: xxxxx...

This key is then saved by the server and tied to the current user session internally on the server.

  1. After session is started, an encrypted token/jwt with an expiry time is generated securely by the browser on a configured interval that doesn't impact performance, and this is used for verification/proof-of-possession, and it's added on all next, future requests.

Example header returned on every request after session stars:

Sec-Secure-Verification: xxxx...

The server then verifies this token, and checks that is not expired.
The browser keeps the secure session until the cookies for that website are cleared, so the session is closed when the cookies are deleted.


Using headers instead of communicating with additional HTTP requests seems more simpler to me, and more fit for a browser protocol, historically new protocols have been introduced using headers. Doesn't seem necessary to do all the other complex extra work, such as the refreshing mechanism. Keep it simple, easy to adapt by websites.

And, in the end, it's all up to the servers to decide if they will implement and respect this security protocol or not.

P.S. Thanks to the team for your work on this and the idea 👍

@arnar
Copy link
Collaborator

arnar commented Apr 10, 2024

First, let me reiterate to be clear: Generating the short-term tokens without server round trips is a good idea, I regret if you felt that was dismissed or misunderstood in my previous reply.

The complexity of the server-round trip and using a plain old cookie is there for a reason. It has nothing to do with signing performance, but for retrofitting binding to existing infrastructure, whether it's legacy systems that are hard to change, or new web apps built on stacks where you can't (or don't want to) select a full stack that supports a new kind of authentication. It's not only about a new header vs. a cookie either, as you need to efficiently do the signature validation of such a new header, although that's more solvable (either you just do it for every request and eat the cost, or you build some scalable caches).

We've tried mapping the work and coordination to do this in our setting, and it was not feasible or realistic due to the number of coordinated migrations that would have to happen.

I don't think a thing like DBSC can take off unless it is realistically deployable within such existing real-world constraints. For settings where you can do anything you want we should definitely discuss if that should be additionally supported.

@wparad
Copy link
Author

wparad commented Apr 10, 2024

I don't think a thing like DBSC can take off unless it is realistically deployable within such existing real-world constraints. For settings where you can do anything you want we should definitely discuss if that should be additionally supported.

There are two really important things here that I think have been left out of the evaluation that I think is imperative we add back in:

  • Many businesses do not control their authentication solution, that means that instead of any of the constraints of "difficulty" of a migration their constraint is "Auth provider X must support this". And many if not all auth providers (other than Firebase) are OAuth based, and the current solution directly conflicts with OAuth, which means that in order for this to actually be rolled out to many apps, the "Most used identity providers" will need to support this now conflicting scheme. This is why the recommendation for OAuth 2 compliance makes sense, and really it would be almost a requirement for it to go through the IETF OAuth Working Group, and for sure it can (I know there are at least 2 other WG members already contributing to this proposal, who are also clearly excited on this topic). For instance you could come to the WG and say "Hey we want DPoP but TPMs calls are too expensive, what alternatives do we have here to support technology available today"
  • My company is one of these "most used identity providers", which means we have a huge amount of control over both the client side SDK and the service side for auth, but what we do not have is the desire to implement multiple different solutions over time, non standard ones that are implemented differently in each browser, support half solutions or ones that don't improve security, or ones that are created without considering the long term. A complaint, albeit complex and non-trivial solution that completely solves the problem, sign us up!

So I think I want to push back on:

We've tried mapping the work and coordination to do this in our setting, and it was not feasible or realistic due to the number of coordinated migrations that would have to happen.

Because realistically it seems like there are both simpler and more complex solutions that make this easier for both the client side and the service side. But without understanding exactly the "implementation example" you are relying on, which is trying to be solved with this the current solution, right now we are looking at something that doesn't add any security for a lot of implementations or the ease of roll-out. I.e. We know what the "solution" looks like from what is in the Explainer, and we know that this "solution" is only a very small part of the real solution (as you have graciously clarified), but we have no idea why the "real solution" isn't being rolled out and where that complexity lies (as per your latest comment).

So, unfortunately to me, if feels like what is being said is We have a use case we want to solve with this and are unwilling share what that use case looks like or consider alternative proposals here. Now, I'm sure that isn't the case, but it's hard to know what the best path to continue here is. Perhaps a recommendation is getting that "example app user story" published in the Explainer, so that others can better collaborate, however I fear the criteria will be "but we can't change any app code or any server code to make this happen", which just feels like, why is this even a public proposal.

Speaking from the perspective of my company with an IdP as a Service, we absolutely excited that a browser is finally onboard here and desperately want to implement something to better improve the security of our sessions for our customers/users, but it has to provide Device Bound Credentials (not Session Credentials) and it needs to be someone complaint with OAuth2 in really any way, at the very least an addition and not an official RFC, but speaking in terms of OAuth2 concepts and flows seems like table stakes.

And I really appreciate your patience in all of these back and forths.
Thank you,
Warren

@arnar
Copy link
Collaborator

arnar commented Apr 12, 2024

I appreciate your patience as well!

This is why the recommendation for OAuth 2 compliance makes sense

I agree with this, but I would like to learn more how session auth on the web is based on OAuth 2 (if it goes beyond just issuing an access token in a cookie), and why DBSC driven refresh isn't in your view bringing us closer to OAuth 2 rather than further away from it.

I ask because our initial motivation for this design is actually in OAuth 2 settings is that we were successful in creating binding for our native app credentials, which are OAuth 2 (the only caveat is that in our setting there is full trust between the AS and RSes, so a lot of problems that OAuth 2 solves for aren't of concern for us). For native apps on Android we bound the refresh tokens to device keys but left access tokens unbound, which enabled scale, and we already had centralized management of the RT->AT exchanges, which enabled the migration without migrating every Google Android app.

(Also note when we say "session auth", we mean specifically not the user authentication flow. There are separate thoughts on how to integrate DBSC session establishment with user auth methods, such as OIDC, WebAuthn, etc. for added assurances.)

@wparad
Copy link
Author

wparad commented Apr 13, 2024

OAuth2 works with the following required user-device <=> server communication:

  • Start Authentication Request (canonically /authorize endpoint) - Receives a one time use code and ~5 other custom properties
  • Complete Authenticate Request (canonically /tokens endpoint) - Receives a refresh_token and an access_token
  • Request new access token using the refresh_token (canonically /tokens endpoint again)

To be in alignment with OAuth to work, we must utilize only these two endpoints /authorize and /tokens and only exactly in the flow when expected to work with most apps out there today.

This means that "out of bound refresh that happens" must be using the existing endpoints. Specifically:

  • The /securesession/start endpoint referenced in the DBSC must instead be the call to the /tokens endpoint that the site is already making, and it must include the one time use code as well as PKCE, Issuer, etc... properties defined by the OAuth server. The best way to achieve this to ask the browser navigator.getPubilcKeyHashFromTMP and then the site itself would take that and send it in the first /tokens request.
  • To refresh the access_token, the site is already calling the /tokens endpoint using the existing refresh_token. Which means to be in alignment with that from the DBSC proposal, DBSC must offer a solution where the site can call navigate.getSignedDataFromTPM. At this point the only thing that is OAuth compliant AND available to be signed is the session credential also known as the refresh_token. Requiring a call to the Authorization Server to get a nonce violates OAuth2 and thus cannot be implemented.

But this also incredibly simplifies what the browser has to do, because it only needs to support two new methods:

  • getTPMPublicKey (or getTMPPublicKeyHash) which the client side SDK/OAuth2 library/etc... will call and pass in the OAuth request to the first call to the /tokens endpoint
  • signDataUsingTMP(refresh_token) - Get a signature for the refresh_token using the private key on the TPM, and the client side SKD/OAuth2 library/etc... will pass this in the OAuth request to the subsequent calls to the /tokens endpoint.

At no point does the browser need to make any additional HTTP Requests on behalf of the site, be told how the site functions or given anything to be saved.


Everything else will be magically handled by the Authorization Server, there is no need for special nonces at all at any time, this is because the Authorization Server will:

  • Save the public key on the service side along with the hash of the Refresh_token when the /tokens endpoint is called the first time
  • Verify the signature passed to the /tokens endpoint the second time using the public key sent the first time. The signature would need to include:
  1. Timestamp
  2. Hash(Public key) or some other identifier
  3. Hash(Session credential)
  4. [optional] but probably some other custom properties that are passed to the signDataUsingTMP.

@tbroyer
Copy link

tbroyer commented Apr 13, 2024

Ive been reading issues and comments for a few days, and I must say I'm having a hard time following your thoughts @wparad.

To me, DBSC is orthogonal to OAuth2 or OIDC. As I understand it, DBSC is only about "session cookies". In an OAuth2/OIDC context, "session cookies" can be used at the AS/IdP/OP or RS/RP sides, and each side can use DBSC (or not) to secure them. Also, access tokens, and refreshing them, is (almost) orthogonal to "sessions"; access tokens are about calling third-party web APIs, so if a site doesn't call third-party APIs (besides OIDC UserInfo endpoint), they don't need to deal with access tokens (and signing out from the IdP/OP can be communicated to the site/RP through the various OIDC Logout extensions). Things are similar with SAML2 or Apereo CAS, or even FedCM (although FedCM is client-side, so entirely between the User-Agent and the IdP, but generally followed by establishing a session on the RP)

There are many different architectures, but looking at https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps so we can share the same terminology, we can see that cookie-based session management is used in both the BFF and Token Mediating architectures, and those sessions can thus be protected with DBSC.
It's not used at all in the Browser-based OAuth 2 Client architecture, which is vulnerable to refresh token and access token theft, and if you're concerned about those, then I'd say use DPoP, or change to use one of the other architectures, and then use DBSC to protect the session cookies.

As a last note: I wouldn't want that a JS API be required; the current HTTP-header-based DBSC proposal looks great.

@wparad
Copy link
Author

wparad commented Apr 13, 2024

It's not used at all in the Browser-based OAuth 2 Client architecture, which is vulnerable to refresh token and access token theft, and if you're concerned about those, then I'd say use DPoP, or change to use one of the other architectures, and then use DBSC to protect the session cookies.

This is exactly the problem, browsers but really any user device, can't use DPoP without support from that browser. All that needs to happen here is that instead of the duplicating DPoP at the browser level, just expose the JS interface to allow the site to perform the equivalent to the DBSC binding using the already existing DPoP semantics.

The first step for DBSC is really "create the JS interface so that anyone can call the TPM", AND then the second step could be "Browser supports a full alternative to DPoP". The problem here is that the current proposal for DBSC in the explainer prevents the usage of DPoP in the browser.

It is exactly the "user-agent <=> IdP" refresh_token flow that I want to secure with DBSC. You are saying it is orthogonal, it's only orthogonal because the current suggested implementation of DBSC in the explainer ignores the fact that OAuth already exists and could be simply utilized to achieve the same security DBSC wants to create without needing to also create a strategy for the browser to call the backend service via a back channel.

All that we need to see happen here is "Browser support TPM integration standard that doesn't require user-verification as the current fido2 implementation basically requires."

Or maybe a better way to say this is, Please support TPM integration for WebAuthN in a completely silent mediation mode that doesn't require user verification.

And if we do that both DBSC and DPoP in the browsers (and really all devices) will be automatically enabled into the security that DBSC wants to provide.

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

4 participants