19

Is there a way to tell GPG, that if it needs to decrypt something, that it can find the private encryption key on one of two smart cards?

My (simplified) setup is as follows:

  • Generated a master key offline with an encryption subkey.
  • Transferred the encryption subkey to Yubikey 1.
  • On Yubikey 1, generated auth and sign subkeys.
  • Transferred the encryption subkey to Yubikey 2.
  • On Yubikey 2, generated auth and sign subkeys.

So I am left with:

gpg2 --list-keys
/Users/scott/.gnupg/pubring.gpg
-------------------------------
pub   3072R/600955C7 2016-09-09
uid       [ultimate] Scott Cariss 
uid       [ultimate] Scott Cariss (msn.com) 
uid       [ultimate] Scott Cariss (bigfish.co.uk)
uid       [ultimate] [jpeg image of size 12378]
sub   2048R/6FE6415F 2016-09-09
sub   2048R/D6DBBCAC 2016-09-09
sub   2048R/01A208C9 2016-09-09
sub   2048R/8D2A1368 2016-10-23
sub   2048R/65B08C5B 2016-10-23

My encryption subkey is shared between smart cards and have individual auth and sign keys on each smart card.

But when I come to decrypt something it always goes to the first smart card and won't find the encryption key on the other smart card. The gpg-agent/pin entry will just ask me to insert the correct smart card.


UPDATE (Workaround)

As already answered, it is not something that GPG supports but I have found a working solution that works for me.

On Mac OS X I use https://www.controlplaneapp.com/ to detect the arrival of one of my smart cards (yubikeys) and get it to run a script:

#!/bin/bash
{
    killall -9 ssh-agent gpg-agent
    for keystub in $(/usr/local/MacGPG2/bin/gpg2 --with-keygrip --list-secret-keys {{EMAIL ADDRESS}} | grep Keygrip | awk '{print $3}'); do rm /Users/{{USERNAME}}/.gnupg/private-keys-v1.d/$keystub.key; done;
    /usr/local/MacGPG2/bin/gpg2 --card-status
    eval $(/usr/local/MacGPG2/bin/gpgconf --launch gpg-agent)
    ssh-add -l
} &> /Users/{{USERNAME}}/bin/gpg-card-change-log.txt

exit 0

As the secret keys are all kept offline, there is no harm in deleting them and then running --card-status which brings in the secret key stubs from the smart card currently plugged in.

Scott
  • 293
  • 2
  • 6
  • 2
    I have tested the above snippet with gpg 2.1.x and 2.2.x, and unfortunately invoking `gpg2 --batch --delete-secret-keys {{KEY_ID}}` doesn't do much. What one needs to do is invoke `gpg --with-keygrip --list-secret-keys {{KEY_ID}}`, get the keygrip values for the subkeys moved to the card, and then delete or rename the corresponding files from `~/.gnupg/private-keys-v1.d/`. Consider this oneliner: `for keystub in $(gpg --with-keygrip --list-secret-keys 0xB1349B0B4B8B7600 | grep Keygrip | awk '{print $3}'); do ls -la ~/.gnupg/private-keys-v1.d/$keystub.key; done`; it's ugly, but it works. – Rouben Tchakhmakhtchian Dec 13 '17 at 07:17
  • Thanks Rouben, when I upgraded to >= 2.1 the delete secret keys no longer worked. I have since upgraded my script to incorporate your comment. I will update my question with my new workaround. – Scott Jan 16 '18 at 13:36
  • I ran into exactly the same problem and since I use several yubikeys I have written a script that lets you select a key and automatically removes the key stubs (I took some steps to make sure the script is quite safe to run). See: https://github.com/rjekker/gpg-switch-card – rje Jan 10 '19 at 22:58

3 Answers3

11

I guess you will have bad luck, and this is not supported by GnuPG. When using OpenPGP smart cards, a secret key dummy is stored in your keyring, holding a reference to the smart card it is stored on. The secret key subpacket looks like this when displayed through gpg --list-packets:

:secret sub key packet:
        version 4, algo 1, created 1358985314, expires 0
        pkey[0]: [2048 bits]
        pkey[1]: [17 bits]
        gnu-divert-to-card S2K, algo: 0, simple checksum, hash: 0
        serial-number:  01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef
        keyid: 9FF7E53ACB4BD3EE

Peeking at the code, it does not seem to me more than one serial number would be supported.

Jens Erat
  • 23,446
  • 12
  • 72
  • 96
  • Would it at all be possible to change this value by a script? I already have a script that restarts the GPG agent when any of my Yubikeys are inserted or ejected. – Scott Mar 27 '17 at 16:44
  • Looks like this has been requested to be fixed https://bugs.gnupg.org/gnupg/issue1798 but not currently being worked on. It seems by the original poster that a workaround is to delete the stub on insert of the card – Scott Mar 27 '17 at 16:52
  • At least with the old `.gpg` keyrings consisting of plain uncompressed OpenPGP packets, this should be possible by simply replacing the serial number. Be aware those are binary files, so look for solutions considering binary replacement. And better create lots of backups when fiddling with the private keyring... – Jens Erat Mar 27 '17 at 18:27
  • Thanks Jens, I have found a workaround and added it to my question. – Scott Mar 28 '17 at 10:54
  • 3
    @Brady Thanks for sharing the GnuPG bug report! FYI, [the GPG bug report you originally brought up on Mar 27](https://bugs.gnupg.org/gnupg/issue1798) has been merged into [this bug report](https://dev.gnupg.org/T2291), which is active. The proposal now seems to augment the private key stub storage format to allow for multiple SmartCard serial numbers to be stored, which sounds promising. For the time being, see my prior comment to your question above. – Rouben Tchakhmakhtchian Dec 13 '17 at 07:25
  • 1
    It looks on the active thread like this may be solved, anybody has a simple to follow explanation / example? – Zorglub29 May 28 '20 at 20:18
4

Running

gpg-connect-agent "scd serialno" "learn --force" /bye

will update the secret key stubs for the PGP keys on the currently inserted key. So running that after key insertion will cause gpg to use the currently inserted key.

Create backup Yubikey with identical PGP keys

-1
resetcard() {
  rm -rf ~/.gnupg/private-keys-v1.d
  killall -9 ssh-agent gpg-agent
}

I found out if you delete this cached folder (im not 100% sure what it is for, but i think it must be some sort of key cache??) and then restart the agent it works just fine afterward. Wish we didn't have to do this

zackify
  • 99