From b8b3361504950689ef1e779fb3357cecf8a9f739 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 3 Feb 2016 12:24:46 +0900 Subject: tests: Add a test for Curve25519. * tests/Makefile.am (tests_bin): Add t-cv25519. * tests/t-cv25519.c: New. -- Signed-off-by: NIIBE Yutaka --- tests/Makefile.am | 2 +- tests/t-cv25519.c | 568 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 tests/t-cv25519.c diff --git a/tests/Makefile.am b/tests/Makefile.am index a5c10dd9..d462f306 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,7 +22,7 @@ tests_bin = \ version mpitests t-sexp t-convert \ t-mpi-bit t-mpi-point curves t-lock \ prime basic keygen pubkey hmac hashtest t-kdf keygrip \ - fips186-dsa aeswrap pkcs1v2 random dsa-rfc6979 t-ed25519 + fips186-dsa aeswrap pkcs1v2 random dsa-rfc6979 t-ed25519 t-cv25519 tests_bin_last = benchmark bench-slope diff --git a/tests/t-cv25519.c b/tests/t-cv25519.c new file mode 100644 index 00000000..69aa005b --- /dev/null +++ b/tests/t-cv25519.c @@ -0,0 +1,568 @@ +/* t-cv25519.c - Check the cv25519 crypto + * Copyright (C) 2016 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 "../src/gcrypt-int.h" + +#include "stopwatch.h" + +#define PGM "t-cv25519" +#define N_TESTS 6 + +#define my_isascii(c) (!((c) & 0x80)) +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) +#define xmalloc(a) gcry_xmalloc ((a)) +#define xcalloc(a,b) gcry_xcalloc ((a),(b)) +#define xstrdup(a) gcry_xstrdup ((a)) +#define xfree(a) gcry_free ((a)) +#define pass() do { ; } while (0) + +static int verbose; +static int debug; +static int error_count; + +static void +print_mpi (const char *text, gcry_mpi_t a) +{ + gcry_error_t err; + char *buf; + void *bufaddr = &buf; + + err = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); + if (err) + fprintf (stderr, "%s: [error printing number: %s]\n", + text, gpg_strerror (err)); + else + { + fprintf (stderr, "%s: %s\n", text, buf); + gcry_free (buf); + } +} + +static void +die (const char *format, ...) +{ + va_list arg_ptr ; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + va_start( arg_ptr, format ) ; + vfprintf (stderr, format, arg_ptr ); + va_end(arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); + exit (1); +} + +static void +fail (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + /* if (wherestr) */ + /* fprintf (stderr, "%s: ", wherestr); */ + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); + error_count++; + if (error_count >= 50) + die ("stopped after 50 errors."); +} + +static void +show (const char *format, ...) +{ + va_list arg_ptr; + + if (!verbose) + return; + 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); +} + + +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); +} + + +/* 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; + + buffer = xmalloc (strlen(string)/2+1); + length = 0; + for (s=string; *s; s +=2 ) + { + if (!hexdigitp (s) || !hexdigitp (s+1)) + return NULL; /* Invalid hex digits. */ + ((unsigned char*)buffer)[length++] = xtoi_2 (s); + } + *r_length = length; + return buffer; +} + +static void +reverse_buffer (unsigned char *buffer, unsigned int length) +{ + unsigned int tmp, i; + + for (i=0; i < length/2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[length-1-i]; + buffer[length-1-i] = tmp; + } +} + + +/* + * Test X25519 functionality through higher layer crypto routines. + * + * Input: K (as hex string), U (as hex string), R (as hex string) + * + * where R is expected result of X25519 (K, U). + * + * It calls gcry_pk_decrypt with Curve25519 private key and let + * it compute X25519. + */ +static void +test_cv (int testno, const char *k_str, const char *u_str, + const char *result_str) +{ + gpg_error_t err; + void *buffer = NULL; + size_t buflen; + unsigned char *p; + gcry_sexp_t s_sk = NULL; + gcry_sexp_t s_data = NULL; + gcry_sexp_t s_result = NULL; + gcry_sexp_t s_tmp = NULL; + unsigned char *res = NULL; + size_t res_len; + + if (verbose > 1) + show ("Running test %d\n", testno); + + if (!(buffer = hex2buffer (k_str, &buflen)) || buflen != 32) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "k", "invalid hex string"); + goto leave; + } + + /* + * Do what decodeScalar25519 does. + */ + p = (unsigned char *)buffer; + p[0] &= 248; + p[31] &= 127; + p[31] |= 64; + reverse_buffer (buffer, buflen); + + if ((err = gcry_sexp_build (&s_sk, NULL, + "(private-key" + " (ecc" + " (curve \"Curve25519\")" + " (flags djb-tweak)" + " (d %b)))", (int)buflen, buffer))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "sk", gpg_strerror (err)); + goto leave; + } + + xfree (buffer); + if (!(buffer = hex2buffer (u_str, &buflen))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "u", "invalid hex string"); + goto leave; + } + + /* + * The procedure of decodeUCoordinate will be done internally + * by _gcry_ecc_mont_decodepoint. So, we just put the little-endian + * binary to build S-exp. + * + * We could add the prefix 0x40, but libgcrypt also supports + * format with no prefix. So, it is OK not to put the prefix. + */ + if ((err = gcry_sexp_build (&s_data, NULL, + "(enc-val" + " (ecdh" + " (e %b)))", (int)buflen, buffer))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "data", gpg_strerror (err)); + goto leave; + } + + xfree (buffer); + buffer = NULL; + + if ((err = gcry_pk_decrypt (&s_result, s_data, s_sk))) + fail ("gcry_pk_decrypt failed for test %d: %s", testno, + gpg_strerror (err)); + + s_tmp = gcry_sexp_find_token (s_result, "value", 0); + if (!s_tmp || !(res = gcry_sexp_nth_buffer (s_tmp, 1, &res_len))) + fail ("gcry_pk_decrypt failed for test %d: %s", testno, "missing value"); + else + { + char *r, *r0; + int i; + + /* To skip the prefix 0x40, for-loop start with i=1 */ + r0 = r = xmalloc (2*(res_len)+1); + if (!r0) + { + fail ("memory allocation", testno); + goto leave; + } + + for (i=1; i < res_len; i++, r += 2) + snprintf (r, 3, "%02x", res[i]); + if (strcmp (result_str, r0)) + { + fail ("gcry_pk_decrypt failed for test %d: %s", + testno, "wrong value returned"); + show (" expected: '%s'", result_str); + show (" got: '%s'", r0); + } + xfree (r0); + } + + leave: + xfree (res); + gcry_sexp_release (s_tmp); + gcry_sexp_release (s_result); + gcry_sexp_release (s_data); + gcry_sexp_release (s_sk); + xfree (buffer); +} + +/* + * Test iterative X25519 computation through lower layer MPI routines. + * + * Input: K (as hex string), ITER, R (as hex string) + * + * where R is expected result of iterating X25519 by ITER times. + * + */ +static void +test_it (int testno, const char *k_str, int iter, const char *result_str) +{ + gcry_ctx_t ctx; + gpg_error_t err; + void *buffer = NULL; + size_t buflen; + gcry_mpi_t mpi_k = NULL; + gcry_mpi_t mpi_x = NULL; + gcry_mpi_point_t P = NULL; + gcry_mpi_point_t Q; + int i; + gcry_mpi_t mpi_kk = NULL; + + if (verbose > 1) + show ("Running test %d: iteration=%d\n", testno, iter); + + gcry_mpi_ec_new (&ctx, NULL, "Curve25519"); + Q = gcry_mpi_point_new (0); + + if (!(buffer = hex2buffer (k_str, &buflen)) || buflen != 32) + { + fail ("error scanning MPI for test %d, %s: %s", + testno, "k", "invalid hex string"); + goto leave; + } + reverse_buffer (buffer, buflen); + if ((err = gcry_mpi_scan (&mpi_x, GCRYMPI_FMT_USG, buffer, buflen, NULL))) + { + fail ("error scanning MPI for test %d, %s: %s", + testno, "x", gpg_strerror (err)); + goto leave; + } + + xfree (buffer); + buffer = NULL; + + P = gcry_mpi_point_set (NULL, mpi_x, NULL, GCRYMPI_CONST_ONE); + + mpi_k = gcry_mpi_copy (mpi_x); + if (debug) + print_mpi ("k", mpi_k); + + for (i = 0; i < iter; i++) + { + /* + * Another variant of decodeScalar25519 thing. + */ + mpi_kk = gcry_mpi_set (mpi_kk, mpi_k); + gcry_mpi_set_bit (mpi_kk, 254); + gcry_mpi_clear_bit (mpi_kk, 255); + gcry_mpi_clear_bit (mpi_kk, 0); + gcry_mpi_clear_bit (mpi_kk, 1); + gcry_mpi_clear_bit (mpi_kk, 2); + + gcry_mpi_ec_mul (Q, mpi_kk, P, ctx); + + P = gcry_mpi_point_set (P, mpi_k, NULL, GCRYMPI_CONST_ONE); + gcry_mpi_ec_get_affine (mpi_k, NULL, Q, ctx); + + if (debug) + print_mpi ("k", mpi_k); + } + + { + unsigned char res[32]; + char *r, *r0; + + gcry_mpi_print (GCRYMPI_FMT_USG, res, 32, NULL, mpi_k); + reverse_buffer (res, 32); + + r0 = r = xmalloc (65); + if (!r0) + { + fail ("memory allocation", testno); + goto leave; + } + + for (i=0; i < 32; i++, r += 2) + snprintf (r, 3, "%02x", res[i]); + + if (strcmp (result_str, r0)) + { + fail ("curv25519 failed for test %d: %s", + testno, "wrong value returned"); + show (" expected: '%s'", result_str); + show (" got: '%s'", r0); + } + xfree (r0); + } + + leave: + gcry_mpi_release (mpi_kk); + gcry_mpi_release (mpi_k); + gcry_mpi_point_release (P); + gcry_mpi_release (mpi_x); + xfree (buffer); + gcry_mpi_point_release (Q); + gcry_ctx_release (ctx); +} + +/* + * X-coordinate of generator of the Curve25519. + */ +#define G_X "0900000000000000000000000000000000000000000000000000000000000000" + +/* + * Test Diffie-Hellman in RFC-7748. + * + * Note that it's not like the ECDH of OpenPGP, where we use + * ephemeral public key. + */ +static void +test_dh (int testno, const char *a_priv_str, const char *a_pub_str, + const char *b_priv_str, const char *b_pub_str, + const char *result_str) +{ + /* Test A for private key corresponds to public key. */ + test_cv (testno, a_priv_str, G_X, a_pub_str); + /* Test B for private key corresponds to public key. */ + test_cv (testno, b_priv_str, G_X, b_pub_str); + /* Test DH with A's private key and B's public key. */ + test_cv (testno, a_priv_str, b_pub_str, result_str); + /* Test DH with B's private key and A's public key. */ + test_cv (testno, b_priv_str, a_pub_str, result_str); +} + + +static void +check_cv25519 (void) +{ + int ntests; + + show ("Checking Curve25519.\n"); + + ntests = 0; + + /* + * Values are cited from RFC-7748: 5.2. Test Vectors. + * Following two tests are for the first type test. + */ + test_cv (1, + "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", + "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", + "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552"); + ntests++; + test_cv (2, + "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d", + "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493", + "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957"); + ntests++; + + /* + * Additional test. Value is from second type test. + */ + test_cv (3, + G_X, + G_X, + "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079"); + ntests++; + + /* + * Following two tests are for the second type test, + * with one iteration and 1,000 iterations. (1,000,000 iterations + * takes too long.) + */ + test_it (4, + G_X, + 1, + "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079"); + ntests++; + + test_it (5, + G_X, + 1000, + "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51"); + ntests++; + + /* + * Last test is from: 6. Diffie-Hellman, 6.1. Curve25519 + */ + test_dh (6, + /* Alice's private key, a */ + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + /* Alice's public key, X25519(a, 9) */ + "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a", + /* Bob's private key, b */ + "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb", + /* Bob's public key, X25519(b, 9) */ + "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", + /* Their shared secret, K */ + "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); + ntests++; + + if (ntests != N_TESTS) + fail ("did %d tests but expected %d", ntests, N_TESTS); + else if ((ntests % 256)) + show_note ("%d tests done\n", ntests); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + + 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", + stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strncmp (*argv, "--", 2)) + die ("unknown option '%s'", *argv); + } + + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + if (!gcry_check_version (GCRYPT_VERSION)) + die ("version mismatch\n"); + if (debug) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u , 0); + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + + start_timer (); + check_cv25519 (); + stop_timer (); + + show ("All tests completed in %s. Errors: %d\n", + elapsed_time (1), error_count); + return !!error_count; +} -- cgit v1.2.1