Page MenuHome GnuPG

Cannot decrypt a message encrypted to a Cv25519 key on a token
Testing, HighPublic

Description

I have a Cv25519 subkey that is used both on its own _and_ as the ECC part of a dual Kyber/ECC key. The Cv25519 key is on a Gnuk token. (I briefly described this setup in T7648).

With GnuPG 2.5.7, trying to decrypt a message encrypted to the Kyber/ECC key works as expected (confirming that T7648 seems fixed). But now, trying to decrypt a message encrypted to the Cv25519 key _only_ fails:

$ gpg -d test.asc 
gpg: encrypted with cv25519 key, ID BF74EFBEBF46197E, created 2018-06-05
      "Damien Goutte-Gattat <dgouttegattat@incenp.org>"
gpg: public key decryption failed: Invalid data
gpg: decryption failed: Invalid data

I also replicated the issue on a separate GNUPGHOME with no Kyber key at all, just the token-based Cv25519.

Relevant fragment from the agent’s log:

gpg-agent[22145] DBG: chan_12 <- SETKEYDESC Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key:%0A%22Damien+Goutte-Gattat+<dgouttegattat@incenp.org>%22%0A>
gpg-agent[22145] DBG: chan_12 -> OK
gpg-agent[22145] DBG: chan_12 <- PKDECRYPT --kem=PGP
gpg-agent[22145] DBG: chan_12 -> S INQUIRE_MAXLEN 4096
gpg-agent[22145] DBG: chan_12 -> INQUIRE CIPHERTEXT
gpg-agent[22145] DBG: chan_12 <- [ 44 20 28 37 3a 65 6e 63 2d 76 61 6c 28 33 3a 65 ...(198 byte(s) skipped) ]
gpg-agent[22145] DBG: chan_12 <- END
gpg-agent[22145] DBG: chan_13 -> SERIALNO --all
gpg-agent[22145] DBG: chan_13 <- S SERIALNO D2760001240100000006121575690000
gpg-agent[22145] DBG: chan_13 <- OK
gpg-agent[22145] DBG: chan_13 -> KEYINFO B5940B7C7D54876AC1538D222EC85F1E8BAA76CF
gpg-agent[22145] SIGUSR2 received - updating card event counter
gpg-agent[22145] DBG: chan_13 <- S KEYINFO B5940B7C7D54876AC1538D222EC85F1E8BAA76CF T D2760001240100000006121575690000 OPENPGP.2 e
gpg-agent[22145] DBG: chan_13 <- OK
gpg-agent[22145] ecc_pgp_kem_decap: ECC cipher text length invalid (32 != 33)
gpg-agent[22145] command 'PKDECRYPT' failed: Invalid data
gpg-agent[22145] DBG: chan_12 -> ERR 67108943 Invalid data <GPG Agent>

Details

Version
2.5.7

Event Timeline

So I’ve played a bit with agent/pkdecrypt.c. I don’t claim to understand everything that is going on there, but:

Looking at the ecc_table entry for Curve25519:

{
  "Curve25519",
  33, 32, 32,
  GCRY_MD_SHA3_256, GCRY_KEM_RAW_X25519,
  1
},

if I try to set the 4th member (point_len) to 33 instead of 32, I get a behaviour that is “inverse” of what I describe above: that is, decrypting a message encrypted to a Cv25519 key only now works, but decrypting a message encrypted to a Cv25519/Kyber key now fails (with the same “Invalid data” error).

So it seems we should expect a slightly different point length depending on whether the encryption was made to a simple Cv25519 key, or to a dual Cv25519/Kyber key. Specifically, in the first case the point length is one byte longer. If I understand correctly (that’s a big if), it is because in that case a 0x40 byte is prepended to the ciphertext.

I’ve tried the following patch, which appears to work:

@@ -484,7 +484,8 @@ ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
     }
   *r_ecc = ecc;
 
-  if (ecc->point_len != ecc_point_len)
+  if (ecc->point_len != ecc_point_len
+      && (ecc_ct[0] == 0x40 && ecc->point_len != ecc_point_len - 1))
     {
       if (opt.verbose)
         log_info ("%s: ECC cipher text length invalid (%zu != %zu)\n",
@@ -504,7 +505,7 @@ ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
       if (s_skey0 && agent_is_tpm2_key (s_skey0))
         {
           err = agent_tpm2d_ecc_kem (ctrl, shadow_info0,
-                                     ecc_ct, ecc->point_len, ecc_ecdh);
+                                     ecc_ct, ecc_point_len, ecc_ecdh);
           if (err)
             {
               log_error ("TPM decryption failed: %s\n", gpg_strerror (err));
@@ -513,7 +514,7 @@ ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
         }
       else
         {
-          err = agent_card_ecc_kem (ctrl, ecc_ct, ecc->point_len, ecc_ecdh);
+          err = agent_card_ecc_kem (ctrl, ecc_ct, ecc_point_len, ecc_ecdh);
           if (err)
             {
               log_error ("smartcard decryption failed: %s\n",
@@ -886,7 +887,7 @@ ecc_kem_decrypt (ctrl_t ctrl, const char *desc_text,
   if (err)
     goto leave;
   err = gnupg_ecc_kem_kdf (kek, kek_len, hashalgo,
-                           ecc->point_len > ecc->scalar_len ?
+                           ecc_point_len > ecc->scalar_len ?
                            /* For Weierstrass curve, extract
                               x-component from the point.  */
                            ecc_ecdh + 1 : ecc_ecdh,

Basically, we

(1) do not error out if the actual point length is one byte longer than expected and the ciphertext starts with the 0x40 prefix;
(2) use the actual point length (ecc_point_len) rather than the expected theoretical length.

gniibe triaged this task as High priority.

My test coverage was not good (even if I daily use Curve25519 on Gnuk Token).
Your analysis is correct.

In the gpg frontend side, it has the prefix 0x40.

Fixed in rGfde915af1cf4: agent: Fix for the prefix 0x40 in the point representation.

gniibe changed the task status from Open to Testing.Fri, Jun 6, 7:27 AM