/* t-ed25519.c - Check the Ed25519 crypto
* Copyright (C) 2013 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-ed25519"
#define N_TESTS 1026
#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 int sign_with_pk;
static int no_verify;
static int custom_data_file;
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);
}
static void
show_sexp (const char *prefix, gcry_sexp_t a)
{
char *buf;
size_t size;
fprintf (stderr, "%s: ", PGM);
if (prefix)
fputs (prefix, stderr);
size = gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, NULL, 0);
buf = xmalloc (size);
gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, buf, size);
fprintf (stderr, "%.*s", (int)size, buf);
gcry_free (buf);
}
/* Prepend FNAME with the srcdir environment variable's value and
retrun 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) */
/* show ("read line: '%s'\n", line); */
return xstrdup (line);
}
/* 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;
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
hexdowncase (char *string)
{
char *p;
for (p=string; *p; p++)
if (my_isascii (*p))
*p = tolower (*p);
}
static void
one_test (int testno, const char *sk, const char *pk,
const char *msg, const char *sig)
{
gpg_error_t err;
int i;
char *p;
void *buffer = NULL;
void *buffer2 = NULL;
size_t buflen, buflen2;
gcry_sexp_t s_tmp, s_tmp2;
gcry_sexp_t s_sk = NULL;
gcry_sexp_t s_pk = NULL;
gcry_sexp_t s_msg= NULL;
gcry_sexp_t s_sig= NULL;
unsigned char *sig_r = NULL;
unsigned char *sig_s = NULL;
char *sig_rs_string = NULL;
size_t sig_r_len, sig_s_len;
if (verbose > 1)
show ("Running test %d\n", testno);
if (!(buffer = hex2buffer (sk, &buflen)))
{
fail ("error building s-exp for test %d, %s: %s",
testno, "sk", "invalid hex string");
goto leave;
}
if (!(buffer2 = hex2buffer (pk, &buflen2)))
{
fail ("error building s-exp for test %d, %s: %s",
testno, "pk", "invalid hex string");
goto leave;
}
if (sign_with_pk)
err = gcry_sexp_build (&s_sk, NULL,
"(private-key"
" (ecc"
" (curve \"Ed25519\")"
" (flags eddsa)"
" (q %b)"
" (d %b)))",
(int)buflen2, buffer2,
(int)buflen, buffer);
else
err = gcry_sexp_build (&s_sk, NULL,
"(private-key"
" (ecc"
" (curve \"Ed25519\")"
" (flags eddsa)"
" (d %b)))",
(int)buflen, buffer);
if (err)
{
fail ("error building s-exp for test %d, %s: %s",
testno, "sk", gpg_strerror (err));
goto leave;
}
if ((err = gcry_sexp_build (&s_pk, NULL,
"(public-key"
" (ecc"
" (curve \"Ed25519\")"
" (flags eddsa)"
" (q %b)))", (int)buflen2, buffer2)))
{
fail ("error building s-exp for test %d, %s: %s",
testno, "pk", gpg_strerror (err));
goto leave;
}
xfree (buffer);
if (!(buffer = hex2buffer (msg, &buflen)))
{
fail ("error building s-exp for test %d, %s: %s",
testno, "msg", "invalid hex string");
goto leave;
}
if ((err = gcry_sexp_build (&s_msg, NULL,
"(data"
" (flags eddsa)"
" (hash-algo sha512)"
" (value %b))", (int)buflen, buffer)))
{
fail ("error building s-exp for test %d, %s: %s",
testno, "msg", gpg_strerror (err));
goto leave;
}
if ((err = gcry_pk_sign (&s_sig, s_msg, s_sk)))
fail ("gcry_pk_sign failed for test %d: %s", testno, gpg_strerror (err));
if (debug)
show_sexp ("sig=", s_sig);
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, "eddsa", 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)
{
sig_r = gcry_sexp_nth_buffer (s_tmp, 1, &sig_r_len);
gcry_sexp_release (s_tmp);
}
s_tmp = gcry_sexp_find_token (s_tmp2, "s", 0);
if (s_tmp)
{
sig_s = gcry_sexp_nth_buffer (s_tmp, 1, &sig_s_len);
gcry_sexp_release (s_tmp);
}
}
}
gcry_sexp_release (s_tmp2); s_tmp2 = NULL;
if (!sig_r || !sig_s)
fail ("gcry_pk_sign failed for test %d: %s", testno, "r or s missing");
else
{
sig_rs_string = xmalloc (2*(sig_r_len + sig_s_len)+1);
p = sig_rs_string;
*p = 0;
for (i=0; i < sig_r_len; i++, p += 2)
snprintf (p, 3, "%02x", sig_r[i]);
for (i=0; i < sig_s_len; i++, p += 2)
snprintf (p, 3, "%02x", sig_s[i]);
if (strcmp (sig_rs_string, sig))
{
fail ("gcry_pk_sign failed for test %d: %s",
testno, "wrong value returned");
show (" expected: '%s'", sig);
show (" got: '%s'", sig_rs_string);
}
}
if (!no_verify)
if ((err = gcry_pk_verify (s_sig, s_msg, s_pk)))
fail ("gcry_pk_verify failed for test %d: %s",
testno, gpg_strerror (err));
leave:
gcry_sexp_release (s_sig);
gcry_sexp_release (s_sk);
gcry_sexp_release (s_pk);
gcry_sexp_release (s_msg);
xfree (buffer);
xfree (buffer2);
xfree (sig_r);
xfree (sig_s);
xfree (sig_rs_string);
}
static void
check_ed25519 (const char *fname)
{
FILE *fp;
int lineno, ntests;
char *line;
int testno;
char *sk, *pk, *msg, *sig;
show ("Checking Ed25519.\n");
fp = fopen (fname, "r");
if (!fp)
die ("error opening '%s': %s\n", fname, strerror (errno));
testno = 0;
sk = pk = msg = sig = NULL;
lineno = ntests = 0;
while ((line = read_textline (fp, &lineno)))
{
if (!strncmp (line, "TST:", 4))
testno = atoi (line+4);
else if (!strncmp (line, "SK:", 3))
copy_data (&sk, line, lineno);
else if (!strncmp (line, "PK:", 3))
copy_data (&pk, line, lineno);
else if (!strncmp (line, "MSG:", 4))
copy_data (&msg, line, lineno);
else if (!strncmp (line, "SIG:", 4))
copy_data (&sig, line, lineno);
else
fail ("unknown tag at input line %d", lineno);
xfree (line);
if (testno && sk && pk && msg && sig)
{
hexdowncase (sig);
one_test (testno, sk, pk, msg, sig);
ntests++;
if (!(ntests % 256))
show_note ("%d of %d tests done\n", ntests, N_TESTS);
xfree (pk); pk = NULL;
xfree (sk); sk = NULL;
xfree (msg); msg = NULL;
xfree (sig); sig = NULL;
}
}
xfree (pk);
xfree (sk);
xfree (msg);
xfree (sig);
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"
" --sign-with-pk also use the public key for signing\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, "--sign-with-pk"))
{
sign_with_pk = 1;
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-ed25519.inp");
else
custom_data_file = 1;
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_ed25519 (fname);
stop_timer ();
xfree (fname);
show ("All tests completed in %s. Errors: %d\n",
elapsed_time (), error_count);
return !!error_count;
}