diff options
author | Werner Koch <[email protected]> | 2023-06-28 15:33:24 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2023-06-28 15:34:19 +0000 |
commit | 101433dfb42b333e48427baf9dd58ac4787c9786 (patch) | |
tree | 34bddebb7e77d36f3b200b3c6ce2a1b603769441 /sm/t-minip12.c | |
parent | sm: Remove duplicated code. (diff) | |
download | gnupg-101433dfb42b333e48427baf9dd58ac4787c9786.tar.gz gnupg-101433dfb42b333e48427baf9dd58ac4787c9786.zip |
sm: Major rewrite of the PKCS#12 parser
* sm/minip12.c: Reworked most of the parser.
(p12_set_verbosity): Add arg debug and change all callers.
* sm/t-minip12.c: Major rewrite to run regression tests unattended.
* sm/Makefile.am (module_maint_tests): Move t-Minit to ...
(module_tests): here.
* tests/cms/samplekeys/Description-p12: New.
--
Note that cram_octet_string stuff has not yet been reworked. I need
to locate the sample files first.
GnuPG-bug-id: 6536
Diffstat (limited to 'sm/t-minip12.c')
-rw-r--r-- | sm/t-minip12.c | 700 |
1 files changed, 655 insertions, 45 deletions
diff --git a/sm/t-minip12.c b/sm/t-minip12.c index 97bbcb9dc..80ba4c69e 100644 --- a/sm/t-minip12.c +++ b/sm/t-minip12.c @@ -1,5 +1,5 @@ /* t-minip12.c - Test driver for minip12.c - * Copyright (C) 2020 g10 Code GmbH + * Copyright (C) 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -15,6 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later */ #include <config.h> @@ -22,6 +23,8 @@ #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> +#include <stdarg.h> +#include <ctype.h> #include "../common/util.h" #include "minip12.h" @@ -31,7 +34,336 @@ static int verbose; static int debug; +static int any_error; +static void die (const char *format, ...) GPGRT_ATTR_NR_PRINTF(1,2); +static void err (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +static void inf (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +/* static void dbg (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); */ +static void printresult (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +static char *my_xstrconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); + +#define xstrconcat my_xstrconcat +#define trim_spaces(a) my_trim_spaces ((a)) +#define my_isascii(c) (!((c) & 0x80)) + + + + + +/* Print diagnostic message and exit with failure. */ +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); +} + + +/* Print diagnostic message. */ +static void +err (const char *format, ...) +{ + va_list arg_ptr; + + any_error = 1; + + 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); +} + + +/* Print an info message. */ +static void +inf (const char *format, ...) +{ + va_list arg_ptr; + + if (verbose) + { + 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); + } +} + + +/* Print a debug message. */ +/* static void */ +/* dbg (const char *format, ...) */ +/* { */ +/* va_list arg_ptr; */ + +/* if (debug) */ +/* { */ +/* fprintf (stderr, "%s: DBG: ", 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); */ +/* } */ +/* } */ + + +/* Print a result line to stdout. */ +static void +printresult (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); +#ifdef HAVE_FLOCKFILE + flockfile (stdout); +#endif + va_start (arg_ptr, format); + vfprintf (stdout, format, arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stdout); + va_end (arg_ptr); + fflush (stdout); +#ifdef HAVE_FLOCKFILE + funlockfile (stdout); +#endif +} + + +/* Helper for xstrconcat and strconcat. */ +static char * +do_strconcat (int xmode, const char *s1, va_list arg_ptr) +{ + const char *argv[48]; + size_t argc; + size_t needed; + char *buffer, *p; + + argc = 0; + argv[argc++] = s1; + needed = strlen (s1); + while (((argv[argc] = va_arg (arg_ptr, const char *)))) + { + needed += strlen (argv[argc]); + if (argc >= DIM (argv)-1) + die ("too may args for strconcat\n"); + argc++; + } + needed++; + buffer = xmode? xmalloc (needed) : malloc (needed); + for (p = buffer, argc=0; argv[argc]; argc++) + p = stpcpy (p, argv[argc]); + + return buffer; +} + + +/* Concatenate the string S1 with all the following strings up to a + NULL. Returns a malloced buffer with the new string or dies on error. */ +static char * +my_xstrconcat (const char *s1, ...) +{ + va_list arg_ptr; + char *result; + + if (!s1) + result = xstrdup (""); + else + { + va_start (arg_ptr, s1); + result = do_strconcat (1, s1, arg_ptr); + va_end (arg_ptr); + } + return result; +} + + +static char * +my_trim_spaces (char *str ) +{ + char *string, *p, *mark; + + string = str; + for (p=string; *p && isspace (*(unsigned char *)p) ; p++) + ; + for (mark=NULL; (*string = *p); string++, p++ ) + if (isspace (*(unsigned char *)p)) + { + if (!mark) + mark = string; + } + else + mark = NULL; + if (mark) + *mark = '\0'; + + return str ; +} + + +/* Prepend FNAME with the srcdir environment variable's value and + * return an allocated filename. */ +static char * +prepend_srcdir (const char *fname) +{ + static const char *srcdir; + + if (!srcdir && !(srcdir = getenv ("srcdir"))) + return xstrdup (fname); + else + return xstrconcat (srcdir, "/", fname, NULL); +} + + +/* (BUFFER,BUFLEN) and return a malloced hexstring. */ +static char * +hash_buffer (const void *buffer, size_t buflen) +{ + unsigned char hash[20]; + char *result; + int i; + + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, buffer, buflen); + result = xmalloc (41); + for (i=0; i < 20; i++) + snprintf (result + 2*i, 3, "%02x", hash[i]); + 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 == '#'); + 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) + { + err ("syntax error at input line %d", lineno); + return; + } + for (s++; my_isascii (*s) && isspace (*s); s++) + ; + *buffer = xstrdup (s); +} + + +static void +hexdowncase (char *string) +{ + char *p; + + if (string) + for (p=string; *p; p++) + if (my_isascii (*p)) + *p = tolower (*p); +} + + +/* Return the value of the variable VARNAME from ~/.gnupg-autogen.rc + * or NULL if it does not exists or is empty. */ +static char * +value_from_gnupg_autogen_rc (const char *varname) +{ + const char *home; + char *fname; + FILE *fp; + char *line = NULL; + char *p; + int lineno = 0; + + if (!(home = getenv ("HOME"))) + home = ""; + fname = xstrconcat (home, "/.gnupg-autogen.rc", NULL); + fp = fopen (fname, "r"); + if (!fp) + goto leave; + + while ((line = read_textline (fp, &lineno))) + { + p = strchr (line, '='); + if (p) + { + *p++ = 0; + trim_spaces (line); + if (!strcmp (line, varname)) + { + trim_spaces (p); + if (*p) + { + memmove (line, p, strlen (p)+1); + if (*line == '~' && line[1] == '/') + { + p = xstrconcat (home, line+1, NULL); + xfree (line); + line = p; + } + break; /* found. */ + } + } + } + xfree (line); + } + + leave: + if (fp) + fclose (fp); + xfree (fname); + return line; +} static void @@ -45,13 +377,90 @@ cert_cb (void *opaque, const unsigned char *cert, size_t certlen) } +/* Parse one PKCS#12 file. Returns zero on success. */ +static int +one_file (const char *name, const char *pass) +{ + FILE *fp; + struct stat st; + unsigned char *buf; + size_t buflen; + gcry_mpi_t *result; + int badpass; + char *curve = NULL; -int -main (int argc, char **argv) + fp = fopen (name, "rb"); + if (!fp) + { + fprintf (stderr, PGM": can't open '%s': %s\n", name, strerror (errno)); + return 1; + } + + if (fstat (fileno(fp), &st)) + { + fprintf (stderr, PGM": can't stat '%s': %s\n", name, strerror (errno)); + return 1; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + fprintf (stderr, "error reading '%s': %s\n", name, strerror (errno)); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, pass, cert_cb, NULL, &badpass, &curve); + if (result) + { + int i, rc; + unsigned char *tmpbuf; + + if (curve) + log_info ("curve: %s\n", curve); + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); + if (rc) + log_error ("%d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + log_info ("%d: %s\n", i, tmpbuf); + gcry_free (tmpbuf); + } + } + } + if (badpass) + log_error ("Bad password given?\n"); + + xfree (buf); + return 0; +} + + +static void +cert_collect_cb (void *opaque, const unsigned char *cert, size_t certlen) +{ + char **certstr = opaque; + char *hash; + + hash = hash_buffer (cert, certlen); + if (*certstr) + { + *certstr = xstrconcat (*certstr, ",", hash, NULL); + xfree (hash); + } + else + *certstr = hash; +} + + +static int +run_one_test (const char *name, const char *desc, const char *pass, + const char *certexpected, const char *keyexpected) { - int last_argc = -1; - char const *name = NULL; - char const *pass = NULL; FILE *fp; struct stat st; unsigned char *buf; @@ -59,6 +468,219 @@ main (int argc, char **argv) gcry_mpi_t *result; int badpass; char *curve = NULL; + char *resulthash = NULL; + char *p; + char *certstr = NULL; + int ret; + + inf ("testing '%s' (%s)", name , desc? desc:""); + fp = fopen (name, "rb"); + if (!fp) + { + err ("can't open '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - test file not found\n", name); + return 1; + } + + if (fstat (fileno (fp), &st)) + { + err ("can't stat '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - error stating test file\n", name); + fclose (fp); + return 1; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + err ("error reading '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - error reading test file\n", name); + fclose (fp); + xfree (buf); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, pass? pass:"", cert_collect_cb, &certstr, + &badpass, &curve); + if (result) + { + int i, rc; + char *tmpstring; + unsigned char *tmpbuf; + char numbuf[20]; + + if (curve) + { + if (verbose > 1) + inf ("curve: %s\n", curve); + tmpstring = xstrconcat ("curve:", curve, "\n", NULL); + } + else + tmpstring = xstrdup ("\n"); + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); + if (rc) + die ("result %d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + if (verbose > 1) + inf ("result %d: %s\n", i, tmpbuf); + snprintf (numbuf, sizeof numbuf, "%d:", i); + p = xstrconcat (tmpstring, numbuf, tmpbuf, "\n", NULL); + xfree (tmpstring); + tmpstring = p; + gcry_free (tmpbuf); + } + } + + resulthash = hash_buffer (tmpstring, strlen (tmpstring)); + xfree (tmpstring); + } + + if (verbose > 1) + { + inf ("cert(exp)=%s", certexpected); + inf ("cert(got)=%s", certstr? certstr:"[null]"); + inf ("key(exp)=%s", keyexpected); + inf ("key(got)=%s", resulthash? resulthash:"[null]"); + } + + ret = 1; + if (!result) + printresult ("FAIL: %s - error from parser\n", name); + else if (certexpected && !certstr) + printresult ("FAIL: %s - expected certs but got none\n", name); + else if (!certexpected && certstr) + printresult ("FAIL: %s - no certs expected but got one\n", name); + else if (certexpected && certstr && strcmp (certexpected, certstr)) + printresult ("FAIL: %s - certs not as expected\n", name); + else if (keyexpected && !resulthash) + printresult ("FAIL: %s - expected key but got none\n", name); + else if (!keyexpected && resulthash) + printresult ("FAIL: %s - key not expected but got one\n", name); + else if (keyexpected && resulthash && strcmp (keyexpected, resulthash)) + printresult ("FAIL: %s - keys not as expected\n", name); + else + { + printresult ("PASS: %s\n", name); + ret = 0; + } + + if (result) + { + int i; + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + xfree (certstr); + xfree (resulthash); + xfree (curve); + xfree (buf); + return ret; +} + + +/* Run a regression test using the Info take from DESCFNAME. */ +static int +run_tests_from_file (const char *descfname) +{ + FILE *fp; + char *descdir; + int lineno, ntests; + char *line; + char *name = NULL; + char *desc = NULL; + char *pass = NULL; + char *cert = NULL; + char *key = NULL; + int ret = 0; + char *p; + + inf ("Running tests from '%s'", descfname); + descdir = xstrdup (descfname); + p = strrchr (descdir, '/'); + if (p) + *p = 0; + else + { + xfree (descdir); + descdir = xstrdup ("."); + } + + fp = fopen (descfname, "r"); + if (!fp) + die ("error opening '%s': %s\n", descfname, strerror (errno)); + + lineno = ntests = 0; + while ((line = read_textline (fp, &lineno))) + { + if (!strncmp (line, "Name:", 5)) + { + if (name) + ret |= run_one_test (name, desc, pass, cert, key); + xfree (cert); cert = NULL; + xfree (desc); desc = NULL; + xfree (pass); pass = NULL; + xfree (key); key = NULL; + copy_data (&name, line, lineno); + if (name) + { + p = xstrconcat (descdir, "/", name, NULL); + xfree (name); + name = p; + } + } + else if (!strncmp (line, "Desc:", 5)) + copy_data (&desc, line, lineno); + else if (!strncmp (line, "Pass:", 5)) + copy_data (&pass, line, lineno); + else if (!strncmp (line, "Cert:", 5)) + { + p = NULL; + copy_data (&p, line, lineno); + hexdowncase (p); + if (p && cert) + cert = xstrconcat (cert, ",", p, NULL); + else + cert = p; + } + else if (!strncmp (line, "Key:", 4)) + { + copy_data (&key, line, lineno); + hexdowncase (key); + } + else + inf ("%s:%d: unknown tag ignored", descfname, lineno); + + xfree (line); + } + if (name) + ret |= run_one_test (name, desc, pass, cert, key); + xfree (name); + xfree (desc); + xfree (pass); + xfree (cert); + xfree (key); + + fclose (fp); + xfree (descdir); + return ret; +} + + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + char const *name = NULL; + char const *pass = NULL; + int ret; if (argc) { argc--; argv++; } @@ -73,8 +695,10 @@ main (int argc, char **argv) else if (!strcmp (*argv, "--help")) { fputs ("usage: " PGM " <pkcs12file> [<passphrase>]\n" + "Without <pkcs12file> a regression test is run\n" "Options:\n" " --verbose print timings etc.\n" + " given twice shows more\n" " --debug flyswatter\n" , stdout); exit (0); @@ -97,7 +721,12 @@ main (int argc, char **argv) } } - if (argc == 1) + if (!argc) + { + name = NULL; + pass = NULL; + } + else if (argc == 1) { name = argv[0]; pass = ""; @@ -109,57 +738,38 @@ main (int argc, char **argv) } else { - fprintf (stderr, "usage: " PGM " <file> [<passphrase>]\n"); + fprintf (stderr, "usage: " PGM " [<file> [<passphrase>]]\n"); exit (1); } gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); - - fp = fopen (name, "rb"); - if (!fp) + if (name) { - fprintf (stderr, PGM": can't open '%s': %s\n", name, strerror (errno)); - return 1; + p12_set_verbosity (verbose, debug); + ret = one_file (name, pass); } - - if (fstat (fileno(fp), &st)) + else { - fprintf (stderr, PGM": can't stat '%s': %s\n", name, strerror (errno)); - return 1; - } + char *descfname, *p; - buflen = st.st_size; - buf = gcry_malloc (buflen+1); - if (!buf || fread (buf, buflen, 1, fp) != 1) - { - fprintf (stderr, "error reading '%s': %s\n", name, strerror (errno)); - return 1; - } - fclose (fp); + if (verbose > 1) + p12_set_verbosity (verbose > 1? (verbose - 1):0, debug); + descfname = prepend_srcdir ("../tests/cms/samplekeys/Description-p12"); + ret = run_tests_from_file (descfname); + xfree (descfname); - result = p12_parse (buf, buflen, pass, cert_cb, NULL, &badpass, &curve); - if (result) - { - int i, rc; - unsigned char *tmpbuf; - - if (curve) - log_info ("curve: %s\n", curve); - for (i=0; result[i]; i++) + /* Check whether we have non-public regression test cases. */ + p = value_from_gnupg_autogen_rc ("GNUPG_EXTRA_TESTS_DIR"); + if (p) { - rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); - if (rc) - log_error ("%d: [error printing number: %s]\n", - i, gpg_strerror (rc)); - else - { - log_info ("%d: %s\n", i, tmpbuf); - gcry_free (tmpbuf); - } + descfname = xstrconcat (p, "/pkcs12/Description", NULL); + xfree (p); + ret |= run_tests_from_file (descfname); + xfree (descfname); } } - return 0; + return ret; } |