ecc: ECDSA adjustments for FIPS 186-4
authorVitezslav Cizek <vcizek@suse.com>
Tue, 27 Oct 2015 13:29:11 +0000 (14:29 +0100)
committerWerner Koch <wk@gnupg.org>
Fri, 18 Mar 2016 14:26:28 +0000 (15:26 +0100)
* cipher/ecc-curves.c: Unmark curve P-192 for FIPS.
* cipher/ecc.c: Add ECDSA self test.
* cipher/pubkey-util.c (_gcry_pk_util_init_encoding_ctx): Use SHA-2
in FIPS mode.
* tests/fipsdrv.c: Add support for ECDSA signatures.
--

Enable ECC in FIPS mode.
According to NIST SP 800-131A, curve P-192 and SHA-1 are disallowed
for key pair generation and signature generation after 2013.

Thanks to Jan Matejek for the patch.
Signed-off-by: Vitezslav Cizek <vcizek@suse.com>
Minor source code re-formatting by -wk.

cipher/ecc-curves.c
cipher/ecc.c
cipher/pubkey-util.c
src/fips.c
tests/fipsdrv.c

index a74501d..91f29cc 100644 (file)
@@ -160,7 +160,7 @@ static const ecc_domain_parms_t domain_parms[] =
     },
 #endif /*0*/
     {
-      "NIST P-192", 192, 1,
+      "NIST P-192", 192, 0,
       MPI_EC_WEIERSTRASS, ECC_DIALECT_STANDARD,
       "0xfffffffffffffffffffffffffffffffeffffffffffffffff",
       "0xfffffffffffffffffffffffffffffffefffffffffffffffc",
index f65203f..5b03530 100644 (file)
@@ -73,6 +73,25 @@ static const char *ecc_names[] =
   };
 
 
+/* Sample NIST P-256 key from RFC 6979 A.2.5 */
+static const char sample_public_key_secp256[] =
+  "(public-key"
+  " (ecc"
+  "  (curve secp256r1)"
+  "  (q #04"
+  /**/  "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"
+  /**/  "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299#)))";
+
+static const char sample_secret_key_secp256[] =
+  "(private-key"
+  " (ecc"
+  "  (curve secp256r1)"
+  "  (d #C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721#)"
+  "  (q #04"
+  /**/  "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"
+  /**/  "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299#)))";
+
+
 /* Registered progress function and its callback value. */
 static void (*progress_cb) (void *, const char*, int, int, int);
 static void *progress_cb_data;
@@ -1956,23 +1975,165 @@ _gcry_pk_ecc_get_sexp (gcry_sexp_t *r_sexp, int mode, mpi_ec_t ec)
      Self-test section.
  */
 
+static const char *
+selftest_sign (gcry_sexp_t pkey, gcry_sexp_t skey)
+{
+  /* Sample data from RFC 6979 section A.2.5, hash is of message "sample" */
+  static const char sample_data[] =
+    "(data (flags rfc6979)"
+    " (hash sha256 #af2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e98915"
+    /**/           "62113d8a62add1bf#))";
+  static const char sample_data_bad[] =
+    "(data (flags rfc6979)"
+    " (hash sha256 #bf2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e98915"
+    /**/           "62113d8a62add1bf#))";
+  static const char signature_r[] =
+    "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716";
+  static const char signature_s[] =
+    "f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8";
+
+  const char *errtxt = NULL;
+  gcry_error_t err;
+  gcry_sexp_t data = NULL;
+  gcry_sexp_t data_bad = NULL;
+  gcry_sexp_t sig = NULL;
+  gcry_sexp_t l1 = NULL;
+  gcry_sexp_t l2 = NULL;
+  gcry_mpi_t r = NULL;
+  gcry_mpi_t s = NULL;
+  gcry_mpi_t calculated_r = NULL;
+  gcry_mpi_t calculated_s = NULL;
+  int cmp;
+
+  err = sexp_sscan (&data, NULL, sample_data, strlen (sample_data));
+  if (!err)
+    err = sexp_sscan (&data_bad, NULL,
+                      sample_data_bad, strlen (sample_data_bad));
+  if (!err)
+    err = _gcry_mpi_scan (&r, GCRYMPI_FMT_HEX, signature_r, 0, NULL);
+  if (!err)
+    err = _gcry_mpi_scan (&s, GCRYMPI_FMT_HEX, signature_s, 0, NULL);
+
+  if (err)
+    {
+      errtxt = "converting data failed";
+      goto leave;
+    }
+
+  err = _gcry_pk_sign (&sig, data, skey);
+  if (err)
+    {
+      errtxt = "signing failed";
+      goto leave;
+    }
+
+  /* check against known signature */
+  errtxt = "signature validity failed";
+  l1 = _gcry_sexp_find_token (sig, "sig-val", 0);
+  if (!l1)
+    goto leave;
+  l2 = _gcry_sexp_find_token (l1, "ecdsa", 0);
+  if (!l2)
+    goto leave;
+
+  sexp_release (l1);
+  l1 = l2;
+
+  l2 = _gcry_sexp_find_token (l1, "r", 0);
+  if (!l2)
+    goto leave;
+  calculated_r = _gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+  if (!calculated_r)
+    goto leave;
+
+  l2 = _gcry_sexp_find_token (l1, "s", 0);
+  if (!l2)
+    goto leave;
+  calculated_s = _gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+  if (!calculated_s)
+    goto leave;
+
+  errtxt = "known sig check failed";
+
+  cmp = _gcry_mpi_cmp (r, calculated_r);
+  if (cmp)
+    goto leave;
+  cmp = _gcry_mpi_cmp (s, calculated_s);
+  if (cmp)
+    goto leave;
+
+  errtxt = NULL;
+
+  /* verify generated signature */
+  err = _gcry_pk_verify (sig, data, pkey);
+  if (err)
+    {
+      errtxt = "verify failed";
+      goto leave;
+    }
+  err = _gcry_pk_verify (sig, data_bad, pkey);
+  if (gcry_err_code (err) != GPG_ERR_BAD_SIGNATURE)
+    {
+      errtxt = "bad signature not detected";
+      goto leave;
+    }
+
+
+ leave:
+  sexp_release (sig);
+  sexp_release (data_bad);
+  sexp_release (data);
+  sexp_release (l1);
+  sexp_release (l2);
+  mpi_release (r);
+  mpi_release (s);
+  mpi_release (calculated_r);
+  mpi_release (calculated_s);
+  return errtxt;
+}
+
 
 static gpg_err_code_t
 selftests_ecdsa (selftest_report_func_t report)
 {
   const char *what;
   const char *errtxt;
+  gcry_error_t err;
+  gcry_sexp_t skey = NULL;
+  gcry_sexp_t pkey = NULL;
+
+  what = "convert";
+  err = sexp_sscan (&skey, NULL, sample_secret_key_secp256,
+                    strlen (sample_secret_key_secp256));
+  if (!err)
+    err = sexp_sscan (&pkey, NULL, sample_public_key_secp256,
+                      strlen (sample_public_key_secp256));
+  if (err)
+    {
+      errtxt = _gcry_strerror (err);
+      goto failed;
+    }
+
+  what = "key consistency";
+  err = ecc_check_secret_key(skey);
+  if (err)
+    {
+      errtxt = _gcry_strerror (err);
+      goto failed;
+    }
 
-  what = "low-level";
-  errtxt = NULL; /*selftest ();*/
+  what = "sign";
+  errtxt = selftest_sign (pkey, skey);
   if (errtxt)
     goto failed;
 
-  /* FIXME:  need more tests.  */
-
+  sexp_release(pkey);
+  sexp_release(skey);
   return 0; /* Succeeded. */
 
  failed:
+  sexp_release(pkey);
+  sexp_release(skey);
   if (report)
     report ("pubkey", GCRY_PK_ECC, what, errtxt);
   return GPG_ERR_SELFTEST_FAILED;
@@ -1996,7 +2157,7 @@ run_selftests (int algo, int extended, selftest_report_func_t report)
 \f
 gcry_pk_spec_t _gcry_pubkey_spec_ecc =
   {
-    GCRY_PK_ECC, { 0, 0 },
+    GCRY_PK_ECC, { 0, 1 },
     (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR),
     "ECC", ecc_names,
     "pabgnhq", "pabgnhqd", "sw", "rs", "pabgnhq",
index d0d6003..76d3923 100644 (file)
@@ -617,7 +617,14 @@ _gcry_pk_util_init_encoding_ctx (struct pk_encoding_ctx *ctx,
   ctx->nbits = nbits;
   ctx->encoding = PUBKEY_ENC_UNKNOWN;
   ctx->flags = 0;
-  ctx->hash_algo = GCRY_MD_SHA1;
+  if (fips_mode ())
+    {
+      ctx->hash_algo = GCRY_MD_SHA256;
+    }
+  else
+    {
+      ctx->hash_algo = GCRY_MD_SHA1;
+    }
   ctx->label = NULL;
   ctx->labellen = 0;
   ctx->saltlen = 20;
index edcbeac..3311ba2 100644 (file)
@@ -549,7 +549,7 @@ run_pubkey_selftests (int extended)
     {
       GCRY_PK_RSA,
       GCRY_PK_DSA,
-      /* GCRY_PK_ECC is not enabled in fips mode.  */
+      GCRY_PK_ECC,
       0
     };
   int idx;
index d757420..b5962cf 100644 (file)
@@ -1721,6 +1721,33 @@ dsa_gen_with_seed (int keysize, const void *seed, size_t seedlen)
 }
 
 
+/* Generate an ECDSA key on the specified curve and return the complete
+   S-expression. */
+static gcry_sexp_t
+ecdsa_gen_key (const char *curve)
+{
+  gpg_error_t err;
+  gcry_sexp_t keyspec, key;
+
+  err = gcry_sexp_build (&keyspec, NULL,
+                         "(genkey"
+                         "  (ecc"
+                         "    (use-fips186)"
+                         "    (curve %s)))",
+                         curve);
+  if (err)
+    die ("gcry_sexp_build failed for ECDSA key generation: %s\n",
+         gpg_strerror (err));
+  err = gcry_pk_genkey (&key, keyspec);
+  if (err)
+    die ("gcry_pk_genkey failed for ECDSA: %s\n", gpg_strerror (err));
+
+  gcry_sexp_release (keyspec);
+
+  return key;
+}
+
+
 /* Print the domain parameter as well as the derive information.  KEY
    is the complete key as returned by dsa_gen.  We print to stdout
    with one parameter per line in hex format using this order: p, q,
@@ -1813,6 +1840,46 @@ print_dsa_domain_parameters (gcry_sexp_t key)
 }
 
 
+/* Print public key Q (in octet-string format) and private key d.
+   KEY is the complete key as returned by ecdsa_gen_key.
+   with one parameter per line in hex format using this order: d, Q. */
+static void
+print_ecdsa_dq (gcry_sexp_t key)
+{
+  gcry_sexp_t l1, l2;
+  gcry_mpi_t mpi;
+  int idx;
+
+  l1 = gcry_sexp_find_token (key, "private-key", 0);
+  if (!l1)
+    die ("private key not found in genkey result\n");
+
+  l2 = gcry_sexp_find_token (l1, "ecc", 0);
+  if (!l2)
+    die ("returned private key not formed as expected\n");
+  gcry_sexp_release (l1);
+  l1 = l2;
+
+  /* Extract the parameters from the S-expression and print them to stdout.  */
+  for (idx=0; "dq"[idx]; idx++)
+    {
+      l2 = gcry_sexp_find_token (l1, "dq"+idx, 1);
+      if (!l2)
+        die ("no %c parameter in returned public key\n", "dq"[idx]);
+      mpi = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+      if (!mpi)
+        die ("no value for %c parameter in returned private key\n","dq"[idx]);
+      gcry_sexp_release (l2);
+      if (standalone_mode)
+        printf ("%c = ", "dQ"[idx]);
+      print_mpi_line (mpi, 1);
+      gcry_mpi_release (mpi);
+    }
+
+  gcry_sexp_release (l1);
+}
+
+
 /* Generate DSA domain parameters for a modulus size of KEYSIZE.  The
    result is printed to stdout with one parameter per line in hex
    format and in this order: p, q, g, seed, counter, h.  If SEED is
@@ -1992,6 +2059,138 @@ run_dsa_verify (const void *data, size_t datalen,
 }
 
 
+\f
+/* Sign DATA of length DATALEN using the key taken from the S-expression
+   encoded KEYFILE. */
+static void
+run_ecdsa_sign (const void *data, size_t datalen,
+                const char *keyfile, const int algo)
+
+{
+  gpg_error_t err;
+  gcry_sexp_t s_data, s_key, s_sig, s_tmp;
+  char hash[128];
+  gcry_mpi_t tmpmpi;
+
+  s_key = read_sexp_from_file (keyfile);
+
+  gcry_md_hash_buffer (algo, hash, data, datalen);
+  err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_USG, hash,
+                       gcry_md_get_algo_dlen(algo), NULL);
+  if (!err)
+    {
+      err = gcry_sexp_build (&s_data, NULL,
+                             "(data (flags raw)(hash %s %M))",
+                             gcry_md_algo_name(algo), tmpmpi);
+      gcry_mpi_release (tmpmpi);
+    }
+  if (err)
+    die ("gcry_sexp_build failed for ECDSA data input: %s\n",
+         gpg_strerror (err));
+
+  err = gcry_pk_sign (&s_sig, s_data, s_key);
+  if (err)
+    {
+      die ("gcry_pk_signed failed: %s\n", gpg_strerror (err));
+    }
+  gcry_sexp_release (s_data);
+  gcry_sexp_release (s_key);
+
+  /* Now return the actual signature.  */
+  s_tmp = gcry_sexp_find_token (s_sig, "sig-val", 0);
+  if (!s_tmp)
+    die ("no sig-val element in returned S-expression\n");
+
+  gcry_sexp_release (s_sig);
+  s_sig = s_tmp;
+  s_tmp = gcry_sexp_find_token (s_sig, "ecdsa", 0);
+  if (!s_tmp)
+    die ("no ecdsa element in returned S-expression\n");
+
+  gcry_sexp_release (s_sig);
+  s_sig = s_tmp;
+
+  s_tmp = gcry_sexp_find_token (s_sig, "r", 0);
+  tmpmpi = gcry_sexp_nth_mpi (s_tmp, 1, GCRYMPI_FMT_USG);
+  if (!tmpmpi)
+    die ("no r parameter in returned S-expression\n");
+  print_mpi_line (tmpmpi, 1);
+  gcry_mpi_release (tmpmpi);
+  gcry_sexp_release (s_tmp);
+
+  s_tmp = gcry_sexp_find_token (s_sig, "s", 0);
+  tmpmpi = gcry_sexp_nth_mpi (s_tmp, 1, GCRYMPI_FMT_USG);
+  if (!tmpmpi)
+    die ("no s parameter in returned S-expression\n");
+  print_mpi_line (tmpmpi, 1);
+  gcry_mpi_release (tmpmpi);
+  gcry_sexp_release (s_tmp);
+
+  gcry_sexp_release (s_sig);
+}
+
+
+\f
+/* Verify DATA of length DATALEN using the public key taken from the
+   S-expression in KEYFILE against the S-expression formatted
+   signature in SIGFILE.  */
+static void
+run_ecdsa_verify (const void *data, size_t datalen,
+                const char *keyfile, const int algo, const char *sigfile)
+
+{
+  gpg_error_t err;
+  gcry_sexp_t s_data, s_key, s_sig;
+  char hash[128];
+  gcry_mpi_t tmpmpi;
+
+  s_key = read_sexp_from_file (keyfile);
+
+  gcry_md_hash_buffer (algo, hash, data, datalen);
+  /* Note that we can't simply use %b with HASH to build the
+     S-expression, because that might yield a negative value.  */
+  err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_USG, hash,
+                       gcry_md_get_algo_dlen(algo), NULL);
+  if (!err)
+    {
+      err = gcry_sexp_build (&s_data, NULL,
+                             "(data (flags raw)(hash %s %M))",
+                             gcry_md_algo_name(algo), tmpmpi);
+      gcry_mpi_release (tmpmpi);
+    }
+  if (err)
+    die ("gcry_sexp_build failed for DSA data input: %s\n",
+         gpg_strerror (err));
+
+  s_sig = read_sexp_from_file (sigfile);
+
+  err = gcry_pk_verify (s_sig, s_data, s_key);
+  if (!err)
+    puts ("GOOD signature");
+  else if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE)
+    puts ("BAD signature");
+  else
+    printf ("ERROR (%s)\n", gpg_strerror (err));
+
+  gcry_sexp_release (s_sig);
+  gcry_sexp_release (s_key);
+  gcry_sexp_release (s_data);
+}
+
+
+/* Generate an ECDSA key with specified domain parameters
+   and print the d and Q values, in the standard octet-string format. */
+static void
+run_ecdsa_gen_key (const char *curve)
+{
+  gcry_sexp_t key;
+
+  key = ecdsa_gen_key (curve);
+  print_ecdsa_dq (key);
+
+  gcry_sexp_release (key);
+}
+
 
 \f
 static void
@@ -2008,7 +2207,8 @@ usage (int show_help)
      "Run a crypto operation using hex encoded input and output.\n"
      "MODE:\n"
      "  encrypt, decrypt, digest, random, hmac-sha,\n"
-     "  rsa-{derive,gen,sign,verify}, dsa-{pqg-gen,gen,sign,verify}\n"
+     "  rsa-{derive,gen,sign,verify},\n"
+     "  dsa-{pqg-gen,gen,sign,verify}, ecdsa-{gen-key,sign,verify}\n"
      "OPTIONS:\n"
      "  --verbose        Print additional information\n"
      "  --binary         Input and output is in binary form\n"
@@ -2017,6 +2217,7 @@ usage (int show_help)
      "  --iv IV          Use the hex encoded IV\n"
      "  --dt DT          Use the hex encoded DT for the RNG\n"
      "  --algo NAME      Use algorithm NAME\n"
+     "  --curve NAME     Select ECC curve spec NAME\n"
      "  --keysize N      Use a keysize of N bits\n"
      "  --signature NAME Take signature from file NAME\n"
      "  --chunk N        Read in chunks of N bytes (implies --binary)\n"
@@ -2039,6 +2240,7 @@ main (int argc, char **argv)
   int progress = 0;
   int use_pkcs1 = 0;
   const char *mode_string;
+  const char *curve_string = NULL;
   const char *key_string = NULL;
   const char *iv_string = NULL;
   const char *dt_string = NULL;
@@ -2154,6 +2356,14 @@ main (int argc, char **argv)
           binary_input = binary_output = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--curve"))
+        {
+          argc--; argv++;
+          if (!argc)
+            usage (0);
+          curve_string = *argv;
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--pkcs1"))
         {
           use_pkcs1 = 1;
@@ -2211,7 +2421,8 @@ main (int argc, char **argv)
       && !mct_server
       && strcmp (mode_string, "random")
       && strcmp (mode_string, "rsa-gen")
-      && strcmp (mode_string, "dsa-gen") )
+      && strcmp (mode_string, "dsa-gen")
+      && strcmp (mode_string, "ecdsa-gen-key") )
     {
       data = read_file (input, !binary_input, &datalen);
       if (!data)
@@ -2501,6 +2712,53 @@ main (int argc, char **argv)
 
       run_dsa_verify (data, datalen, key_string, signature_string);
     }
+  else if (!strcmp (mode_string, "ecdsa-gen-key"))
+    {
+      if (!curve_string)
+        die ("option --curve containing name of the specified curve is required in this mode\n");
+      run_ecdsa_gen_key (curve_string);
+    }
+  else if (!strcmp (mode_string, "ecdsa-sign"))
+    {
+      int algo;
+
+      if (!key_string)
+        die ("option --key is required in this mode\n");
+      if (access (key_string, R_OK))
+        die ("option --key needs to specify an existing keyfile\n");
+      if (!algo_string)
+        die ("use --algo to specify the digest algorithm\n");
+      algo = gcry_md_map_name (algo_string);
+      if (!algo)
+        die ("digest algorithm `%s' is not supported\n", algo_string);
+
+      if (!data)
+        die ("no data available (do not use --chunk)\n");
+
+      run_ecdsa_sign (data, datalen, key_string, algo);
+    }
+  else if (!strcmp (mode_string, "ecdsa-verify"))
+    {
+      int algo;
+
+      if (!key_string)
+        die ("option --key is required in this mode\n");
+      if (access (key_string, R_OK))
+        die ("option --key needs to specify an existing keyfile\n");
+      if (!algo_string)
+        die ("use --algo to specify the digest algorithm\n");
+      algo = gcry_md_map_name (algo_string);
+      if (!algo)
+        die ("digest algorithm `%s' is not supported\n", algo_string);
+      if (!data)
+        die ("no data available (do not use --chunk)\n");
+      if (!signature_string)
+        die ("option --signature is required in this mode\n");
+      if (access (signature_string, R_OK))
+        die ("option --signature needs to specify an existing file\n");
+
+      run_ecdsa_verify (data, datalen, key_string, algo, signature_string);
+    }
   else
     usage (0);