How to check if your GPG key is in cache - demu.red

Followup Part2
Followup Part3

GPG Cache Checking

Until this week I had been using a hacky way of checking if my laptop GPG key was cached. I utilize this for my WM's bar so I can tell if the key is open (or if I need to refresh it for mutt/backups), as well as letting scripts cleanly fail if it is not cached (like My Backups). I call it a hack, as it technically worked, but I was using an implied reaction... Which broke this week. Well, it is more that I corrected my GPG set up by clearing my yubikey neo, and reconfiguring it to work again, at which point the reaction was no longer as desired.

Digression: Yubikey Neo GPG Reset

Resetting the GPG Smartcard side of the Yubikey Neo isn't immediately obvious. What do I mean? Well, in gpg2 --card-edit the factory-reset option is not supported.

So, the solution was to create a file containing the following:

/hex
scd serialno
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
/echo Card has been successfully reset.

And then run gpg-connect-agent -f FILE_NAME. This wiped the smartcard clean, and allowed me to start fresh.

Broken Hack

My previous solution, echo "1234" | gpg2 --no-tty --quiet --batch --local-user KEY_HERE -as - >/dev/null 2>&1 && echo "1" || echo "0", was based on a snippet of code which was intended as a quick way to make sure you knew the correct passphrase. I had noticed that if the key was already cached you would get an automatic pass, and was using this as a quick and dirty test. Unfortunately (or perhaps fortunately, as it spurred me to find a clean way and write this post) this hack no longer worked after I reconfigured my Yubikey and left stubfiles on my laptop.

My Keys

I have a master key (not kept on laptop) and an encryption key made while creating the master key (which I don't use); my Signature, Encryption, and Authentication subkeys I purposely made; and my laptop Encryption subkey. The "Laptop Encryption key" is a key I use for pass and My Backups. I allow this to stay on the laptop, but segregate its use to things I need to work automagically. The first three subkeys live on my yubikey, and it must be inserted to use them.

Digression: Master and Subkeys

If you use GPG, you should use the Master and Subkey set up. There are a number of benefits. Two of the big ones:

  • You can keep the master key offline
    • This is used for new key creation, and web-of-trust signing
  • You can revoke subkeys, while maintaning your master key's web-of-trust
    • The alternative is to start from ground zero

There is a very long conversation to be had here. Instead of going into it, I'm going to link you to a good post on the topic, Creating the Perfect GPG Keypair.

Why it Broke

The old hack fails now, as my Signature subkey no longer lives on the laptop. I attempted to tweak the code to remove the need for signing, but couldn't get past it.

A Clean Solution

I had a feeling that GPG itself would need some kind of way of knowing a key was cached, so there had to be an interface somewhere. After a few hours of exercising my Google Fu, interrupted by a social gathering, I noticed a few uses of gpg-connect-agent that made it looked promising. The man page doesn't really cover the command adequately in my opinion. It also fails to mention the normal interactive commands... I found this out after echo "help" | gpg-connect-agent spit out a number of [^/]COMMANDS not listed in the man page. At this point I guessed that it might actually have an interactive shell, and found that it did. After reading through all the help COMMAND outputs, I saw a few that looked promising and used some more Google Fu.

~ -> gpg-connect-agent 'help keyinfo' /bye
# KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] <keygrip>
# 
# Return information about the key specified by the KEYGRIP.  If the
# key is not available GPG_ERR_NOT_FOUND is returned.  If the option
# --list is given the keygrip is ignored and information about all
# available keys are returned.  If --ssh-list is given information
# about all keys listed in the sshcontrol are returned.  With --with-ssh
# information from sshcontrol is always added to the info. Unless --data
# is given, the information is returned as a status line using the format:
# 
#   KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>
# 
# KEYGRIP is the keygrip.
# 
# TYPE is describes the type of the key:
#     'D' - Regular key stored on disk,
#     'T' - Key is stored on a smartcard (token),
#     'X' - Unknown type,
#     '-' - Key is missing.
# 
# SERIALNO is an ASCII string with the serial number of the
#          smartcard.  If the serial number is not known a single
#          dash '-' is used instead.
# 
# IDSTR is the IDSTR used to distinguish keys on a smartcard.  If it
#       is not known a dash is used instead.
# 
# CACHED is 1 if the passphrase for the key was found in the key cache.
#        If not, a '-' is used instead.
# 
# PROTECTION describes the key protection type:
#     'P' - The key is protected with a passphrase,
#     'C' - The key is not protected,
#     '-' - Unknown protection.
# 
# FPR returns the formatted ssh-style fingerprint of the key.  It is only
#     printed if the option --ssh-fpr has been used.  It defaults to '-'.
# 
# TTL is the TTL in seconds for that key or '-' if n/a.
# 
# FLAGS is a word consisting of one-letter flags:
#       'D' - The key has been disabled,
#       'S' - The key is listed in sshcontrol (requires --with-ssh),
#       'c' - Use of the key needs to be confirmed,
#       '-' - No flags given.
# 
# More information may be added in the future.
OK

This is where I found that keyinfo --list, or non interactively gpg-connect-agent 'keyinfo --list' /bye will spit out some tasty info.

~ -> gpg-connect-agent 'keyinfo --list' /bye
S KEYINFO REDACTEDREDACTEDREDACTEDREDACTEDREDACTED D - - - P - - -
S KEYINFO REDACTEDREDACTEDREDACTEDREDACTEDREDACTED D - - 1 P - - -
S KEYINFO REDACTEDREDACTEDREDACTEDREDACTEDREDACTED T REDACTEDREDACTEDREDACTEDREDACTED OPENPGP.1 - - - - -
S KEYINFO REDACTEDREDACTEDREDACTEDREDACTEDREDACTED T REDACTEDREDACTEDREDACTEDREDACTED OPENPGP.3 - - - - -
S KEYINFO REDACTEDREDACTEDREDACTEDREDACTEDREDACTED T REDACTEDREDACTEDREDACTEDREDACTED OPENPGP.2 - - - - -
OK

Notice that singular '1'?

YES!

Now time to Regex this beauty! And some AWK for good measure.

gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {match($0, /S\sKEYINFO\s\S+\s\S\s\S+\s\S+\s(\S)\s\S\s\S+\s\S+\s\S/, m); if(m[1]==1){CACHED=1}} END{print CACHED}'

EDIT 19FEB2017: In hind sight, there is a better awk, which now doesn't have to be gawk:

gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END{if($0!=""){print CACHED} else {print "none"}}'

Part2
Part3
WM's bar
My Backups
Creating the Perfect GPG Keypair

- demure