Commit message: ecc: Add input validation for X25519.

* cipher/ecc.c (ecc_decrypt_raw): Add input validation.
* mpi/ec.c (ec_p_init): Use scratch buffer for bad points.
(_gcry_mpi_ec_bad_point): New.

--

Following is the paper describing the attack:

    May the Fourth Be With You: A Microarchitectural Side Channel Attack
    on Real-World Applications of Curve25519
    by Daniel Genkin, Luke Valenta, and Yuval Yarom

In the current implementation, we do output checking and it results an
error for those bad points.  However, when attacked, the computation
will done with leak of private key, even it will results errors.  To
mitigate leak, we added input validation.

Note that we only list bad points with MSB=0.  By X25519, MSB is
always cleared.

In future, we should implement constant-time field computation.  Then,
this input validation could be removed, if performance is important
and we are sure for no leak.

CVE-id: CVE-2017-0379
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
CVE: CVE-2017-0379
CWE: CWE-200
Vulnerability description: Libgcrypt before 1.8.1 does not properly consider Curve25519 side-channel attacks, which makes it easier for attackers to discover a secret key, related to cipher/ecc.c and mpi/ec.c.

Project: libgcrypt
File: ecc.c

ecc_decrypt_raw (gcry_sexp_t *r_plain, gcry_sexp_t s_data, gcry_sexp_t keyparms)
{
  unsigned int nbits;
  gpg_err_code_t rc;
  struct pk_encoding_ctx ctx;
  gcry_sexp_t l1 = NULL;
  gcry_mpi_t data_e = NULL;
  ECC_secret_key sk;
  gcry_mpi_t mpi_g = NULL;
  char *curvename = NULL;
  mpi_ec_t ec = NULL;
  mpi_point_struct kG;
  mpi_point_struct R;
  gcry_mpi_t r = NULL;
  int flags = 0;

  memset (&sk, 0, sizeof sk);
  point_init (&kG);
  point_init (&R);

  _gcry_pk_util_init_encoding_ctx (&ctx, PUBKEY_OP_DECRYPT,
                                   (nbits = ecc_get_nbits (keyparms)));

  /* Look for flags. */
  l1 = sexp_find_token (keyparms, "flags", 0);
  if (l1)
    {
      rc = _gcry_pk_util_parse_flaglist (l1, &flags, NULL);
      if (rc)
        goto leave;
    }
  sexp_release (l1);
  l1 = NULL;

  /*
   * Extract the data.
   */
  rc = _gcry_pk_util_preparse_encval (s_data, ecc_names, &l1, &ctx);
  if (rc)
    goto leave;
  rc = sexp_extract_param (l1, NULL, "e", &data_e, NULL);
  if (rc)
    goto leave;
  if (DBG_CIPHER)
    log_printmpi ("ecc_decrypt  d_e", data_e);
  if (mpi_is_opaque (data_e))
    {
      rc = GPG_ERR_INV_DATA;
      goto leave;
    }

  /*
   * Extract the key.
   */
  rc = sexp_extract_param (keyparms, NULL, "-p?a?b?g?n?h?+d",
                           &sk.E.p, &sk.E.a, &sk.E.b, &mpi_g, &sk.E.n,
                           &sk.E.h, &sk.d, NULL);
  if (rc)
    goto leave;
  if (mpi_g)
    {
      point_init (&sk.E.G);
      rc = _gcry_ecc_os2ec (&sk.E.G, mpi_g);
      if (rc)
        goto leave;
    }
  /* Add missing parameters using the optional curve parameter.  */
  sexp_release (l1);
  l1 = sexp_find_token (keyparms, "curve", 5);
  if (l1)
    {
      curvename = sexp_nth_string (l1, 1);
      if (curvename)
        {
          rc = _gcry_ecc_fill_in_curve (0, curvename, &sk.E, NULL);
          if (rc)
            goto leave;
        }
    }
  /* Guess required fields if a curve parameter has not been given.  */
  if (!curvename)
    {
      sk.E.model = MPI_EC_WEIERSTRASS;
      sk.E.dialect = ECC_DIALECT_STANDARD;
      if (!sk.E.h)
	sk.E.h = mpi_const (MPI_C_ONE);
    }
  if (DBG_CIPHER)
    {
      log_debug ("ecc_decrypt info: %s/%s\n",
                 _gcry_ecc_model2str (sk.E.model),
                 _gcry_ecc_dialect2str (sk.E.dialect));
      if (sk.E.name)
        log_debug  ("ecc_decrypt name: %s\n", sk.E.name);
      log_printmpi ("ecc_decrypt    p", sk.E.p);
      log_printmpi ("ecc_decrypt    a", sk.E.a);
      log_printmpi ("ecc_decrypt    b", sk.E.b);
      log_printpnt ("ecc_decrypt  g",   &sk.E.G, NULL);
      log_printmpi ("ecc_decrypt    n", sk.E.n);
      log_printmpi ("ecc_decrypt    h", sk.E.h);
      if (!fips_mode ())
        log_printmpi ("ecc_decrypt    d", sk.d);
    }
  if (!sk.E.p || !sk.E.a || !sk.E.b || !sk.E.G.x || !sk.E.n || !sk.E.h || !sk.d)
    {
      rc = GPG_ERR_NO_OBJ;
      goto leave;
    }


  ec = _gcry_mpi_ec_p_internal_new (sk.E.model, sk.E.dialect, flags,
                                    sk.E.p, sk.E.a, sk.E.b);

  /*
   * Compute the plaintext.
   */
  if (ec->model == MPI_EC_MONTGOMERY)
    rc = _gcry_ecc_mont_decodepoint (data_e, ec, &kG);
  else
    rc = _gcry_ecc_os2ec (&kG, data_e);
  if (rc)
    goto leave;

  if (DBG_CIPHER)
    log_printpnt ("ecc_decrypt    kG", &kG, NULL);

  if ((flags & PUBKEY_FLAG_DJB_TWEAK))
    {
      /* For X25519, by its definition, validation should not be done.  */
      /* (Instead, we do output check.)
       *
       * However, to mitigate secret key leak from our implementation,
       * we also do input validation here.  For constant-time
       * implementation, we can remove this input validation.
       */
      if (_gcry_mpi_ec_bad_point (&kG, ec))
        {
          rc = GPG_ERR_INV_DATA;
          goto leave;
        }
    }
  else if (!_gcry_mpi_ec_curve_point (&kG, ec))
    {
      rc = GPG_ERR_INV_DATA;
      goto leave;
    }

  /* R = dkG */
  _gcry_mpi_ec_mul_point (&R, sk.d, &kG, ec);

  /* The following is false: assert( mpi_cmp_ui( R.x, 1 )==0 );, so:  */
  {
    gcry_mpi_t x, y;

    x = mpi_new (0);
    if (ec->model == MPI_EC_MONTGOMERY)
      y = NULL;
    else
      y = mpi_new (0);

    if (_gcry_mpi_ec_get_affine (x, y, &R, ec))
      {
        rc = GPG_ERR_INV_DATA;
        goto leave;
        /*
         * Note for X25519.
         *
         * By the definition of X25519, this is the case where X25519
         * returns 0, mapping infinity to zero.  However, we
         * deliberately let it return an error.
         *
         * For X25519 ECDH, comming here means that it might be
         * decrypted by anyone with the shared secret of 0 (the result
         * of this function could be always 0 by other scalar values,
         * other than the private key of SK.D).
         *
         * So, it looks like an encrypted message but it can be
         * decrypted by anyone, or at least something wrong
         * happens.  Recipient should not proceed as if it were
         * properly encrypted message.
         *
         * This handling is needed for our major usage of GnuPG,
         * where it does the One-Pass Diffie-Hellman method,
         * C(1, 1, ECC CDH), with an ephemeral key.
         */
      }

    if (y)
      r = _gcry_ecc_ec2os (x, y, sk.E.p);
    else
      {
        unsigned char *rawmpi;
        unsigned int rawmpilen;

        rawmpi = _gcry_mpi_get_buffer_extra (x, nbits/8, -1,
                                             &rawmpilen, NULL);
        if (!rawmpi)
          {
            rc = gpg_err_code_from_syserror ();
            goto leave;
          }
        else
          {
            rawmpi[0] = 0x40;
            rawmpilen++;
            r = mpi_new (0);
            mpi_set_opaque (r, rawmpi, rawmpilen*8);
          }
      }
    if (!r)
      rc = gpg_err_code_from_syserror ();
    else
      rc = 0;
    mpi_free (x);
    mpi_free (y);
  }
  if (DBG_CIPHER)
    log_printmpi ("ecc_decrypt  res", r);

  if (!rc)
    rc = sexp_build (r_plain, NULL, "(value %m)", r);

 leave:
  point_free (&R);
  point_free (&kG);
  _gcry_mpi_release (r);
  _gcry_mpi_release (sk.E.p);
  _gcry_mpi_release (sk.E.a);
  _gcry_mpi_release (sk.E.b);
  _gcry_mpi_release (mpi_g);
  point_free (&sk.E.G);
  _gcry_mpi_release (sk.E.n);
  _gcry_mpi_release (sk.E.h);
  _gcry_mpi_release (sk.d);
  _gcry_mpi_release (data_e);
  xfree (curvename);
  sexp_release (l1);
  _gcry_mpi_ec_free (ec);
  _gcry_pk_util_free_encoding_ctx (&ctx);
  if (DBG_CIPHER)
    log_debug ("ecc_decrypt    => %s\n", gpg_strerror (rc));
  return rc;
}

Project: libgcrypt
File: ec.c

_gcry_mpi_ec_curve_point (gcry_mpi_point_t point, mpi_ec_t ctx)
{
  int res = 0;
  gcry_mpi_t x, y, w;

  x = mpi_new (0);
  y = mpi_new (0);
  w = mpi_new (0);

  switch (ctx->model)
    {
    case MPI_EC_WEIERSTRASS:
      {
        gcry_mpi_t xxx;

        if (_gcry_mpi_ec_get_affine (x, y, point, ctx))
          goto leave;

        xxx = mpi_new (0);

        /* y^2 == x^3 + a·x + b */
        ec_pow2 (y, y, ctx);

        ec_pow3 (xxx, x, ctx);
        ec_mulm (w, ctx->a, x, ctx);
        ec_addm (w, w, ctx->b, ctx);
        ec_addm (w, w, xxx, ctx);

        if (!mpi_cmp (y, w))
          res = 1;

        _gcry_mpi_release (xxx);
      }
      break;
    case MPI_EC_MONTGOMERY:
      {
#define xx y
        /* With Montgomery curve, only X-coordinate is valid.  */
        if (_gcry_mpi_ec_get_affine (x, NULL, point, ctx))
          goto leave;

        /* The equation is: b * y^2 == x^3 + a · x^2 + x */
        /* We check if right hand is quadratic residue or not by
           Euler's criterion.  */
        /* CTX->A has (a-2)/4 and CTX->B has b^-1 */
        ec_mulm (w, ctx->a, mpi_const (MPI_C_FOUR), ctx);
        ec_addm (w, w, mpi_const (MPI_C_TWO), ctx);
        ec_mulm (w, w, x, ctx);
        ec_pow2 (xx, x, ctx);
        ec_addm (w, w, xx, ctx);
        ec_addm (w, w, mpi_const (MPI_C_ONE), ctx);
        ec_mulm (w, w, x, ctx);
        ec_mulm (w, w, ctx->b, ctx);
#undef xx
        /* Compute Euler's criterion: w^(p-1)/2 */
#define p_minus1 y
        ec_subm (p_minus1, ctx->p, mpi_const (MPI_C_ONE), ctx);
        mpi_rshift (p_minus1, p_minus1, 1);
        ec_powm (w, w, p_minus1, ctx);

        res = !mpi_cmp_ui (w, 1);
#undef p_minus1
      }
      break;
    case MPI_EC_EDWARDS:
      {
        if (_gcry_mpi_ec_get_affine (x, y, point, ctx))
          goto leave;

        /* a · x^2 + y^2 - 1 - b · x^2 · y^2 == 0 */
        ec_pow2 (x, x, ctx);
        ec_pow2 (y, y, ctx);
        if (ctx->dialect == ECC_DIALECT_ED25519)
          mpi_sub (w, ctx->p, x);
        else
          ec_mulm (w, ctx->a, x, ctx);
        ec_addm (w, w, y, ctx);
        ec_subm (w, w, mpi_const (MPI_C_ONE), ctx);
        ec_mulm (x, x, y, ctx);
        ec_mulm (x, x, ctx->b, ctx);
        ec_subm (w, w, x, ctx);
        if (!mpi_cmp_ui (w, 0))
          res = 1;
      }
      break;
    }

 leave:
  _gcry_mpi_release (w);
  _gcry_mpi_release (x);
  _gcry_mpi_release (y);

  return res;
}