Recently my phone went out of order for a day. I immediately switched over to my backup phone and was back to normal after one hour.
This was a great opportunity to check if I still had all T’s crossed and I’s dotted and full control over my data in case of an unexpected event - like missing the phone.
Surprise: I wasn’t fully prepared.
While most applications and their data were recovered, easily restored and no data was lost, I realized that the Multi-Factor-token generator that I use on my phone had no backup. I used it everyday to get access to customer sites and resources.
Using my phone as the token generator had put me in a situation where I was dependent on that particular phone. Some websites offer one-time codes during the setup of the MFA and those I still had, but the token generator itself was a single point of failure.
Gaining access
Out of pure luck I was able to return to a stable phone situation within a day. That made looking at that problem easier.
The One-time-password (OTP) generator I use on the phone allows me to export and import the accounts. The exported data also includes the secret used to setup the account/token. Interesting.
I would rather not have that come astray in a random backup being taken in the background. I wanted a fallback application on my desktop in order to be able to generate the tokens there as well in case I needed to.
Security
Almost all secrets I have are stored in pass and that keeps them GPG encrypted. This is the natural place for the token secrets as well in my case. The solution needed to be able to fetch the secrets dynamically from the store and use them to generate the one-time passwords.
Sounds do-able.
A solution forms.
The tool oathtool
is precisely designed to generate one-time passwords from secrets. Neat.
The command for generating a token would look like this:
$ oathtool --totp --base32 -d 6 $(echo '$secret' | base32)
055131
Click to expand...
--totp
: The default for this parameter is SHA1, which is identical to the information I got from the exported file from the token generator.--base32|-b
: The tokens are usually base43 encoded, so we do this as well.--digits|-d
: The default number of digits according to my export was six. This parameter defines the same number of digits in the one-time password.
Now we need to fetch the secret from the password store and place it inside the command.
$ oathtool --totp -b -d 6 $(pass show otp/customer_a | head -n 1)
636296
That seems to work. The returned token has changed, since some minutes had passed since I ran it the last time.
Quick and dirty and secure integration
Now, how to integrate this rather messy command into the daily workflow?
For starters I add a bash alias:
alias otp.customer_a="oathtool --totp -b -d 6 $(pass show otp/customer_a | head -n 1)"
As mentioned in this article this is rather simple in my setup, but an equivalent entry in .bashrc
should do the trick as well.
However: This does not work.
When running the alias the one-time password never changes. It stays the same, no matter how long I wait.
I managed to stumble on a quirky limitation of bash alias
. Aliases evaluate their command when they are being defined, not when they are being executed. That means the alias otp.customer_a
was not calling the password store to fetch the secret, but rather had done this already once when initialised. Just check the alias definition:
# Set alias
$ alias otp.customer_a="oathtool --totp -b -d 6 $(pass show otp/customer_a | head -n 1)"
# Show alias
$ alias otp.customer_a
alias otp.customer_a='oathtool --totp -b -d 6 secret_string'
Let’s re-arrange the command a bit to solve this:
$ alias otp.customer_a="pass show otp/customer_a | head -n 1 | tr -d '\n' | xargs -0 oathtool --totp -b -d 6"
$ alias otp.customer_a
alias otp.customer_a='pass show otp/customer_a | head -n 1 | tr -d '\''\n'\'' | xargs -0 oathtool --totp -b -d 6'
Click expand...
- Now the alias executes the pass command every time.
head -n 1
limits the return from pass to the first line. That line contains usually the password (in this case the secret) string.td -d '\n'
removes any trailing newlines. The additional'
-characters just escape the ones in the command.xargs -0
hands the result from pass over tooathtool
.
The result is local one-time password generator and backup in case the phone is not available. Still the secrets are protected. Probably better than on the phone.
otp.customer_a
112233
A quick verification of the codes with the phone based generator showed the same results and I was able to get access using this one-time passwords as well.
Lessons learned
- I should keep up to date with the data and configuration on my phone and think more often about security in that context. Though the phone itself is protected against malicious access, there are a couple of more steps that can be taken to increase the security further.
- I need to store the secret for the MFA setup safe in order to be able to restore access to customer resources more quickly.
- Other procedures for setting up the backup-phone worked as planned and no data would be lost in case of a loss.
- Reading the man page of bash back in the day did more good than harm. Though it’s quite long, I still benefit from it when I can recall those limitation on
alias
.
Update 2022-11-25
A colleague hinted that pass
already has a otp-plugin available.
After a quick look it looks like I have re-invented the wheel. That happens in the middle of a crisis.
The installation is (under Fedora) fairly simple:
sudo dnf install -y pass-otp
All other steps in the post above can be compressed further:
Put the OTP key in URI format into pass
.
$ pass otp insert otp/customer_a
Enter otpauth:// URI for customer_a:
Retype otpauth:// URI for customer_a:
All this does is putting the string into the pass GPG file at the first line. Adding additional information into the other lines below will work as usual.
Fetch the OTP token when needed:
$ pass otp otp/customer_a
123456
Configure the alias:
alias otp.customer_a="pass otp otp/customer_a"
This solution is cleaner, easier to implement and to maintain as well. Thanks @kid for pointing to that plugin. Guess I have to go and re-configure this now really quick.
Update 2023-05-10
Using this over several months made me add a small extension to the alias.
alias otp.customer_a="pass otp otp/customer_a | xclip && echo 'OTP copied to clipboard.'"