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

Add initial Steam TOTP support #1206

Merged
merged 1 commit into from
Nov 21, 2017

Conversation

joelsmith
Copy link
Contributor

@joelsmith joelsmith commented Nov 20, 2017

Add initial Steam TOTP support

  • Add the concept of custom TOTP encoders, each with potential for custom
    code alphabet, length, step interval and code direction (i.e. reversed)
  • Select custom encoder via overload of the digits field of a loaded entry
  • Allow selection of custom encoders via the "TOTP Settings" field's
    size, as currently done by KeeTrayTOTP for Steam. Use "S" for the
    short name of the Steam custom encoder
  • Allow selection of custom encoders via the "otp" field by appending
    a "&encoder=" field to the URL query. For example,
    "&encoder=steam"
  • Update TOTP set-up dialog to permit selection between (default,
    steam, custom) settings.

Description

Adds the ability to generate Steam Guard TOTP codes using the secret
associated with a Steam account.

Motivation and context

Fixes #594

How has this been tested?

Unit tests added

Tested existing TOTP entries

Added new Steam TOTP entries and verified code output

Screenshots (if appropriate):

screenshot_20171120_112911

screenshot_20171120_113540

Types of changes

  • ✅ New feature (non-breaking change which adds functionality)

Checklist:

  • ✅ I have read the CONTRIBUTING document. [REQUIRED]
  • ✅ My code follows the code style of this project. [REQUIRED]
  • ✅ All new and existing tests passed. [REQUIRED]
  • ✅ I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
  • ✅ I have added tests to cover my changes.

@joelsmith joelsmith force-pushed the feature/steamtotp branch 2 times, most recently from 3f78d25 to 34bc71b Compare November 20, 2017 19:00
@weslly weslly self-requested a review November 20, 2017 19:06
@weslly
Copy link
Contributor

weslly commented Nov 20, 2017

Looks pretty good! I just tested and it works, but the timing seems to be a few seconds off (about 10sec). My guess is the mobile app uses the server_time value as the offset instead of the device time, is this correct?

@joelsmith
Copy link
Contributor Author

joelsmith commented Nov 20, 2017

This relies upon the local device time being correct. I'm not sure if it's better to try to figure out the right time before generating a code or to just have a requirement that the user keep their system time up to date.

For what it's worth, when I run it on my PC, it's sync'ed pretty much perfectly with my phone's Steam app.

@weslly
Copy link
Contributor

weslly commented Nov 20, 2017

Actually my phone clock was a few seconds ahead from my computer time, I updated it and the codes are changing at the same time.

@weslly weslly requested a review from a team November 20, 2017 20:51
QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp);
QString digitsRx = "(?:\\d+)";
// build a regex that supports any encoders that we support.
foreach (QTotp::Encoder encoder, QTotp::encoders) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use the foreach macro, use C++11 range-based for loops (remember to cast to const to avoid detaching).

foreach (QTotp::Encoder encoder, QTotp::encoders) {
digitsRx.append("|").append(encoder.shortName);
}
QRegExp rx(QString("(\\d+);(%1)").arg(digitsRx));
Copy link
Member

@phoerious phoerious Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use QRegularExpression, not QRegExp. I'm also not too big a fan of dynamically assembled regular expressions. They can easily introduce bugs and even security issues if not escaped properly.


bool isDefault = step == QTotp::defaultStep &&
digits == QTotp::defaultDigits;
bool isSteam = digits == QTotp::ENCODER_STEAM;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use brackets here to visually separate the parts of these statements better.

@@ -31,6 +31,28 @@
const quint8 QTotp::defaultStep = 30;
const quint8 QTotp::defaultDigits = 6;

// Custom encoder types. Each should be unique and >= 128 and < 255
// Values have no meaning outside of keepassxc
const quint8 QTotp::ENCODER_STEAM = 254;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use

/**
 * ...
 */

doc comments.

const quint8& digits,
const quint8& step)
quint8 digits,
quint8 step)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😁

for (QMap<quint8, QTotp::Encoder>::const_iterator iter = QTotp::encoders.cbegin(); iter != QTotp::encoders.cend(); ++iter) {
const QTotp::Encoder& enc = iter.value();
QVERIFY2(enc.digits != 0,
qPrintable(QString("Custom encoders cannot have zero-value for digits field: %1(%2)").arg(enc.name).arg(iter.key())));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please shorten the lines here. Insert line breaks after arg() calls like so:

qPrintable(QString("Custom encoders cannot have zero-value for digits field: %1(%2)")
                   .arg(enc.name)
                   .arg(iter.key())));

qPrintable(QString("nameToEncoder doesn't reference this custome encoder: %1(%2) %3").arg(enc.name).arg(iter.key()).arg(enc.shortName)));
}

for (QMap<QString, quint8>::const_iterator iter = QTotp::nameToEncoder.cbegin(); iter != QTotp::nameToEncoder.cend(); ++iter) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use range-based for on QTotp::nameToEncoder.keys().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to change it (especially since the data set is small).

IMO, that's one that Qt got wrong. Doing an allocation to create a temporary list of keys, then iterating over the keys then doing a map lookup to get the value makes what should be an O(n) operation into an O(n*log(n)) operation. But Qt doesn't provide a way to iterate over the key/value pair in O(n) except by using the iterators in a way that isn't compatible with range-based for loops.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keys() approach is a lot more readable, though. Not too happy with either solution. I guess it's fine as is.

qPrintable(QString("nameToEncoder doesn't reference the right custom encoder: %1(%2)").arg(iter.value()).arg(iter.key())));
}

for (QMap<QString, quint8>::const_iterator iter = QTotp::shortNameToEncoder.cbegin(); iter != QTotp::shortNameToEncoder.cend(); ++iter) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dito.

QString secret = "otpauth://totp/"
"test:[email protected]?secret=63BEDWCQZKTQWPESARIERL5DTTQFCJTK&issuer=Valve&algorithm="
"SHA1&digits=5&period=30&encoder=steam";
QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any official references which say that these values are correct? If yes, please reference them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are not. I created a throw-away Steam account, then used their Steam app and captured their codes at two separate times. Should I add a note to that effect?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps not a bad idea.

@joelsmith
Copy link
Contributor Author

@phoerious changes pushed based upon your feedback. PTAL.

Copy link
Member

@phoerious phoerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot something. 😉

// in the corresponding Encoder
// NOTE: when updating this map, a corresponding edit to the settings regex must be made
// in Entry::totpSeed()
const QMap<QString, quint8> QTotp::shortNameToEncoder{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use doc comments here, too.

// These map the "encoder=" URL parameter of the "otp" field to our internal encoder number
// that overloads the digits field. Make sure that the key matches the name value
// in the corresponding Encoder
const QMap<QString, quint8> QTotp::nameToEncoder{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

@adolfogc
Copy link
Contributor

Will this be a good time to, respectively, rename src/totp/totp.h and src/totp/totp.cpp to src/totp/QTotp.h and src/totp/QTotp.cpp?

@joelsmith
Copy link
Contributor Author

@phoerious thanks for your further review. I have added the comments that I missed. If you're happy with the changes, I'll rebase and squash.

@joelsmith
Copy link
Contributor Author

@adolfogc IMO, the file and the class should be renamed Totp since it has nothing to do with Qt. I'd say that now is a fine time for a rename (but in a separate PR).

@phoerious
Copy link
Member

phoerious commented Nov 21, 2017

I agree that QTotp should be renamed to Totp. QTotp is totally misleading. I think that should be done in a separate PR, though.

* Add the concept of custom TOTP encoders, each with potential for custom
  code alphabet, length, step interval and code direction (i.e. reversed)
* Select custom encoder via overload of the digits field of a loaded entry
* Allow selection of custom encoders via the "TOTP Settings" field's
  size, as currently done by KeeTrayTOTP for Steam. Use "S" for the
  short name of the Steam custom encoder
* Allow selection of custom encoders via the "otp" field by appending
  a "&encoder=<name>" field to the URL query. For example,
  "&encoder=steam"
* Update TOTP set-up dialog to permit selection between (default,
  steam, custom) settings.
@joelsmith
Copy link
Contributor Author

rebased/squashed

@adolfogc
Copy link
Contributor

@phoerious @joelsmith I agree with you, using the "Q prefix" can be misleading.

@phoerious phoerious merged commit 0eb7936 into keepassxreboot:develop Nov 21, 2017
@weslly
Copy link
Contributor

weslly commented Nov 21, 2017

I agree with the name change. I'll submit a PR.

@joelsmith joelsmith deleted the feature/steamtotp branch November 21, 2017 17:58
phoerious added a commit that referenced this pull request Feb 27, 2018
- Add support for KDBX 4.0, Argon2 and ChaCha20 [#148, #1179, #1230, #1494]
- Add SSH Agent feature [#1098, #1450, #1463]
- Add preview panel with details of the selected entry [#879, #1338]
- Add more and configurable columns to entry table and allow copying of values by double click [#1305]
- Add KeePassXC-Browser API as a replacement for KeePassHTTP [#608]
- Deprecate KeePassHTTP [#1392]
- Add support for Steam one-time passwords [#1206]
- Add support for multiple Auto-Type sequences for a single entry [#1390]
- Adjust YubiKey HMAC-SHA1 challenge-response key generation for KDBX 4.0 [#1060]
- Replace qHttp with cURL for website icon downloads [#1460]
- Remove lock file [#1231]
- Add option to create backup file before saving [#1385]
- Ask to save a generated password before closing the entry password generator [#1499]
- Resolve placeholders recursively [#1078]
- Add Auto-Type button to the toolbar [#1056]
- Improve window focus handling for Auto-Type dialogs [#1204, #1490]
- Auto-Type dialog and password generator can now be exited with ESC [#1252, #1412]
- Add optional dark tray icon [#1154]
- Add new "Unsafe saving" option to work around saving problems with file sync services [#1385]
- Add IBus support to AppImage and additional image formats to Windows builds [#1534, #1537]
- Add diceware password generator to CLI [#1406]
- Add --key-file option to CLI [#816, #824]
- Add DBus interface for opening and closing KeePassXC databases [#283]
- Add KDBX compression options to database settings [#1419]
- Discourage use of old fixed-length key files in favor of arbitrary files [#1326, #1327]
- Correct reference resolution in entry fields [#1486]
- Fix window state and recent databases not being remembered on exit [#1453]
- Correct history item generation when configuring TOTP for an entry [#1446]
- Correct multiple TOTP bugs [#1414]
- Automatic saving after every change is now a default [#279]
- Allow creation of new entries during search [#1398]
- Correct menu issues on macOS [#1335]
- Allow compilation on OpenBSD [#1328]
- Improve entry attachments view [#1139, #1298]
- Fix auto lock for Gnome and Xfce [#910, #1249]
- Don't remember key files in file dialogs when the setting is disabled [#1188]
- Improve database merging and conflict resolution [#807, #1165]
- Fix macOS pasteboard issues [#1202]
- Improve startup times on some platforms [#1205]
- Hide the notes field by default [#1124]
- Toggle main window by clicking tray icon with the middle mouse button [#992]
- Fix custom icons not copied over when databases are merged [#1008]
- Allow use of DEL key to delete entries [#914]
- Correct intermittent crash due to stale history items [#1527]
- Sanitize newline characters in title, username and URL fields [#1502]
- Reopen previously opened databases in correct order [#774]
- Use system's zxcvbn library if available [#701]
- Implement various i18n improvements [#690, #875, #1436]
@gpchelkin
Copy link

May we expect some docs (or maybe a link) on how to set this up, please? Couldn't find anything about how to get the Key. Thanks for the work!

@weslly
Copy link
Contributor

weslly commented Mar 1, 2018

@gpchelkin https://github.com/victor-rds/KeeTrayTOTP/blob/master/README.md

@phoerious phoerious added pr: new feature Pull request that adds a new feature and removed new feature labels Nov 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature: TOTP pr: new feature Pull request that adds a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Steam Guard Support for TOTP
5 participants