Loading encryption subkey to smartcard mutates fingerprint
alex-u410 opened this issue · 1 comments
Hello, I am trying to generate a ed+cv25519 signing+encryption key on a smartcard. When I push the keys to the smartcard using gpg-connect-agent
, the encryption subkey fingerprint gets mutated relative to the one in my keyring.
Here is my debugging code to create an entity and output a secret key file
ent := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
}
now := time.Unix(1709329403, 0)
cfgTime := func() time.Time { return now }
cfg := &propacket.Config{
Rand: bytes.NewReader(ent),
Algorithm: propacket.PubKeyAlgoEdDSA,
Curve: propacket.Curve25519,
KeyLifetimeSecs: 0, // no expiration
Time: cfgTime,
V5Keys: false,
}
entity, err := propgp.NewEntity(keyID, "", "", cfg)
...
serNew := bytes.NewBuffer(nil)
err := entity.SerializePrivate(serNew, nil)
if err != nil {
panic("SerializePrivate new" + err.Error())
}
_ = os.WriteFile(secretPath, serNew.Bytes(), 0644)
I load that key into my keyring and get the fingerprints + keygrips
>gpg --import <secretPath>
gpg: key EA0063F6C7ACB985: public key "test-sc" imported
gpg: key EA0063F6C7ACB985: secret key imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: secret keys read: 1
gpg: secret keys unchanged: 1
>gpg --show-key --with-colons <secretPath>
sec:-:255:22:EA0063F6C7ACB985:1709329403:::-:::scESC:::::ed25519:::0:
fpr:::::::::D3A4BD8DD05557DC6C469D37EA0063F6C7ACB985:
grp:::::::::11FA927B453C99646DCE42A4EC33E7CDE9B5D80A:
uid:-::::1709329403::26898656831648DBFB10988947B1523808F13B24::test-sc::::::::::0:
ssb:-:255:18:9090FA627420EF33:1709329403::::::e:::::cv25519::
fpr:::::::::3CDC4501333D5FA52B98F28A9090FA627420EF33:
grp:::::::::6BD4B392CE9CA0994038B4ACB2A8EEA33C42B73B:
> gpg --with-subkey-fingerprints --list-key test-sc
pub ed25519 2024-03-01 [SC]
D3A4BD8DD05557DC6C469D37EA0063F6C7ACB985
uid [ unknown] test-sc
sub cv25519 2024-03-01 [E]
3CDC4501333D5FA52B98F28A9090FA627420EF33
The fingerprints from the file match the ones in my keyring, as expected.
I then load the keys to the card:
NOTE: I am formatting the key's timestamp by converting it to a
time.Time
object and formatting withkeyTimestamp.UTC().Format("20060102T150405")
>gpg-connect-agent
> KEYTOCARD 11FA927B453C99646DCE42A4EC33E7CDE9B5D80A D2760001240100000006199714170000 OPENPGP.1 20240301T214323
> KEYTOCARD 6BD4B392CE9CA0994038B4ACB2A8EEA33C42B73B D2760001240100000006199714170000 OPENPGP.2 20240301T214323
> /bye
However, when I run gpg --card-status
, I see the following fingerprints:
Signature key ....: D3A4 BD8D D055 57DC 6C46 9D37 EA00 63F6 C7AC B985
created ....: 2024-03-01 21:43:23
Encryption key....: 7684 69AC 284E EABB 2000 4B7C CFF6 8A7F 6CBA 9554
created ....: 2024-03-01 21:43:23
The signature key's fingerprint matches the one in my keyring, but the encryption subkey does not.
If I instead generate a key with gpg, both fingerprints stay the same when loading to a smartcard:
>gpg --batch --passphrase "" --quick-generate-key test-sc-2 ed25519 sign never
gpg: revocation certificate stored as '~/.gnupg/openpgp-revocs.d/0A842270AB61EFC33F2E298B1717B3D9583B854F.rev'
>gpg --batch --passphrase "" --quick-add-key 0A842270AB61EFC33F2E298B1717B3D9583B854F cv25519 encr never
> gpg --export-secret-key 0A842270AB61EFC33F2E298B1717B3D9583B854F > foo
> gpg --show-key --with-colons foo
sec:u:255:22:1717B3D9583B854F:1709585144:::u:::scESC:::::ed25519:::0:
fpr:::::::::0A842270AB61EFC33F2E298B1717B3D9583B854F:
grp:::::::::6D886491D5771C42D8ADF0BE748E79B86F7D8B4E:
uid:u::::1709585144::6B8541341C8D57622DF9AD9AE28036BCD5DB5D97::test-sc-2::::::::::0:
ssb:u:255:18:C5721B49EBE22FE3:1709585186::::::e:::::cv25519::
fpr:::::::::3F718B3457D3AC317031DCA2C5721B49EBE22FE3:
grp:::::::::7038DDE1064A68FB96BAF95D0EC985E309290599:
> gpg-connect-agent
> KEYTOCARD 6D886491D5771C42D8ADF0BE748E79B86F7D8B4E D2760001240100000006199714170000 OPENPGP.1 20240304T204544
> KEYTOCARD 7038DDE1064A68FB96BAF95D0EC985E309290599 D2760001240100000006199714170000 OPENPGP.2 20240304T204626
> /bye
> gpg --card-status
...
Signature key ....: 0A84 2270 AB61 EFC3 3F2E 298B 1717 B3D9 583B 854F
created ....: 2024-03-04 20:45:44
Encryption key....: 3F71 8B34 57D3 AC31 7031 DCA2 C572 1B49 EBE2 2FE3
created ....: 2024-03-04 20:46:26
...
This behavior indicates some discrepancy being created in the go-crypto
serialized secret file, like perhaps a field is not getting set, and then gets set by gpg-connect-agent
when loading. However, I've tried a lot of different flags and the depth of Entity
configuration is a bit dizzying so I thought I'd see if any of the maintainers have thoughts here.
A few notes:
- Importantly, the encryption key cryptographically works just fine; if encryption/decryption functionality targets the card's primary key (i.e. ed25519) fingerprint, the encryption subkey is used, as you would expect. However, it's unsettling to see a discrepancy like this.
- I am using
go-crypto
specifically to feed curated entropy into the rand reader, as I can find no way to do this with gpg. I need my gpg keys to be determinsitic - It is not possible to export a key/keyfile from a smartcard due to limitations in OpenPGP data persistence, so I can't see exactly what changed. And as mentioned above, I cannot find a way to feed specific entropy bytes to pgp when generating keys, so I can't cross compare that way either.
- This issue presents itself for multiple smartcard types and manufacturers (Yubikey 5A + C, Nitrokey 3A + C)
Any help would be appreciated! Hopefully I've just overlooked something obvious in the configuration.
The problem was two-fold:
- I was not adding the ECDH params to
KEYTOCARD
. I should have been using e.g.KEYTOCARD 6D886491D5771C42D8ADF0BE748E79B86F7D8B4E D2760001240100000006199714170000 OPENPGP.1 20240304T204544 03010A09
- Even with the added ECDH params, I needed to update gpg, as older versions (2.3) do not work with this argument.
Hope this helps someone! I will close the issue as it is unrelated to this repo :)