#!/bin/sh # ga - manage Google authenticator (RFC6238) seeds # # Copyright 2014 David Mazieres. You may use and distribute this code # under the terms of the GPLv3 or later, and additionally you may link # against openssl libcrypto. # # This program comes with ABSOLUTELY NO WARRANTY. This program # REDUCES YOUR SECURITY by making two-factor authentication not really # two factor. You may decide using this program is useful where the # alternative is only one factor authentication, but that is your # decision alone. The author DOES NOT REPRESENT THIS SOFTWARE AS MORE # SECURE THAN ANY ALTERNATIVE; it most likely is less secure. # Installation: # Just put it somewhere on your path (e.g., $HOME/bin/ga). # # Requirements: # gnupg # # Optional dependencies: # xclip, xsel - to send login codes to the X clipboard instead of stdout # pbcopy - to send login codes to the OSX clipboard instaed of stdout # zbar - to parse QR codes from screen with -c option # qrencode, imagemagick - to display QR codes with -v option # Tip: If you need to retrieve seeds from an android phone, try # running the following command as root on your phone: # # sqlite3 -header \ # /data/data/com.google.android.apps.authenticator2/databases/databases \ # 'SELECT email, secret FROM accounts;' exe=google-auth-code suffix=$(uname -sm | sed -e 's/ /-/g') prog=$(basename $0) keydir="${XDG_CONFIG_HOME-${HOME}/.config}/ga" bindir="$keydir/bin" gpgcmd="gpg" gpgoptions="--pinentry-mode loopback -q --no-mdc-warning" usage() { echo "usage: $prog [-x] service # get token for service" >&2 echo " $prog -[x]c service # record seed for new service" >&2 echo " $prog -[x]v service # view qrcode for service" >&2 echo " $prog -l # list services setup" >&2 echo "(The -x* flags disable use of X11 even when \$DISPLAY is set.)" >&2 exit 1 } if [[ "$OSTYPE" == "darwin"* ]]; then export DISPLAY="" fi case "$1" in -x) shift unset DISPLAY ;; -x*) unset DISPLAY ;; esac test "$#" -ge 1 -a "$#" -le 2 || usage case $(gsettings get org.gnome.crypto.cache gpg-cache-method 2>/dev/null) in "'idle'"|"'timeout'"|"") ;; *) cat >&2 <&2 exit 1; fi if ! test "$bindir/$exe.$suffix" -nt "$self"; then mkdir -p "$bindir" sed -e '1,/^BEGIN CODE/d' "$self" > "$bindir/$exe.c" cc_cmd=cc if test -n "$CC"; then cc_cmd=$CC fi $cc_cmd $(pkg-config --cflags libcrypto) -Wall -g \ -o "$bindir/$exe.$suffix" "$bindir/$exe.c" \ $(pkg-config --libs libcrypto) \ || exit 1 fi if test "$#" -eq 1 -a x"$1" != x"-l"; then mode="" target="$1" else mode="$1" target="$2" fi gk() { while read line; do nosecret="${line%%\?*}" account="${nosecret#QR-Code:otpauth://totp/}" if test x"$nosecret" != x"$account"; then echo -n "Use $account? (y) " > /dev/tty read ans < /dev/tty if test x"$ans" = x -o y = "$ans" -o yes = "$ans"; then echo "${line#QR-Code:}" return fi fi done } case "$mode" in "") if code=$($gpgcmd $gpgoptions -d "$keydir/$target.gpg" \ | "$bindir/$exe.$suffix" -); then if test -n "$DISPLAY" && command -v xclip > /dev/null; then echo "Sending code to X clipboard" echo -n "$code" | xclip -i elif test -n "$DISPLAY" && command -v xsel > /dev/null; then echo "Sending code to X clipboard" echo -n "$code" | xsel -i elif test -n "$DISPLAY" && command -v pbcopy > /dev/null; then echo "Sending code to OSX clipboard" printf "$code" | pbcopy else echo "$code" fi else exit 1 fi ;; -c|-xc) umask 077 if test -f "$keydir/$target.gpg"; then echo "$keydir/$target.gpg exists already; please delete it." exit 1 fi mkdir -p "$keydir" unset key if test -n "$DISPLAY"; then key=$(zbarimg x:root 2> /dev/null | gk) fi if test -z "$key"; then echo -n "Enter Base32-encoded authenticator key: " read key if test -z "$key"; then echo "Aborted." exit 0 fi fi echo "key is $key." echo "$key" | "$bindir/$exe.$suffix" - 0 || exit 1 echo "$key" | $gpgcmd $gpgoptions -c -o "$keydir/$2.gpg" ;; -v|-xv) secret=$($gpgcmd $gpgoptions -d "$keydir/$target.gpg") || exit 1 case "$secret" in otpauth://totp/*) ;; *) secret="otpauth://totp/$target?secret=$secret&issuer=$target" ;; esac if test -z "$DISPLAY"; then printf "$secret" | tr -d ' ' | qrencode -t ansi elif command -v display > /dev/null; then printf "$secret" | tr -d ' ' | qrencode -o- | display - elif command -v xv > /dev/null; then printf "$secret" | tr -d ' ' | qrencode -o- | xv - elif command -v xloadimage > /dev/null; then printf "$secret" | tr -d ' ' | qrencode -o- | xloadimage stdin else printf "$secret" | tr -d ' ' | qrencode -t ansi fi ;; -l) ls "$keydir/" | sed -ne "s/.gpg$//p" ;; *) usage ;; esac exit 0 BEGIN CODE /* Google authenticator code calculation, link with -lcrypto */ #include #include #include #include #include #include #include #ifdef __APPLE__ #include #include #else #include #include #endif char *progname; static const signed char a2b32[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const int a2b32rem[8] = {0, -1, 1, -1, 2, 3, -1, 4}; ssize_t armor32len (const char *_s) { const u_char *s = (const u_char *) _s; const u_char *p = s; ssize_t len; while (a2b32[*p] >= 0) p++; len = p - s; return a2b32rem[len & 7] < 0 ? -1 : len; } ssize_t dearmor32len (const char *s) { ssize_t len = armor32len (s); return len < 0 ? -1 : (len >> 3) * 5 + a2b32rem[len & 7]; } ssize_t dearmor32 (void *out, const char *_s) { const u_char *s = (const u_char *) _s; ssize_t len = armor32len (_s); int rem = a2b32rem [len & 7]; size_t outlen = (len >> 3) * 5 + rem; char *d = out; int c0, c1, c2, c3, c4, c5, c6, c7; const u_char *e; if (rem < 0) return -1; if (len <= 0) return 0; for (e = s + (len & ~7); s < e; s += 8, d += 5) { c0 = a2b32[s[0]]; c1 = a2b32[s[1]]; d[0] = c0 << 3 | c1 >> 2; c2 = a2b32[s[2]]; c3 = a2b32[s[3]]; d[1] = c1 << 6 | c2 << 1 | c3 >> 4; c4 = a2b32[s[4]]; d[2] = c3 << 4 | c4 >> 1; c5 = a2b32[s[5]]; c6 = a2b32[s[6]]; d[3] = c4 << 7 | c5 << 2 | c6 >> 3; c7 = a2b32[s[7]]; d[4] = c6 << 5 | c7; } if (rem >= 1) { c0 = a2b32[s[0]]; c1 = a2b32[s[1]]; *d++ = c0 << 3 | c1 >> 2; if (rem >= 2) { c2 = a2b32[s[2]]; c3 = a2b32[s[3]]; *d++ = c1 << 6 | c2 << 1 | c3 >> 4; if (rem >= 3) { c4 = a2b32[s[4]]; *d++ = c3 << 4 | c4 >> 1; if (rem >= 4) { c5 = a2b32[s[5]]; c6 = a2b32[s[6]]; *d++ = c4 << 7 | c5 << 2 | c6 >> 3; } } } } assert (d == (char *) out + outlen); return len; } int dearmor32a (void **outp, size_t *outlen, char *in) { if (armor32len (in) != strlen (in)) return 0; dearmor32 ((*outp = malloc ((*outlen = dearmor32len (in)))), in); return 1; } uint32_t hotp (const void *key, size_t keylen, unsigned long val) { int i; unsigned char valbytes[8]; unsigned char hash[20]; unsigned char *p; for (i = 8; i--; val >>= 8) valbytes[i] = val; #ifdef __APPLE__ CCHmac (kCCHmacAlgSHA1, key, keylen, valbytes, sizeof (valbytes), hash); #else HMAC (EVP_sha1 (), key, keylen, valbytes, sizeof (valbytes), hash, NULL); #endif p = hash + (hash[sizeof (hash) - 1] & 0xf); return ((p[0] & 0x7f) << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } static const char * findsecret(const char *in) { static const char totp[] = "otpauth://totp/"; static const char qsecret[] = "?secret="; static const char asecret[] = "&secret="; const char *start; if (strncmp (in, totp, sizeof(totp) - 1)) return in; if ((start = strstr(in, qsecret))) return start + sizeof(qsecret) - 1; if ((start = strstr(in, asecret))) return start + sizeof(asecret) - 1; return start; } char * getsecret(const char *in) { size_t pos = 0; char *ret = malloc(pos+1); if (!ret) return NULL; if (!(in = findsecret (in))) return NULL; for(; *in != '&'; ++in) { char *tmp = ret; if (*in == ' ' || *in == '\t') continue; if (!(ret = realloc (ret, ++pos + 1))) { free (tmp); return NULL; } ret[pos-1] = *in; if (!*in) break; } ret[pos] = '\0'; return ret; } void usage (void) { fprintf (stderr, "usage: %s []\n" " %s - []\n", progname, progname); exit (1); } int main (int argc, char **argv) { char *keystr; void *key; size_t keylen; unsigned long val; uint32_t code; char line[256] = ""; progname = strrchr (argv[0], '/'); if (!progname) progname = argv[0]; else progname++; if (argc < 2 || argc > 3) usage (); if (strcmp (argv[1], "-")) keystr = argv[1]; else { int n; fgets (line, sizeof (line), stdin); n = strlen (line); if (n > 0 && line[n - 1] == '\n') line[n - 1] = '\0'; else if (n + 1 == sizeof (line)) { fprintf (stderr, "%s: input too long\n", progname); exit (1); } else if (n == 0) { /* If gpg or something fails on input. But allow null key with \n. */ fprintf (stderr, "%s: aborted\n", progname); exit (1); } keystr = line; } if (!(keystr = getsecret(keystr)) || !dearmor32a (&key, &keylen, keystr)) { fprintf (stderr, "%s: bad key\n", progname); exit (1); } if (argc == 3) val = strtoul (argv[2], NULL, 0); else val = (unsigned long) time (NULL) / 30; code = hotp (key, keylen, val); printf ("%06ld\n", (unsigned long) code % 1000000); return 0; }