diff --git a/tests/t-ecdsa.c b/tests/t-ecdsa.c index fa0a2ef9..725fcb4f 100644 --- a/tests/t-ecdsa.c +++ b/tests/t-ecdsa.c @@ -1,656 +1,681 @@ /* t-ecdsa.c - Check the ECDSA crypto * Copyright (C) 2021 g10 Code GmbH * * This file is part of Libgcrypt. * * Libgcrypt is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Libgcrypt is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "stopwatch.h" #define PGM "t-ecdsa" #include "t-common.h" #define N_TESTS 320 static int no_verify; static int custom_data_file; static int in_fips_mode; static void show_note (const char *format, ...) { va_list arg_ptr; if (!verbose && getenv ("srcdir")) fputs (" ", stderr); /* To align above "PASS: ". */ else fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); if (*format && format[strlen(format)-1] != '\n') putc ('\n', stderr); va_end (arg_ptr); } /* Prepend FNAME with the srcdir environment variable's value and * return an allocated filename. */ char * prepend_srcdir (const char *fname) { static const char *srcdir; char *result; if (!srcdir && !(srcdir = getenv ("srcdir"))) srcdir = "."; result = xmalloc (strlen (srcdir) + 1 + strlen (fname) + 1); strcpy (result, srcdir); strcat (result, "/"); strcat (result, fname); return result; } /* Read next line but skip over empty and comment lines. Caller must xfree the result. */ static char * read_textline (FILE *fp, int *lineno) { char line[4096]; char *p; do { if (!fgets (line, sizeof line, fp)) { if (feof (fp)) return NULL; die ("error reading input line: %s\n", strerror (errno)); } ++*lineno; p = strchr (line, '\n'); if (!p) die ("input line %d not terminated or too long\n", *lineno); *p = 0; for (p--;p > line && my_isascii (*p) && isspace (*p); p--) *p = 0; } while (!*line || *line == '#'); /* if (debug) */ /* info ("read line: '%s'\n", line); */ return xstrdup (line); } /* * The input line is like: * * [P-224,SHA-224] * */ static void parse_annotation (char **buffer, char **buffer2, const char *line, int lineno) { const char *s; xfree (*buffer); *buffer = NULL; xfree (*buffer2); *buffer2 = NULL; s = strchr (line, '-'); if (!s) { fail ("syntax error at input line %d", lineno); return; } *buffer = xstrdup (s-1); (*buffer)[5] = 0; /* Remove ','. */ s = strchr (s+1, ','); if (!s) { fail ("syntax error at input line %d", lineno); return; } *buffer2 = xstrdup (s+1); (*buffer2)[strlen (*buffer2) - 1] = 0; /* Remove ']'. */ } /* Copy the data after the tag to BUFFER. BUFFER will be allocated as needed. */ static void copy_data (char **buffer, const char *line, int lineno) { const char *s; xfree (*buffer); *buffer = NULL; s = strchr (line, '='); if (!s) { fail ("syntax error at input line %d", lineno); return; } for (s++; my_isascii (*s) && isspace (*s); s++) ; *buffer = xstrdup (s); } /* Convert STRING consisting of hex characters into its binary representation and return it as an allocated buffer. The valid length of the buffer is returned at R_LENGTH. The string is delimited by end of string. The function returns NULL on error. */ static void * hex2buffer (const char *string, size_t *r_length) { const char *s; unsigned char *buffer; size_t length; int odd; odd = ((strlen (string) & 1)); buffer = xmalloc (strlen (string)/2 + odd + 1); if (odd) { length = 1; s = string; buffer[0] = xtoi_1 (s); s++; } else { length = 0; s = string; } for (; *s; s +=2 ) { if (!hexdigitp (s) || !hexdigitp (s+1)) { xfree (buffer); return NULL; /* Invalid hex digits. */ } buffer[length++] = xtoi_2 (s); } *r_length = length; return buffer; } static void one_test_sexp (const char *curvename, const char *sha_alg, const char *x, const char *y, const char *d, const char *msg, const char *k, const char *r, const char *s) { gpg_error_t err; int i; char *p0; void *buffer = NULL; void *buffer2 = NULL; void *buffer3 = NULL; size_t buflen, buflen2, buflen3; unsigned char *pkbuffer = NULL; size_t pkbuflen; char curvename_gcrypt[11]; gcry_ctx_t ctx = NULL; int md_algo; const char *data_tmpl; + char data_tmpl2[256]; gcry_md_hd_t hd = NULL; gcry_sexp_t s_pk = NULL; gcry_sexp_t s_sk = NULL; - gcry_sexp_t s_sig= NULL; + gcry_sexp_t s_sig = NULL, s_sig2 = NULL; gcry_sexp_t s_tmp, s_tmp2; unsigned char *out_r = NULL; unsigned char *out_s = NULL; size_t out_r_len, out_s_len; char *sig_r_string = NULL; char *sig_s_string = NULL; if (verbose > 1) info ("Running test %s\n", sha_alg); if (!strcmp (curvename, "P-224") || !strcmp (curvename, "P-256") || !strcmp (curvename, "P-384") || !strcmp (curvename, "P-521")) { memcpy (curvename_gcrypt, "NIST ", 5); strcpy (curvename_gcrypt+5, curvename); } else { fail ("error for test, %s: %s: %s", "ECC curve", "invalid", curvename); goto leave; } if (!strcmp (sha_alg, "SHA-224")) md_algo = GCRY_MD_SHA224; else if (!strcmp (sha_alg, "SHA-256")) md_algo = GCRY_MD_SHA256; else if (!strcmp (sha_alg, "SHA-384")) md_algo = GCRY_MD_SHA384; else if (!strcmp (sha_alg, "SHA-512")) md_algo = GCRY_MD_SHA512; else if (!strcmp (sha_alg, "SHA-512224")) md_algo = GCRY_MD_SHA512_224; else if (!strcmp (sha_alg, "SHA-512256")) md_algo = GCRY_MD_SHA512_256; else { fail ("error for test, %s: %s: %s", "SHA algo", "invalid", sha_alg); goto leave; } err = gcry_md_open (&hd, md_algo, 0); if (err) { fail ("algo %d, gcry_md_open failed: %s\n", md_algo, gpg_strerror (err)); return; } if (!(buffer = hex2buffer (x, &buflen))) { fail ("error parsing for test, %s: %s", "Qx", "invalid hex string"); goto leave; } if (!(buffer2 = hex2buffer (y, &buflen2))) { fail ("error parsing for test, %s: %s", "Qy", "invalid hex string"); goto leave; } if (!(buffer3 = hex2buffer (d, &buflen3))) { fail ("error parsing for test, %s: %s", "d", "invalid hex string"); goto leave; } pkbuflen = buflen + buflen2 + 1; pkbuffer = xmalloc (pkbuflen); pkbuffer[0] = 0x04; memcpy (pkbuffer+1, buffer, buflen); memcpy (pkbuffer+1+buflen, buffer2, buflen2); err = gcry_sexp_build (&s_sk, NULL, "(private-key (ecc (curve %s)(q %b)(d %b)))", curvename_gcrypt, (int)pkbuflen, pkbuffer, (int)buflen3, buffer3); if (err) { fail ("error building SEXP for test, %s: %s", "sk", gpg_strerror (err)); goto leave; } err = gcry_sexp_build (&s_pk, NULL, "(public-key (ecc (curve %s)(q %b)))", curvename_gcrypt, (int)pkbuflen, pkbuffer); if (err) { fail ("error building SEXP for test, %s: %s", "pk", gpg_strerror (err)); goto leave; } xfree (buffer); xfree (buffer2); xfree (buffer3); buffer = buffer2 = buffer3 = NULL; xfree (pkbuffer); pkbuffer = NULL; if (!(buffer = hex2buffer (msg, &buflen))) { fail ("error parsing for test, %s: %s", "msg", "invalid hex string"); goto leave; } gcry_md_write (hd, buffer, buflen); xfree (buffer); buffer = NULL; if (!(buffer2 = hex2buffer (k, &buflen2))) { fail ("error parsing for test, %s: %s", "salt_val", "invalid hex string"); goto leave; } err = gcry_pk_random_override_new (&ctx, buffer2, buflen2); if (err) { fail ("error setting salt for test: %s", gpg_strerror (err)); goto leave; } xfree (buffer2); buffer2 = NULL; data_tmpl = "(data(flags raw)(hash %s %b)(label %b))"; err = gcry_pk_hash_sign (&s_sig, data_tmpl, s_sk, hd, ctx); if (err) { fail ("gcry_pk_hash_sign failed: %s", gpg_strerror (err)); goto leave; } + if (snprintf (data_tmpl2, sizeof(data_tmpl2), + "(data(flags raw)(hash %s %%b)(label %%b))", + gcry_md_algo_name(md_algo)) >= sizeof(data_tmpl2)) + { + fail ("snprintf out of bounds"); + goto leave; + } + err = gcry_pk_hash_sign (&s_sig2, data_tmpl2, s_sk, hd, ctx); + if (err) + { + fail ("gcry_pk_hash_sign with explicit hash algorithm %s failed: %s", + gcry_md_algo_name (md_algo), gpg_strerror (err)); + goto leave; + } + out_r_len = out_s_len = 0; out_s = out_r = NULL; s_tmp2 = NULL; s_tmp = gcry_sexp_find_token (s_sig, "sig-val", 0); if (s_tmp) { s_tmp2 = s_tmp; s_tmp = gcry_sexp_find_token (s_tmp2, "ecdsa", 0); if (s_tmp) { gcry_sexp_release (s_tmp2); s_tmp2 = s_tmp; s_tmp = gcry_sexp_find_token (s_tmp2, "r", 0); if (s_tmp) { const char *p; size_t n; out_r_len = buflen3; out_r = xmalloc (out_r_len); if (!out_r) { err = gpg_error_from_syserror (); gcry_sexp_release (s_tmp); gcry_sexp_release (s_tmp2); goto leave; } p = gcry_sexp_nth_data (s_tmp, 1, &n); if (n == out_r_len) memcpy (out_r, p, out_r_len); else { memset (out_r, 0, out_r_len - n); memcpy (out_r + out_r_len - n, p, n); } gcry_sexp_release (s_tmp); } s_tmp = gcry_sexp_find_token (s_tmp2, "s", 0); if (s_tmp) { const char *p; size_t n; out_s_len = out_r_len; out_s = xmalloc (out_s_len); if (!out_s) { err = gpg_error_from_syserror (); gcry_sexp_release (s_tmp); gcry_sexp_release (s_tmp2); goto leave; } p = gcry_sexp_nth_data (s_tmp, 1, &n); if (n == out_s_len) memcpy (out_s, p, out_s_len); else { memset (out_s, 0, out_s_len - n); memcpy (out_s + out_s_len - n, p, n); } gcry_sexp_release (s_tmp); } } } gcry_sexp_release (s_tmp2); sig_r_string = xmalloc (2*out_r_len+1); p0 = sig_r_string; *p0 = 0; for (i=0; i < out_r_len; i++, p0 += 2) snprintf (p0, 3, "%02x", out_r[i]); sig_s_string = xmalloc (2*out_s_len+1); p0 = sig_s_string; *p0 = 0; for (i=0; i < out_s_len; i++, p0 += 2) snprintf (p0, 3, "%02x", out_s[i]); if (strcmp (sig_r_string + (strcmp (curvename, "P-521") == 0), r) || strcmp (sig_s_string + (strcmp (curvename, "P-521") == 0), s)) { fail ("gcry_pkey_op failed: %s", "wrong value returned"); info (" expected: '%s'", r); info (" got: '%s'", sig_r_string); info (" expected: '%s'", s); info (" got: '%s'", sig_s_string); } if (!no_verify) { err = gcry_pk_hash_verify (s_sig, data_tmpl, s_pk, hd, ctx); if (err) fail ("gcry_pk_hash_verify failed for test: %s", gpg_strerror (err)); + + /* TODO Verifying with data_tmpl2 crashes because gcry_pk_hash_verify() + * does not support specifying the hash algorithm explicitly. See + * https://dev.gnupg.org/T6066, which tracks this problem. */ + err = gcry_pk_hash_verify (s_sig2, data_tmpl, s_pk, hd, ctx); + if (err) + fail ("gcry_pk_hash_verify with explicit hash algorithm %s failed: %s", + gcry_md_algo_name (md_algo), gpg_strerror (err)); } leave: gcry_ctx_release (ctx); gcry_sexp_release (s_sig); + gcry_sexp_release (s_sig2); gcry_sexp_release (s_sk); gcry_sexp_release (s_pk); if (hd) gcry_md_close (hd); xfree (buffer); xfree (buffer2); xfree (buffer3); xfree (out_r); xfree (out_s); xfree (sig_r_string); xfree (sig_s_string); xfree (pkbuffer); } static void check_ecdsa (const char *fname) { FILE *fp; int lineno, ntests; char *line; char *curve, *sha_alg; char *x, *y; char *d; char *msg, *k, *r, *s; info ("Checking ECDSA.\n"); fp = fopen (fname, "r"); if (!fp) die ("error opening '%s': %s\n", fname, strerror (errno)); curve = NULL; sha_alg = NULL; x = y = d = NULL; msg = k = r = s = NULL; lineno = ntests = 0; while ((line = read_textline (fp, &lineno))) { if (!strncmp (line, "[", 1)) parse_annotation (&curve, &sha_alg, line, lineno); else if (!strncmp (line, "Msg =", 5)) copy_data (&msg, line, lineno); else if (!strncmp (line, "d =", 3)) copy_data (&d, line, lineno); else if (!strncmp (line, "Qx =", 4)) copy_data (&x, line, lineno); else if (!strncmp (line, "Qy =", 4)) copy_data (&y, line, lineno); else if (!strncmp (line, "k =", 3)) copy_data (&k, line, lineno); else if (!strncmp (line, "R =", 3)) copy_data (&r, line, lineno); else if (!strncmp (line, "S =", 3)) copy_data (&s, line, lineno); else fail ("unknown tag at input line %d", lineno); xfree (line); if (curve && sha_alg && x && y && d && msg && k && r && s) { one_test_sexp (curve, sha_alg, x, y, d, msg, k, r, s); ntests++; if (!(ntests % 256)) show_note ("%d of %d tests done\n", ntests, N_TESTS); xfree (msg); msg = NULL; xfree (x); x = NULL; xfree (y); y = NULL; xfree (d); d = NULL; xfree (k); k = NULL; xfree (r); r = NULL; xfree (s); s = NULL; } } xfree (curve); xfree (sha_alg); xfree (x); xfree (y); xfree (d); xfree (msg); xfree (k); xfree (r); xfree (s); if (ntests != N_TESTS && !custom_data_file) fail ("did %d tests but expected %d", ntests, N_TESTS); else if ((ntests % 256)) show_note ("%d tests done\n", ntests); fclose (fp); } int main (int argc, char **argv) { int last_argc = -1; char *fname = NULL; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--help")) { fputs ("usage: " PGM " [options]\n" "Options:\n" " --verbose print timings etc.\n" " --debug flyswatter\n" " --no-verify skip the verify test\n" " --data FNAME take test data from file FNAME\n", stdout); exit (0); } else if (!strcmp (*argv, "--verbose")) { verbose++; argc--; argv++; } else if (!strcmp (*argv, "--debug")) { verbose += 2; debug++; argc--; argv++; } else if (!strcmp (*argv, "--no-verify")) { no_verify = 1; argc--; argv++; } else if (!strcmp (*argv, "--data")) { argc--; argv++; if (argc) { xfree (fname); fname = xstrdup (*argv); argc--; argv++; } } else if (!strncmp (*argv, "--", 2)) die ("unknown option '%s'", *argv); } if (!fname) fname = prepend_srcdir ("t-ecdsa.inp"); else custom_data_file = 1; xgcry_control ((GCRYCTL_DISABLE_SECMEM, 0)); if (!gcry_check_version (GCRYPT_VERSION)) die ("version mismatch\n"); if (debug) xgcry_control ((GCRYCTL_SET_DEBUG_FLAGS, 1u , 0)); xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0)); xgcry_control ((GCRYCTL_INITIALIZATION_FINISHED, 0)); if (gcry_fips_mode_active ()) in_fips_mode = 1; start_timer (); check_ecdsa (fname); stop_timer (); xfree (fname); info ("All tests completed in %s. Errors: %d\n", elapsed_time (1), error_count); return !!error_count; }