pull down to refresh

https://m.stacker.news/5610

How to get 2FA on the command lineHow to get 2FA on the command line

Getting Time-based One-Time-Password for 2FA on the command line.

AbstractAbstract

This HOW-TO has been succesfully tested on Ubuntu 22.04.3 LTS so let's assume you have a similar setup.

There is no shortage of OTP 2FA apps availiable for your phone, such as Authy , FreeOTP or even the not so recommended Google Authenticator.

These apps take an initial secret code and create a TOTP anytime you need a 2FA code for login.

Some advantages of doing 2FA on the CLI are:

  1. Easy to add, maintain, and backup with a simple key=val text file
  2. Copy/Paste is easier than typing digits displayed on your phone
  3. No issues with being locked out due to dead/lost/new phones

InstallationInstallation

Make sure you're logged in as a regular user (not as root).

Install the two utility with:

sudo apt install oathtool gpg

We'll use a helper script as well as a file of initial secrets encrypted with GnuPG for better security.

sudo touch /usr/local/bin/totp

and, with your editor of choice, put the content below on the file and save it.

#!/bin/bash
# 
# Time-based One-time Password algorithm (TOTP) helper script
# Save shared secrets on disk protected with GnuPG encryption
# Easily generate OTPs for two-factor authorization (2FA)
#
# Setup:
# Install requirements with `sudo apt install oathtool gpg`
# Setup gpg as per https://keyring.debian.org/creating-key.html
#
# Adapt the 3 variables below:
# - KEYFILE: file that holds the name/key pairs
# - UID: GnuPG user ID to use for encryption
# - KEYID: GnuPG key ID to use for encryption
#
# Good to know:
# - get gpg keys with: gpg --list-keys --keyid-format short user@example.com
#
# - the $KEYFILE itself is in clear and has the format:
#     aws=hQIMAxevVAas6A+AAQ//cJL/v3O6CCurdzVkCk5yEGa6sZgWWw6AkH/QenVmTSj...
#     twitter=hQIMAxevVAas6A+AAQ/9H8h0yde7zErfF/8qwohD5Zw7q85FlI+IIFC1Kk5Ifpw...
#     github=hQIMAxevVAas6A+AARAAm8T//mqNyBEz4Y/HGGlNgFUzk8vOaylMdE/TbDzVI...
#
# - the shared secrets are stored encrypted with gpg then base64-ed
# - keys are never deleted, only appended
# - the last available key for the chosen service is used
# - to restore the previous key, manually delete the last key from $KEYFILE
#
# Authors:
# - https://www.sendthemtomir.com/blog/cli-2-factor-authentication and
# - https://karl-voit.at/2019/03/03/oathtool-otp/, Karl Voit, tools@Karl-Voit.at
# - Paolo Greppi, paolo.greppi@libpf.com
# LICENSE: GPLv3

set -e

KEYFILE="$HOME/.totpkeys"
UID="user@example.com"
KEYID="9E2A4CEF"

if [ -z "$1" ]; then
  echo
  echo "Usage:"
  echo "   totp list"
  echo "   totp get google"
  echo "   totp set google QUBAYAYXV5KANLHI"
  exit
fi

if [ "$1" = 'list' ]; then
   KEYS=$(sed 's/^\([^=]*\)=.*$/- \1/g' "$KEYFILE")
   echo "Available keys:"
   echo "$KEYS"
   exit
fi

if [ "$1" = 'get' ]; then
  if [ -z "$2" ]; then
    echo "$0: Missing service name"
    $0
    exit
  fi
  TOTPKEY=$(sed -n "s/${2}=//p" "$KEYFILE" | tail -n 1)
  if [ -z "$TOTPKEY" ]; then
    echo "$0: Bad Service Name '$2'"
    $0
    exit
  fi
  TOTPKEY=$(echo "$TOTPKEY" | base64 -d | gpg --decrypt -r "$UID" -u "$KEYID" 2> /dev/null)
  oathtool --totp -b "$TOTPKEY"
  exit
fi

if [ "$1" = 'set' ]; then
  if [ -z "$2" ]; then
    echo "$0: Missing service name"
    $0
    exit
  fi
  if [ -z "$3" ]; then
    echo "$0: Missing key"
    $0
    exit
  fi
  oathtool --totp -b "$3" > /dev/null # verify secret
  TOTPKEY=$(echo "$3" | gpg --encrypt -r "$UID" -u "$KEYID" | base64 -w0)
  echo "$2=$TOTPKEY" >> "$KEYFILE"
  exit
fi

echo "Command $1 unknown"
$0

Make it executable with :

sudo chmod +x /usr/local/bin/totp

If all went well, we can get a 2FA code on command line with:

$ totp twitter
078321

That's all folks.That's all folks.

Now you have a Time-based One-Time-Password for 2FA on the command line. Enjoy !!

267 sats \ 1 reply \ @nym 22 Nov 2023

Good writeup. Here are some security suggestions:

  1. Improved File Permissions:
    chmod 600 "$KEYFILE"
    chmod 700 /path/to/your/script.sh
    • Ensure that the key file ($KEYFILE) and the script itself have strict file permissions. This can be done using chmod to restrict access to only the necessary users, typically just the owner.
  2. Input Validation:
    if [[ ! "$2" =~ ^[a-zA-Z0-9_]+$ ]]; then
        echo "Invalid service name"
        exit 1
    fi
    • Add validation for the inputs, especially for the service name and key, to prevent injection attacks or accidental misconfiguration.
  3. Error Handling and Logging:
    log_file="/var/log/totp_script.log"
    
    log() {
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$log_file"
    }
    
    # Example usage within the script:
    log "Generating TOTP for service $2"
    • Implement better error handling and logging to track script usage and errors. This can help in auditing and troubleshooting.
  4. Encrypted Backups:
    backup_file="$HOME/.totpkeys_backup_$(date '+%Y%m%d')"
    
    cp "$KEYFILE" "$backup_file"
    gpg --encrypt -r "$UID" "$backup_file"
    • Create a mechanism for encrypted backups of the $KEYFILE. This can be a simple script that encrypts and copies the file to a secure location.
  5. Enhanced GnuPG Handling:
    • Ensure that the GnuPG configuration is secure. This may include setting up a strong key passphrase, using a secure keyring, and keeping the GnuPG software up to date.
  6. Avoid Hardcoded Information:
    UID=${TOTP_UID:-"default_user@example.com"}
    KEYID=${TOTP_KEYID:-"default_keyid"}
    • Instead of hardcoding the GnuPG user ID and key ID, consider passing them as arguments or setting them as environment variables.
  7. Restrict Script Execution:
    if [ "$(id -u)" -ne "expected_user_id" ]; then
        echo "This script can only be run by a specific user."
        exit 1
    fi
    • Restrict the script to be executable only by the intended users. This can be done by checking the user ID at the beginning of the script.
  8. Prompt for Confirmation on Sensitive Actions:
    read -p "Are you sure you want to set a new key for $2? [y/N] " response
    if [[ ! "$response" =~ ^[Yy]$ ]]; then
        echo "Operation canceled."
        exit 1
    fi
    • For operations like setting a new key, prompt for user confirmation to prevent accidental changes.
  9. Use Temporary Files for Sensitive Data:
    tmpfile=$(mktemp /tmp/.totp.XXXXXX)
    chmod 600 "$tmpfile"
    # Use $tmpfile for intermediate steps
    rm -f "$tmpfile"
    • Instead of directly writing sensitive data to files, use temporary files with restricted permissions and ensure they are securely deleted after use.
reply

Thank you so much. I'll include each and everyone of your suggestions.

reply