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
This commit is contained in:
Werner Koch 2023-06-28 17:33:24 +02:00
parent c926967d85
commit 101433dfb4
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
7 changed files with 1951 additions and 870 deletions

View File

@ -77,8 +77,8 @@ gpgsm_LDFLAGS =
gpgsm_DEPENDENCIES = $(resource_objs)
module_tests =
module_maint_tests = t-minip12
module_tests = t-minip12
module_maint_tests =
t_common_src =
t_common_ldadd = $(libcommon) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) \

View File

@ -804,7 +804,7 @@ set_debug (void)
/* minip12.c may be used outside of GnuPG, thus we don't have the
* opt structure over there. */
p12_set_verbosity (opt.verbose);
p12_set_verbosity (opt.verbose, opt.debug);
}

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@
#include <gcrypt.h>
void p12_set_verbosity (int verbose);
void p12_set_verbosity (int verbose, int debug);
gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length,
const char *pw,

View File

@ -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,10 @@ cert_cb (void *opaque, const unsigned char *cert, size_t certlen)
}
int
main (int argc, char **argv)
/* Parse one PKCS#12 file. Returns zero on success. */
static int
one_file (const char *name, const char *pass)
{
int last_argc = -1;
char const *name = NULL;
char const *pass = NULL;
FILE *fp;
struct stat st;
unsigned char *buf;
@ -60,63 +389,6 @@ main (int argc, char **argv)
int badpass;
char *curve = 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 " <pkcs12file> [<passphrase>]\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))
{
fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
exit (1);
}
}
if (argc == 1)
{
name = argv[0];
pass = "";
}
else if (argc == 2)
{
name = argv[0];
pass = argv[1];
}
else
{
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)
{
@ -131,8 +403,8 @@ main (int argc, char **argv)
}
buflen = st.st_size;
buf = gcry_malloc (buflen+1);
if (!buf || fread (buf, buflen, 1, fp) != 1)
buf = xmalloc (buflen+1);
if (fread (buf, buflen, 1, fp) != 1)
{
fprintf (stderr, "error reading '%s': %s\n", name, strerror (errno));
return 1;
@ -160,6 +432,344 @@ main (int argc, char **argv)
}
}
}
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)
{
FILE *fp;
struct stat st;
unsigned char *buf;
size_t buflen;
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++; }
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
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);
}
else if (!strcmp (*argv, "--verbose"))
{
verbose++;
argc--; argv++;
}
else if (!strcmp (*argv, "--debug"))
{
verbose += 2;
debug++;
argc--; argv++;
}
else if (!strncmp (*argv, "--", 2))
{
fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
exit (1);
}
}
if (!argc)
{
name = NULL;
pass = NULL;
}
else if (argc == 1)
{
name = argv[0];
pass = "";
}
else if (argc == 2)
{
name = argv[0];
pass = argv[1];
}
else
{
fprintf (stderr, "usage: " PGM " [<file> [<passphrase>]]\n");
exit (1);
}
gcry_control (GCRYCTL_DISABLE_SECMEM, NULL);
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL);
if (name)
{
p12_set_verbosity (verbose, debug);
ret = one_file (name, pass);
}
else
{
char *descfname, *p;
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);
/* Check whether we have non-public regression test cases. */
p = value_from_gnupg_autogen_rc ("GNUPG_EXTRA_TESTS_DIR");
if (p)
{
descfname = xstrconcat (p, "/pkcs12/Description", NULL);
xfree (p);
ret |= run_tests_from_file (descfname);
xfree (descfname);
}
}
return ret;
}

View File

@ -0,0 +1,20 @@
# Description-p12 - Machine readable description of our P12 test vectors
Name: ov-user.p12
Desc: Private test key from www.openvalidation.org
Pass: start
Cert: 4753a910e0c8b4caa8663ca0e4273a884eb5397d
Key: 93be89edd11214ab74280d988a665b6beef876c5
Name: ov-server.p12
Desc: Private test key from www.openvalidation.org
Pass: start
Cert: 1997fadf6cc1af03e4845c4cba38fb2397315143
Key: 63b1d7233e75c3a462cb4b8ea3ad285e8ecba91c
Name: opensc-test.p12
Desc: PKCS#12 key and certificates taken from OpenSC (RC2+3DES,PKCS#8)
Pass: password
Cert: 115abfc3ae554092a57ade74177fedf9459af5d2
Cert: a0d6d318952c313ff8c33cd3f629647ff1de76b3
Key: 5a36c61706367ecdb52e8779e3a32bbac1069fa1

View File

@ -1,10 +1,5 @@
This is a collection of keys we use with the regression tests.
opensc-tests.p12 PKCS#12 key and certificates taken from OpenSC.
Passphrase is "password"
ov-user.p12 Private tests keys from www.openvalidation.org.
ov-server.p12 Passphrase for both is "start"
For the *.p12 files see Description-p12
ossl-rentec-user.pem An OpenSSL generated user certificate using a
bunch of attributes and DC RDNs.
@ -21,4 +16,3 @@ steed-self-signing-nonthority.pem
The STEED Self-Signing Nonthority.
68A638998DFABAC510EA645CE34F9686B2EDF7EA.key
The private Key of The STEED Self-Signing Nonthority.