-
Notifications
You must be signed in to change notification settings - Fork 237
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
Disallow subsequent OTPs once validated via timesteps #43
Disallow subsequent OTPs once validated via timesteps #43
Conversation
In order to be fully RFC 6238 compliant for TOTPs, no valid OTP may be used more than once for a given timestep. By storing the timestep of the last successfully validated OTP, we achieve this desired behaviour. TOTP#timecode is a private method, so we had to duplicate it in order to obtain the current server timestep. Thankfully it's simply time divided by interval. I was having issues with using `resource.save!` in the strategy class as a means of object persistence to the database. Ultimately, I opted to use the ActiveRecord `save()` method inside the devise model which is what Devise does in its own model definitions. Burning the timestep bumps the `updated_at` value for the model. Not sure if this is a feature or bug. Refs devise-two-factor#30
This change technically prevents a replay attack by those who can shoulder-surf or MiTM the victim. Going to request a CVE ID CVE requested. |
@@ -44,11 +44,25 @@ | |||
expect(subject.valid_otp?(otp)).to be true | |||
end | |||
|
|||
it 'does not validate a previously precisely correct OTP' do |
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.
I would probably move these new specs into a separate context, then move the setup logic into a before block. Something like:
context 'with a stored consumed_timestep' do
context 'given a precisely correct OTP'
let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now) }
before do
subject.valid_otp?(consumed_otp)
end
it 'fails to validate' do
expect(subject.valid_otp?(consumed_otp)).to be false
end
end
context 'given a previously valid OTP within the allowed drift' do
let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true) }
before do
subject.valid_otp?(consumed_otp)
end
it 'fails to validate' do
expect(subject.valid_otp?(consumed_otp).to be false
end
end
end
It make it clearer, at a glance, what part of the spec is the setup, and what part is the expectation.
Your changes make sense to me. I'm a little worried about adding side effects to I also wonder what the best approach is for telling our existing users that they'll need to add a new column. New users are fine, since the generator will handle that, but anyone else will need a new migration. I suspect we'll be able to get away with just making a new generator for the new column. Thanks for the contribution! |
For those who are just upgrading, possible to provide a post-upgrade message in bundler? I think I've seen that done before. |
I'll be addressing your comments and update the PR when I get some time Friday |
Awesome, thanks! I would be okay with a post-upgrade message. |
Renaming the methods for better semantics and Rubyisms
As for notifying users of the migration change, I'm still trying to find the "best practice" for doing so. Right now I think just a Post install message seems a little heavy-handed an ill-fitting, and there's no post-upgrade functionality in gemspec. |
Yeah, I agree that a post-install message may not be optimal, although it could caution that the installer should check the github for the latest upgrade recommendations. I've liked it when repositories that have an UPGRADING file with the major breaking changes between patch levels. |
I could also potentially turn on the Github Wiki for this repo, if that would be a better place. |
If I understand Devise's gem correctly, by virtue of adding That combined with a README announcing the breaking change should be sufficient. I feel like this PR is ready to go, with y'all doing the necessary README (or wiki) updating and version bumping as required. |
Thanks Justin! This looks good to me. I'll just wait for one of my coworkers to take a look too, in case something slipped past me. Sorry about the late response, I wasn't at my computer all weekend :) |
Disallow subsequent OTPs once validated via timesteps
Looks good to me. Thank you for the contribution! We'll push out a new version (probably a major version bump) as soon as we can. |
Assigned CVE-2015-7225. |
Bump devise-two-factor to 2.0.0 Addresses internal https://dev.gitlab.org/gitlab/gitlabhq/issues/2605 See devise-two-factor/devise-two-factor#43 See merge request !1363
…valid-otps Disallow subsequent OTPs once validated via timesteps
In order to be fully RFC 6238 compliant for TOTPs, no valid OTP may be used more than once for a given timestep. By storing the timestep of the last successfully validated OTP, we achieve this desired behaviour.
TOTP#timecode is a private method, so we had to duplicate it in order to obtain the current server timestep. Thankfully it's simply time divided by interval.
I was having issues with using
resource.save!
in the strategy class as a means of object persistence to the database. Ultimately, I opted to use the ActiveRecordsave()
method inside the devise model which is what Devise does in its own model definitions.Burning the timestep bumps the
updated_at
value for the model. Not sure if this is a feature or bug.Shitty .gif of it working in the demo app:
![burned-otps](https://cloud.githubusercontent.com/assets/740289/9697650/233f1950-5363-11e5-889a-ccf6a000173d.gif)
Refs #30