diff --git a/tests/gpgscm/ffi.c b/tests/gpgscm/ffi.c
index c91d4aabe..42facee13 100644
--- a/tests/gpgscm/ffi.c
+++ b/tests/gpgscm/ffi.c
@@ -1,1419 +1,1428 @@
/* FFI interface for TinySCHEME.
*
* Copyright (C) 2016 g10 code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if HAVE_LIBREADLINE
#define GNUPG_LIBREADLINE_H_INCLUDED
#include
#include
#endif
#include "../../common/util.h"
#include "../../common/exechelp.h"
#include "../../common/sysutils.h"
#include "private.h"
#include "ffi.h"
#include "ffi-private.h"
/* For use in nice error messages. */
static const char *
ordinal_suffix (int n)
{
switch (n)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
assert (! "reached");
}
int
ffi_bool_value (scheme *sc, pointer p)
{
return ! (p == sc->F);
}
static pointer
do_logand (scheme *sc, pointer args)
{
FFI_PROLOG ();
unsigned int v, acc = ~0;
while (args != sc->NIL)
{
FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args);
acc &= v;
}
FFI_RETURN_INT (sc, acc);
}
static pointer
do_logior (scheme *sc, pointer args)
{
FFI_PROLOG ();
unsigned int v, acc = 0;
while (args != sc->NIL)
{
FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args);
acc |= v;
}
FFI_RETURN_INT (sc, acc);
}
static pointer
do_logxor (scheme *sc, pointer args)
{
FFI_PROLOG ();
unsigned int v, acc = 0;
while (args != sc->NIL)
{
FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args);
acc ^= v;
}
FFI_RETURN_INT (sc, acc);
}
static pointer
do_lognot (scheme *sc, pointer args)
{
FFI_PROLOG ();
unsigned int v;
FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_INT (sc, ~v);
}
/* User interface. */
static pointer
do_flush_stdio (scheme *sc, pointer args)
{
FFI_PROLOG ();
FFI_ARGS_DONE_OR_RETURN (sc, args);
fflush (stdout);
fflush (stderr);
FFI_RETURN (sc);
}
int use_libreadline;
/* Read a string, and return a pointer to it. Returns NULL on EOF. */
char *
rl_gets (const char *prompt)
{
static char *line = NULL;
char *p;
xfree (line);
#if HAVE_LIBREADLINE
{
line = readline (prompt);
if (line && *line)
add_history (line);
}
#else
{
size_t max_size = 0xff;
printf ("%s", prompt);
fflush (stdout);
line = xtrymalloc (max_size);
if (line != NULL)
fgets (line, max_size, stdin);
}
#endif
/* Strip trailing whitespace. */
if (line && strlen (line) > 0)
for (p = &line[strlen (line) - 1]; isspace (*p); p--)
*p = 0;
return line;
}
static pointer
do_prompt (scheme *sc, pointer args)
{
FFI_PROLOG ();
const char *prompt;
const char *line;
FFI_ARG_OR_RETURN (sc, const char *, prompt, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
line = rl_gets (prompt);
if (! line)
FFI_RETURN_POINTER (sc, sc->EOF_OBJ);
FFI_RETURN_STRING (sc, line);
}
static pointer
do_sleep (scheme *sc, pointer args)
{
FFI_PROLOG ();
unsigned int seconds;
FFI_ARG_OR_RETURN (sc, unsigned int, seconds, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
sleep (seconds);
FFI_RETURN (sc);
}
static pointer
do_usleep (scheme *sc, pointer args)
{
FFI_PROLOG ();
useconds_t microseconds;
FFI_ARG_OR_RETURN (sc, useconds_t, microseconds, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
usleep (microseconds);
FFI_RETURN (sc);
}
static pointer
do_chdir (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
FFI_ARG_OR_RETURN (sc, char *, name, path, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (chdir (name))
FFI_RETURN_ERR (sc, errno);
FFI_RETURN (sc);
}
static pointer
do_strerror (scheme *sc, pointer args)
{
FFI_PROLOG ();
int error;
FFI_ARG_OR_RETURN (sc, int, error, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_STRING (sc, gpg_strerror (error));
}
static pointer
do_getenv (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
char *value;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
value = getenv (name);
FFI_RETURN_STRING (sc, value ? value : "");
}
static pointer
do_setenv (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
char *value;
int overwrite;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARG_OR_RETURN (sc, char *, value, string, args);
FFI_ARG_OR_RETURN (sc, int, overwrite, bool, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (gnupg_setenv (name, value, overwrite))
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN (sc);
}
static pointer
do_exit (scheme *sc, pointer args)
{
FFI_PROLOG ();
int retcode;
FFI_ARG_OR_RETURN (sc, int, retcode, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
exit (retcode);
}
/* XXX: use gnupgs variant b/c mode as string */
static pointer
do_open (scheme *sc, pointer args)
{
FFI_PROLOG ();
int fd;
char *pathname;
int flags;
mode_t mode = 0;
FFI_ARG_OR_RETURN (sc, char *, pathname, path, args);
FFI_ARG_OR_RETURN (sc, int, flags, number, args);
if (args != sc->NIL)
FFI_ARG_OR_RETURN (sc, mode_t, mode, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
fd = open (pathname, flags, mode);
if (fd == -1)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN_INT (sc, fd);
}
static pointer
do_fdopen (scheme *sc, pointer args)
{
FFI_PROLOG ();
FILE *stream;
int fd;
char *mode;
int kind;
FFI_ARG_OR_RETURN (sc, int, fd, number, args);
FFI_ARG_OR_RETURN (sc, char *, mode, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
stream = fdopen (fd, mode);
if (stream == NULL)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
if (setvbuf (stream, NULL, _IONBF, 0) != 0)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
kind = 0;
if (strchr (mode, 'r'))
kind |= port_input;
if (strchr (mode, 'w'))
kind |= port_output;
FFI_RETURN_POINTER (sc, sc->vptr->mk_port_from_file (sc, stream, kind));
}
static pointer
do_close (scheme *sc, pointer args)
{
FFI_PROLOG ();
int fd;
FFI_ARG_OR_RETURN (sc, int, fd, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_ERR (sc, close (fd) == 0 ? 0 : gpg_error_from_syserror ());
}
static pointer
do_seek (scheme *sc, pointer args)
{
FFI_PROLOG ();
int fd;
off_t offset;
int whence;
FFI_ARG_OR_RETURN (sc, int, fd, number, args);
FFI_ARG_OR_RETURN (sc, off_t, offset, number, args);
FFI_ARG_OR_RETURN (sc, int, whence, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_ERR (sc, lseek (fd, offset, whence) == (off_t) -1
? gpg_error_from_syserror () : 0);
}
static pointer
do_mkdtemp (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *template;
char buffer[128];
char *name;
FFI_ARG_OR_RETURN (sc, char *, template, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (strlen (template) > sizeof buffer - 1)
FFI_RETURN_ERR (sc, EINVAL);
strncpy (buffer, template, sizeof buffer);
name = gnupg_mkdtemp (buffer);
if (name == NULL)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN_STRING (sc, name);
}
static pointer
do_unlink (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (unlink (name) == -1)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN (sc);
}
static gpg_error_t
unlink_recursively (const char *name)
{
gpg_error_t err = 0;
struct stat st;
if (stat (name, &st) == -1)
return gpg_error_from_syserror ();
if (S_ISDIR (st.st_mode))
{
DIR *dir;
struct dirent *dent;
dir = opendir (name);
if (dir == NULL)
return gpg_error_from_syserror ();
while ((dent = readdir (dir)))
{
char *child;
if (strcmp (dent->d_name, ".") == 0
|| strcmp (dent->d_name, "..") == 0)
continue;
child = xtryasprintf ("%s/%s", name, dent->d_name);
if (child == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = unlink_recursively (child);
xfree (child);
if (err == gpg_error_from_errno (ENOENT))
err = 0;
if (err)
goto leave;
}
leave:
closedir (dir);
if (! err)
rmdir (name);
return err;
}
else
if (unlink (name) == -1)
return gpg_error_from_syserror ();
return 0;
}
static pointer
do_unlink_recursively (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = unlink_recursively (name);
FFI_RETURN (sc);
}
static pointer
do_rename (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *old;
char *new;
FFI_ARG_OR_RETURN (sc, char *, old, string, args);
FFI_ARG_OR_RETURN (sc, char *, new, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (rename (old, new) == -1)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN (sc);
}
static pointer
do_getcwd (scheme *sc, pointer args)
{
FFI_PROLOG ();
pointer result;
char *cwd;
FFI_ARGS_DONE_OR_RETURN (sc, args);
cwd = gnupg_getcwd ();
if (cwd == NULL)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
result = sc->vptr->mk_string (sc, cwd);
xfree (cwd);
FFI_RETURN_POINTER (sc, result);
}
static pointer
do_mkdir (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
char *mode;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARG_OR_RETURN (sc, char *, mode, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (gnupg_mkdir (name, mode) == -1)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN (sc);
}
static pointer
do_rmdir (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *name;
FFI_ARG_OR_RETURN (sc, char *, name, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (rmdir (name) == -1)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
FFI_RETURN (sc);
}
static pointer
do_get_isotime (scheme *sc, pointer args)
{
FFI_PROLOG ();
gnupg_isotime_t timebuf;
FFI_ARGS_DONE_OR_RETURN (sc, args);
gnupg_get_isotime (timebuf);
FFI_RETURN_STRING (sc, timebuf);
}
+static pointer
+do_get_time (scheme *sc, pointer args)
+{
+ FFI_PROLOG ();
+ FFI_ARGS_DONE_OR_RETURN (sc, args);
+ FFI_RETURN_INT (sc, gnupg_get_time ());
+}
+
static pointer
do_getpid (scheme *sc, pointer args)
{
FFI_PROLOG ();
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_INT (sc, getpid ());
}
static pointer
do_srandom (scheme *sc, pointer args)
{
FFI_PROLOG ();
int seed;
FFI_ARG_OR_RETURN (sc, int, seed, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
srand (seed);
FFI_RETURN (sc);
}
static int
random_scaled (int scale)
{
int v;
#ifdef HAVE_RAND
v = rand ();
#else
v = random ();
#endif
#ifndef RAND_MAX /* for SunOS */
#define RAND_MAX 32767
#endif
return ((int) (1 + (int) ((float) scale * v / (RAND_MAX + 1.0))) - 1);
}
static pointer
do_random (scheme *sc, pointer args)
{
FFI_PROLOG ();
int scale;
FFI_ARG_OR_RETURN (sc, int, scale, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_INT (sc, random_scaled (scale));
}
static pointer
do_make_random_string (scheme *sc, pointer args)
{
FFI_PROLOG ();
int size;
pointer chunk;
char *p;
FFI_ARG_OR_RETURN (sc, int, size, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (size < 0)
return ffi_sprintf (sc, "size must be positive");
chunk = sc->vptr->mk_counted_string (sc, NULL, size);
if (sc->no_memory)
FFI_RETURN_ERR (sc, ENOMEM);
for (p = sc->vptr->string_value (chunk); size; p++, size--)
*p = (char) random_scaled (256);
FFI_RETURN_POINTER (sc, chunk);
}
/* estream functions. */
struct es_object_box
{
estream_t stream;
int closed;
};
static void
es_object_finalize (scheme *sc, void *data)
{
struct es_object_box *box = data;
(void) sc;
if (! box->closed)
es_fclose (box->stream);
xfree (box);
}
static void
es_object_to_string (scheme *sc, char *out, size_t size, void *data)
{
struct es_object_box *box = data;
(void) sc;
snprintf (out, size, "#estream %p", box->stream);
}
static struct foreign_object_vtable es_object_vtable =
{
es_object_finalize,
es_object_to_string,
};
static pointer
es_wrap (scheme *sc, estream_t stream)
{
struct es_object_box *box = xmalloc (sizeof *box);
if (box == NULL)
return sc->NIL;
box->stream = stream;
box->closed = 0;
return sc->vptr->mk_foreign_object (sc, &es_object_vtable, box);
}
static struct es_object_box *
es_unwrap (scheme *sc, pointer object)
{
(void) sc;
if (! is_foreign_object (object))
return NULL;
if (sc->vptr->get_foreign_object_vtable (object) != &es_object_vtable)
return NULL;
return sc->vptr->get_foreign_object_data (object);
}
#define CONVERSION_estream(SC, X) es_unwrap (SC, X)
#define IS_A_estream(SC, X) es_unwrap (SC, X)
static pointer
do_es_fclose (scheme *sc, pointer args)
{
FFI_PROLOG ();
struct es_object_box *box;
FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = es_fclose (box->stream);
if (! err)
box->closed = 1;
FFI_RETURN (sc);
}
static pointer
do_es_read (scheme *sc, pointer args)
{
FFI_PROLOG ();
struct es_object_box *box;
size_t bytes_to_read;
pointer result;
void *buffer;
size_t bytes_read;
FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args);
FFI_ARG_OR_RETURN (sc, size_t, bytes_to_read, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
buffer = xtrymalloc (bytes_to_read);
if (buffer == NULL)
FFI_RETURN_ERR (sc, ENOMEM);
err = es_read (box->stream, buffer, bytes_to_read, &bytes_read);
if (err)
FFI_RETURN_ERR (sc, err);
result = sc->vptr->mk_counted_string (sc, buffer, bytes_read);
xfree (buffer);
FFI_RETURN_POINTER (sc, result);
}
static pointer
do_es_feof (scheme *sc, pointer args)
{
FFI_PROLOG ();
struct es_object_box *box;
FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_POINTER (sc, es_feof (box->stream) ? sc->T : sc->F);
}
static pointer
do_es_write (scheme *sc, pointer args)
{
FFI_PROLOG ();
struct es_object_box *box;
const char *buffer;
size_t bytes_to_write, bytes_written;
FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args);
/* XXX how to get the length of the string buffer? scheme strings
may contain \0. */
FFI_ARG_OR_RETURN (sc, const char *, buffer, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
bytes_to_write = strlen (buffer);
while (bytes_to_write > 0)
{
err = es_write (box->stream, buffer, bytes_to_write, &bytes_written);
if (err)
break;
bytes_to_write -= bytes_written;
buffer += bytes_written;
}
FFI_RETURN (sc);
}
/* Process handling. */
static pointer
do_spawn_process (scheme *sc, pointer args)
{
FFI_PROLOG ();
pointer arguments;
char **argv;
size_t len;
unsigned int flags;
estream_t infp;
estream_t outfp;
estream_t errfp;
pid_t pid;
FFI_ARG_OR_RETURN (sc, pointer, arguments, list, args);
FFI_ARG_OR_RETURN (sc, unsigned int, flags, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = ffi_list2argv (sc, arguments, &argv, &len);
if (err == gpg_error (GPG_ERR_INV_VALUE))
return ffi_sprintf (sc, "%luth element of first argument is "
"neither string nor symbol",
(unsigned long) len);
if (err)
FFI_RETURN_ERR (sc, err);
if (verbose > 1)
{
char **p;
fprintf (stderr, "Executing:");
for (p = argv; *p; p++)
fprintf (stderr, " '%s'", *p);
fprintf (stderr, "\n");
}
err = gnupg_spawn_process (argv[0], (const char **) &argv[1],
NULL,
NULL,
flags,
&infp, &outfp, &errfp, &pid);
xfree (argv);
#define IMC(A, B) \
_cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1)
#define IMS(A, B) \
_cons (sc, es_wrap (sc, (A)), (B), 1)
FFI_RETURN_POINTER (sc, IMS (infp,
IMS (outfp,
IMS (errfp,
IMC (pid, sc->NIL)))));
#undef IMS
#undef IMC
}
static pointer
do_spawn_process_fd (scheme *sc, pointer args)
{
FFI_PROLOG ();
pointer arguments;
char **argv;
size_t len;
int infd, outfd, errfd;
pid_t pid;
FFI_ARG_OR_RETURN (sc, pointer, arguments, list, args);
FFI_ARG_OR_RETURN (sc, int, infd, number, args);
FFI_ARG_OR_RETURN (sc, int, outfd, number, args);
FFI_ARG_OR_RETURN (sc, int, errfd, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = ffi_list2argv (sc, arguments, &argv, &len);
if (err == gpg_error (GPG_ERR_INV_VALUE))
return ffi_sprintf (sc, "%luth element of first argument is "
"neither string nor symbol",
(unsigned long) len);
if (err)
FFI_RETURN_ERR (sc, err);
if (verbose > 1)
{
char **p;
fprintf (stderr, "Executing:");
for (p = argv; *p; p++)
fprintf (stderr, " '%s'", *p);
fprintf (stderr, "\n");
}
err = gnupg_spawn_process_fd (argv[0], (const char **) &argv[1],
infd, outfd, errfd, &pid);
xfree (argv);
FFI_RETURN_INT (sc, pid);
}
static pointer
do_wait_process (scheme *sc, pointer args)
{
FFI_PROLOG ();
const char *name;
pid_t pid;
int hang;
int retcode;
FFI_ARG_OR_RETURN (sc, const char *, name, string, args);
FFI_ARG_OR_RETURN (sc, pid_t, pid, number, args);
FFI_ARG_OR_RETURN (sc, int, hang, bool, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = gnupg_wait_process (name, pid, hang, &retcode);
if (err == GPG_ERR_GENERAL)
err = 0; /* Let the return code speak for itself. */
FFI_RETURN_INT (sc, retcode);
}
static pointer
do_wait_processes (scheme *sc, pointer args)
{
FFI_PROLOG ();
pointer list_names;
char **names;
pointer list_pids;
size_t i, count;
pid_t *pids;
int hang;
int *retcodes;
pointer retcodes_list = sc->NIL;
FFI_ARG_OR_RETURN (sc, pointer, list_names, list, args);
FFI_ARG_OR_RETURN (sc, pointer, list_pids, list, args);
FFI_ARG_OR_RETURN (sc, int, hang, bool, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
if (sc->vptr->list_length (sc, list_names)
!= sc->vptr->list_length (sc, list_pids))
return
sc->vptr->mk_string (sc, "length of first two arguments must match");
err = ffi_list2argv (sc, list_names, &names, &count);
if (err == gpg_error (GPG_ERR_INV_VALUE))
return ffi_sprintf (sc, "%lu%s element of first argument is "
"neither string nor symbol",
(unsigned long) count,
ordinal_suffix ((int) count));
if (err)
FFI_RETURN_ERR (sc, err);
err = ffi_list2intv (sc, list_pids, (int **) &pids, &count);
if (err == gpg_error (GPG_ERR_INV_VALUE))
return ffi_sprintf (sc, "%lu%s element of second argument is "
"not a number",
(unsigned long) count,
ordinal_suffix ((int) count));
if (err)
FFI_RETURN_ERR (sc, err);
retcodes = xtrycalloc (sizeof *retcodes, count);
if (retcodes == NULL)
{
xfree (names);
xfree (pids);
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
}
err = gnupg_wait_processes ((const char **) names, pids, count, hang,
retcodes);
if (err == GPG_ERR_GENERAL)
err = 0; /* Let the return codes speak. */
for (i = 0; i < count; i++)
retcodes_list =
(sc->vptr->cons) (sc,
sc->vptr->mk_integer (sc,
(long) retcodes[count-1-i]),
retcodes_list);
xfree (names);
xfree (pids);
xfree (retcodes);
FFI_RETURN_POINTER (sc, retcodes_list);
}
static pointer
do_pipe (scheme *sc, pointer args)
{
FFI_PROLOG ();
int filedes[2];
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = gnupg_create_pipe (filedes);
#define IMC(A, B) \
_cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1)
FFI_RETURN_POINTER (sc, IMC (filedes[0],
IMC (filedes[1], sc->NIL)));
#undef IMC
}
static pointer
do_inbound_pipe (scheme *sc, pointer args)
{
FFI_PROLOG ();
int filedes[2];
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = gnupg_create_inbound_pipe (filedes, NULL, 0);
#define IMC(A, B) \
_cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1)
FFI_RETURN_POINTER (sc, IMC (filedes[0],
IMC (filedes[1], sc->NIL)));
#undef IMC
}
static pointer
do_outbound_pipe (scheme *sc, pointer args)
{
FFI_PROLOG ();
int filedes[2];
FFI_ARGS_DONE_OR_RETURN (sc, args);
err = gnupg_create_outbound_pipe (filedes, NULL, 0);
#define IMC(A, B) \
_cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1)
FFI_RETURN_POINTER (sc, IMC (filedes[0],
IMC (filedes[1], sc->NIL)));
#undef IMC
}
/* Test helper functions. */
static pointer
do_file_equal (scheme *sc, pointer args)
{
FFI_PROLOG ();
pointer result = sc->F;
char *a_name, *b_name;
int binary;
const char *mode;
FILE *a_stream = NULL, *b_stream = NULL;
struct stat a_stat, b_stat;
#define BUFFER_SIZE 1024
char a_buf[BUFFER_SIZE], b_buf[BUFFER_SIZE];
#undef BUFFER_SIZE
size_t chunk;
FFI_ARG_OR_RETURN (sc, char *, a_name, string, args);
FFI_ARG_OR_RETURN (sc, char *, b_name, string, args);
FFI_ARG_OR_RETURN (sc, int, binary, bool, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
mode = binary ? "rb" : "r";
a_stream = fopen (a_name, mode);
if (a_stream == NULL)
goto errout;
b_stream = fopen (b_name, mode);
if (b_stream == NULL)
goto errout;
if (fstat (fileno (a_stream), &a_stat) < 0)
goto errout;
if (fstat (fileno (b_stream), &b_stat) < 0)
goto errout;
if (binary && a_stat.st_size != b_stat.st_size)
{
if (verbose)
fprintf (stderr, "Files %s and %s differ in size %lu != %lu\n",
a_name, b_name, (unsigned long) a_stat.st_size,
(unsigned long) b_stat.st_size);
goto out;
}
while (! feof (a_stream))
{
chunk = sizeof a_buf;
chunk = fread (a_buf, 1, chunk, a_stream);
if (chunk == 0 && ferror (a_stream))
goto errout; /* some error */
if (fread (b_buf, 1, chunk, b_stream) < chunk)
{
if (feof (b_stream))
goto out; /* short read */
goto errout; /* some error */
}
if (chunk > 0 && memcmp (a_buf, b_buf, chunk) != 0)
goto out;
}
fread (b_buf, 1, 1, b_stream);
if (! feof (b_stream))
goto out; /* b is longer */
/* They match. */
result = sc->T;
out:
if (a_stream)
fclose (a_stream);
if (b_stream)
fclose (b_stream);
FFI_RETURN_POINTER (sc, result);
errout:
err = gpg_error_from_syserror ();
goto out;
}
static pointer
do_splice (scheme *sc, pointer args)
{
FFI_PROLOG ();
int source;
char buffer[1024];
ssize_t bytes_read;
pointer sinks, sink;
FFI_ARG_OR_RETURN (sc, int, source, number, args);
sinks = args;
if (sinks == sc->NIL)
return ffi_sprintf (sc, "need at least one sink");
for (sink = sinks; sink != sc->NIL; sink = pair_cdr (sink), ffi_arg_index++)
if (! sc->vptr->is_number (pair_car (sink)))
return ffi_sprintf (sc, "%d%s argument is not a number",
ffi_arg_index, ordinal_suffix (ffi_arg_index));
while (1)
{
bytes_read = read (source, buffer, sizeof buffer);
if (bytes_read == 0)
break;
if (bytes_read < 0)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
for (sink = sinks; sink != sc->NIL; sink = pair_cdr (sink))
{
int fd = sc->vptr->ivalue (pair_car (sink));
char *p = buffer;
ssize_t left = bytes_read;
while (left)
{
ssize_t written = write (fd, p, left);
if (written < 0)
FFI_RETURN_ERR (sc, gpg_error_from_syserror ());
assert (written <= left);
left -= written;
p += written;
}
}
}
FFI_RETURN (sc);
}
static pointer
do_string_index (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *haystack;
char needle;
ssize_t offset = 0;
char *position;
FFI_ARG_OR_RETURN (sc, char *, haystack, string, args);
FFI_ARG_OR_RETURN (sc, char, needle, character, args);
if (args != sc->NIL)
{
FFI_ARG_OR_RETURN (sc, ssize_t, offset, number, args);
if (offset < 0)
return ffi_sprintf (sc, "offset must be positive");
if (offset > strlen (haystack))
return ffi_sprintf (sc, "offset exceeds haystack");
}
FFI_ARGS_DONE_OR_RETURN (sc, args);
position = strchr (haystack+offset, needle);
if (position)
FFI_RETURN_INT (sc, position - haystack);
else
FFI_RETURN_POINTER (sc, sc->F);
}
static pointer
do_string_rindex (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *haystack;
char needle;
ssize_t offset = 0;
char *position;
FFI_ARG_OR_RETURN (sc, char *, haystack, string, args);
FFI_ARG_OR_RETURN (sc, char, needle, character, args);
if (args != sc->NIL)
{
FFI_ARG_OR_RETURN (sc, ssize_t, offset, number, args);
if (offset < 0)
return ffi_sprintf (sc, "offset must be positive");
if (offset > strlen (haystack))
return ffi_sprintf (sc, "offset exceeds haystack");
}
FFI_ARGS_DONE_OR_RETURN (sc, args);
position = strrchr (haystack+offset, needle);
if (position)
FFI_RETURN_INT (sc, position - haystack);
else
FFI_RETURN_POINTER (sc, sc->F);
}
static pointer
do_string_contains (scheme *sc, pointer args)
{
FFI_PROLOG ();
char *haystack;
char *needle;
FFI_ARG_OR_RETURN (sc, char *, haystack, string, args);
FFI_ARG_OR_RETURN (sc, char *, needle, string, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_POINTER (sc, strstr (haystack, needle) ? sc->T : sc->F);
}
static pointer
do_get_verbose (scheme *sc, pointer args)
{
FFI_PROLOG ();
FFI_ARGS_DONE_OR_RETURN (sc, args);
FFI_RETURN_INT (sc, verbose);
}
static pointer
do_set_verbose (scheme *sc, pointer args)
{
FFI_PROLOG ();
int new_verbosity, old;
FFI_ARG_OR_RETURN (sc, int, new_verbosity, number, args);
FFI_ARGS_DONE_OR_RETURN (sc, args);
old = verbose;
verbose = new_verbosity;
FFI_RETURN_INT (sc, old);
}
gpg_error_t
ffi_list2argv (scheme *sc, pointer list, char ***argv, size_t *len)
{
int i;
*len = sc->vptr->list_length (sc, list);
*argv = xtrycalloc (*len + 1, sizeof **argv);
if (*argv == NULL)
return gpg_error_from_syserror ();
for (i = 0; sc->vptr->is_pair (list); list = sc->vptr->pair_cdr (list))
{
if (sc->vptr->is_string (sc->vptr->pair_car (list)))
(*argv)[i++] = sc->vptr->string_value (sc->vptr->pair_car (list));
else if (sc->vptr->is_symbol (sc->vptr->pair_car (list)))
(*argv)[i++] = sc->vptr->symname (sc->vptr->pair_car (list));
else
{
xfree (*argv);
*argv = NULL;
*len = i;
return gpg_error (GPG_ERR_INV_VALUE);
}
}
(*argv)[i] = NULL;
return 0;
}
gpg_error_t
ffi_list2intv (scheme *sc, pointer list, int **intv, size_t *len)
{
int i;
*len = sc->vptr->list_length (sc, list);
*intv = xtrycalloc (*len, sizeof **intv);
if (*intv == NULL)
return gpg_error_from_syserror ();
for (i = 0; sc->vptr->is_pair (list); list = sc->vptr->pair_cdr (list))
{
if (sc->vptr->is_number (sc->vptr->pair_car (list)))
(*intv)[i++] = sc->vptr->ivalue (sc->vptr->pair_car (list));
else
{
xfree (*intv);
*intv = NULL;
*len = i;
return gpg_error (GPG_ERR_INV_VALUE);
}
}
return 0;
}
char *
ffi_schemify_name (const char *s, int macro)
{
/* Fixme: We should use xtrystrdup and return NULL. However, this
* requires a lot more changes. Simply returning S as done
* originally is not an option. */
char *n = xstrdup (s), *p;
/* if (n == NULL) */
/* return s; */
for (p = n; *p; p++)
{
*p = (char) tolower (*p);
/* We convert _ to - in identifiers. We allow, however, for
function names to start with a leading _. The functions in
this namespace are not yet finalized and might change or
vanish without warning. Use them with care. */
if (! macro
&& p != n
&& *p == '_')
*p = '-';
}
return n;
}
pointer
ffi_sprintf (scheme *sc, const char *format, ...)
{
pointer result;
va_list listp;
char *expression;
int size, written;
va_start (listp, format);
size = vsnprintf (NULL, 0, format, listp);
va_end (listp);
expression = xtrymalloc (size + 1);
if (expression == NULL)
return NULL;
va_start (listp, format);
written = vsnprintf (expression, size + 1, format, listp);
va_end (listp);
assert (size == written);
result = sc->vptr->mk_string (sc, expression);
xfree (expression);
return result;
}
void
ffi_scheme_eval (scheme *sc, const char *format, ...)
{
va_list listp;
char *expression;
int size, written;
va_start (listp, format);
size = vsnprintf (NULL, 0, format, listp);
va_end (listp);
expression = xtrymalloc (size + 1);
if (expression == NULL)
return;
va_start (listp, format);
written = vsnprintf (expression, size + 1, format, listp);
va_end (listp);
assert (size == written);
sc->vptr->load_string (sc, expression);
xfree (expression);
}
gpg_error_t
ffi_init (scheme *sc, const char *argv0, const char *scriptname,
int argc, const char **argv)
{
int i;
pointer args = sc->NIL;
/* bitwise arithmetic */
ffi_define_function (sc, logand);
ffi_define_function (sc, logior);
ffi_define_function (sc, logxor);
ffi_define_function (sc, lognot);
/* libc. */
ffi_define_constant (sc, O_RDONLY);
ffi_define_constant (sc, O_WRONLY);
ffi_define_constant (sc, O_RDWR);
ffi_define_constant (sc, O_CREAT);
ffi_define_constant (sc, O_APPEND);
#ifndef O_BINARY
# define O_BINARY 0
#endif
#ifndef O_TEXT
# define O_TEXT 0
#endif
ffi_define_constant (sc, O_BINARY);
ffi_define_constant (sc, O_TEXT);
ffi_define_constant (sc, STDIN_FILENO);
ffi_define_constant (sc, STDOUT_FILENO);
ffi_define_constant (sc, STDERR_FILENO);
ffi_define_constant (sc, SEEK_SET);
ffi_define_constant (sc, SEEK_CUR);
ffi_define_constant (sc, SEEK_END);
ffi_define_function (sc, sleep);
ffi_define_function (sc, usleep);
ffi_define_function (sc, chdir);
ffi_define_function (sc, strerror);
ffi_define_function (sc, getenv);
ffi_define_function (sc, setenv);
ffi_define_function_name (sc, "_exit", exit);
ffi_define_function (sc, open);
ffi_define_function (sc, fdopen);
ffi_define_function (sc, close);
ffi_define_function (sc, seek);
ffi_define_function_name (sc, "_mkdtemp", mkdtemp);
ffi_define_function (sc, unlink);
ffi_define_function (sc, unlink_recursively);
ffi_define_function (sc, rename);
ffi_define_function (sc, getcwd);
ffi_define_function (sc, mkdir);
ffi_define_function (sc, rmdir);
ffi_define_function (sc, get_isotime);
+ ffi_define_function (sc, get_time);
ffi_define_function (sc, getpid);
/* Random numbers. */
ffi_define_function (sc, srandom);
ffi_define_function (sc, random);
ffi_define_function (sc, make_random_string);
/* Process management. */
ffi_define_function (sc, spawn_process);
ffi_define_function (sc, spawn_process_fd);
ffi_define_function (sc, wait_process);
ffi_define_function (sc, wait_processes);
ffi_define_function (sc, pipe);
ffi_define_function (sc, inbound_pipe);
ffi_define_function (sc, outbound_pipe);
/* estream functions. */
ffi_define_function_name (sc, "es-fclose", es_fclose);
ffi_define_function_name (sc, "es-read", es_read);
ffi_define_function_name (sc, "es-feof", es_feof);
ffi_define_function_name (sc, "es-write", es_write);
/* Test helper functions. */
ffi_define_function (sc, file_equal);
ffi_define_function (sc, splice);
ffi_define_function (sc, string_index);
ffi_define_function (sc, string_rindex);
ffi_define_function_name (sc, "string-contains?", string_contains);
/* User interface. */
ffi_define_function (sc, flush_stdio);
ffi_define_function (sc, prompt);
/* Configuration. */
ffi_define_function_name (sc, "*verbose*", get_verbose);
ffi_define_function_name (sc, "*set-verbose!*", set_verbose);
ffi_define (sc, "*argv0*", sc->vptr->mk_string (sc, argv0));
ffi_define (sc, "*scriptname*", sc->vptr->mk_string (sc, scriptname));
for (i = argc - 1; i >= 0; i--)
{
pointer value = sc->vptr->mk_string (sc, argv[i]);
args = (sc->vptr->cons) (sc, value, args);
}
ffi_define (sc, "*args*", args);
#if _WIN32
ffi_define (sc, "*pathsep*", sc->vptr->mk_character (sc, ';'));
#else
ffi_define (sc, "*pathsep*", sc->vptr->mk_character (sc, ':'));
#endif
ffi_define (sc, "*win32*",
#if _WIN32
sc->T
#else
sc->F
#endif
);
ffi_define (sc, "*stdin*",
sc->vptr->mk_port_from_file (sc, stdin, port_input));
ffi_define (sc, "*stdout*",
sc->vptr->mk_port_from_file (sc, stdout, port_output));
ffi_define (sc, "*stderr*",
sc->vptr->mk_port_from_file (sc, stderr, port_output));
return 0;
}
diff --git a/tests/gpgscm/ffi.scm b/tests/gpgscm/ffi.scm
index b62fd1f8a..3f2e553ab 100644
--- a/tests/gpgscm/ffi.scm
+++ b/tests/gpgscm/ffi.scm
@@ -1,49 +1,52 @@
;; FFI interface for TinySCHEME.
;;
;; Copyright (C) 2016 g10 Code GmbH
;;
;; This file is part of GnuPG.
;;
;; GnuPG is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.
;;
;; GnuPG 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 General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, see .
;; Foreign function wrapper. Expects F to return a list with the
;; first element being the `error_t' value returned by the foreign
;; function. The error is thrown, or the cdr of the result is
;; returned.
(define (ffi-apply name f args)
(let ((result (apply f args)))
(cond
((string? result)
(ffi-fail name args result))
((not (= (car result) 0))
(ffi-fail name args (strerror (car result))))
((and (= (car result) 0) (pair? (cdr result))) (cadr result))
((= (car result) 0) '())
(else
(throw (list "Result violates FFI calling convention: " result))))))
(define (ffi-fail name args message)
(let ((args' (open-output-string)))
(write (cons (string->symbol name) args) args')
(throw (string-append
(get-output-string args') ": " message))))
;; Pseudo-definitions for foreign functions. Evaluates to no code,
;; but serves as documentation.
(macro (ffi-define form))
;; Runtime support.
;; Low-level mechanism to terminate the process.
(ffi-define (_exit status))
+
+;; Get the current time in seconds since the epoch.
+(ffi-define (get-time))
diff --git a/tests/gpgscm/time.scm b/tests/gpgscm/time.scm
new file mode 100644
index 000000000..a9b06d075
--- /dev/null
+++ b/tests/gpgscm/time.scm
@@ -0,0 +1,42 @@
+;; Simple time manipulation library.
+;;
+;; Copyright (C) 2017 g10 Code GmbH
+;;
+;; This file is part of GnuPG.
+;;
+;; GnuPG is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GnuPG 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 General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, see .
+
+;; This library mimics what GnuPG thinks about expiration times.
+;; Granularity is one second. Its focus is not on correctness.
+
+;; Conversion functions.
+(define (minutes->seconds minutes)
+ (* minutes 60))
+(define (hours->seconds hours)
+ (* hours 60 60))
+(define (days->seconds days)
+ (* days 24 60 60))
+(define (weeks->seconds weeks)
+ (days->seconds (* weeks 7)))
+(define (months->seconds months)
+ (days->seconds (* months 30)))
+(define (years->seconds years)
+ (days->seconds (* years 365)))
+
+(define (time-matches? a b slack)
+ (< (abs (- a b)) slack))
+(assert (time-matches? (hours->seconds 1) (hours->seconds 2) (hours->seconds 2)))
+(assert (time-matches? (hours->seconds 2) (hours->seconds 1) (hours->seconds 2)))
+(assert (not (time-matches? (hours->seconds 4) (hours->seconds 1) (hours->seconds 2))))
+(assert (not (time-matches? (hours->seconds 1) (hours->seconds 4) (hours->seconds 2))))
diff --git a/tests/openpgp/quick-key-manipulation.scm b/tests/openpgp/quick-key-manipulation.scm
index d43f7b53a..c0007d414 100755
--- a/tests/openpgp/quick-key-manipulation.scm
+++ b/tests/openpgp/quick-key-manipulation.scm
@@ -1,154 +1,164 @@
#!/usr/bin/env gpgscm
-;; Copyright (C) 2016 g10 Code GmbH
+;; Copyright (C) 2016-2017 g10 Code GmbH
;;
;; This file is part of GnuPG.
;;
;; GnuPG is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.
;;
;; GnuPG 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 General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, see .
(load (with-path "defs.scm"))
+(load (with-path "time.scm"))
(setup-environment)
;; XXX because of --always-trust, the trustdb is not created.
;; Therefore, we redefine GPG without --always-trust.
(define GPG `(,(tool 'gpg) --no-permission-warning))
(define (exact id)
(string-append "=" id))
(define (count-uids-of-secret-key id)
(length (filter (lambda (x) (and (string=? "uid" (car x))
(not (string=? "r" (cadr x)))))
(gpg-with-colons
`(--with-fingerprint
--list-secret-keys ,(exact id))))))
(define alpha "Alpha ")
(define bravo "Bravo ")
(define (key-data key)
(filter (lambda (x) (or (string=? (car x) "pub")
(string=? (car x) "sub")))
(gpg-with-colons `(-k ,key))))
(setenv "PINENTRY_USER_DATA" "test" #t)
(info "Checking quick key generation...")
(call-check `(,@GPG --quick-generate-key ,alpha))
(define keyinfo (gpg-with-colons `(-k ,(exact alpha))))
(define fpr (:fpr (assoc "fpr" keyinfo)))
(assert (= 1 (count-uids-of-secret-key alpha)))
(assert (not (equal? "" (:expire (assoc "pub" keyinfo)))))
(info "Checking that we can add a user ID...")
;; Make sure the key capabilities don't change when we add a user id.
;; (See bug #2697.)
(let ((pre (key-data (exact alpha)))
(result (call-check `(,@GPG --quick-add-uid ,(exact alpha) ,bravo)))
(post (key-data (exact alpha))))
(if (not (equal? pre post))
(begin
(display "Key capabilities changed when adding a user id:")
(newline)
(display " Pre: ")
(display pre)
(newline)
(display " Post: ")
(display post)
(newline)
(exit 1))))
(assert (= 2 (count-uids-of-secret-key alpha)))
(assert (= 2 (count-uids-of-secret-key bravo)))
(info "Checking that we can revoke a user ID...")
(call-check `(,@GPG --quick-revoke-uid ,(exact bravo) ,alpha))
(assert (= 1 (count-uids-of-secret-key bravo)))
(info "Checking that we can change the expiration time.")
(define (expiration-time id)
(:expire (assoc "pub" (gpg-with-colons `(-k ,id)))))
;; Remove the expiration date.
(call-check `(,@gpg --quick-set-expire ,fpr "0"))
(assert (equal? "" (expiration-time fpr)))
;; Make the key expire in one year.
(call-check `(,@gpg --quick-set-expire ,fpr "1y"))
-;; XXX It'd be nice to check that the value is right.
-(assert (not (equal? "" (expiration-time fpr))))
+(assert (time-matches? (+ (get-time) (years->seconds 1))
+ (string->number (expiration-time fpr))
+ (minutes->seconds 5)))
;;
;; Check --quick-addkey
;;
;; Get the subkeys.
(define (get-subkeys)
(filter (lambda (x) (equal? "sub" (car x)))
(gpg-with-colons `(-k ,fpr))))
;; This keeps track of the number of subkeys.
(define count (length (get-subkeys)))
(for-each-p
"Checking that we can add subkeys..."
(lambda (args check)
(set! count (+ 1 count))
(call-check `(,@gpg --quick-add-key ,fpr ,@args))
(let ((subkeys (get-subkeys)))
(assert (= count (length subkeys)))
(if check (check (last subkeys)))))
;; A bunch of arguments...
'(()
(- - -)
(default default never)
(rsa sign "2d")
(rsa1024 sign "2w")
(rsa2048 encr "2m")
(rsa4096 sign,auth "2y")
(future-default))
;; ... with functions to check that the created key matches the
;; expectations (or #f for no tests).
(list
#f
#f
(lambda (subkey)
(assert (equal? "" (:expire subkey))))
(lambda (subkey)
(assert (= 1 (:alg subkey)))
(assert (string-contains? (:cap subkey) "s"))
- (assert (not (equal? "" (:expire subkey)))))
+ (assert (time-matches? (+ (get-time) (days->seconds 2))
+ (string->number (:expire subkey))
+ (minutes->seconds 5))))
(lambda (subkey)
(assert (= 1 (:alg subkey)))
(assert (= 1024 (:length subkey)))
(assert (string-contains? (:cap subkey) "s"))
- (assert (not (equal? "" (:expire subkey)))))
+ (assert (time-matches? (+ (get-time) (weeks->seconds 2))
+ (string->number (:expire subkey))
+ (minutes->seconds 5))))
(lambda (subkey)
(assert (= 1 (:alg subkey)))
(assert (= 2048 (:length subkey)))
(assert (string-contains? (:cap subkey) "e"))
- (assert (not (equal? "" (:expire subkey)))))
+ (assert (time-matches? (+ (get-time) (months->seconds 2))
+ (string->number (:expire subkey))
+ (minutes->seconds 5))))
(lambda (subkey)
(assert (= 1 (:alg subkey)))
(assert (= 4096 (:length subkey)))
(assert (string-contains? (:cap subkey) "s"))
(assert (string-contains? (:cap subkey) "a"))
- (assert (not (equal? "" (:expire subkey)))))
+ (assert (time-matches? (+ (get-time) (years->seconds 2))
+ (string->number (:expire subkey))
+ (minutes->seconds 5))))
#f))
diff --git a/tests/openpgp/tofu.scm b/tests/openpgp/tofu.scm
index ca5786b4e..2e32765e8 100755
--- a/tests/openpgp/tofu.scm
+++ b/tests/openpgp/tofu.scm
@@ -1,450 +1,449 @@
#!/usr/bin/env gpgscm
;; Copyright (C) 2016 g10 Code GmbH
;;
;; This file is part of GnuPG.
;;
;; GnuPG is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.
;;
;; GnuPG 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 General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, see .
(load (with-path "defs.scm"))
+(load (with-path "time.scm"))
(setup-environment)
(define GPGTIME 1480943782)
;; Generate a --faked-system-time parameter for a particular offset.
(define (faketime delta)
(string-append "--faked-system-time=" (number->string (+ GPGTIME delta))))
-;; A convenience function for the above.
-(define (days->seconds days) (* days 24 60 60))
;; Redefine GPG without --always-trust and a fixed time.
(define GPG `(,(tool 'gpg) --no-permission-warning ,(faketime 0)))
(catch (skip "Tofu not supported")
(call-check `(,@GPG --trust-model=tofu --list-config)))
(define KEYS '("1C005AF3" "BE04EB2B" "B662E42F"))
;; Import the test keys.
(for-each (lambda (keyid)
(call-check `(,@GPG --import
,(in-srcdir "tofu/conflicting/"
(string-append keyid ".gpg"))))
(catch (fail "Missing key" keyid)
(call-check `(,@GPG --list-keys ,keyid))))
KEYS)
;; Get tofu policy for KEYID. Any remaining arguments are simply
;; passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (getpolicy keyid . args)
(let ((policy
(list-ref (assoc "tfs" (gpg-with-colons
`(--trust-model=tofu --with-tofu-info
,@args
--list-keys ,keyid))) 5)))
(unless (member policy '("auto" "good" "unknown" "bad" "ask"))
(fail "Bad policy:" policy))
policy))
;; Check that KEYID's tofu policy matches EXPECTED-POLICY. Any
;; remaining arguments are simply passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (checkpolicy keyid expected-policy . args)
(let ((policy (apply getpolicy `(,keyid ,@args))))
(unless (string=? policy expected-policy)
(fail keyid ": Expected policy to be" expected-policy
"but got" policy))))
;; Get the trust level for KEYID. Any remaining arguments are simply
;; passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (gettrust keyid . args)
(let ((trust
(list-ref (assoc "pub" (gpg-with-colons
`(--trust-model=tofu
,@args
--list-keys ,keyid))) 1)))
(unless (and (= 1 (string-length trust))
(member (string-ref trust 0) (string->list "oidreqnmfuws-")))
(fail "Bad trust value:" trust))
trust))
;; Check that KEYID's trust level matches EXPECTED-TRUST. Any
;; remaining arguments are simply passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (checktrust keyid expected-trust . args)
(let ((trust (apply gettrust `(,keyid ,@args))))
(unless (string=? trust expected-trust)
(fail keyid ": Expected trust to be" expected-trust
"but got" trust))))
;; Set key KEYID's policy to POLICY. Any remaining arguments are
;; passed as options to gpg.
(define (setpolicy keyid policy . args)
(call-check `(,@GPG --trust-model=tofu ,@args
--tofu-policy ,policy ,keyid)))
(info "Checking tofu policies and trust...")
;; Carefully remove the TOFU db.
(catch '() (unlink (path-join GNUPGHOME "tofu.db")))
;; Verify a message. There should be no conflict and the trust
;; policy should be set to auto.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
(checkpolicy "1C005AF3" "auto")
;; Check default trust.
(checktrust "1C005AF3" "m")
;; Trust should be derived lazily. Thus, if the policy is set to
;; auto and we change --tofu-default-policy, then the trust should
;; change as well. Try it.
(checktrust "1C005AF3" "f" '--tofu-default-policy=good)
(checktrust "1C005AF3" "-" '--tofu-default-policy=unknown)
(checktrust "1C005AF3" "n" '--tofu-default-policy=bad)
;; Change the policy to something other than auto and make sure the
;; policy and the trust are correct.
(for-each-p
"Setting a fixed policy..."
(lambda (policy)
(let ((expected-trust
(cond
((string=? "good" policy) "f")
((string=? "unknown" policy) "-")
(else "n"))))
(setpolicy "1C005AF3" policy)
;; Since we have a fixed policy, the trust level shouldn't
;; change if we change the default policy.
(for-each-p
""
(lambda (default-policy)
(checkpolicy "1C005AF3" policy
'--tofu-default-policy default-policy)
(checktrust "1C005AF3" expected-trust
'--tofu-default-policy default-policy))
'("auto" "good" "unknown" "bad" "ask"))))
'("good" "unknown" "bad"))
;; At the end, 1C005AF3's policy should be bad.
(checkpolicy "1C005AF3" "bad")
;; 1C005AF3 and BE04EB2B conflict. A policy setting of "auto"
;; (BE04EB2B's state) will result in an effective policy of ask. But,
;; a policy setting of "bad" will result in an effective policy of
;; bad.
(setpolicy "BE04EB2B" "auto")
(checkpolicy "BE04EB2B" "ask")
(checkpolicy "1C005AF3" "bad")
;; 1C005AF3, B662E42F, and BE04EB2B conflict. We change BE04EB2B's
;; policy to auto and leave 1C005AF3's policy at bad. This conflict
;; should cause BE04EB2B's effective policy to be ask (since it is
;; auto), but not affect 1C005AF3's policy.
(setpolicy "BE04EB2B" "auto")
(checkpolicy "BE04EB2B" "ask")
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/B662E42F-1.txt")))
(checkpolicy "BE04EB2B" "ask")
(checkpolicy "1C005AF3" "bad")
(checkpolicy "B662E42F" "ask")
;; Check that the stats are emitted correctly.
(display "Checking TOFU stats...\n")
(define (check-counts keyid expected-sigs expected-sig-days
expected-encs expected-enc-days . args)
(let*
((tfs (assoc "tfs"
(gpg-with-colons
`(--trust-model=tofu --with-tofu-info
,@args --list-keys ,keyid))))
(sigs (string->number (list-ref tfs 3)))
(sig-days (string->number (list-ref tfs 11)))
(encs (string->number (list-ref tfs 4)))
(enc-days (string->number (list-ref tfs 12)))
)
; (display keyid) (display ": ") (display tfs) (display "\n")
(unless (= sigs expected-sigs)
(fail keyid ": # signatures (" sigs ") does not match expected"
"# signatures (" expected-sigs ").\n"))
(unless (= sig-days expected-sig-days)
(fail keyid ": # signature days (" sig-days ")"
"does not match expected"
"# signature days (" expected-sig-days ").\n"))
(unless (= encs expected-encs)
(fail keyid ": # encryptions (" encs ") does not match expected"
"# encryptions (" expected-encs ").\n"))
(unless (= enc-days expected-enc-days)
(fail keyid ": # encryption days (" encs ")"
"does not match expected"
"# encryption days (" expected-enc-days ").\n"))
))
;; Carefully remove the TOFU db.
(catch '() (unlink (path-join GNUPGHOME "tofu.db")))
(check-counts "1C005AF3" 0 0 0 0)
(check-counts "BE04EB2B" 0 0 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; Verify a message. The signature count should increase by 1.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
(check-counts "1C005AF3" 1 1 0 0)
;; Verify the same message. The signature count should remain the
;; same.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
(check-counts "1C005AF3" 1 1 0 0)
;; Verify another message.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-2.txt")))
(check-counts "1C005AF3" 2 1 0 0)
;; Verify another message.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-3.txt")))
(check-counts "1C005AF3" 3 1 0 0)
;; Verify a message from a different sender. The signature count
;; should increase by 1 for that key.
(call-check `(,@GPG --trust-model=tofu
--verify ,(in-srcdir "tofu/conflicting/BE04EB2B-1.txt")))
(check-counts "1C005AF3" 3 1 0 0)
(check-counts "BE04EB2B" 1 1 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; Verify another message on a new day. (Recall: we are interested in
;; when the message was first verified, not when the signer claimed
;; that it was signed.)
(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-4.txt")))
(check-counts "1C005AF3" 4 2 0 0)
(check-counts "BE04EB2B" 1 1 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; And another.
(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
--verify ,(in-srcdir "tofu/conflicting/1C005AF3-5.txt")))
(check-counts "1C005AF3" 5 2 0 0)
(check-counts "BE04EB2B" 1 1 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; Another, but for a different key.
(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
--verify ,(in-srcdir "tofu/conflicting/BE04EB2B-2.txt")))
(check-counts "1C005AF3" 5 2 0 0)
(check-counts "BE04EB2B" 2 2 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; And add a third day.
(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 4))
--verify ,(in-srcdir "tofu/conflicting/BE04EB2B-3.txt")))
(check-counts "1C005AF3" 5 2 0 0)
(check-counts "BE04EB2B" 3 3 0 0)
(check-counts "B662E42F" 0 0 0 0)
(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 4))
--verify ,(in-srcdir "tofu/conflicting/BE04EB2B-4.txt")))
(check-counts "1C005AF3" 5 2 0 0)
(check-counts "BE04EB2B" 4 3 0 0)
(check-counts "B662E42F" 0 0 0 0)
;; Check that we detect the following attack:
;;
;; Alice and Bob each have a key and cross sign them. Bob then adds a
;; new user id, "Alice". TOFU should now detect a conflict, because
;; Alice only signed Bob's "Bob" user id.
(display "Checking cross sigs...\n")
(define GPG `(,(tool 'gpg) --no-permission-warning
--faked-system-time=1476304861))
;; Carefully remove the TOFU db.
(catch '() (unlink (path-join GNUPGHOME "tofu.db")))
(define DIR "tofu/cross-sigs")
;; The test keys.
(define KEYA "1938C3A0E4674B6C217AC0B987DB2814EC38277E")
(define KEYB "DC463A16E42F03240D76E8BA8B48C6BD871C2247")
(define KEYIDA (substring KEYA (- (string-length KEYA) 8)))
(define KEYIDB (substring KEYB (- (string-length KEYB) 8)))
(define (verify-messages)
(for-each
(lambda (key)
(for-each
(lambda (i)
(let ((fn (in-srcdir DIR (string-append key "-" i ".txt"))))
(call-check `(,@GPG --trust-model=tofu --verify ,fn))))
(list "1" "2")))
(list KEYIDA KEYIDB)))
;; Import the public keys.
(display " > Two keys. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-1.gpg"))))
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-1.gpg"))))
;; Make sure the tofu engine registers the keys.
(verify-messages)
(display "<\n")
;; Since there is no conflict, the policy should be auto.
(checkpolicy KEYA "auto")
(checkpolicy KEYB "auto")
;; Import the cross sigs.
(display " > Adding cross signatures. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-2.gpg"))))
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-2.gpg"))))
(verify-messages)
(display "<\n")
;; There is still no conflict, so the policy shouldn't have changed.
(checkpolicy KEYA "auto")
(checkpolicy KEYB "auto")
;; Import the conflicting user id.
(display " > Adding conflicting user id. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-3.gpg"))))
(verify-messages)
(display "<\n")
(checkpolicy KEYA "ask")
(checkpolicy KEYB "ask")
;; Import Alice's signature on the conflicting user id. Since there
;; is now a cross signature, we should revert to the default policy.
(display " > Adding cross signature on user id. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-4.gpg"))))
(verify-messages)
(display "<\n")
(checkpolicy KEYA "auto")
(checkpolicy KEYB "auto")
;; Remove the keys.
(call-check `(,@GPG --delete-key ,KEYA))
(call-check `(,@GPG --delete-key ,KEYB))
;; Check that we detect the following attack:
;;
;; Alice has an ultimately trusted key and she signs Bob's key. Then
;; Bob adds a new user id, "Alice". TOFU should now detect a
;; conflict, because Alice only signed Bob's "Bob" user id.
;;
;;
;; The Alice key:
;; pub rsa2048 2016-10-11 [SC]
;; 1938C3A0E4674B6C217AC0B987DB2814EC38277E
;; uid [ultimate] Spy Cow
;; sub rsa2048 2016-10-11 [E]
;;
;; The Bob key:
;;
;; pub rsa2048 2016-10-11 [SC]
;; DC463A16E42F03240D76E8BA8B48C6BD871C2247
;; uid [ full ] Spy R. Cow
;; uid [ full ] Spy R. Cow
;; sub rsa2048 2016-10-11 [E]
(display "Checking UTK sigs...\n")
(define GPG `(,(tool 'gpg) --no-permission-warning
--faked-system-time=1476304861))
;; Carefully remove the TOFU db.
(catch '() (unlink (path-join GNUPGHOME "tofu.db")))
(define DIR "tofu/cross-sigs")
;; The test keys.
(define KEYA "1938C3A0E4674B6C217AC0B987DB2814EC38277E")
(define KEYB "DC463A16E42F03240D76E8BA8B48C6BD871C2247")
(define KEYIDA (substring KEYA (- (string-length KEYA) 8)))
(define KEYIDB (substring KEYB (- (string-length KEYB) 8)))
(define (verify-messages)
(for-each
(lambda (key)
(for-each
(lambda (i)
(let ((fn (in-srcdir DIR (string-append key "-" i ".txt"))))
(call-check `(,@GPG --trust-model=tofu --verify ,fn))))
(list "1" "2")))
(list KEYIDA KEYIDB)))
;; Import the public keys.
(display " > Two keys. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-1.gpg"))))
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-1.gpg"))))
(display "<\n")
(checkpolicy KEYA "auto")
(checkpolicy KEYB "auto")
;; Import the cross sigs.
(display " > Adding cross signatures. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-2.gpg"))))
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-2.gpg"))))
(display "<\n")
(checkpolicy KEYA "auto")
(checkpolicy KEYB "auto")
;; Make KEYA ultimately trusted.
(display (string-append " > Marking " KEYA " as ultimately trusted. "))
(pipe:do
(pipe:echo (string-append KEYA ":6:\n"))
(pipe:gpg `(--import-ownertrust)))
(display "<\n")
;; An ultimately trusted key's policy is good.
(checkpolicy KEYA "good")
;; A key signed by a UTK for which there is no policy gets the default
;; policy of good.
(checkpolicy KEYB "good")
;; Import the conflicting user id.
(display " > Adding conflicting user id. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-3.gpg"))))
(verify-messages)
(display "<\n")
(checkpolicy KEYA "good")
(checkpolicy KEYB "ask")
;; Import Alice's signature on the conflicting user id.
(display " > Adding cross signature on user id. ")
(call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-4.gpg"))))
(verify-messages)
(display "<\n")
(checkpolicy KEYA "good")
(checkpolicy KEYB "good")
;; Remove the keys.
(call-check `(,@GPG --delete-key ,KEYA))
(call-check `(,@GPG --delete-key ,KEYB))