timvisee / prs

🔐 A secure, fast & convenient password manager CLI using GPG and git to sync.
https://gitlab.com/timvisee/prs
GNU General Public License v3.0
211 stars 8 forks source link

TOTP support #14

Closed colemickens closed 1 year ago

colemickens commented 1 year ago

I was very excited to finally get rid of gopass but it seems prs is missing totp functionality. It would be great if it could be supported.

timvisee commented 1 year ago

Great to see you're using prs. I did not initially plan to integrate TOTP, because adding it in the very same password store usually defeats the purpose 2FA.

However, if this really is something you're missing, I'd be happy to take a look into implementing it.

Does gopasss implementation function well, because then I can use that as a baseline?

Please note that you can use gopass alongside prs with the same store in the mean time for TOTP functionally.

colemickens commented 1 year ago

@timvisee I do understand some folks are of that opinion. I keep my GPG on a yubikey with its own PIN so it clears my bar for "enough like a second factor".

I do think that gopass has implemented the TOTP support well, and I'd be more than happy to test a prs implementation against my various TOTP-enabled store entries that I traditionally use with gopass.

Thanks for prs and being open to this feature request!

timvisee commented 1 year ago

Do you have a suggestion for how this could be configured?

I'm thinking of putting the secret token in a regular pass file like:

pass

Site: example.org
User: example
Totp: 1234567890abcdef

because gopass seems to do it that way.

And the following commands to work with this:

prs totp show myfile
prs totp copy myfile
prs totp live/watch myfile
prs totp add/edit myfile
prs totp help

# or their shorts
prs otp s
prs otp c
prs otp w
prs otp a

Where watch and copy would simply stdout the current token. Watch would keep showing the token with a timer. Add would prompt you for the secret token to merge into a file (or create a new file).

And maybe a prs show myfile --totp flag to show file contents like normal but with the secret token replaced with the current TOTP token.

Note that I haven't worked with TOTP on a CLI before, so I might be missing useful stuff.

colemickens commented 1 year ago

The UX that I'm going from with gopass is roughly:

> gopass show "websites/schwab.com/colemickens"

[password redacted]
comment:
url: schwab.com
username: colemickens
otpauth://totp/schwab?secret=XXredactXX
❯ /nix/store/z8pv90b2ysynalwzq5pvhi2smg1hknfx-gopass-1.14.9/bin/gopass totp schwab
Entry "schwab" not found. Starting search...
[ 0] websites/developer.schwab.com/cole.mickens@gmail.com
[ 1] websites/schwab.com/colemickens

Found secrets - Please select an entry (q to abort) [0]: 1
1
websites/schwab.com/colemickens
xxxxxx
⚠ ([q] to stop. -o flag to avoid.) This OTP password still lasts for:
]  1 / 25 [Gpass                                                                                                                          ]   4.00%

(it's a little much, imo, with the progress bar, but it loops and spits out totp codes til killed)

❯ /nix/store/z8pv90b2ysynalwzq5pvhi2smg1hknfx-gopass-1.14.9/bin/gopass totp --clip schwab
Entry "schwab" not found. Starting search...
[ 0] websites/developer.schwab.com/cole.mickens@gmail.com
[ 1] websites/schwab.com/colemickens

Found secrets - Please select an entry (q to abort) [0]: 1
1
websites/schwab.com/colemickens
✔ Copied token for websites/schwab.com/colemickens to clipboard. Will clear in 45 seconds.

from the gopass manpage:

This requires that the password contain either a full otpauth:// URI or a TOTP secret prefixed by '2fa:'.

The otpauth url seems to support extra parameters in the URL too, I think one or two of mine generated 8 digit codes, and that appears in the otpauth url.

If you need specifics I can gopass grep to try to find all my otpauth examples.

I don't know if Password Store for Android supports both. I think I basically use otpauth:// everywhere.

timvisee commented 1 year ago

Thanks for your elaborate answer.

Yes, could you query your store for any other formats you may be using? For example: do you always use otpauth://? Is it ever prefixed with a proper like 2FA: or TOTP:? Do you ever have multiple tokens in a single file? Are there any other differences?

I have a basic TOTP token show command working now on a separate branch. The digits parameter (for 8) is already supported.

I'm asking this how because knowing this might speed up implementing this.

The workflow will likely be a little bit different, to eliminate ambiguity and improve usability. It'll definitely be less noisy.

Do you have any suggestions thus far?

colemickens commented 1 year ago

So totp: seems to be the raw key.

Any time that I have the otpauth url entry, it is on its own line. Granted, gopass has been more and more aggressive about trying to parse out the extra data, and recently affected some 2fa codes (hence why I opened this issue, actually).

I can confirm the Android app is able to generate TOTP codes for both formats.

timvisee commented 1 year ago

I've implemented the first TOTP bits. Would you mind to test it out?

I'd love to hear about your findings and opinions on the following:

  1. Does this work on your TOTP (otpauth) tokens?
  2. Does this work on your other TOTP (raw key) tokens?
  3. Should prs totp show show the time to live (-q does not)?
  4. The prs totp copy (without --no-recopy) tries to be smart, and waits to recopy the token when it changes within the clipboard timeout, what do you think of this?
    (for example, if the current token has a time to live of 2 seconds and you invoke the command, it copies the token, waits for 2 seconds, and copies the updated token)
  5. Do you currently add otpauth URLs to your store manually with prs edit?

You can find a binary here. Or clone and build yourself from the 44-add-totp-support branch over on GitLab (not GitHub) here.

This adds:

And variations:

$ prs totp show test
109 152 (valid for 12s)

$ prs totp show test -q
109152

$ prs totp copy test
Token copied to clipboard. Recopying changing token after 12 seconds...
Token copied to clipboard. Clearing after 8 seconds...

$ prs totp copy test --no-recopy
Token copied to clipboard. Clearing after 20 seconds...

$ prs totp live test
109 152 (valid for 12s)
# updates live

$ prs totp live test -q
109152
# updates live

$ prs totp live test --follow
109 152 (valid for 12s)
123 456 (valid for 30s)
654 321 (valid for 30s)
# updates live

You can have multiple TOTP secrets in a single file if you'd like. By default the totp commands will select the first one. Prefix each with a different property name to make them selectable, for example:

mypassword
totp1: otpauth://totp/SECRET
totp2: otpauth://totp/SECRET
other: RAWTOTPSECRETHERE

and use

prs totp show -p totp1
prs totp show -p totp2
prs totp show -p other

Please see prs totp help for further details on these commands.

colemickens commented 1 year ago

Interestingly I circled back to this when I noticed that gopass didn't support Steam, and it seems totp-rs doesn't either. Understandable given the text of the RFC but all the same I opened this: https://github.com/constantoine/totp-rs/issues/45

Initial testing otherwise seems to work but I'd like to test further before saying I properly vetted it.

Thanks! It might be my imagination but it felt very snappy already.

colemickens commented 1 year ago

Here's a failure with prs that works with gopass:

~
❯ prs totp clip discord --verbose
Secret: colemickens/discord.com
error: failed to generate TOTP token
caused by: invalid TOTP secret URL
caused by: The length of the shared secret MUST be at least 128 bits. 80 bits is not enough

For a detailed log add '--verbose'
For more information add '--help'

~
❯ gopass totp --clip discord
Entry "discord" not found. Starting search...
✅ Found exact match in "colemickens/discord.com"
✔ Copied token for colemickens/discord.com to clipboard. Will clear in 45 seconds.

~
❯ gopass show discord
⚠ Entry "discord" not found. Starting search...
✅ Found exact match in "colemickens/discord.com"
Secret: colemickens/discord.com

pw-redacted
comments: These are your Discord backup codes for account cole.mickens@gmail.com.  Keep them safe!
login: cole.mickens@gmail.com
otpauth://totp/Discord%3Acole.mickens%40gmail.com?secret=GXXXL3YYSFZZZQ6R&issuer=discord&digits=6
Pantamis commented 1 year ago

Hi !

I am really excited by this project as it makes a CLI for pass on windows possible ! (and I love Rust)

Just to say that I have the same issue with PayPal TOTP too: it is only 80 bits.

This can be a stong argument for totp-rs to support such TOTP !

timvisee commented 1 year ago

@colemickens Thanks for getting back to me. The 128-bit size is enforced by totp-rs. I've opened an issue to remove this limitation, which would fix the above errors: https://github.com/constantoine/totp-rs/issues/46

If totp-rs isn't open to support this, I'll implement this separately.

@Pantamis Awesome to hear, thanks! Thank you for noting PayPal also uses a smaller amount of bits, that's strengthens the argument to support < 128-bits.

timvisee commented 1 year ago

I've implemented support for Steam tokens and short TOTP secrets. This is on a totp-rs downstream development branch, and I'm unsure if this will be fully implemented upstream. Anyway, the important thing is: Steam tokens and short secrets work now.

@colemickens @Pantamis Would you both mind to give it another spin (for Steam and Discord/PayPal)?

You can find the latest binary here: https://gitlab.com/timvisee/prs/-/jobs/3544324842/artifacts/browse (source)

colemickens commented 1 year ago

I'll need to reboot to Windows later to troubleshoot against the desktop authenticator to see if I've made a mistake or if there's a bug.

edit: I spot checked maybe 10-12 other entries, they all generated codes, though I didn't test those.

timvisee commented 1 year ago

@colemickens Thanks for testing. I assume your Steam entry to have a different format then what I got, causing parsing issues. Could you share its URI (with the secret blacked-out)?

Pantamis commented 1 year ago

I tested PayPal and Discord, it works for me too now ! (and the generated code is correct)

I compiled 44-add-totp-support branch myself directly to test it.

I don't have Steam account so I can't test for this setting.

colemickens commented 1 year ago

Wine is truly an amazing thing. See screenshot.

Of note, the Steam 5-"digit" codes, aren't actually all digits, they include letters too?

screenshot-1672962967

colemickens commented 1 year ago

And for good measure:

╭ zeph  ~ 0.90s
╰─▶ prs show steam | grep otpauth
Secret: colemickens/steamcommunity.com
otpauth://totp/Steam-redact?secret=ZMIyyyEMDEaaaTNbbbbUcccNBNKHxxxN&digits=5&period=60

edit: oops, so much for censoring the steam username :P

timvisee commented 1 year ago

@colemickens Thanks for giving this a spin!

I've recently made some changes that should improve things. The latest binary can be found here: https://gitlab.com/timvisee/prs/-/jobs/3562589842/artifacts/browse

Your otpauth URL for Steam doesn't have enough information for it to be properly detected. The Steam desktop authenticator tool probably doesn't care because Steam is the only type it supports. You should set the issuer to Steam to make this work (with the latest update), this is also what other authenticators output for Steam.

Besides, I see that you've set the period to 60. I believe this must be 30 for Steam.

Changing your URL to this should do the trick:
otpauth://totp/Steam-redact?secret=ZMIyyyEMDEaaaTNbbbbUcccNBNKHxxxN&digits=5&period=30&issuer=Steam

Ps: to view live output use prs totp watch steam

colemickens commented 1 year ago

With the new build, and the otpauth changes you suggested:

screenshot-1672963896

(aka, it works - I tested a full login on steamcommunity.com as well)

So cool! :) Thank you!

timvisee commented 1 year ago

Awesome to see it works now! I wonder what could be done to improve parsing, but since it isn't standardized this is probably very hard.

I'll leave this open until a new prs version is released that includes this.

colemickens commented 1 year ago

The only thing I can think of is some documentation, either on prs or on totp-rs (and linked to from prs) that indicates specially supported issuer= query params.

timvisee commented 1 year ago

prs v0.4.0 has now been released which includes this.

Changelog