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

How to ensure TOTP is one-time in a distributed system. #69

Open
giamma opened this issue May 22, 2023 · 5 comments
Open

How to ensure TOTP is one-time in a distributed system. #69

giamma opened this issue May 22, 2023 · 5 comments
Assignees
Labels
question Further information is requested

Comments

@giamma
Copy link

giamma commented May 22, 2023

Consider a clustered RESTful application that generates and validates TOTP using this library.

Is it sufficient to use the same seed across all replicas of the application in order to produce a TOTP that would be validated by any other replica node? In other words, is it safe to assume that each replica node, given the same configuration, should produce the same TOTP and should be able to validate the TOTP produced by any other node?

If the above is true, how to deal with the fact that once the OTP is used on one node, being it a one-time password, no other replica node should accept it?

Is this something users would need to build on top of the library? How about defining a pluggable strategy that would allow your users to store the generated TOTP in a shared storage, for example a self-expiring distributed cache based on hazelcast? If a used token was stored in a shared map (user -> token) until it expires and is removed, no other node would be able to use it.

@BastiaanJansen BastiaanJansen self-assigned this May 22, 2023
@BastiaanJansen BastiaanJansen added the question Further information is requested label May 22, 2023
@BastiaanJansen
Copy link
Owner

In other words, is it safe to assume that each replica node, given the same configuration, should produce the same TOTP and should be able to validate the TOTP produced by any other node?

Yes, all nodes will generate the same TOTP when given the same configuration. Make sure to also synchronize the system clock.

If the above is true, how to deal with the fact that once the OTP is used on one node, being it a one-time password, no other replica node should accept it?

In contrary to the name, OTP's are not only valid for one time. They are valid for the time window they are generated in +- delay window. Otherwise a user could only sign in once every X seconds. The time window only ensures that when a token is compromised, the attacker only has the token period (30 seconds by default) + delay window (default 0) to coordinate a sign in attempt.

This means that all replica nodes should accept all TOTP's in a valid window, even when the token was already used. The library has no knowledge of used tokens.
Of course if your requirements demand that a token can only be used once, you could as you suggested use shared storage to store the token for as long as it is deemed invalid. And before you verify if a token is valid, check if the token is already in the store. Redis (or Hazelcast) could be a good option. But this is not built in because this is not specified in RFC 6238.

@giamma
Copy link
Author

giamma commented May 22, 2023

Hi, thank you for the quick reply. I do believe that the one-time requirement is part of the RFC, see section 5.2,

Note that a prover may send the same OTP inside a given time-step window multiple times to a verifier. The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.

As such Hazelcast/Redis or other similar solution seems necessary to me. Maybe you could consider as an improvement the possibility for your library to allow users to implement an interface to tell whether the OTP was already used or not.

Thank you.

@BastiaanJansen
Copy link
Owner

BastiaanJansen commented May 22, 2023

You are absolutely correct, I must have missed that :).

A pluggable system is a valid option. As you said, there are many ways the developer could make sure a token is only used once. I would rather not implement many different strategies for different stores in order to not bloat the library, but one default (in-memory) implementation could be useful. The user could switch strategies when necessary. Maybe developing multiple companion libraries for different strategies (Redis / Hazelcast) would be the way to go in order to not depend on external libraries for strategies you don't use.

I do accept PR's so if you have the time to think with me / open a PR, it will be appreciated!

@Caltalys
Copy link

Caltalys commented Jun 1, 2023

In this case you need something like key manager. you can use an interface or an abstraction layer that provides a consistent interface for interacting with the key manager. Maybe database or 3rd party key manager, anh should have a in-memory manager as default.

@svschouw-bb
Copy link

The RFC suggests a delayWindow of 1 or 2 to allow for time drift. This means that at any one moment 3 or 5 tokens are valid. But given that the time drift between the user and server does not change wildly, we only need to store the actually used counter (as calculated in com.bastiaanjansen.otp.TOTPGenerator.verify(String, int)), and make sure for every subsequent validation the counter is strictly higher than the previous time. But this would require this method to return the actually used counter instead of just true/false. An external mechanism could then compare the counter against the previously used counter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants