Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am
index 93880f8ce..34f2c5d3d 100644
--- a/dirmngr/Makefile.am
+++ b/dirmngr/Makefile.am
@@ -1,163 +1,163 @@
# Makefile.am - dirmngr
# Copyright (C) 2002 Klarälvdalens Datakonsult AB
# Copyright (C) 2004, 2007, 2010 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = OAUTHORS ONEWS ChangeLog-2011 tls-ca.pem
dist_pkgdata_DATA = sks-keyservers.netCA.pem
bin_PROGRAMS = dirmngr dirmngr-client
if USE_LDAPWRAPPER
libexec_PROGRAMS = dirmngr_ldap
endif
noinst_PROGRAMS = $(module_tests) $(module_net_tests) $(module_maint_tests)
TESTS = $(module_tests) $(module_net_tests)
-AM_CPPFLAGS = -I$(top_srcdir)/common
+AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(USE_C99_CFLAGS) \
$(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) \
$(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(NTBTLS_CFLAGS) \
$(LIBGNUTLS_CFLAGS)
if HAVE_W32_SYSTEM
ldap_url = ldap-url.h ldap-url.c
else
ldap_url =
endif
if USE_LDAPWRAPPER
extraldap_src = ldap-wrapper.c
else
extraldap_src = ldap-wrapper-ce.c dirmngr_ldap.c
endif
noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h
dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \
certcache.c certcache.h \
loadswdb.c \
cdb.h cdblib.c misc.c dirmngr-err.h \
ocsp.c ocsp.h validate.c validate.h \
dns-stuff.c dns-stuff.h \
http.c http.h http-common.c http-common.h http-ntbtls.c \
ks-action.c ks-action.h ks-engine.h \
ks-engine-hkp.c ks-engine-http.c ks-engine-finger.c ks-engine-kdns.c
if USE_LIBDNS
dirmngr_SOURCES += dns.c dns.h
endif
if USE_LDAP
dirmngr_SOURCES += ldapserver.h ldapserver.c ldap.c w32-ldap-help.h \
ldap-wrapper.h ldap-parse-uri.c ldap-parse-uri.h \
ks-engine-ldap.c $(ldap_url) $(extraldap_src)
ldaplibs = $(LDAPLIBS)
else
ldaplibs =
endif
dirmngr_LDADD = $(libcommonpth) \
$(DNSLIBS) $(LIBASSUAN_LIBS) \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(NPTH_LIBS) \
$(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) $(LIBINTL) $(LIBICONV)
if USE_LDAP
dirmngr_LDADD += $(ldaplibs)
endif
if !USE_LDAPWRAPPER
dirmngr_LDADD += $(ldaplibs)
endif
dirmngr_LDFLAGS = $(extra_bin_ldflags)
if USE_LDAPWRAPPER
dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url)
dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) $(LIBGCRYPT_CFLAGS)
dirmngr_ldap_LDFLAGS =
dirmngr_ldap_LDADD = $(libcommon) \
$(GPG_ERROR_LIBS) $(LIBGCRYPT_LIBS) $(LDAPLIBS) \
$(LBER_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS)
endif
dirmngr_client_SOURCES = dirmngr-client.c
dirmngr_client_LDADD = $(libcommon) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBGCRYPT_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV)
dirmngr_client_LDFLAGS = $(extra_bin_ldflags)
t_common_src = t-support.h
if USE_LIBDNS
t_common_src += dns.c dns.h
endif
t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
$(GPG_ERROR_LIBS) $(NETLIBS) \
$(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \
$(DNSLIBS) $(LIBINTL) $(LIBICONV)
module_tests =
if USE_LDAP
module_tests += t-ldap-parse-uri
endif
# Test which need a network connections are only used in maintainer mode.
if MAINTAINER_MODE
module_net_tests = t-dns-stuff
else
module_net_tests =
endif
# Tests which are only for manually testing are only build in maintainer-mode.
if MAINTAINER_MODE
module_maint_tests = t-http
else
module_maint_tests =
endif
# http tests
# We need to add the KSBA flags in case we are building against GNUTLS.
# In that case NTBTLS flags are empty, but we need ksba anyway.
t_http_SOURCES = $(t_common_src) t-http.c http.c dns-stuff.c http-common.c
t_http_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \
$(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \
$(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS)
t_http_LDADD = $(t_common_ldadd) \
$(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS)
t_ldap_parse_uri_SOURCES = \
t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \
http.c http-common.c dns-stuff.c \
$(ldap_url) $(t_common_src)
t_ldap_parse_uri_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \
$(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
t_ldap_parse_uri_LDADD = $(ldaplibs) $(t_common_ldadd) $(DNSLIBS)
t_dns_stuff_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \
$(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
t_dns_stuff_SOURCES = $(t_common_src) t-dns-stuff.c dns-stuff.c
t_dns_stuff_LDADD = $(t_common_ldadd) $(DNSLIBS)
$(PROGRAMS) : $(libcommon) $(libcommonpth)
diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c
index 01cface5f..c1ff635de 100644
--- a/dirmngr/dirmngr-client.c
+++ b/dirmngr/dirmngr-client.c
@@ -1,934 +1,934 @@
/* dirmngr-client.c - A client for the dirmngr daemon
* Copyright (C) 2004, 2007 g10 Code GmbH
* Copyright (C) 2002, 2003 Free Software Foundation, Inc.
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <gpg-error.h>
#include <assuan.h>
#include "../common/logging.h"
#include "../common/argparse.h"
#include "../common/stringhelp.h"
#include "../common/mischelp.h"
#include "../common/strlist.h"
#include "../common/asshelp.h"
-#include "i18n.h"
-#include "util.h"
-#include "init.h"
+#include "../common/i18n.h"
+#include "../common/util.h"
+#include "../common/init.h"
/* Constants for the options. */
enum
{
oQuiet = 'q',
oVerbose = 'v',
oLocal = 'l',
oUrl = 'u',
oOCSP = 500,
oPing,
oCacheCert,
oValidate,
oLookup,
oLoadCRL,
oSquidMode,
oPEM,
oEscapedPEM,
oForceDefaultResponder
};
/* The list of options as used by the argparse.c code. */
static ARGPARSE_OPTS opts[] = {
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("be somewhat more quiet") },
{ oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") },
{ oPing, "ping", 0, N_("check whether a dirmngr is running")},
{ oCacheCert,"cache-cert",0, N_("add a certificate to the cache")},
{ oValidate, "validate", 0, N_("validate a certificate")},
{ oLookup, "lookup", 0, N_("lookup a certificate")},
{ oLocal, "local", 0, N_("lookup only locally stored certificates")},
{ oUrl, "url", 0, N_("expect an URL for --lookup")},
{ oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")},
{ oSquidMode,"squid-mode",0, N_("special mode for use by Squid")},
{ oPEM, "pem", 0, N_("expect certificates in PEM format")},
{ oForceDefaultResponder, "force-default-responder", 0,
N_("force the use of the default OCSP responder")},
{ 0, NULL, 0, NULL }
};
/* The usual structure for the program flags. */
static struct
{
int quiet;
int verbose;
const char *dirmngr_program;
int force_default_responder;
int pem;
int escaped_pem; /* PEM is additional percent encoded. */
int url; /* Expect an URL. */
int local; /* Lookup up only local certificates. */
int use_ocsp;
} opt;
/* Communication structure for the certificate inquire callback. */
struct inq_cert_parm_s
{
assuan_context_t ctx;
const unsigned char *cert;
size_t certlen;
};
/* Base64 conversion tables. */
static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static unsigned char asctobin[256]; /* runtime initialized */
/* Build the helptable for radix64 to bin conversion. */
static void
init_asctobin (void)
{
static int initialized;
int i;
unsigned char *s;
if (initialized)
return;
initialized = 1;
for (i=0; i < 256; i++ )
asctobin[i] = 255; /* Used to detect invalid characters. */
for (s=bintoasc, i=0; *s; s++, i++)
asctobin[*s] = i;
}
/* Prototypes. */
static gpg_error_t read_certificate (const char *fname,
unsigned char **rbuf, size_t *rbuflen);
static gpg_error_t do_check (assuan_context_t ctx,
const unsigned char *cert, size_t certlen);
static gpg_error_t do_cache (assuan_context_t ctx,
const unsigned char *cert, size_t certlen);
static gpg_error_t do_validate (assuan_context_t ctx,
const unsigned char *cert, size_t certlen);
static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename);
static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern);
static gpg_error_t squid_loop_body (assuan_context_t ctx);
/* Function called by argparse.c to display information. */
static const char *
my_strusage (int level)
{
const char *p;
switch(level)
{
case 11: p = "dirmngr-client (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p =
_("Usage: dirmngr-client [options] "
"[certfile|pattern] (-h for help)\n");
break;
case 41: p =
_("Syntax: dirmngr-client [options] [certfile|pattern]\n"
"Test an X.509 certificate against a CRL or do an OCSP check\n"
"The process returns 0 if the certificate is valid, 1 if it is\n"
"not valid and other error codes for general failures\n");
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
assuan_context_t ctx;
gpg_error_t err;
unsigned char *certbuf;
size_t certbuflen = 0;
int cmd_ping = 0;
int cmd_cache_cert = 0;
int cmd_validate = 0;
int cmd_lookup = 0;
int cmd_loadcrl = 0;
int cmd_squid_mode = 0;
early_system_init ();
set_strusage (my_strusage);
log_set_prefix ("dirmngr-client",
GPGRT_LOG_WITH_PREFIX);
/* For W32 we need to initialize the socket subsystem. Because we
don't use Pth we need to do this explicit. */
#ifdef HAVE_W32_SYSTEM
{
WSADATA wsadat;
WSAStartup (0x202, &wsadat);
}
#endif /*HAVE_W32_SYSTEM*/
/* Init Assuan. */
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
/* Setup I18N. */
i18n_init();
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* Do not remove the args. */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oQuiet: opt.quiet++; break;
case oOCSP: opt.use_ocsp++; break;
case oPing: cmd_ping = 1; break;
case oCacheCert: cmd_cache_cert = 1; break;
case oValidate: cmd_validate = 1; break;
case oLookup: cmd_lookup = 1; break;
case oUrl: opt.url = 1; break;
case oLocal: opt.local = 1; break;
case oLoadCRL: cmd_loadcrl = 1; break;
case oPEM: opt.pem = 1; break;
case oSquidMode:
opt.pem = 1;
opt.escaped_pem = 1;
cmd_squid_mode = 1;
break;
case oForceDefaultResponder: opt.force_default_responder = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
exit (2);
if (cmd_ping)
err = 0;
else if (cmd_lookup || cmd_loadcrl)
{
if (!argc)
usage (1);
err = 0;
}
else if (cmd_squid_mode)
{
err = 0;
if (argc)
usage (1);
}
else if (!argc)
{
err = read_certificate (NULL, &certbuf, &certbuflen);
if (err)
log_error (_("error reading certificate from stdin: %s\n"),
gpg_strerror (err));
}
else if (argc == 1)
{
err = read_certificate (*argv, &certbuf, &certbuflen);
if (err)
log_error (_("error reading certificate from '%s': %s\n"),
*argv, gpg_strerror (err));
}
else
{
err = 0;
usage (1);
}
if (log_get_errorcount (0))
exit (2);
if (certbuflen > 20000)
{
log_error (_("certificate too large to make any sense\n"));
exit (2);
}
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program
? opt.dirmngr_program
: gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR),
! cmd_ping,
opt.verbose,
0,
NULL, NULL);
if (err)
{
log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (err));
exit (2);
}
if (cmd_ping)
;
else if (cmd_squid_mode)
{
while (!(err = squid_loop_body (ctx)))
;
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
}
else if (cmd_lookup)
{
int last_err = 0;
for (; argc; argc--, argv++)
{
err = do_lookup (ctx, *argv);
if (err)
{
log_error (_("lookup failed: %s\n"), gpg_strerror (err));
last_err = err;
}
}
err = last_err;
}
else if (cmd_loadcrl)
{
int last_err = 0;
for (; argc; argc--, argv++)
{
err = do_loadcrl (ctx, *argv);
if (err)
{
log_error (_("loading CRL '%s' failed: %s\n"),
*argv, gpg_strerror (err));
last_err = err;
}
}
err = last_err;
}
else if (cmd_cache_cert)
{
err = do_cache (ctx, certbuf, certbuflen);
xfree (certbuf);
}
else if (cmd_validate)
{
err = do_validate (ctx, certbuf, certbuflen);
xfree (certbuf);
}
else
{
err = do_check (ctx, certbuf, certbuflen);
xfree (certbuf);
}
assuan_release (ctx);
if (cmd_ping)
{
if (!opt.quiet)
log_info (_("a dirmngr daemon is up and running\n"));
return 0;
}
else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode)
return err? 1:0;
else if (cmd_cache_cert)
{
if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE )
{
if (!opt.quiet)
log_info (_("certificate already cached\n"));
}
else if (err)
{
log_error (_("error caching certificate: %s\n"),
gpg_strerror (err));
return 1;
}
return 0;
}
else if (cmd_validate && err)
{
log_error (_("validation of certificate failed: %s\n"),
gpg_strerror (err));
return 1;
}
else if (!err)
{
if (!opt.quiet)
log_info (_("certificate is valid\n"));
return 0;
}
else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED )
{
if (!opt.quiet)
log_info (_("certificate has been revoked\n"));
return 1;
}
else
{
log_error (_("certificate check failed: %s\n"), gpg_strerror (err));
return 2;
}
}
/* Print status line from the assuan protocol. */
static gpg_error_t
status_cb (void *opaque, const char *line)
{
(void)opaque;
if (opt.verbose > 2)
log_info (_("got status: '%s'\n"), line);
return 0;
}
/* Print data as retrieved by the lookup function. */
static gpg_error_t
data_cb (void *opaque, const void *buffer, size_t length)
{
gpg_error_t err;
struct b64state *state = opaque;
if (buffer)
{
err = b64enc_write (state, buffer, length);
if (err)
log_error (_("error writing base64 encoding: %s\n"),
gpg_strerror (err));
}
return 0;
}
/* Read the first PEM certificate from the file FNAME. If fname is
NULL the next certificate is read from stdin. The certificate is
returned in an alloced buffer whose address will be returned in
RBUF and its length in RBUFLEN. */
static gpg_error_t
read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen)
{
FILE *fp;
int c;
int pos;
int value;
unsigned char *buf;
size_t bufsize, buflen;
enum {
s_init, s_idle, s_lfseen, s_begin,
s_b64_0, s_b64_1, s_b64_2, s_b64_3,
s_waitend
} state = s_init;
init_asctobin ();
fp = fname? fopen (fname, "r") : stdin;
if (!fp)
return gpg_error_from_errno (errno);
pos = 0;
value = 0;
bufsize = 8192;
buf = xmalloc (bufsize);
buflen = 0;
while ((c=getc (fp)) != EOF)
{
int escaped_c = 0;
if (opt.escaped_pem)
{
if (c == '%')
{
char tmp[2];
if ((c = getc(fp)) == EOF)
break;
tmp[0] = c;
if ((c = getc(fp)) == EOF)
break;
tmp[1] = c;
if (!hexdigitp (tmp) || !hexdigitp (tmp+1))
{
log_error ("invalid percent escape sequence\n");
state = s_idle; /* Force an error. */
/* Skip to end of line. */
while ( (c=getc (fp)) != EOF && c != '\n')
;
goto ready;
}
c = xtoi_2 (tmp);
escaped_c = 1;
}
else if (c == '\n')
goto ready; /* Ready. */
}
switch (state)
{
case s_idle:
if (c == '\n')
{
state = s_lfseen;
pos = 0;
}
break;
case s_init:
state = s_lfseen;
case s_lfseen:
if (c != "-----BEGIN "[pos])
state = s_idle;
else if (pos == 10)
state = s_begin;
else
pos++;
break;
case s_begin:
if (c == '\n')
state = s_b64_0;
break;
case s_b64_0:
case s_b64_1:
case s_b64_2:
case s_b64_3:
{
if (buflen >= bufsize)
{
bufsize += 8192;
buf = xrealloc (buf, bufsize);
}
if (c == '-')
state = s_waitend;
else if ((c = asctobin[c & 0xff]) == 255 )
; /* Just skip invalid base64 characters. */
else if (state == s_b64_0)
{
value = c << 2;
state = s_b64_1;
}
else if (state == s_b64_1)
{
value |= (c>>4)&3;
buf[buflen++] = value;
value = (c<<4)&0xf0;
state = s_b64_2;
}
else if (state == s_b64_2)
{
value |= (c>>2)&15;
buf[buflen++] = value;
value = (c<<6)&0xc0;
state = s_b64_3;
}
else
{
value |= c&0x3f;
buf[buflen++] = value;
state = s_b64_0;
}
}
break;
case s_waitend:
/* Note that we do not check that the base64 decoder has
been left in the expected state. We assume that the PEM
header is just fine. However we need to wait for the
real LF and not a trailing percent escaped one. */
if (c== '\n' && !escaped_c)
goto ready;
break;
default:
BUG();
}
}
ready:
if (fname)
fclose (fp);
if (state == s_init && c == EOF)
{
xfree (buf);
return gpg_error (GPG_ERR_EOF);
}
else if (state != s_waitend)
{
log_error ("no certificate or invalid encoded\n");
xfree (buf);
return gpg_error (GPG_ERR_INV_ARMOR);
}
*rbuf = buf;
*rbuflen = buflen;
return 0;
}
/* Read a binary certificate from the file FNAME. If fname is NULL the
file is read from stdin. The certificate is returned in an alloced
buffer whose address will be returned in RBUF and its length in
RBUFLEN. */
static gpg_error_t
read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen)
{
gpg_error_t err;
FILE *fp;
unsigned char *buf;
size_t nread, bufsize, buflen;
if (opt.pem)
return read_pem_certificate (fname, rbuf, rbuflen);
else if (fname)
{
/* A filename has been given. Let's just assume it is in PEM
format and decode it, and fall back to interpreting it as
binary certificate if that fails. */
err = read_pem_certificate (fname, rbuf, rbuflen);
if (! err)
return 0;
}
fp = fname? fopen (fname, "rb") : stdin;
if (!fp)
return gpg_error_from_errno (errno);
buf = NULL;
bufsize = buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize);
else
buf = xrealloc (buf, bufsize);
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
err = gpg_error_from_errno (errno);
xfree (buf);
if (fname)
fclose (fp);
return err;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
if (fname)
fclose (fp);
*rbuf = buf;
*rbuflen = buflen;
return 0;
}
/* Callback for the inquire fiunction to send back the certificate. */
static gpg_error_t
inq_cert (void *opaque, const char *line)
{
struct inq_cert_parm_s *parm = opaque;
gpg_error_t err;
if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10]))
{
err = assuan_send_data (parm->ctx, parm->cert, parm->certlen);
}
else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))
{
/* We don't support this but dirmngr might ask for it. So
simply ignore it by sending back and empty value. */
err = assuan_send_data (parm->ctx, NULL, 0);
}
else if (!strncmp (line, "SENDCERT_SKI", 12)
&& (line[12]==' ' || !line[12]))
{
/* We don't support this but dirmngr might ask for it. So
simply ignore it by sending back an empty value. */
err = assuan_send_data (parm->ctx, NULL, 0);
}
else if (!strncmp (line, "SENDISSUERCERT", 14)
&& (line[14] == ' ' || !line[14]))
{
/* We don't support this but dirmngr might ask for it. So
simply ignore it by sending back an empty value. */
err = assuan_send_data (parm->ctx, NULL, 0);
}
else
{
log_info (_("unsupported inquiry '%s'\n"), line);
err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
/* Note that this error will let assuan_transact terminate
immediately instead of return the error to the caller. It is
not clear whether this is the desired behaviour - it may
change in future. */
}
return err;
}
/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP.
Return a proper error code. */
static gpg_error_t
do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
struct inq_cert_parm_s parm;
memset (&parm, 0, sizeof parm);
parm.ctx = ctx;
parm.cert = cert;
parm.certlen = certlen;
err = assuan_transact (ctx,
(opt.use_ocsp && opt.force_default_responder
? "CHECKOCSP --force-default-responder"
: opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"),
NULL, NULL, inq_cert, &parm, status_cb, NULL);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
return err;
}
/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP.
Return a proper error code. */
static gpg_error_t
do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
struct inq_cert_parm_s parm;
memset (&parm, 0, sizeof parm);
parm.ctx = ctx;
parm.cert = cert;
parm.certlen = certlen;
err = assuan_transact (ctx, "CACHECERT", NULL, NULL,
inq_cert, &parm,
status_cb, NULL);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
return err;
}
/* Check the certificate CERT,CERTLEN for validity using dirmngrs
internal validate feature. Return a proper error code. */
static gpg_error_t
do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
struct inq_cert_parm_s parm;
memset (&parm, 0, sizeof parm);
parm.ctx = ctx;
parm.cert = cert;
parm.certlen = certlen;
err = assuan_transact (ctx, "VALIDATE", NULL, NULL,
inq_cert, &parm,
status_cb, NULL);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
return err;
}
/* Load a CRL into the dirmngr. */
static gpg_error_t
do_loadcrl (assuan_context_t ctx, const char *filename)
{
gpg_error_t err;
const char *s;
char *fname, *line, *p;
if (opt.url)
fname = xstrdup (filename);
else
{
#ifdef HAVE_CANONICALIZE_FILE_NAME
fname = canonicalize_file_name (filename);
if (!fname)
{
log_error ("error canonicalizing '%s': %s\n",
filename, strerror (errno));
return gpg_error (GPG_ERR_GENERAL);
}
#else
fname = xstrdup (filename);
#endif
if (*fname != '/')
{
log_error (_("absolute file name expected\n"));
return gpg_error (GPG_ERR_GENERAL);
}
}
line = xmalloc (8 + 6 + strlen (fname) * 3 + 1);
p = stpcpy (line, "LOADCRL ");
if (opt.url)
p = stpcpy (p, "--url ");
for (s = fname; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
err = assuan_transact (ctx, line, NULL, NULL,
NULL, NULL,
status_cb, NULL);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
xfree (line);
xfree (fname);
return err;
}
/* Do a LDAP lookup using PATTERN and print the result in a base-64
encoded format. */
static gpg_error_t
do_lookup (assuan_context_t ctx, const char *pattern)
{
gpg_error_t err;
const unsigned char *s;
char *line, *p;
struct b64state state;
if (opt.verbose)
log_info (_("looking up '%s'\n"), pattern);
err = b64enc_start (&state, stdout, NULL);
if (err)
return err;
line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1);
p = stpcpy (line, "LOOKUP ");
if (opt.url)
p = stpcpy (p, "--url ");
if (opt.local)
p = stpcpy (p, "--cache-only ");
for (s=pattern; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
err = assuan_transact (ctx, line,
data_cb, &state,
NULL, NULL,
status_cb, NULL);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
err = b64enc_finish (&state);
xfree (line);
return err;
}
/* The body of an endless loop: Read a line from stdin, retrieve the
certificate from it, validate it and print "ERR" or "OK" to stdout.
Continue. */
static gpg_error_t
squid_loop_body (assuan_context_t ctx)
{
gpg_error_t err;
unsigned char *certbuf;
size_t certbuflen = 0;
err = read_pem_certificate (NULL, &certbuf, &certbuflen);
if (gpg_err_code (err) == GPG_ERR_EOF)
return err;
if (err)
{
log_error (_("error reading certificate from stdin: %s\n"),
gpg_strerror (err));
puts ("ERROR");
return 0;
}
err = do_check (ctx, certbuf, certbuflen);
xfree (certbuf);
if (!err)
{
if (opt.verbose)
log_info (_("certificate is valid\n"));
puts ("OK");
}
else
{
if (!opt.quiet)
{
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED )
log_info (_("certificate has been revoked\n"));
else
log_error (_("certificate check failed: %s\n"),
gpg_strerror (err));
}
puts ("ERROR");
}
fflush (stdout);
return 0;
}
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index f05bdd127..e3e02fe6b 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1,2239 +1,2239 @@
/* dirmngr.c - Keyserver and X.509 LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef HAVE_INOTIFY_INIT
# include <sys/inotify.h>
#endif /*HAVE_INOTIFY_INIT*/
#include <npth.h>
#include "dirmngr-err.h"
#if HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
#endif /*HTTP_USE_GNUTLS*/
#define GNUPG_COMMON_NEED_AFLOCAL
#include "dirmngr.h"
#include <assuan.h>
#include "certcache.h"
#include "crlcache.h"
#include "crlfetch.h"
#include "misc.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
-#include "asshelp.h"
+#include "../common/asshelp.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "../common/init.h"
-#include "gc-opt-flags.h"
+#include "../common/gc-opt-flags.h"
#include "dns-stuff.h"
#include "http-common.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values {
aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aServer,
aDaemon,
aSupervised,
aListCRLs,
aLoadCRL,
aFetchCRL,
aShutdown,
aFlush,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oDebugLevel,
oGnutlsDebug,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oBatch,
oDisableHTTP,
oDisableLDAP,
oDisableIPv4,
oIgnoreLDAPDP,
oIgnoreHTTPDP,
oIgnoreOCSPSvcUrl,
oHonorHTTPProxy,
oHTTPProxy,
oLDAPProxy,
oOnlyLDAPProxy,
oLDAPFile,
oLDAPTimeout,
oLDAPAddServers,
oOCSPResponder,
oOCSPSigner,
oOCSPMaxClockSkew,
oOCSPMaxPeriod,
oOCSPCurrentPeriod,
oMaxReplies,
oHkpCaCert,
oFakedSystemTime,
oForce,
oAllowOCSP,
oAllowVersionCheck,
oSocketName,
oLDAPWrapperProgram,
oHTTPWrapperProgram,
oIgnoreCertExtension,
oUseTor,
oNoUseTor,
oKeyServer,
oNameServer,
oDisableCheckOwnSocket,
oStandardResolver,
oRecursiveResolver,
oResolverTimeout,
aTest
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_c (aSupervised, "supervised", N_("run in supervised mode")),
#endif
ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")),
ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check",
N_("allow online software version check")),
ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
N_("ignore HTTP CRL distribution points")),
ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
N_("ignore LDAP CRL distribution points")),
ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
N_("ignore certificate contained OCSP service URLs")),
ARGPARSE_s_s (oHTTPProxy, "http-proxy",
N_("|URL|redirect all HTTP requests to URL")),
ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
N_("|HOST|use HOST for LDAP queries")),
ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
N_("do not use fallback hosts with --ldap-proxy")),
ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
N_("|FILE|read LDAP server list from FILE")),
ARGPARSE_s_n (oLDAPAddServers, "add-servers",
N_("add new servers discovered in CRL distribution"
" points to serverlist")),
ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
N_("|N|set LDAP timeout to N seconds")),
ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
N_("|URL|use OCSP responder at URL")),
ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
N_("|FPR|OCSP response signed by FPR")),
ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
ARGPARSE_s_i (oMaxReplies, "max-replies",
N_("|N|do not return more than N items in one query")),
ARGPARSE_s_s (oNameServer, "nameserver", "@"),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")),
ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"),
ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"),
ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"),
ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"),
ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"),
ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
"of all commands and options)\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_DNS_VALUE , "dns" },
{ DBG_NETWORK_VALUE, "network" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_MAX_REPLIES 10
#define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */
/* For the cleanup handler we need to keep track of the socket's name. */
static const char *socket_name;
/* If the socket has been redirected, this is the name of the
redirected socket.. */
static const char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Only if this flag has been set will we remove the socket file. */
static int cleanup_socket;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Helper to implement --debug-level. */
static const char *debug_level;
/* Helper to set the NTBTLS or GNUTLS log level. */
static int opt_gnutls_debug = -1;
/* Flag indicating that a shutdown has been requested. */
static volatile int shutdown_pending;
/* Flags to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to control the Tor mode. */
static enum
{ TOR_MODE_AUTO = 0, /* Switch to NO or YES */
TOR_MODE_NEVER, /* Never use Tor. */
TOR_MODE_NO, /* Do not use Tor */
TOR_MODE_YES, /* Use Tor */
TOR_MODE_FORCE /* Force using Tor */
} tor_mode;
/* Counter for the active connections. */
static int active_connections;
/* This flag is set by any network access and used by the housekeeping
* thread to run background network tasks. */
static int network_activity_seen;
/* A list of filenames registred with --hkp-cacert. */
static strlist_t hkp_cacert_filenames;
/* The timer tick used for housekeeping stuff. */
#define TIMERTICK_INTERVAL (60)
/* How oft to run the housekeeping. */
#define HOUSEKEEPING_INTERVAL (600)
/* This union is used to avoid compiler warnings in case a pointer is
64 bit and an int 32 bit. We store an integer in a pointer and get
it back later (npth_getspecific et al.). */
union int_and_ptr_u
{
int aint;
assuan_fd_t afd;
void *aptr;
};
/* The key used to store the current file descriptor in the thread
local storage. We use this in conjunction with the
log_set_pid_suffix_cb feature. */
#ifndef HAVE_W32_SYSTEM
static int my_tlskey_current_fd;
#endif
/* Prototypes. */
static void cleanup (void);
#if USE_LDAP
static ldap_server_t parse_ldapserver_file (const char* filename);
#endif /*USE_LDAP*/
static fingerprint_list_t parse_ocsp_signer (const char *string);
static void netactivity_action (void);
static void handle_connections (assuan_fd_t listen_fd);
/* NPth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static const char *
my_strusage( int level )
{
const char *p;
switch ( level )
{
case 11: p = "@DIRMNGR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Callback from libksba to hash a provided buffer. Our current
implementation does only allow SHA-1 for hashing. This may be
extended by mapping the name, testing for algorithm availibility
and adjust the length checks accordingly. */
static gpg_error_t
my_ksba_hash_buffer (void *arg, const char *oid,
const void *buffer, size_t length, size_t resultsize,
unsigned char *result, size_t *resultlen)
{
(void)arg;
if (oid && strcmp (oid, "1.3.14.3.2.26"))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (resultsize < 20)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
gcry_md_hash_buffer (2, result, buffer, length);
*resultlen = 20;
return 0;
}
/* GNUTLS log function callback. */
#ifdef HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
int n;
n = strlen (text);
while (n && text[n-1] == '\n')
n--;
log_debug ("gnutls:L%d: %.*s\n", level, n, text);
}
#endif /*HTTP_USE_GNUTLS*/
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
log_info (_("valid debug levels are: %s\n"),
"none, basic, advanced, expert, guru");
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
{
opt.verbose = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
}
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
#if HTTP_USE_NTBTLS
if (opt_gnutls_debug >= 0)
{
ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
}
#elif HTTP_USE_GNUTLS
if (opt_gnutls_debug >= 0)
{
gnutls_global_set_log_function (my_gnutls_log);
gnutls_global_set_log_level (opt_gnutls_debug);
}
#endif /*HTTP_USE_GNUTLS*/
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_tor_mode (void)
{
if (dirmngr_use_tor ())
{
/* Enable Tor mode and when called again force a new curcuit
* (e.g. on SIGHUP). */
enable_dns_tormode (1);
if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1))
{
log_error ("error enabling Tor mode: %s\n", strerror (errno));
log_info ("(is your Libassuan recent enough?)\n");
}
}
else
disable_dns_tormode ();
}
/* Return true if Tor shall be used. */
int
dirmngr_use_tor (void)
{
if (tor_mode == TOR_MODE_AUTO)
{
/* FIXME: Figure out whether Tor is running. */
}
if (tor_mode == TOR_MODE_FORCE)
return 2; /* Use Tor (using 2 to indicate force mode) */
else if (tor_mode == TOR_MODE_YES)
return 1; /* Use Tor */
else
return 0; /* Do not use Tor. */
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
es_fputs (text, es_stderr);
es_putc ('\n', es_stderr);
dirmngr_exit (2);
}
/* Helper to stop the reaper thread for the ldap wrapper. */
static void
shutdown_reaper (void)
{
#if USE_LDAP
ldap_wrapper_wait_connections ();
#endif
}
/* Handle options which are allowed to be reset after program start.
Return true if the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
if (!pargs)
{ /* Reset mode. */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.ldap_wrapper_program = NULL;
opt.disable_http = 0;
opt.disable_ldap = 0;
opt.honor_http_proxy = 0;
opt.http_proxy = NULL;
opt.ldap_proxy = NULL;
opt.only_ldap_proxy = 0;
opt.ignore_http_dp = 0;
opt.ignore_ldap_dp = 0;
opt.ignore_ocsp_service_url = 0;
opt.allow_ocsp = 0;
opt.allow_version_check = 0;
opt.ocsp_responder = NULL;
opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
opt.ocsp_max_period = 90 * 86400; /* 90 days. */
opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
opt.max_replies = DEFAULT_MAX_REPLIES;
while (opt.ocsp_signer)
{
fingerprint_list_t tmp = opt.ocsp_signer->next;
xfree (opt.ocsp_signer);
opt.ocsp_signer = tmp;
}
FREE_STRLIST (opt.ignored_cert_extensions);
http_register_tls_ca (NULL);
FREE_STRLIST (hkp_cacert_filenames);
FREE_STRLIST (opt.keyserver);
/* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_AUTO;
disable_check_own_socket = 0;
enable_standard_resolver (0);
set_dns_timeout (0);
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
case oLogFile:
if (!reread)
return 0; /* Not handled. */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oLDAPWrapperProgram:
opt.ldap_wrapper_program = pargs->r.ret_str;
break;
case oHTTPWrapperProgram:
opt.http_wrapper_program = pargs->r.ret_str;
break;
case oDisableHTTP: opt.disable_http = 1; break;
case oDisableLDAP: opt.disable_ldap = 1; break;
case oDisableIPv4: opt.disable_ipv4 = 1; break;
case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
case oAllowOCSP: opt.allow_ocsp = 1; break;
case oAllowVersionCheck: opt.allow_version_check = 1; break;
case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
case oOCSPSigner:
opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str);
break;
case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
case oHkpCaCert:
{
/* We need to register the filenames with gnutls (http.c) and
* also for our own cert cache. */
char *tmpname;
/* Do tilde expansion and make path absolute. */
tmpname = make_absfilename (pargs->r.ret_str, NULL);
http_register_tls_ca (tmpname);
add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str);
xfree (tmpname);
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
break;
case oUseTor:
tor_mode = TOR_MODE_FORCE;
break;
case oNoUseTor:
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_NEVER;
break;
case oStandardResolver: enable_standard_resolver (1); break;
case oRecursiveResolver: enable_recursive_resolver (1); break;
case oKeyServer:
if (*pargs->r.ret_str)
add_to_strlist (&opt.keyserver, pargs->r.ret_str);
break;
case oNameServer:
set_dns_nameserver (pargs->r.ret_str);
break;
case oResolverTimeout:
set_dns_timeout (pargs->r.ret_int);
break;
default:
return 0; /* Not handled. */
}
set_dns_verbose (opt.verbose, !!DBG_DNS);
http_set_verbose (opt.verbose, !!DBG_NETWORK);
set_dns_disable_ipv4 (opt.disable_ipv4);
return 1; /* Handled. */
}
#ifndef HAVE_W32_SYSTEM
static int
pid_suffix_callback (unsigned long *r_suffix)
{
union int_and_ptr_u value;
memset (&value, 0, sizeof value);
value.aptr = npth_getspecific (my_tlskey_current_fd);
*r_suffix = value.aint;
return (*r_suffix != -1); /* Use decimal representation. */
}
#endif /*!HAVE_W32_SYSTEM*/
#if HTTP_USE_NTBTLS
static void
my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv)
{
(void)opaque;
if (level == -1)
log_logv_with_prefix (GPGRT_LOG_INFO, "ntbtls: ", fmt, argv);
else
{
char prefix[10+20];
snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level);
log_logv_with_prefix (GPGRT_LOG_DEBUG, prefix, fmt, argv);
}
}
#endif
static void
thread_init (void)
{
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now with NPth running we can set the logging callback. Our
windows implementation does not yet feature the NPth TLS
functions. */
#ifndef HAVE_W32_SYSTEM
if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
log_set_pid_suffix_cb (pid_suffix_callback);
#endif /*!HAVE_W32_SYSTEM*/
}
int
main (int argc, char **argv)
{
enum cmd_and_opt_values cmd = 0;
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
#if USE_LDAP
char *ldapfile = NULL;
#endif /*USE_LDAP*/
int debug_wait = 0;
int rc;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
set_strusage (my_strusage);
log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the libraries. */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
/* Init TLS library. */
#if HTTP_USE_NTBTLS
if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
/* Init Assuan. */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor);
setup_libgcrypt_logging ();
#if HTTP_USE_NTBTLS
ntbtls_set_log_handler (my_ntbtls_log_handler, NULL);
#endif
/* Setup defaults. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Reset rereadable options to default values. */
parse_rereadable_options (NULL, 0);
/* LDAP defaults. */
opt.add_new_ldapservers = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
/* Other defaults. */
/* Check whether we have a config file given on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
{
gnupg_set_homedir (pargs.r.ret_str);
}
}
socket_name = dirmngr_socket_name ();
if (default_config)
configname = make_filename (gnupg_homedir (), DIRMNGR_NAME".conf", NULL );
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aServer:
case aDaemon:
case aSupervised:
case aShutdown:
case aFlush:
case aListCRLs:
case aLoadCRL:
case aFetchCRL:
case aGPGConfList:
case aGPGConfTest:
cmd = pargs.r_opt;
break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* Config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: /* Ignore this option here. */; break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oLDAPFile:
# if USE_LDAP
ldapfile = pargs.r.ret_str;
# endif /*USE_LDAP*/
break;
case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
case oLDAPTimeout:
opt.ldaptimeout = pargs.r.ret_int;
break;
case oFakedSystemTime:
gnupg_set_time ((time_t)pargs.r.ret_ulong, 0);
break;
case oForce: opt.force = 1; break;
case oSocketName: socket_name = pargs.r.ret_str; break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (!opt.homedir_cache)
opt.homedir_cache = xstrdup (gnupg_homedir ());
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (!access ("/etc/"DIRMNGR_NAME, F_OK)
&& !strncmp (gnupg_homedir (), "/etc/", 5))
log_info
("NOTE: DirMngr is now a proper part of %s. The configuration and"
" other directory names changed. Please check that no other version"
" of dirmngr is still installed. To disable this warning, remove the"
" directory '/etc/dirmngr'.\n", GNUPG_NAME);
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
set_debug ();
set_tor_mode ();
/* Get LDAP server list from file. */
#if USE_LDAP
if (!ldapfile)
{
ldapfile = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
opt.ldapservers = parse_ldapserver_file (ldapfile);
xfree (ldapfile);
}
else
opt.ldapservers = parse_ldapserver_file (ldapfile);
#endif /*USE_LDAP*/
#ifndef HAVE_W32_SYSTEM
/* We need to ignore the PIPE signal because the we might log to a
socket and that code handles EPIPE properly. The ldap wrapper
also requires us to ignore this silly signal. Assuan would set
this signal to ignore anyway.*/
signal (SIGPIPE, SIG_IGN);
#endif
/* Ready. Now to our duties. */
if (!cmd)
cmd = aServer;
rc = 0;
if (cmd == aServer)
{
/* Note that this server mode is mainly useful for debugging. */
if (argc)
wrong_args ("--server");
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
http_register_netactivity_cb (netactivity_action);
start_command_handler (ASSUAN_INVALID_FD);
shutdown_reaper ();
}
#ifndef HAVE_W32_SYSTEM
else if (cmd == aSupervised)
{
/* In supervised mode, we expect file descriptor 3 to be an
already opened, listening socket.
We will also not detach from the controlling process or close
stderr; the supervisor should handle all of that. */
struct stat statbuf;
if (fstat (3, &statbuf) == -1 && errno == EBADF)
{
log_error ("file descriptor 3 must be validin --supervised mode\n");
dirmngr_exit (1);
}
socket_name = gnupg_get_socket_name (3);
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
else
log_set_prefix (NULL, 0);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (3);
shutdown_reaper ();
}
#endif /*HAVE_W32_SYSTEM*/
else if (cmd == aDaemon)
{
assuan_fd_t fd;
pid_t pid;
int len;
struct sockaddr_un serv_addr;
if (argc)
wrong_args ("--daemon");
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
#ifndef HAVE_W32_SYSTEM
if (strchr (socket_name, ':'))
{
log_error (_("colons are not allowed in the socket name\n"));
dirmngr_exit (1);
}
#endif
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
cleanup ();
dirmngr_exit (1);
}
{
int redirected;
if (assuan_sock_set_sockaddr_un (socket_name,
(struct sockaddr*)&serv_addr,
&redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), socket_name);
else
log_error ("error preparing socket '%s': %s\n",
socket_name,
gpg_strerror (gpg_error_from_syserror ()));
dirmngr_exit (1);
}
if (redirected)
{
redir_socket_name = xstrdup (serv_addr.sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n",
socket_name, redir_socket_name);
}
}
len = SUN_LEN (&serv_addr);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Fixme: We should test whether a dirmngr is already running. */
gnupg_remove (redir_socket_name? redir_socket_name : socket_name);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
}
if (rc != -1
&& (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
serv_addr.sun_path,
gpg_strerror (gpg_error_from_errno (errno)));
assuan_sock_close (fd);
dirmngr_exit (1);
}
cleanup_socket = 1;
if (gnupg_chmod (serv_addr.sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
serv_addr.sun_path, strerror (errno));
if (listen (FD2INT (fd), 5) == -1)
{
log_error (_("listen() failed: %s\n"), strerror (errno));
assuan_sock_close (fd);
dirmngr_exit (1);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), serv_addr.sun_path);
es_fflush (NULL);
/* Note: We keep the dirmngr_info output only for the sake of
existing scripts which might use this to detect a successful
start of the dirmngr. */
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
pid = getpid ();
es_printf ("set %s=%s;%lu;1\n",
DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
#else
pid = fork();
if (pid == (pid_t)-1)
{
log_fatal (_("error forking process: %s\n"), strerror (errno));
dirmngr_exit (1);
}
if (pid)
{ /* We are the parent */
char *infostr;
/* Don't let cleanup() remove the socket - the child is
responsible for doing that. */
cleanup_socket = 0;
close (fd);
/* Create the info string: <name>:<pid>:<protocol_version> */
if (asprintf (&infostr, "%s=%s:%lu:1",
DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0)
{
log_error (_("out of core\n"));
kill (pid, SIGTERM);
dirmngr_exit (1);
}
/* Print the environment string, so that the caller can use
shell's eval to set it. But see above. */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
}
free (infostr);
exit (0);
/*NEVER REACHED*/
} /* end parent */
/*
This is the child
*/
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
dirmngr_exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
dirmngr_exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
if (chdir("/"))
{
log_error ("chdir to / failed: %s\n", strerror (errno));
dirmngr_exit (1);
}
}
#endif
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (fd);
shutdown_reaper ();
}
else if (cmd == aListCRLs)
{
/* Just list the CRL cache and exit. */
if (argc)
wrong_args ("--list-crls");
crl_cache_init ();
crl_cache_list (es_stdout);
}
else if (cmd == aLoadCRL)
{
struct server_control_s ctrlbuf;
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
if (!argc)
rc = crl_cache_load (&ctrlbuf, NULL);
else
{
for (; !rc && argc; argc--, argv++)
rc = crl_cache_load (&ctrlbuf, *argv);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFetchCRL)
{
ksba_reader_t reader;
struct server_control_s ctrlbuf;
if (argc != 1)
wrong_args ("--fetch-crl URL");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
rc = crl_fetch (&ctrlbuf, argv[0], &reader);
if (rc)
log_error (_("fetching CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
else
{
rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
if (rc)
log_error (_("processing CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
crl_close_reader (reader);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFlush)
{
/* Delete cache and exit. */
if (argc)
wrong_args ("--flush");
rc = crl_cache_flush();
}
else if (cmd == aGPGConfTest)
dirmngr_exit (0);
else if (cmd == aGPGConfList)
{
unsigned long flags = 0;
char *filename;
char *filename_esc;
/* First the configuration file. This is not an option, but it
is vital information for GPG Conf. */
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
"dirmngr.conf", NULL );
filename = percent_escape (opt.config_filename, NULL);
es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n",
GC_OPT_FLAG_DEFAULT, filename);
xfree (filename);
es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* --csh and --sh are mutually exclusive, something we can not
express in GPG Conf. --options is only usable from the
command line, really. --debug-all interacts with --debug,
and having both of them is thus problematic. --no-detach is
also only usable on the command line. --batch is unused. */
filename = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("ldaptimeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
es_printf ("max-replies:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("allow-version-check:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE);
es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* Note: The next one is to fix a typo in gpgconf - should be
removed eventually. */
es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE);
filename_esc = percent_escape (get_default_keyserver (0), NULL);
es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
es_printf ("nameserver:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("resolver-timeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, 0);
}
cleanup ();
return !!rc;
}
static void
cleanup (void)
{
crl_cache_deinit ();
cert_cache_deinit (1);
reload_dns_stuff (1);
#if USE_LDAP
ldapserver_list_free (opt.ldapservers);
#endif /*USE_LDAP*/
opt.ldapservers = NULL;
if (cleanup_socket)
{
cleanup_socket = 0;
if (redir_socket_name)
gnupg_remove (redir_socket_name);
else if (socket_name && *socket_name)
gnupg_remove (socket_name);
}
}
void
dirmngr_exit (int rc)
{
cleanup ();
exit (rc);
}
void
dirmngr_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
if (opt.http_proxy)
ctrl->http_proxy = xstrdup (opt.http_proxy);
ctrl->http_no_crl = 1;
}
void
dirmngr_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
ctrl->magic = 0xdeadbeef;
xfree (ctrl->http_proxy);
ctrl->http_proxy = NULL;
}
/* Create a list of LDAP servers from the file FILENAME. Returns the
list or NULL in case of errors.
The format fo such a file is line oriented where empty lines and
lines starting with a hash mark are ignored. All other lines are
assumed to be colon seprated with these fields:
1. field: Hostname
2. field: Portnumber
3. field: Username
4. field: Password
5. field: Base DN
*/
#if USE_LDAP
static ldap_server_t
parse_ldapserver_file (const char* filename)
{
char buffer[1024];
char *p;
ldap_server_t server, serverstart, *serverend;
int c;
unsigned int lineno = 0;
estream_t fp;
fp = es_fopen (filename, "r");
if (!fp)
{
log_error (_("error opening '%s': %s\n"), filename, strerror (errno));
return NULL;
}
serverstart = NULL;
serverend = &serverstart;
while (es_fgets (buffer, sizeof buffer, fp))
{
lineno++;
if (!*buffer || buffer[strlen(buffer)-1] != '\n')
{
if (*buffer && es_feof (fp))
; /* Last line not terminated - continue. */
else
{
log_error (_("%s:%u: line too long - skipped\n"),
filename, lineno);
while ( (c=es_fgetc (fp)) != EOF && c != '\n')
; /* Skip until end of line. */
continue;
}
}
/* Skip empty and comment lines.*/
for (p=buffer; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
/* Parse the colon separated fields. */
server = ldapserver_parse_one (buffer, filename, lineno);
if (server)
{
*serverend = server;
serverend = &server->next;
}
}
if (es_ferror (fp))
log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
es_fclose (fp);
return serverstart;
}
#endif /*USE_LDAP*/
static fingerprint_list_t
parse_ocsp_signer (const char *string)
{
gpg_error_t err;
char *fname;
estream_t fp;
char line[256];
char *p;
fingerprint_list_t list, *list_tail, item;
unsigned int lnr = 0;
int c, i, j;
int errflag = 0;
/* Check whether this is not a filename and treat it as a direct
fingerprint specification. */
if (!strpbrk (string, "/.~\\"))
{
item = xcalloc (1, sizeof *item);
for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
if ( string[i] != ':' )
item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (string+i) || !string[i]))
{
log_error (_("%s:%u: invalid fingerprint detected\n"),
"--ocsp-signer", 0);
xfree (item);
return NULL;
}
return item;
}
/* Well, it is a filename. */
if (*string == '/' || (*string == '~' && string[1] == '/'))
fname = make_filename (string, NULL);
else
{
if (string[0] == '.' && string[1] == '/' )
string += 2;
fname = make_filename (gnupg_homedir (), string, NULL);
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
list = NULL;
list_tail = &list;
for (;;)
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
if (!es_feof (fp))
{
err = gpg_error_from_syserror ();
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
}
es_fclose (fp);
if (errflag)
{
while (list)
{
fingerprint_list_t tmp = list->next;
xfree (list);
list = tmp;
}
}
xfree (fname);
return list; /* Ready. */
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
/* */: GPG_ERR_INCOMPLETE_LINE);
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
continue;
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
item = xcalloc (1, sizeof *item);
*list_tail = item;
list_tail = &item->next;
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
errflag = 1;
}
i++;
while (spacep (p+i))
i++;
if (p[i] && p[i] != '\n')
log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
}
/*NOTREACHED*/
}
/*
Stuff used in daemon mode.
*/
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the NPTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process' original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!opt.config_filename)
return; /* No config file. */
fp = fopen (opt.config_filename, "r");
if (!fp)
{
log_error (_("option file '%s': %s\n"),
opt.config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
set_debug ();
set_tor_mode ();
}
/* A global function which allows us to trigger the reload stuff from
other places. */
void
dirmngr_sighup_action (void)
{
log_info (_("SIGHUP received - "
"re-reading configuration and flushing caches\n"));
reread_configuration ();
cert_cache_deinit (0);
crl_cache_deinit ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
reload_dns_stuff (0);
ks_hkp_reload ();
}
/* This function is called if some network activity was done. At this
* point we know the we have a network and we can decide whether to
* run scheduled background tasks soon. The function should return
* quickly and only trigger actions for another thread. */
static void
netactivity_action (void)
{
network_activity_seen = 1;
}
/* The signal handler. */
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
dirmngr_sighup_action ();
break;
case SIGUSR1:
cert_cache_print_stats ();
break;
case SIGUSR2:
log_info (_("SIGUSR2 received - no action defined\n"));
break;
case SIGTERM:
if (!shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
else
log_info (_("SIGTERM received - still %d active connections\n"),
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
dirmngr_exit (0);
}
break;
case SIGINT:
log_info (_("SIGINT received - immediate shutdown\n"));
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
dirmngr_exit (0);
break;
default:
log_info (_("signal %d received - no action defined\n"), signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Thread to do the housekeeping. */
static void *
housekeeping_thread (void *arg)
{
static int sentinel;
time_t curtime;
struct server_control_s ctrlbuf;
(void)arg;
curtime = gnupg_get_time ();
if (sentinel)
{
log_info ("housekeeping is already going on\n");
return NULL;
}
sentinel++;
if (opt.verbose > 1)
log_info ("starting housekeeping\n");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
ks_hkp_housekeeping (curtime);
if (network_activity_seen)
{
network_activity_seen = 0;
if (opt.allow_version_check)
dirmngr_load_swdb (&ctrlbuf, 0);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
if (opt.verbose > 1)
log_info ("ready with housekeeping\n");
sentinel--;
return NULL;
}
#if GPGRT_GCC_HAVE_PUSH_PRAGMA
# pragma GCC push_options
# pragma GCC optimize ("no-strict-overflow")
#endif
static int
time_for_housekeeping_p (time_t curtime)
{
static time_t last_housekeeping;
if (!last_housekeeping)
last_housekeeping = curtime;
if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
|| last_housekeeping > curtime /*(be prepared for y2038)*/)
{
last_housekeeping = curtime;
return 1;
}
return 0;
}
#if GPGRT_GCC_HAVE_PUSH_PRAGMA
# pragma GCC pop_options
#endif
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
if (time_for_housekeeping_p (gnupg_get_time ()))
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
log_error ("error preparing housekeeping thread: %s\n", strerror (err));
else
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
if (err)
log_error ("error spawning housekeeping thread: %s\n",
strerror (err));
npth_attr_destroy (&tattr);
}
}
}
/* Check the nonce on a new connection. This is a NOP unless we are
using our Unix domain socket emulation under Windows. */
static int
check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT (fd), strerror (errno));
assuan_sock_close (fd);
return -1;
}
else
return 0;
}
/* Helper to call a connection's main function. */
static void *
start_connection_thread (void *arg)
{
union int_and_ptr_u argval;
gnupg_fd_t fd;
memset (&argval, 0, sizeof argval);
argval.aptr = arg;
fd = argval.afd;
if (check_nonce (fd, &socket_nonce))
{
log_error ("handler nonce check FAILED\n");
return NULL;
}
#ifndef HAVE_W32_SYSTEM
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
active_connections++;
if (opt.verbose)
log_info (_("handler for fd %d started\n"), FD2INT (fd));
start_command_handler (fd);
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"), FD2INT (fd));
active_connections--;
#ifndef HAVE_W32_SYSTEM
argval.afd = ASSUAN_INVALID_FD;
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
return NULL;
}
#ifdef HAVE_INOTIFY_INIT
/* Read an inotify event and return true if it matches NAME. */
static int
my_inotify_is_name (int fd, const char *name)
{
union {
struct inotify_event ev;
char _buf[sizeof (struct inotify_event) + 100 + 1];
} buf;
int n;
const char *s;
s = strrchr (name, '/');
if (s && s[1])
name = s + 1;
n = npth_read (fd, &buf, sizeof buf);
if (n < sizeof (struct inotify_event))
return 0;
if (buf.ev.len < strlen (name)+1)
return 0;
if (strcmp (buf.ev.name, name))
return 0; /* Not the desired file. */
return 1; /* Found. */
}
#endif /*HAVE_INOTIFY_INIT*/
/* Main loop in daemon mode. Note that LISTEN_FD will be owned by
* this function. */
static void
handle_connections (assuan_fd_t listen_fd)
{
npth_attr_t tattr;
#ifndef HAVE_W32_SYSTEM
int signo;
#endif
struct sockaddr_un paddr;
socklen_t plen = sizeof( paddr );
gnupg_fd_t fd;
int nfd, ret;
fd_set fdset, read_fdset;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
int my_inotify_fd = -1;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM /* FIXME */
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
#ifdef HAVE_INOTIFY_INIT
if (disable_check_own_socket)
my_inotify_fd = -1;
else if ((my_inotify_fd = inotify_init ()) == -1)
log_info ("error enabling fast daemon termination: %s\n",
strerror (errno));
else
{
/* We need to watch the directory for the file because there
* won't be an IN_DELETE_SELF for a socket file. */
char *slash = strrchr (socket_name, '/');
log_assert (slash && slash[1]);
*slash = 0;
if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1)
{
close (my_inotify_fd);
my_inotify_fd = -1;
}
*slash = '/';
}
#endif /*HAVE_INOTIFY_INIT*/
/* Setup the fdset. It has only one member. This is because we use
pth_select instead of pth_accept to properly sync timeouts with
to full second. */
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
if (my_inotify_fd > nfd)
nfd = my_inotify_fd;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
/* Main loop. */
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
/* assuan_sock_close (listen_fd); */
/* listen_fd = -1; */
FD_ZERO (&fdset);
nfd = -1;
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
nfd = my_inotify_fd;
}
}
/* Take a copy of the fdset. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
if (shutdown_pending)
{
/* Do not anymore accept connections. */
continue;
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset)
&& my_inotify_is_name (my_inotify_fd, socket_name))
{
shutdown_pending = 1;
log_info ("socket file has been removed - shutting down\n");
}
#endif /*HAVE_INOTIFY_INIT*/
if (FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else
{
char threadname[50];
union int_and_ptr_u argval;
npth_t thread;
memset (&argval, 0, sizeof argval);
argval.afd = fd;
snprintf (threadname, sizeof threadname,
"conn fd=%d", FD2INT(fd));
ret = npth_create (&thread, &tattr,
start_connection_thread, argval.aptr);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret) );
assuan_sock_close (fd);
}
npth_setname_np (thread, threadname);
}
fd = GNUPG_INVALID_FD;
}
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1)
close (my_inotify_fd);
#endif /*HAVE_INOTIFY_INIT*/
npth_attr_destroy (&tattr);
if (listen_fd != GNUPG_INVALID_FD)
assuan_sock_close (fd);
cleanup ();
log_info ("%s %s stopped\n", strusage(11), strusage(13));
}
const char*
dirmngr_get_current_socket_name (void)
{
if (socket_name)
return socket_name;
else
return dirmngr_socket_name ();
}
diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c
index a0acb8e48..836ced0c1 100644
--- a/dirmngr/dirmngr_ldap.c
+++ b/dirmngr/dirmngr_ldap.c
@@ -1,732 +1,732 @@
/* dirmngr-ldap.c - The LDAP helper for dirmngr.
* Copyright (C) 2004 g10 Code GmbH
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <errno.h>
#include <assert.h>
#include <sys/time.h>
#include <unistd.h>
#ifndef USE_LDAPWRAPPER
# include <npth.h>
#endif
#ifdef HAVE_W32_SYSTEM
# include <winsock2.h>
# include <winldap.h>
# include <winber.h>
# include <fcntl.h>
# include "ldap-url.h"
#else
/* For OpenLDAP, to enable the API that we're using. */
# define LDAP_DEPRECATED 1
# include <ldap.h>
#endif
#include <gpg-error.h>
#include "../common/logging.h"
#include "../common/argparse.h"
#include "../common/stringhelp.h"
#include "../common/mischelp.h"
#include "../common/strlist.h"
-#include "i18n.h"
-#include "util.h"
+#include "../common/i18n.h"
+#include "../common/util.h"
#include "../common/init.h"
/* With the ldap wrapper, there is no need for the npth_unprotect and leave
functions; thus we redefine them to nops. If we are not using the
ldap wrapper process we need to include the prototype for our
module's main function. */
#ifdef USE_LDAPWRAPPER
static void npth_unprotect (void) { }
static void npth_protect (void) { }
#else
# include "./ldap-wrapper.h"
#endif
#ifdef HAVE_W32CE_SYSTEM
# include "w32-ldap-help.h"
# define my_ldap_init(a,b) \
_dirmngr_ldap_init ((a), (b))
# define my_ldap_simple_bind_s(a,b,c) \
_dirmngr_ldap_simple_bind_s ((a),(b),(c))
# define my_ldap_search_st(a,b,c,d,e,f,g,h) \
_dirmngr_ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h))
# define my_ldap_first_attribute(a,b,c) \
_dirmngr_ldap_first_attribute ((a),(b),(c))
# define my_ldap_next_attribute(a,b,c) \
_dirmngr_ldap_next_attribute ((a),(b),(c))
# define my_ldap_get_values_len(a,b,c) \
_dirmngr_ldap_get_values_len ((a),(b),(c))
# define my_ldap_free_attr(a) \
xfree ((a))
#else
# define my_ldap_init(a,b) ldap_init ((a), (b))
# define my_ldap_simple_bind_s(a,b,c) ldap_simple_bind_s ((a), (b), (c))
# define my_ldap_search_st(a,b,c,d,e,f,g,h) \
ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h))
# define my_ldap_first_attribute(a,b,c) ldap_first_attribute ((a),(b),(c))
# define my_ldap_next_attribute(a,b,c) ldap_next_attribute ((a),(b),(c))
# define my_ldap_get_values_len(a,b,c) ldap_get_values_len ((a),(b),(c))
# define my_ldap_free_attr(a) ldap_memfree ((a))
#endif
#ifdef HAVE_W32_SYSTEM
typedef LDAP_TIMEVAL my_ldap_timeval_t;
#else
typedef struct timeval my_ldap_timeval_t;
#endif
#define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */
/* Constants for the options. */
enum
{
oQuiet = 'q',
oVerbose = 'v',
oTimeout = 500,
oMulti,
oProxy,
oHost,
oPort,
oUser,
oPass,
oEnvPass,
oDN,
oFilter,
oAttr,
oOnlySearchTimeout,
oLogWithPID
};
/* The list of options as used by the argparse.c code. */
static ARGPARSE_OPTS opts[] = {
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("be somewhat more quiet") },
{ oTimeout, "timeout", 1, N_("|N|set LDAP timeout to N seconds")},
{ oMulti, "multi", 0, N_("return all values in"
" a record oriented format")},
{ oProxy, "proxy", 2,
N_("|NAME|ignore host part and connect through NAME")},
{ oHost, "host", 2, N_("|NAME|connect to host NAME")},
{ oPort, "port", 1, N_("|N|connect to port N")},
{ oUser, "user", 2, N_("|NAME|use user NAME for authentication")},
{ oPass, "pass", 2, N_("|PASS|use password PASS"
" for authentication")},
{ oEnvPass, "env-pass", 0, N_("take password from $DIRMNGR_LDAP_PASS")},
{ oDN, "dn", 2, N_("|STRING|query DN STRING")},
{ oFilter, "filter", 2, N_("|STRING|use STRING as filter expression")},
{ oAttr, "attr", 2, N_("|STRING|return the attribute STRING")},
{ oOnlySearchTimeout, "only-search-timeout", 0, "@"},
{ oLogWithPID,"log-with-pid", 0, "@"},
{ 0, NULL, 0, NULL }
};
/* A structure with module options. This is not a static variable
because if we are not build as a standalone binary, each thread
using this module needs to handle its own values. */
struct my_opt_s
{
int quiet;
int verbose;
my_ldap_timeval_t timeout;/* Timeout for the LDAP search functions. */
unsigned int alarm_timeout; /* And for the alarm based timeout. */
int multi;
estream_t outstream; /* Send output to this stream. */
/* Note that we can't use const for the strings because ldap_* are
not defined that way. */
char *proxy; /* Host and Port override. */
char *user; /* Authentication user. */
char *pass; /* Authentication password. */
char *host; /* Override host. */
int port; /* Override port. */
char *dn; /* Override DN. */
char *filter;/* Override filter. */
char *attr; /* Override attribute. */
};
typedef struct my_opt_s *my_opt_t;
/* Prototypes. */
#ifndef HAVE_W32_SYSTEM
static void catch_alarm (int dummy);
#endif
static int process_url (my_opt_t myopt, const char *url);
/* Function called by argparse.c to display information. */
#ifdef USE_LDAPWRAPPER
static const char *
my_strusage (int level)
{
const char *p;
switch(level)
{
case 11: p = "dirmngr_ldap (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p =
_("Usage: dirmngr_ldap [options] [URL] (-h for help)\n");
break;
case 41: p =
_("Syntax: dirmngr_ldap [options] [URL]\n"
"Internal LDAP helper for Dirmngr\n"
"Interface and options may change without notice\n");
break;
default: p = NULL;
}
return p;
}
#endif /*!USE_LDAPWRAPPER*/
int
#ifdef USE_LDAPWRAPPER
main (int argc, char **argv)
#else
ldap_wrapper_main (char **argv, estream_t outstream)
#endif
{
#ifndef USE_LDAPWRAPPER
int argc;
#endif
ARGPARSE_ARGS pargs;
int any_err = 0;
char *p;
int only_search_timeout = 0;
struct my_opt_s my_opt_buffer;
my_opt_t myopt = &my_opt_buffer;
char *malloced_buffer1 = NULL;
memset (&my_opt_buffer, 0, sizeof my_opt_buffer);
early_system_init ();
#ifdef USE_LDAPWRAPPER
set_strusage (my_strusage);
log_set_prefix ("dirmngr_ldap", GPGRT_LOG_WITH_PREFIX);
/* Setup I18N and common subsystems. */
i18n_init();
init_common_subsystems (&argc, &argv);
es_set_binary (es_stdout);
myopt->outstream = es_stdout;
#else /*!USE_LDAPWRAPPER*/
myopt->outstream = outstream;
for (argc=0; argv[argc]; argc++)
;
#endif /*!USE_LDAPWRAPPER*/
/* LDAP defaults */
myopt->timeout.tv_sec = DEFAULT_LDAP_TIMEOUT;
myopt->timeout.tv_usec = 0;
myopt->alarm_timeout = 0;
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* Do not remove the args. */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: myopt->verbose++; break;
case oQuiet: myopt->quiet++; break;
case oTimeout:
myopt->timeout.tv_sec = pargs.r.ret_int;
myopt->timeout.tv_usec = 0;
myopt->alarm_timeout = pargs.r.ret_int;
break;
case oOnlySearchTimeout: only_search_timeout = 1; break;
case oMulti: myopt->multi = 1; break;
case oUser: myopt->user = pargs.r.ret_str; break;
case oPass: myopt->pass = pargs.r.ret_str; break;
case oEnvPass:
myopt->pass = getenv ("DIRMNGR_LDAP_PASS");
break;
case oProxy: myopt->proxy = pargs.r.ret_str; break;
case oHost: myopt->host = pargs.r.ret_str; break;
case oPort: myopt->port = pargs.r.ret_int; break;
case oDN: myopt->dn = pargs.r.ret_str; break;
case oFilter: myopt->filter = pargs.r.ret_str; break;
case oAttr: myopt->attr = pargs.r.ret_str; break;
case oLogWithPID:
{
unsigned int oldflags;
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_WITH_PID);
}
break;
default :
#ifdef USE_LDAPWRAPPER
pargs.err = ARGPARSE_PRINT_ERROR;
#else
pargs.err = ARGPARSE_PRINT_WARNING; /* No exit() please. */
#endif
break;
}
}
if (only_search_timeout)
myopt->alarm_timeout = 0;
if (myopt->proxy)
{
malloced_buffer1 = xtrystrdup (myopt->proxy);
if (!malloced_buffer1)
{
log_error ("error copying string: %s\n", strerror (errno));
return 1;
}
myopt->host = malloced_buffer1;
p = strchr (myopt->host, ':');
if (p)
{
*p++ = 0;
myopt->port = atoi (p);
}
if (!myopt->port)
myopt->port = 389; /* make sure ports gets overridden. */
}
if (myopt->port < 0 || myopt->port > 65535)
log_error (_("invalid port number %d\n"), myopt->port);
#ifdef USE_LDAPWRAPPER
if (log_get_errorcount (0))
exit (2);
if (argc < 1)
usage (1);
#else
/* All passed arguments should be fine in this case. */
assert (argc);
#endif
#ifdef USE_LDAPWRAPPER
if (myopt->alarm_timeout)
{
#ifndef HAVE_W32_SYSTEM
# if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION)
struct sigaction act;
act.sa_handler = catch_alarm;
sigemptyset (&act.sa_mask);
act.sa_flags = 0;
if (sigaction (SIGALRM,&act,NULL))
# else
if (signal (SIGALRM, catch_alarm) == SIG_ERR)
# endif
log_fatal ("unable to register timeout handler\n");
#endif
}
#endif /*USE_LDAPWRAPPER*/
for (; argc; argc--, argv++)
if (process_url (myopt, *argv))
any_err = 1;
xfree (malloced_buffer1);
return any_err;
}
#ifndef HAVE_W32_SYSTEM
static void
catch_alarm (int dummy)
{
(void)dummy;
_exit (10);
}
#endif
static void
set_timeout (my_opt_t myopt)
{
#ifdef HAVE_W32_SYSTEM
/* FIXME for W32. */
(void)myopt;
#else
if (myopt->alarm_timeout)
alarm (myopt->alarm_timeout);
#endif
}
/* Helper for fetch_ldap(). */
static int
print_ldap_entries (my_opt_t myopt, LDAP *ld, LDAPMessage *msg, char *want_attr)
{
LDAPMessage *item;
int any = 0;
for (npth_unprotect (), item = ldap_first_entry (ld, msg), npth_protect ();
item;
npth_unprotect (), item = ldap_next_entry (ld, item), npth_protect ())
{
BerElement *berctx;
char *attr;
if (myopt->verbose > 1)
log_info (_("scanning result for attribute '%s'\n"),
want_attr? want_attr : "[all]");
if (myopt->multi)
{ /* Write item marker. */
if (es_fwrite ("I\0\0\0\0", 5, 1, myopt->outstream) != 1)
{
log_error (_("error writing to stdout: %s\n"),
strerror (errno));
return -1;
}
}
for (npth_unprotect (), attr = my_ldap_first_attribute (ld, item, &berctx),
npth_protect ();
attr;
npth_unprotect (), attr = my_ldap_next_attribute (ld, item, berctx),
npth_protect ())
{
struct berval **values;
int idx;
if (myopt->verbose > 1)
log_info (_(" available attribute '%s'\n"), attr);
set_timeout (myopt);
/* I case we want only one attribute we do a case
insensitive compare without the optional extension
(i.e. ";binary"). Case insensitive is not really correct
but the best we can do. */
if (want_attr)
{
char *cp1, *cp2;
int cmpres;
cp1 = strchr (want_attr, ';');
if (cp1)
*cp1 = 0;
cp2 = strchr (attr, ';');
if (cp2)
*cp2 = 0;
cmpres = ascii_strcasecmp (want_attr, attr);
if (cp1)
*cp1 = ';';
if (cp2)
*cp2 = ';';
if (cmpres)
{
my_ldap_free_attr (attr);
continue; /* Not found: Try next attribute. */
}
}
npth_unprotect ();
values = my_ldap_get_values_len (ld, item, attr);
npth_protect ();
if (!values)
{
if (myopt->verbose)
log_info (_("attribute '%s' not found\n"), attr);
my_ldap_free_attr (attr);
continue;
}
if (myopt->verbose)
{
log_info (_("found attribute '%s'\n"), attr);
if (myopt->verbose > 1)
for (idx=0; values[idx]; idx++)
log_info (" length[%d]=%d\n",
idx, (int)values[0]->bv_len);
}
if (myopt->multi)
{ /* Write attribute marker. */
unsigned char tmp[5];
size_t n = strlen (attr);
tmp[0] = 'A';
tmp[1] = (n >> 24);
tmp[2] = (n >> 16);
tmp[3] = (n >> 8);
tmp[4] = (n);
if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1
|| es_fwrite (attr, n, 1, myopt->outstream) != 1)
{
log_error (_("error writing to stdout: %s\n"),
strerror (errno));
ldap_value_free_len (values);
my_ldap_free_attr (attr);
ber_free (berctx, 0);
return -1;
}
}
for (idx=0; values[idx]; idx++)
{
if (myopt->multi)
{ /* Write value marker. */
unsigned char tmp[5];
size_t n = values[0]->bv_len;
tmp[0] = 'V';
tmp[1] = (n >> 24);
tmp[2] = (n >> 16);
tmp[3] = (n >> 8);
tmp[4] = (n);
if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1)
{
log_error (_("error writing to stdout: %s\n"),
strerror (errno));
ldap_value_free_len (values);
my_ldap_free_attr (attr);
ber_free (berctx, 0);
return -1;
}
}
if (es_fwrite (values[0]->bv_val, values[0]->bv_len,
1, myopt->outstream) != 1)
{
log_error (_("error writing to stdout: %s\n"),
strerror (errno));
ldap_value_free_len (values);
my_ldap_free_attr (attr);
ber_free (berctx, 0);
return -1;
}
any = 1;
if (!myopt->multi)
break; /* Print only the first value. */
}
ldap_value_free_len (values);
my_ldap_free_attr (attr);
if (want_attr || !myopt->multi)
break; /* We only want to return the first attribute. */
}
ber_free (berctx, 0);
}
if (myopt->verbose > 1 && any)
log_info ("result has been printed\n");
return any?0:-1;
}
/* Helper for the URL based LDAP query. */
static int
fetch_ldap (my_opt_t myopt, const char *url, const LDAPURLDesc *ludp)
{
LDAP *ld;
LDAPMessage *msg;
int rc = 0;
char *host, *dn, *filter, *attrs[2], *attr;
int port;
int ret;
host = myopt->host? myopt->host : ludp->lud_host;
port = myopt->port? myopt->port : ludp->lud_port;
dn = myopt->dn? myopt->dn : ludp->lud_dn;
filter = myopt->filter? myopt->filter : ludp->lud_filter;
attrs[0] = myopt->attr? myopt->attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL;
attrs[1] = NULL;
attr = attrs[0];
if (!port)
port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389;
if (myopt->verbose)
{
log_info (_("processing url '%s'\n"), url);
if (myopt->user)
log_info (_(" user '%s'\n"), myopt->user);
if (myopt->pass)
log_info (_(" pass '%s'\n"), *myopt->pass?"*****":"");
if (host)
log_info (_(" host '%s'\n"), host);
log_info (_(" port %d\n"), port);
if (dn)
log_info (_(" DN '%s'\n"), dn);
if (filter)
log_info (_(" filter '%s'\n"), filter);
if (myopt->multi && !myopt->attr && ludp->lud_attrs)
{
int i;
for (i=0; ludp->lud_attrs[i]; i++)
log_info (_(" attr '%s'\n"), ludp->lud_attrs[i]);
}
else if (attr)
log_info (_(" attr '%s'\n"), attr);
}
if (!host || !*host)
{
log_error (_("no host name in '%s'\n"), url);
return -1;
}
if (!myopt->multi && !attr)
{
log_error (_("no attribute given for query '%s'\n"), url);
return -1;
}
if (!myopt->multi && !myopt->attr
&& ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1])
log_info (_("WARNING: using first attribute only\n"));
set_timeout (myopt);
npth_unprotect ();
ld = my_ldap_init (host, port);
npth_protect ();
if (!ld)
{
log_error (_("LDAP init to '%s:%d' failed: %s\n"),
host, port, strerror (errno));
return -1;
}
npth_unprotect ();
/* Fixme: Can we use MYOPT->user or is it shared with other theeads?. */
ret = my_ldap_simple_bind_s (ld, myopt->user, myopt->pass);
npth_protect ();
#ifdef LDAP_VERSION3
if (ret == LDAP_PROTOCOL_ERROR)
{
/* Protocol error could mean that the server only supports v3. */
int version = LDAP_VERSION3;
if (myopt->verbose)
log_info ("protocol error; retrying bind with v3 protocol\n");
npth_unprotect ();
ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version);
ret = my_ldap_simple_bind_s (ld, myopt->user, myopt->pass);
npth_protect ();
}
#endif
if (ret)
{
log_error (_("binding to '%s:%d' failed: %s\n"),
host, port, ldap_err2string (ret));
ldap_unbind (ld);
return -1;
}
set_timeout (myopt);
npth_unprotect ();
rc = my_ldap_search_st (ld, dn, ludp->lud_scope, filter,
myopt->multi && !myopt->attr && ludp->lud_attrs?
ludp->lud_attrs:attrs,
0,
&myopt->timeout, &msg);
npth_protect ();
if (rc == LDAP_SIZELIMIT_EXCEEDED && myopt->multi)
{
if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, myopt->outstream) != 1)
{
log_error (_("error writing to stdout: %s\n"), strerror (errno));
return -1;
}
}
else if (rc)
{
#ifdef HAVE_W32CE_SYSTEM
log_error ("searching '%s' failed: %d\n", url, rc);
#else
log_error (_("searching '%s' failed: %s\n"),
url, ldap_err2string (rc));
#endif
if (rc != LDAP_NO_SUCH_OBJECT)
{
/* FIXME: Need deinit (ld)? */
/* Hmmm: Do we need to released MSG in case of an error? */
return -1;
}
}
rc = print_ldap_entries (myopt, ld, msg, myopt->multi? NULL:attr);
ldap_msgfree (msg);
ldap_unbind (ld);
return rc;
}
/* Main processing. Take the URL and run the LDAP query. The result
is printed to stdout, errors are logged to the log stream. */
static int
process_url (my_opt_t myopt, const char *url)
{
int rc;
LDAPURLDesc *ludp = NULL;
if (!ldap_is_ldap_url (url))
{
log_error (_("'%s' is not an LDAP URL\n"), url);
return -1;
}
if (ldap_url_parse (url, &ludp))
{
log_error (_("'%s' is an invalid LDAP URL\n"), url);
return -1;
}
rc = fetch_ldap (myopt, url, ludp);
ldap_free_urldesc (ludp);
return rc;
}
diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c
index 57b3b7e7f..ec9a09523 100644
--- a/dirmngr/dns-stuff.c
+++ b/dirmngr/dns-stuff.c
@@ -1,2283 +1,2283 @@
/* dns-stuff.c - DNS related code including CERT RR (rfc-4398)
* Copyright (C) 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
* Copyright (C) 2005, 2006, 2009, 2015. 2016 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
# include <iphlpapi.h>
#else
# if HAVE_SYSTEM_RESOLVER
# include <netinet/in.h>
# include <arpa/nameser.h>
# include <resolv.h>
# endif
# include <netdb.h>
#endif
#include <string.h>
#include <unistd.h>
/* William Ahern's DNS library, included as a source copy. */
#ifdef USE_LIBDNS
# include "dns.h"
#endif
/* dns.c has a dns_p_free but it is not exported. We use our own
* wrapper here so that we do not accidentally use xfree which would
* be wrong for dns.c allocated data. */
#define dns_free(a) free ((a))
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "./dirmngr-err.h"
-#include "util.h"
-#include "host2net.h"
+#include "../common/util.h"
+#include "../common/host2net.h"
#include "dns-stuff.h"
#ifdef USE_NPTH
# define my_unprotect() npth_unprotect ()
# define my_protect() npth_protect ()
#else
# define my_unprotect() do { } while(0)
# define my_protect() do { } while(0)
#endif
/* We allow the use of 0 instead of AF_UNSPEC - check this assumption. */
#if AF_UNSPEC != 0
# error AF_UNSPEC does not have the value 0
#endif
/* Windows does not support the AI_ADDRCONFIG flag - use zero instead. */
#ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0
#endif
/* Not every installation has gotten around to supporting SRVs or
CERTs yet... */
#ifndef T_SRV
#define T_SRV 33
#endif
#ifndef T_CERT
# define T_CERT 37
#endif
/* The standard SOCKS and TOR ports. */
#define SOCKS_PORT 1080
#define TOR_PORT 9050
#define TOR_PORT2 9150 /* (Used by the Tor browser) */
/* The default nameserver used in Tor mode. */
#define DEFAULT_NAMESERVER "8.8.8.8"
/* The default timeout in seconds for libdns requests. */
#define DEFAULT_TIMEOUT 30
/* Two flags to enable verbose and debug mode. */
static int opt_verbose;
static int opt_debug;
/* The timeout in seconds for libdns requests. */
static int opt_timeout;
/* The flag to disable IPv4 access - right now this only skips
* returned A records. */
static int opt_disable_ipv4;
/* If set force the use of the standard resolver. */
static int standard_resolver;
/* If set use recursive resolver when available. */
static int recursive_resolver;
/* If set Tor mode shall be used. */
static int tor_mode;
/* A string with the nameserver IP address used with Tor.
(40 should be sufficient for v6 but we add some extra for a scope.) */
static char tor_nameserver[40+20];
/* Two strings to hold the credentials presented to Tor. */
static char tor_socks_user[30];
static char tor_socks_password[20];
#ifdef USE_LIBDNS
/* Libdns gobal data. */
struct libdns_s
{
struct dns_resolv_conf *resolv_conf;
struct dns_hosts *hosts;
struct dns_hints *hints;
struct sockaddr_storage socks_host;
} libdns;
/* If this flag is set, libdns shall be reinited for the next use. */
static int libdns_reinit_pending;
/* The Tor port to be used. */
static int libdns_tor_port;
#endif /*USE_LIBDNS*/
/* Calling this function with YES set to True forces the use of the
* standard resolver even if dirmngr has been built with support for
* an alternative resolver. */
void
enable_standard_resolver (int yes)
{
standard_resolver = yes;
}
/* Return true if the standard resolver is used. */
int
standard_resolver_p (void)
{
return standard_resolver;
}
/* Calling this function with YES switches libdns into recursive mode.
* It has no effect on the standard resolver. */
void
enable_recursive_resolver (int yes)
{
recursive_resolver = yes;
#ifdef USE_LIBDNS
libdns_reinit_pending = 1;
#endif
}
/* Return true iff the recursive resolver is used. */
int
recursive_resolver_p (void)
{
#if USE_LIBDNS
return !standard_resolver && recursive_resolver;
#else
return 0;
#endif
}
/* Puts this module eternally into Tor mode. When called agained with
* NEW_CIRCUIT request a new TOR circuit for the next DNS query. */
void
enable_dns_tormode (int new_circuit)
{
if (!*tor_socks_user || new_circuit)
{
static unsigned int counter;
gpgrt_snprintf (tor_socks_user, sizeof tor_socks_user,
"dirmngr-%lu", (unsigned long)getpid ());
gpgrt_snprintf (tor_socks_password, sizeof tor_socks_password,
"p%u", counter);
counter++;
}
tor_mode = 1;
}
/* Disable tor mode. */
void
disable_dns_tormode (void)
{
tor_mode = 0;
}
/* Set verbosity and debug mode for this module. */
void
set_dns_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Set the Disable-IPv4 flag so that the name resolver does not return
* A addresses. */
void
set_dns_disable_ipv4 (int yes)
{
opt_disable_ipv4 = !!yes;
}
/* Set the timeout for libdns requests to SECONDS. A value of 0 sets
* the default timeout and values are capped at 10 minutes. */
void
set_dns_timeout (int seconds)
{
if (!seconds)
seconds = DEFAULT_TIMEOUT;
else if (seconds < 1)
seconds = 1;
else if (seconds > 600)
seconds = 600;
opt_timeout = seconds;
}
/* Change the default IP address of the nameserver to IPADDR. The
address needs to be a numerical IP address and will be used for the
next DNS query. Note that this is only used in Tor mode. */
void
set_dns_nameserver (const char *ipaddr)
{
strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER,
sizeof tor_nameserver -1);
tor_nameserver[sizeof tor_nameserver -1] = 0;
#ifdef USE_LIBDNS
libdns_reinit_pending = 1;
libdns_tor_port = 0; /* Start again with the default port. */
#endif
}
/* Free an addressinfo linked list as returned by resolve_dns_name. */
void
free_dns_addrinfo (dns_addrinfo_t ai)
{
while (ai)
{
dns_addrinfo_t next = ai->next;
xfree (ai);
ai = next;
}
}
#ifndef HAVE_W32_SYSTEM
/* Return H_ERRNO mapped to a gpg-error code. Will never return 0. */
static gpg_error_t
get_h_errno_as_gpg_error (void)
{
gpg_err_code_t ec;
switch (h_errno)
{
case HOST_NOT_FOUND: ec = GPG_ERR_NO_NAME; break;
case TRY_AGAIN: ec = GPG_ERR_TRY_LATER; break;
case NO_RECOVERY: ec = GPG_ERR_SERVER_FAILED; break;
case NO_DATA: ec = GPG_ERR_NO_DATA; break;
default: ec = GPG_ERR_UNKNOWN_ERRNO; break;
}
return gpg_error (ec);
}
#endif /*!HAVE_W32_SYSTEM*/
static gpg_error_t
map_eai_to_gpg_error (int ec)
{
gpg_error_t err;
switch (ec)
{
case EAI_AGAIN: err = gpg_error (GPG_ERR_EAGAIN); break;
case EAI_BADFLAGS: err = gpg_error (GPG_ERR_INV_FLAG); break;
case EAI_FAIL: err = gpg_error (GPG_ERR_SERVER_FAILED); break;
case EAI_MEMORY: err = gpg_error (GPG_ERR_ENOMEM); break;
#ifdef EAI_NODATA
case EAI_NODATA: err = gpg_error (GPG_ERR_NO_DATA); break;
#endif
case EAI_NONAME: err = gpg_error (GPG_ERR_NO_NAME); break;
case EAI_SERVICE: err = gpg_error (GPG_ERR_NOT_SUPPORTED); break;
case EAI_FAMILY: err = gpg_error (GPG_ERR_EAFNOSUPPORT); break;
case EAI_SOCKTYPE: err = gpg_error (GPG_ERR_ESOCKTNOSUPPORT); break;
#ifndef HAVE_W32_SYSTEM
# ifdef EAI_ADDRFAMILY
case EAI_ADDRFAMILY:err = gpg_error (GPG_ERR_EADDRNOTAVAIL); break;
# endif
case EAI_SYSTEM: err = gpg_error_from_syserror (); break;
#endif
default: err = gpg_error (GPG_ERR_UNKNOWN_ERRNO); break;
}
return err;
}
#ifdef USE_LIBDNS
static gpg_error_t
libdns_error_to_gpg_error (int serr)
{
gpg_err_code_t ec;
switch (serr)
{
case 0: ec = 0; break;
case DNS_ENOBUFS: ec = GPG_ERR_BUFFER_TOO_SHORT; break;
case DNS_EILLEGAL: ec = GPG_ERR_INV_OBJ; break;
case DNS_EORDER: ec = GPG_ERR_INV_ORDER; break;
case DNS_ESECTION: ec = GPG_ERR_DNS_SECTION; break;
case DNS_EUNKNOWN: ec = GPG_ERR_DNS_UNKNOWN; break;
case DNS_EADDRESS: ec = GPG_ERR_DNS_ADDRESS; break;
case DNS_ENOQUERY: ec = GPG_ERR_DNS_NO_QUERY; break;
case DNS_ENOANSWER:ec = GPG_ERR_DNS_NO_ANSWER; break;
case DNS_EFETCHED: ec = GPG_ERR_ALREADY_FETCHED; break;
case DNS_ESERVICE: ec = GPG_ERR_NOT_SUPPORTED; break;
case DNS_ENONAME: ec = GPG_ERR_NO_NAME; break;
case DNS_EFAIL: ec = GPG_ERR_SERVER_FAILED; break;
case DNS_ECONNFIN: ec = GPG_ERR_DNS_CLOSED; break;
case DNS_EVERIFY: ec = GPG_ERR_DNS_VERIFY; break;
default:
if (serr >= 0)
ec = gpg_err_code_from_errno (serr);
else
ec = GPG_ERR_DNS_UNKNOWN;
break;
}
return gpg_error (ec);
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
/* Initialize libdns. Returns 0 on success; prints a diagnostic and
* returns an error code on failure. */
static gpg_error_t
libdns_init (void)
{
gpg_error_t err;
struct libdns_s ld;
int derr;
char *cfgstr = NULL;
if (libdns.resolv_conf)
return 0; /* Already initialized. */
memset (&ld, 0, sizeof ld);
ld.resolv_conf = dns_resconf_open (&derr);
if (!ld.resolv_conf)
{
err = libdns_error_to_gpg_error (derr);
log_error ("failed to allocate DNS resconf object: %s\n",
gpg_strerror (err));
goto leave;
}
if (tor_mode)
{
if (!*tor_nameserver)
set_dns_nameserver (NULL);
if (!libdns_tor_port)
libdns_tor_port = TOR_PORT;
cfgstr = xtryasprintf ("[%s]:53", tor_nameserver);
if (!cfgstr)
err = gpg_error_from_syserror ();
else
err = libdns_error_to_gpg_error
(dns_resconf_pton (&ld.resolv_conf->nameserver[0], cfgstr));
if (err)
log_error ("failed to set nameserver '%s': %s\n",
cfgstr, gpg_strerror (err));
if (err)
goto leave;
ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
xfree (cfgstr);
cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port);
if (!cfgstr)
err = gpg_error_from_syserror ();
else
err = libdns_error_to_gpg_error
(dns_resconf_pton (&ld.socks_host, cfgstr));
if (err)
{
log_error ("failed to set socks server '%s': %s\n",
cfgstr, gpg_strerror (err));
goto leave;
}
}
else
{
#ifdef HAVE_W32_SYSTEM
ULONG ninfo_len;
PFIXED_INFO ninfo;
PIP_ADDR_STRING pip;
int idx;
ninfo_len = 2048;
ninfo = xtrymalloc (ninfo_len);
if (!ninfo)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (GetNetworkParams (ninfo, &ninfo_len))
{
log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1));
err = gpg_error (GPG_ERR_GENERAL);
xfree (ninfo);
goto leave;
}
for (idx=0, pip = &(ninfo->DnsServerList);
pip && idx < DIM (ld.resolv_conf->nameserver);
pip = pip->Next)
{
if (opt_debug)
log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String);
err = libdns_error_to_gpg_error
(dns_resconf_pton (&ld.resolv_conf->nameserver[idx],
pip->IpAddress.String));
if (err)
log_error ("failed to set nameserver[%d] '%s': %s\n",
idx, pip->IpAddress.String, gpg_strerror (err));
else
idx++;
}
xfree (ninfo);
#else /* Unix */
const char *fname;
fname = "/etc/resolv.conf";
err = libdns_error_to_gpg_error
(dns_resconf_loadpath (ld.resolv_conf, fname));
if (err)
{
log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
fname = "/etc/nsswitch.conf";
err = libdns_error_to_gpg_error
(dns_nssconf_loadpath (ld.resolv_conf, fname));
if (err)
{
/* This is not a fatal error: nsswitch.conf is not used on
* all systems; assume classic behavior instead. */
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
if (opt_debug)
log_debug ("dns: fallback resolution order, files then DNS\n");
ld.resolv_conf->lookup[0] = 'f';
ld.resolv_conf->lookup[1] = 'b';
ld.resolv_conf->lookup[2] = '\0';
err = GPG_ERR_NO_ERROR;
}
else if (!strchr (ld.resolv_conf->lookup, 'b'))
{
/* No DNS resolution type found in the list. This might be
* due to systemd based systems which allow for custom
* keywords which are not known to us and thus we do not
* know whether DNS is wanted or not. Becuase DNS is
* important for our infrastructure, we forcefully append
* DNS to the end of the list. */
if (strlen (ld.resolv_conf->lookup)+2 < sizeof ld.resolv_conf->lookup)
{
if (opt_debug)
log_debug ("dns: appending DNS to resolution order\n");
strcat (ld.resolv_conf->lookup, "b");
}
else
log_error ("failed to append DNS to resolution order\n");
}
#endif /* Unix */
}
ld.hosts = dns_hosts_open (&derr);
if (!ld.hosts)
{
log_error ("failed to load hosts file: %s\n", gpg_strerror (err));
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* dns_hints_local for stub mode, dns_hints_root for recursive. */
ld.hints = (recursive_resolver
? dns_hints_root (ld.resolv_conf, &derr)
: dns_hints_local (ld.resolv_conf, &derr));
if (!ld.hints)
{
log_error ("failed to load DNS hints: %s\n", gpg_strerror (err));
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* All fine. Make the data global. */
libdns = ld;
if (opt_debug)
log_debug ("dns: libdns initialized%s\n", tor_mode?" (tor mode)":"");
leave:
xfree (cfgstr);
return err;
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
/* Deinitialize libdns. */
static void
libdns_deinit (void)
{
struct libdns_s ld;
if (!libdns.resolv_conf)
return; /* Not initialized. */
ld = libdns;
memset (&libdns, 0, sizeof libdns);
dns_hints_close (ld.hints);
dns_hosts_close (ld.hosts);
dns_resconf_close (ld.resolv_conf);
}
#endif /*USE_LIBDNS*/
/* SIGHUP action handler for this module. With FORCE set objects are
* all immediately released. */
void
reload_dns_stuff (int force)
{
#ifdef USE_LIBDNS
if (force)
{
libdns_deinit ();
libdns_reinit_pending = 0;
}
else
{
libdns_reinit_pending = 1;
libdns_tor_port = 0; /* Start again with the default port. */
}
#else
(void)force;
#endif
}
#ifdef USE_LIBDNS
/*
* Initialize libdns if needed and open a dns_resolver context.
* Returns 0 on success and stores the new context at R_RES. On
* failure an error code is returned and NULL stored at R_RES.
*/
static gpg_error_t
libdns_res_open (struct dns_resolver **r_res)
{
gpg_error_t err;
struct dns_resolver *res;
int derr;
*r_res = NULL;
if (libdns_reinit_pending)
{
libdns_reinit_pending = 0;
libdns_deinit ();
}
err = libdns_init ();
if (err)
return err;
if (!opt_timeout)
set_dns_timeout (0);
res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL,
dns_opts (.socks_host = &libdns.socks_host,
.socks_user = tor_socks_user,
.socks_password = tor_socks_password ),
&derr);
if (!res)
return libdns_error_to_gpg_error (derr);
*r_res = res;
return 0;
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
/* Helper to test whether we need to try again after having switched
* the Tor port. */
static int
libdns_switch_port_p (gpg_error_t err)
{
if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED
&& libdns_tor_port == TOR_PORT)
{
/* Switch port and try again. */
if (opt_debug)
log_debug ("dns: switching from SOCKS port %d to %d\n",
TOR_PORT, TOR_PORT2);
libdns_tor_port = TOR_PORT2;
libdns_reinit_pending = 1;
return 1;
}
return 0;
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
/* Wrapper around dns_res_submit. */
static gpg_error_t
libdns_res_submit (struct dns_resolver *res, const char *qname,
enum dns_type qtype, enum dns_class qclass)
{
return libdns_error_to_gpg_error (dns_res_submit (res, qname, qtype, qclass));
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
/* Standard event handling loop. */
gpg_error_t
libdns_res_wait (struct dns_resolver *res)
{
gpg_error_t err;
while ((err = libdns_error_to_gpg_error (dns_res_check (res)))
&& gpg_err_code (err) == GPG_ERR_EAGAIN)
{
if (dns_res_elapsed (res) > opt_timeout)
{
err = gpg_error (GPG_ERR_DNS_TIMEOUT);
break;
}
my_unprotect ();
dns_res_poll (res, 1);
my_protect ();
}
return err;
}
#endif /*USE_LIBDNS*/
#ifdef USE_LIBDNS
static gpg_error_t
resolve_name_libdns (const char *name, unsigned short port,
int want_family, int want_socktype,
dns_addrinfo_t *r_dai, char **r_canonname)
{
gpg_error_t err;
dns_addrinfo_t daihead = NULL;
dns_addrinfo_t dai;
struct dns_resolver *res = NULL;
struct dns_addrinfo *ai = NULL;
struct addrinfo hints;
struct addrinfo *ent;
char portstr_[21];
char *portstr = NULL;
char *namebuf = NULL;
int derr;
*r_dai = NULL;
if (r_canonname)
*r_canonname = NULL;
memset (&hints, 0, sizeof hints);
hints.ai_family = want_family;
hints.ai_socktype = want_socktype;
hints.ai_flags = AI_ADDRCONFIG;
if (r_canonname)
hints.ai_flags |= AI_CANONNAME;
if (port)
{
snprintf (portstr_, sizeof portstr_, "%hu", port);
portstr = portstr_;
}
err = libdns_res_open (&res);
if (err)
goto leave;
if (is_ip_address (name))
{
hints.ai_flags |= AI_NUMERICHOST;
/* libdns does not grok brackets - remove them. */
if (*name == '[' && name[strlen(name)-1] == ']')
{
namebuf = xtrymalloc (strlen (name));
if (!namebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (namebuf, name+1);
namebuf[strlen (namebuf)-1] = 0;
name = namebuf;
}
}
ai = dns_ai_open (name, portstr, 0, &hints, res, &derr);
if (!ai)
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Loop over all records. */
for (;;)
{
err = libdns_error_to_gpg_error (dns_ai_nextent (&ent, ai));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
if (daihead)
err = 0; /* We got some results, we're good. */
break; /* Ready. */
}
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
{
if (dns_ai_elapsed (ai) > opt_timeout)
{
err = gpg_error (GPG_ERR_DNS_TIMEOUT);
goto leave;
}
my_unprotect ();
dns_ai_poll (ai, 1);
my_protect ();
continue;
}
if (err)
goto leave;
if (r_canonname && ! *r_canonname && ent && ent->ai_canonname)
{
*r_canonname = xtrystrdup (ent->ai_canonname);
if (!*r_canonname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Libdns appends the root zone part which is problematic
* for most other functions - strip it. */
if (**r_canonname && (*r_canonname)[strlen (*r_canonname)-1] == '.')
(*r_canonname)[strlen (*r_canonname)-1] = 0;
}
dai = xtrymalloc (sizeof *dai + ent->ai_addrlen -1);
if (dai == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
dai->family = ent->ai_family;
dai->socktype = ent->ai_socktype;
dai->protocol = ent->ai_protocol;
dai->addrlen = ent->ai_addrlen;
memcpy (dai->addr, ent->ai_addr, ent->ai_addrlen);
dai->next = daihead;
daihead = dai;
xfree (ent);
}
leave:
dns_ai_close (ai);
dns_res_close (res);
if (err)
{
if (r_canonname)
{
xfree (*r_canonname);
*r_canonname = NULL;
}
free_dns_addrinfo (daihead);
}
else
*r_dai = daihead;
xfree (namebuf);
return err;
}
#endif /*USE_LIBDNS*/
/* Resolve a name using the standard system function. */
static gpg_error_t
resolve_name_standard (const char *name, unsigned short port,
int want_family, int want_socktype,
dns_addrinfo_t *r_dai, char **r_canonname)
{
gpg_error_t err = 0;
dns_addrinfo_t daihead = NULL;
dns_addrinfo_t dai;
struct addrinfo *aibuf = NULL;
struct addrinfo hints, *ai;
char portstr[21];
int ret;
*r_dai = NULL;
if (r_canonname)
*r_canonname = NULL;
memset (&hints, 0, sizeof hints);
hints.ai_family = want_family;
hints.ai_socktype = want_socktype;
hints.ai_flags = AI_ADDRCONFIG;
if (r_canonname)
hints.ai_flags |= AI_CANONNAME;
if (is_ip_address (name))
hints.ai_flags |= AI_NUMERICHOST;
if (port)
snprintf (portstr, sizeof portstr, "%hu", port);
else
*portstr = 0;
/* We can't use the AI_IDN flag because that does the conversion
using the current locale. However, GnuPG always used UTF-8. To
support IDN we would need to make use of the libidn API. */
ret = getaddrinfo (name, *portstr? portstr : NULL, &hints, &aibuf);
if (ret)
{
aibuf = NULL;
err = map_eai_to_gpg_error (ret);
if (gpg_err_code (err) == GPG_ERR_NO_NAME)
{
/* There seems to be a bug in the glibc getaddrinfo function
if the CNAME points to a long list of A and AAAA records
in which case the function return NO_NAME. Let's do the
CNAME redirection again. */
char *cname;
if (get_dns_cname (name, &cname))
goto leave; /* Still no success. */
ret = getaddrinfo (cname, *portstr? portstr : NULL, &hints, &aibuf);
xfree (cname);
if (ret)
{
aibuf = NULL;
err = map_eai_to_gpg_error (ret);
goto leave;
}
err = 0; /* Yep, now it worked. */
}
else
goto leave;
}
if (r_canonname && aibuf && aibuf->ai_canonname)
{
*r_canonname = xtrystrdup (aibuf->ai_canonname);
if (!*r_canonname)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
for (ai = aibuf; ai; ai = ai->ai_next)
{
if (ai->ai_family != AF_INET6 && ai->ai_family != AF_INET)
continue;
if (opt_disable_ipv4 && ai->ai_family == AF_INET)
continue;
dai = xtrymalloc (sizeof *dai + ai->ai_addrlen - 1);
dai->family = ai->ai_family;
dai->socktype = ai->ai_socktype;
dai->protocol = ai->ai_protocol;
dai->addrlen = ai->ai_addrlen;
memcpy (dai->addr, ai->ai_addr, ai->ai_addrlen);
dai->next = daihead;
daihead = dai;
}
leave:
if (aibuf)
freeaddrinfo (aibuf);
if (err)
{
if (r_canonname)
{
xfree (*r_canonname);
*r_canonname = NULL;
}
free_dns_addrinfo (daihead);
}
else
*r_dai = daihead;
return err;
}
/* This a wrapper around getaddrinfo with slightly different semantics.
NAME is the name to resolve.
PORT is the requested port or 0.
WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4.
WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM.
On success the result is stored in a linked list with the head
stored at the address R_AI; the caller must call gpg_addrinfo_free
on this. If R_CANONNAME is not NULL the official name of the host
is stored there as a malloced string; if that name is not available
NULL is stored. */
gpg_error_t
resolve_dns_name (const char *name, unsigned short port,
int want_family, int want_socktype,
dns_addrinfo_t *r_ai, char **r_canonname)
{
gpg_error_t err;
#ifdef USE_LIBDNS
if (!standard_resolver)
{
err = resolve_name_libdns (name, port, want_family, want_socktype,
r_ai, r_canonname);
if (err && libdns_switch_port_p (err))
err = resolve_name_libdns (name, port, want_family, want_socktype,
r_ai, r_canonname);
}
else
#endif /*USE_LIBDNS*/
err = resolve_name_standard (name, port, want_family, want_socktype,
r_ai, r_canonname);
if (opt_debug)
log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err));
return err;
}
#ifdef USE_LIBDNS
/* Resolve an address using libdns. */
static gpg_error_t
resolve_addr_libdns (const struct sockaddr *addr, int addrlen,
unsigned int flags, char **r_name)
{
gpg_error_t err;
char host[DNS_D_MAXNAME + 1];
struct dns_resolver *res = NULL;
struct dns_packet *ans = NULL;
struct dns_ptr ptr;
int derr;
*r_name = NULL;
/* First we turn ADDR into a DNS name (with ".arpa" suffix). */
err = 0;
if (addr->sa_family == AF_INET6)
{
const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr;
if (!dns_aaaa_arpa (host, sizeof host, (void*)&a6->sin6_addr))
err = gpg_error (GPG_ERR_INV_OBJ);
}
else if (addr->sa_family == AF_INET)
{
const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr;
if (!dns_a_arpa (host, sizeof host, (void*)&a4->sin_addr))
err = gpg_error (GPG_ERR_INV_OBJ);
}
else
err = gpg_error (GPG_ERR_EAFNOSUPPORT);
if (err)
goto leave;
err = libdns_res_open (&res);
if (err)
goto leave;
err = libdns_res_submit (res, host, DNS_T_PTR, DNS_C_IN);
if (err)
goto leave;
err = libdns_res_wait (res);
if (err)
goto leave;
ans = dns_res_fetch (res, &derr);
if (!ans)
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Check the rcode. */
switch (dns_p_rcode (ans))
{
case DNS_RC_NOERROR:
break;
case DNS_RC_NXDOMAIN:
err = gpg_error (GPG_ERR_NO_NAME);
break;
default:
err = GPG_ERR_SERVER_FAILED;
goto leave;
}
/* Parse the result. */
if (!err)
{
struct dns_rr rr;
struct dns_rr_i rri;
memset (&rri, 0, sizeof rri);
dns_rr_i_init (&rri, ans);
rri.section = DNS_S_ALL & ~DNS_S_QD;
rri.name = host;
rri.type = DNS_T_PTR;
if (!dns_rr_grep (&rr, 1, &rri, ans, &derr))
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
err = libdns_error_to_gpg_error (dns_ptr_parse (&ptr, &rr, ans));
if (err)
goto leave;
/* Copy result. */
*r_name = xtrystrdup (ptr.host);
if (!*r_name)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Libdns appends the root zone part which is problematic
* for most other functions - strip it. */
if (**r_name && (*r_name)[strlen (*r_name)-1] == '.')
(*r_name)[strlen (*r_name)-1] = 0;
}
else /* GPG_ERR_NO_NAME */
{
char *buffer, *p;
int buflen;
int ec;
buffer = ptr.host;
buflen = sizeof ptr.host;
p = buffer;
if (addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
{
*p++ = '[';
buflen -= 2;
}
ec = getnameinfo (addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST);
if (ec)
{
err = map_eai_to_gpg_error (ec);
goto leave;
}
if (addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
strcat (buffer, "]");
}
leave:
dns_free (ans);
dns_res_close (res);
return err;
}
#endif /*USE_LIBDNS*/
/* Resolve an address using the standard system function. */
static gpg_error_t
resolve_addr_standard (const struct sockaddr *addr, int addrlen,
unsigned int flags, char **r_name)
{
gpg_error_t err;
int ec;
char *buffer, *p;
int buflen;
*r_name = NULL;
buflen = NI_MAXHOST;
buffer = xtrymalloc (buflen + 2 + 1);
if (!buffer)
return gpg_error_from_syserror ();
if ((flags & DNS_NUMERICHOST) || tor_mode)
ec = EAI_NONAME;
else
ec = getnameinfo (addr, addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD);
if (!ec && *buffer == '[')
ec = EAI_FAIL; /* A name may never start with a bracket. */
else if (ec == EAI_NONAME)
{
p = buffer;
if (addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
{
*p++ = '[';
buflen -= 2;
}
ec = getnameinfo (addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST);
if (!ec && addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
strcat (buffer, "]");
}
if (ec)
err = map_eai_to_gpg_error (ec);
else
{
p = xtryrealloc (buffer, strlen (buffer)+1);
if (!p)
err = gpg_error_from_syserror ();
else
{
buffer = p;
err = 0;
}
}
if (err)
xfree (buffer);
else
*r_name = buffer;
return err;
}
/* A wrapper around getnameinfo. */
gpg_error_t
resolve_dns_addr (const struct sockaddr *addr, int addrlen,
unsigned int flags, char **r_name)
{
gpg_error_t err;
#ifdef USE_LIBDNS
/* Note that we divert to the standard resolver for NUMERICHOST. */
if (!standard_resolver && !(flags & DNS_NUMERICHOST))
{
err = resolve_addr_libdns (addr, addrlen, flags, r_name);
if (err && libdns_switch_port_p (err))
err = resolve_addr_libdns (addr, addrlen, flags, r_name);
}
else
#endif /*USE_LIBDNS*/
err = resolve_addr_standard (addr, addrlen, flags, r_name);
if (opt_debug)
log_debug ("dns: resolve_dns_addr(): %s\n", gpg_strerror (err));
return err;
}
/* Check whether NAME is an IP address. Returns a true if it is
* either an IPv6 or a IPv4 numerical address. The actual return
* values can also be used to identify whether it is v4 or v6: The
* true value will surprisingly be 4 for IPv4 and 6 for IPv6. */
int
is_ip_address (const char *name)
{
const char *s;
int ndots, dblcol, n;
if (*name == '[')
return 6; /* yes: A legal DNS name may not contain this character;
this must be bracketed v6 address. */
if (*name == '.')
return 0; /* No. A leading dot is not a valid IP address. */
/* Check whether this is a v6 address. */
ndots = n = dblcol = 0;
for (s=name; *s; s++)
{
if (*s == ':')
{
ndots++;
if (s[1] == ':')
{
ndots++;
if (dblcol)
return 0; /* No: Only one "::" allowed. */
dblcol++;
if (s[1])
s++;
}
n = 0;
}
else if (*s == '.')
goto legacy;
else if (!strchr ("0123456789abcdefABCDEF", *s))
return 0; /* No: Not a hex digit. */
else if (++n > 4)
return 0; /* To many digits in a group. */
}
if (ndots > 7)
return 0; /* No: Too many colons. */
else if (ndots > 1)
return 6; /* Yes: At least 2 colons indicate an v6 address. */
legacy:
/* Check whether it is legacy IP address. */
ndots = n = 0;
for (s=name; *s; s++)
{
if (*s == '.')
{
if (s[1] == '.')
return 0; /* No: Double dot. */
if (atoi (s+1) > 255)
return 0; /* No: Ipv4 byte value too large. */
ndots++;
n = 0;
}
else if (!strchr ("0123456789", *s))
return 0; /* No: Not a digit. */
else if (++n > 3)
return 0; /* No: More than 3 digits. */
}
return (ndots == 3)? 4 : 0;
}
/* Return true if NAME is an onion address. */
int
is_onion_address (const char *name)
{
size_t len;
len = name? strlen (name) : 0;
if (len < 8 || strcmp (name + len - 6, ".onion"))
return 0;
/* Note that we require at least 2 characters before the suffix. */
return 1; /* Yes. */
}
/* libdns version of get_dns_cert. */
#ifdef USE_LIBDNS
static gpg_error_t
get_dns_cert_libdns (const char *name, int want_certtype,
void **r_key, size_t *r_keylen,
unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
{
gpg_error_t err;
struct dns_resolver *res = NULL;
struct dns_packet *ans = NULL;
struct dns_rr rr;
struct dns_rr_i rri;
char host[DNS_D_MAXNAME + 1];
int derr;
int qtype;
/* Get the query type from WANT_CERTTYPE (which in general indicates
* the subtype we want). */
qtype = (want_certtype < DNS_CERTTYPE_RRBASE
? T_CERT
: (want_certtype - DNS_CERTTYPE_RRBASE));
err = libdns_res_open (&res);
if (err)
goto leave;
if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host)
{
err = gpg_error (GPG_ERR_ENAMETOOLONG);
goto leave;
}
err = libdns_res_submit (res, name, qtype, DNS_C_IN);
if (err)
goto leave;
err = libdns_res_wait (res);
if (err)
goto leave;
ans = dns_res_fetch (res, &derr);
if (!ans)
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Check the rcode. */
switch (dns_p_rcode (ans))
{
case DNS_RC_NOERROR: break;
case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
default: err = GPG_ERR_SERVER_FAILED; break;
}
if (err)
goto leave;
memset (&rri, 0, sizeof rri);
dns_rr_i_init (&rri, ans);
rri.section = DNS_S_ALL & ~DNS_S_QD;
rri.name = host;
rri.type = qtype;
err = gpg_error (GPG_ERR_NOT_FOUND);
while (dns_rr_grep (&rr, 1, &rri, ans, &derr))
{
unsigned char *rp = ans->data + rr.rd.p;
unsigned short len = rr.rd.len;
u16 subtype;
if (!len)
{
/* Definitely too short - skip. */
}
else if (want_certtype >= DNS_CERTTYPE_RRBASE
&& rr.type == (want_certtype - DNS_CERTTYPE_RRBASE)
&& r_key)
{
*r_key = xtrymalloc (len);
if (!*r_key)
err = gpg_error_from_syserror ();
else
{
memcpy (*r_key, rp, len);
*r_keylen = len;
err = 0;
}
goto leave;
}
else if (want_certtype >= DNS_CERTTYPE_RRBASE)
{
/* We did not found the requested RR - skip. */
}
else if (rr.type == T_CERT && len > 5)
{
/* We got a CERT type. */
subtype = buf16_to_u16 (rp);
rp += 2; len -= 2;
/* Skip the CERT key tag and algo which we don't need. */
rp += 3; len -= 3;
if (want_certtype && want_certtype != subtype)
; /* Not the requested subtype - skip. */
else if (subtype == DNS_CERTTYPE_PGP && len && r_key && r_keylen)
{
/* PGP subtype */
*r_key = xtrymalloc (len);
if (!*r_key)
err = gpg_error_from_syserror ();
else
{
memcpy (*r_key, rp, len);
*r_keylen = len;
err = 0;
}
goto leave;
}
else if (subtype == DNS_CERTTYPE_IPGP
&& len && len < 1023 && len >= rp[0] + 1)
{
/* IPGP type */
*r_fprlen = rp[0];
if (*r_fprlen)
{
*r_fpr = xtrymalloc (*r_fprlen);
if (!*r_fpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_fpr, rp+1, *r_fprlen);
}
else
*r_fpr = NULL;
if (len > *r_fprlen + 1)
{
*r_url = xtrymalloc (len - (*r_fprlen + 1) + 1);
if (!*r_url)
{
err = gpg_error_from_syserror ();
xfree (*r_fpr);
*r_fpr = NULL;
goto leave;
}
memcpy (*r_url, rp + *r_fprlen + 1, len - (*r_fprlen + 1));
(*r_url)[len - (*r_fprlen + 1)] = 0;
}
else
*r_url = NULL;
err = 0;
goto leave;
}
else
{
/* Unknown subtype or record too short - skip. */
}
}
else
{
/* Not a requested type - skip. */
}
}
leave:
dns_free (ans);
dns_res_close (res);
return err;
}
#endif /*USE_LIBDNS*/
/* Standard resolver version of get_dns_cert. */
static gpg_error_t
get_dns_cert_standard (const char *name, int want_certtype,
void **r_key, size_t *r_keylen,
unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
{
#ifdef HAVE_SYSTEM_RESOLVER
gpg_error_t err;
unsigned char *answer;
int r;
u16 count;
/* Allocate a 64k buffer which is the limit for an DNS response. */
answer = xtrymalloc (65536);
if (!answer)
return gpg_error_from_syserror ();
err = gpg_error (GPG_ERR_NOT_FOUND);
r = res_query (name, C_IN,
(want_certtype < DNS_CERTTYPE_RRBASE
? T_CERT
: (want_certtype - DNS_CERTTYPE_RRBASE)),
answer, 65536);
/* Not too big, not too small, no errors and at least 1 answer. */
if (r >= sizeof (HEADER) && r <= 65536
&& (((HEADER *)(void *) answer)->rcode) == NOERROR
&& (count = ntohs (((HEADER *)(void *) answer)->ancount)))
{
int rc;
unsigned char *pt, *emsg;
emsg = &answer[r];
pt = &answer[sizeof (HEADER)];
/* Skip over the query */
rc = dn_skipname (pt, emsg);
if (rc == -1)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
pt += rc + QFIXEDSZ;
/* There are several possible response types for a CERT request.
We're interested in the PGP (a key) and IPGP (a URI) types.
Skip all others. TODO: A key is better than a URI since
we've gone through all this bother to fetch it, so favor that
if we have both PGP and IPGP? */
while (count-- > 0 && pt < emsg)
{
u16 type, class, dlen, ctype;
rc = dn_skipname (pt, emsg); /* the name we just queried for */
if (rc == -1)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
pt += rc;
/* Truncated message? 15 bytes takes us to the point where
we start looking at the ctype. */
if ((emsg - pt) < 15)
break;
type = buf16_to_u16 (pt);
pt += 2;
class = buf16_to_u16 (pt);
pt += 2;
if (class != C_IN)
break;
/* ttl */
pt += 4;
/* data length */
dlen = buf16_to_u16 (pt);
pt += 2;
/* Check the type and parse. */
if (want_certtype >= DNS_CERTTYPE_RRBASE
&& type == (want_certtype - DNS_CERTTYPE_RRBASE)
&& r_key)
{
*r_key = xtrymalloc (dlen);
if (!*r_key)
err = gpg_error_from_syserror ();
else
{
memcpy (*r_key, pt, dlen);
*r_keylen = dlen;
err = 0;
}
goto leave;
}
else if (want_certtype >= DNS_CERTTYPE_RRBASE)
{
/* We did not found the requested RR. */
pt += dlen;
}
else if (type == T_CERT)
{
/* We got a CERT type. */
ctype = buf16_to_u16 (pt);
pt += 2;
/* Skip the CERT key tag and algo which we don't need. */
pt += 3;
dlen -= 5;
/* 15 bytes takes us to here */
if (want_certtype && want_certtype != ctype)
; /* Not of the requested certtype. */
else if (ctype == DNS_CERTTYPE_PGP && dlen && r_key && r_keylen)
{
/* PGP type */
*r_key = xtrymalloc (dlen);
if (!*r_key)
err = gpg_error_from_syserror ();
else
{
memcpy (*r_key, pt, dlen);
*r_keylen = dlen;
err = 0;
}
goto leave;
}
else if (ctype == DNS_CERTTYPE_IPGP
&& dlen && dlen < 1023 && dlen >= pt[0] + 1)
{
/* IPGP type */
*r_fprlen = pt[0];
if (*r_fprlen)
{
*r_fpr = xtrymalloc (*r_fprlen);
if (!*r_fpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_fpr, &pt[1], *r_fprlen);
}
else
*r_fpr = NULL;
if (dlen > *r_fprlen + 1)
{
*r_url = xtrymalloc (dlen - (*r_fprlen + 1) + 1);
if (!*r_url)
{
err = gpg_error_from_syserror ();
xfree (*r_fpr);
*r_fpr = NULL;
goto leave;
}
memcpy (*r_url, &pt[*r_fprlen + 1],
dlen - (*r_fprlen + 1));
(*r_url)[dlen - (*r_fprlen + 1)] = '\0';
}
else
*r_url = NULL;
err = 0;
goto leave;
}
/* No subtype matches, so continue with the next answer. */
pt += dlen;
}
else
{
/* Not a requested type - might be a CNAME. Try next item. */
pt += dlen;
}
}
}
leave:
xfree (answer);
return err;
#else /*!HAVE_SYSTEM_RESOLVER*/
(void)name;
(void)want_certtype;
(void)r_key;
(void)r_keylen;
(void)r_fpr;
(void)r_fprlen;
(void)r_url;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!HAVE_SYSTEM_RESOLVER*/
}
/* Returns 0 on success or an error code. If a PGP CERT record was
found, the malloced data is returned at (R_KEY, R_KEYLEN) and
the other return parameters are set to NULL/0. If an IPGP CERT
record was found the fingerprint is stored as an allocated block at
R_FPR and its length at R_FPRLEN; an URL is allocated as a
string and returned at R_URL. If WANT_CERTTYPE is 0 this function
returns the first CERT found with a supported type; it is expected
that only one CERT record is used. If WANT_CERTTYPE is one of the
supported certtypes only records with this certtype are considered
and the first found is returned. (R_KEY,R_KEYLEN) are optional. */
gpg_error_t
get_dns_cert (const char *name, int want_certtype,
void **r_key, size_t *r_keylen,
unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
{
gpg_error_t err;
if (r_key)
*r_key = NULL;
if (r_keylen)
*r_keylen = 0;
*r_fpr = NULL;
*r_fprlen = 0;
*r_url = NULL;
#ifdef USE_LIBDNS
if (!standard_resolver)
{
err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
r_fpr, r_fprlen, r_url);
if (err && libdns_switch_port_p (err))
err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
r_fpr, r_fprlen, r_url);
}
else
#endif /*USE_LIBDNS*/
err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen,
r_fpr, r_fprlen, r_url);
if (opt_debug)
log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err));
return err;
}
static int
priosort(const void *a,const void *b)
{
const struct srventry *sa=a,*sb=b;
if(sa->priority>sb->priority)
return 1;
else if(sa->priority<sb->priority)
return -1;
else
return 0;
}
/* Libdns based helper for getsrv. Note that it is expected that NULL
* is stored at the address of LIST and 0 is stored at the address of
* R_COUNT. */
#ifdef USE_LIBDNS
static gpg_error_t
getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count)
{
gpg_error_t err;
struct dns_resolver *res = NULL;
struct dns_packet *ans = NULL;
struct dns_rr rr;
struct dns_rr_i rri;
char host[DNS_D_MAXNAME + 1];
int derr;
unsigned int srvcount = 0;
err = libdns_res_open (&res);
if (err)
goto leave;
if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host)
{
err = gpg_error (GPG_ERR_ENAMETOOLONG);
goto leave;
}
err = libdns_res_submit (res, name, DNS_T_SRV, DNS_C_IN);
if (err)
goto leave;
err = libdns_res_wait (res);
if (err)
goto leave;
ans = dns_res_fetch (res, &derr);
if (!ans)
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Check the rcode. */
switch (dns_p_rcode (ans))
{
case DNS_RC_NOERROR: break;
case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
default: err = GPG_ERR_SERVER_FAILED; break;
}
if (err)
goto leave;
memset (&rri, 0, sizeof rri);
dns_rr_i_init (&rri, ans);
rri.section = DNS_S_ALL & ~DNS_S_QD;
rri.name = host;
rri.type = DNS_T_SRV;
while (dns_rr_grep (&rr, 1, &rri, ans, &derr))
{
struct dns_srv dsrv;
struct srventry *srv;
struct srventry *newlist;
err = libdns_error_to_gpg_error (dns_srv_parse(&dsrv, &rr, ans));
if (err)
goto leave;
newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
if (!newlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
*list = newlist;
memset (&(*list)[srvcount], 0, sizeof(struct srventry));
srv = &(*list)[srvcount];
srvcount++;
srv->priority = dsrv.priority;
srv->weight = dsrv.weight;
srv->port = dsrv.port;
mem2str (srv->target, dsrv.target, sizeof srv->target);
/* Libdns appends the root zone part which is problematic for
* most other functions - strip it. */
if (*srv->target && (srv->target)[strlen (srv->target)-1] == '.')
(srv->target)[strlen (srv->target)-1] = 0;
}
*r_count = srvcount;
leave:
if (err)
{
xfree (*list);
*list = NULL;
}
dns_free (ans);
dns_res_close (res);
return err;
}
#endif /*USE_LIBDNS*/
/* Standard resolver based helper for getsrv. Note that it is
* expected that NULL is stored at the address of LIST and 0 is stored
* at the address of R_COUNT. */
static gpg_error_t
getsrv_standard (const char *name,
struct srventry **list, unsigned int *r_count)
{
#ifdef HAVE_SYSTEM_RESOLVER
union {
unsigned char ans[2048];
HEADER header[1];
} res;
unsigned char *answer = res.ans;
HEADER *header = res.header;
unsigned char *pt, *emsg;
int r, rc;
u16 dlen;
unsigned int srvcount = 0;
u16 count;
/* Do not allow a query using the standard resolver in Tor mode. */
if (tor_mode)
return gpg_error (GPG_ERR_NOT_ENABLED);
my_unprotect ();
r = res_query (name, C_IN, T_SRV, answer, sizeof res.ans);
my_protect ();
if (r < 0)
return get_h_errno_as_gpg_error ();
if (r < sizeof (HEADER))
return gpg_error (GPG_ERR_SERVER_FAILED);
if (r > sizeof res.ans)
return gpg_error (GPG_ERR_SYSTEM_BUG);
if (header->rcode != NOERROR || !(count=ntohs (header->ancount)))
return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */
emsg = &answer[r];
pt = &answer[sizeof(HEADER)];
/* Skip over the query */
rc = dn_skipname (pt, emsg);
if (rc == -1)
goto fail;
pt += rc + QFIXEDSZ;
while (count-- > 0 && pt < emsg)
{
struct srventry *srv;
u16 type, class;
struct srventry *newlist;
newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
if (!newlist)
goto fail;
*list = newlist;
memset (&(*list)[srvcount], 0, sizeof(struct srventry));
srv = &(*list)[srvcount];
srvcount++;
rc = dn_skipname (pt, emsg); /* The name we just queried for. */
if (rc == -1)
goto fail;
pt += rc;
/* Truncated message? */
if ((emsg-pt) < 16)
goto fail;
type = buf16_to_u16 (pt);
pt += 2;
/* We asked for SRV and got something else !? */
if (type != T_SRV)
goto fail;
class = buf16_to_u16 (pt);
pt += 2;
/* We asked for IN and got something else !? */
if (class != C_IN)
goto fail;
pt += 4; /* ttl */
dlen = buf16_to_u16 (pt);
pt += 2;
srv->priority = buf16_to_ushort (pt);
pt += 2;
srv->weight = buf16_to_ushort (pt);
pt += 2;
srv->port = buf16_to_ushort (pt);
pt += 2;
/* Get the name. 2782 doesn't allow name compression, but
* dn_expand still works to pull the name out of the packet. */
rc = dn_expand (answer, emsg, pt, srv->target, sizeof srv->target);
if (rc == 1 && srv->target[0] == 0) /* "." */
{
xfree(*list);
*list = NULL;
return 0;
}
if (rc == -1)
goto fail;
pt += rc;
/* Corrupt packet? */
if (dlen != rc+6)
goto fail;
}
*r_count = srvcount;
return 0;
fail:
xfree (*list);
*list = NULL;
return gpg_error (GPG_ERR_GENERAL);
#else /*!HAVE_SYSTEM_RESOLVER*/
(void)name;
(void)list;
(void)r_count;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!HAVE_SYSTEM_RESOLVER*/
}
/* Query a SRV record for SERVICE and PROTO for NAME. If SERVICE is
* NULL, NAME is expected to contain the full query name. Note that
* we do not return NONAME but simply store 0 at R_COUNT. On error an
* error code is returned and 0 stored at R_COUNT. */
gpg_error_t
get_dns_srv (const char *name, const char *service, const char *proto,
struct srventry **list, unsigned int *r_count)
{
gpg_error_t err;
char *namebuffer = NULL;
unsigned int srvcount;
int i;
*list = NULL;
*r_count = 0;
srvcount = 0;
/* If SERVICE is given construct the query from it and PROTO. */
if (service)
{
namebuffer = xtryasprintf ("_%s._%s.%s",
service, proto? proto:"tcp", name);
if (!namebuffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
name = namebuffer;
}
#ifdef USE_LIBDNS
if (!standard_resolver)
{
err = getsrv_libdns (name, list, &srvcount);
if (err && libdns_switch_port_p (err))
err = getsrv_libdns (name, list, &srvcount);
}
else
#endif /*USE_LIBDNS*/
err = getsrv_standard (name, list, &srvcount);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_NAME)
err = 0;
goto leave;
}
/* Now we have an array of all the srv records. */
/* Order by priority */
qsort(*list,srvcount,sizeof(struct srventry),priosort);
/* For each priority, move the zero-weighted items first. */
for (i=0; i < srvcount; i++)
{
int j;
for (j=i;j < srvcount && (*list)[i].priority == (*list)[j].priority; j++)
{
if((*list)[j].weight==0)
{
/* Swap j with i */
if(j!=i)
{
struct srventry temp;
memcpy (&temp,&(*list)[j],sizeof(struct srventry));
memcpy (&(*list)[j],&(*list)[i],sizeof(struct srventry));
memcpy (&(*list)[i],&temp,sizeof(struct srventry));
}
break;
}
}
}
/* Run the RFC-2782 weighting algorithm. We don't need very high
quality randomness for this, so regular libc srand/rand is
sufficient. */
{
static int done;
if (!done)
{
done = 1;
srand (time (NULL)*getpid());
}
}
for (i=0; i < srvcount; i++)
{
int j;
float prio_count=0,chose;
for (j=i; j < srvcount && (*list)[i].priority == (*list)[j].priority; j++)
{
prio_count+=(*list)[j].weight;
(*list)[j].run_count=prio_count;
}
chose=prio_count*rand()/RAND_MAX;
for (j=i;j<srvcount && (*list)[i].priority==(*list)[j].priority;j++)
{
if (chose<=(*list)[j].run_count)
{
/* Swap j with i */
if(j!=i)
{
struct srventry temp;
memcpy(&temp,&(*list)[j],sizeof(struct srventry));
memcpy(&(*list)[j],&(*list)[i],sizeof(struct srventry));
memcpy(&(*list)[i],&temp,sizeof(struct srventry));
}
break;
}
}
}
leave:
if (opt_debug)
{
if (err)
log_debug ("dns: getsrv(%s): %s\n", name, gpg_strerror (err));
else
log_debug ("dns: getsrv(%s) -> %u records\n", name, srvcount);
}
if (!err)
*r_count = srvcount;
xfree (namebuffer);
return err;
}
#ifdef USE_LIBDNS
/* libdns version of get_dns_cname. */
gpg_error_t
get_dns_cname_libdns (const char *name, char **r_cname)
{
gpg_error_t err;
struct dns_resolver *res;
struct dns_packet *ans = NULL;
struct dns_cname cname;
int derr;
err = libdns_res_open (&res);
if (err)
goto leave;
err = libdns_res_submit (res, name, DNS_T_CNAME, DNS_C_IN);
if (err)
goto leave;
err = libdns_res_wait (res);
if (err)
goto leave;
ans = dns_res_fetch (res, &derr);
if (!ans)
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Check the rcode. */
switch (dns_p_rcode (ans))
{
case DNS_RC_NOERROR: break;
case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
default: err = GPG_ERR_SERVER_FAILED; break;
}
if (err)
goto leave;
/* Parse the result into CNAME. */
err = libdns_error_to_gpg_error (dns_p_study (ans));
if (err)
goto leave;
if (!dns_d_cname (&cname, sizeof cname, name, strlen (name), ans, &derr))
{
err = libdns_error_to_gpg_error (derr);
goto leave;
}
/* Copy result. */
*r_cname = xtrystrdup (cname.host);
if (!*r_cname)
err = gpg_error_from_syserror ();
else
{
/* Libdns appends the root zone part which is problematic
* for most other functions - strip it. */
if (**r_cname && (*r_cname)[strlen (*r_cname)-1] == '.')
(*r_cname)[strlen (*r_cname)-1] = 0;
}
leave:
dns_free (ans);
dns_res_close (res);
return err;
}
#endif /*USE_LIBDNS*/
/* Standard resolver version of get_dns_cname. */
gpg_error_t
get_dns_cname_standard (const char *name, char **r_cname)
{
#ifdef HAVE_SYSTEM_RESOLVER
gpg_error_t err;
int rc;
union {
unsigned char ans[2048];
HEADER header[1];
} res;
unsigned char *answer = res.ans;
HEADER *header = res.header;
unsigned char *pt, *emsg;
int r;
char *cname;
int cnamesize = 1025;
u16 count;
/* Do not allow a query using the standard resolver in Tor mode. */
if (tor_mode)
return -1;
my_unprotect ();
r = res_query (name, C_IN, T_CERT, answer, sizeof res.ans);
my_protect ();
if (r < 0)
return get_h_errno_as_gpg_error ();
if (r < sizeof (HEADER))
return gpg_error (GPG_ERR_SERVER_FAILED);
if (r > sizeof res.ans)
return gpg_error (GPG_ERR_SYSTEM_BUG);
if (header->rcode != NOERROR || !(count=ntohs (header->ancount)))
return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */
if (count != 1)
return gpg_error (GPG_ERR_SERVER_FAILED);
emsg = &answer[r];
pt = &answer[sizeof(HEADER)];
rc = dn_skipname (pt, emsg);
if (rc == -1)
return gpg_error (GPG_ERR_SERVER_FAILED);
pt += rc + QFIXEDSZ;
if (pt >= emsg)
return gpg_error (GPG_ERR_SERVER_FAILED);
rc = dn_skipname (pt, emsg);
if (rc == -1)
return gpg_error (GPG_ERR_SERVER_FAILED);
pt += rc + 2 + 2 + 4;
if (pt+2 >= emsg)
return gpg_error (GPG_ERR_SERVER_FAILED);
pt += 2; /* Skip rdlen */
cname = xtrymalloc (cnamesize);
if (!cname)
return gpg_error_from_syserror ();
rc = dn_expand (answer, emsg, pt, cname, cnamesize -1);
if (rc == -1)
{
xfree (cname);
return gpg_error (GPG_ERR_SERVER_FAILED);
}
*r_cname = xtryrealloc (cname, strlen (cname)+1);
if (!*r_cname)
{
err = gpg_error_from_syserror ();
xfree (cname);
return err;
}
return 0;
#else /*!HAVE_SYSTEM_RESOLVER*/
(void)name;
(void)r_cname;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif /*!HAVE_SYSTEM_RESOLVER*/
}
gpg_error_t
get_dns_cname (const char *name, char **r_cname)
{
gpg_error_t err;
*r_cname = NULL;
#ifdef USE_LIBDNS
if (!standard_resolver)
{
err = get_dns_cname_libdns (name, r_cname);
if (err && libdns_switch_port_p (err))
err = get_dns_cname_libdns (name, r_cname);
return err;
}
#endif /*USE_LIBDNS*/
err = get_dns_cname_standard (name, r_cname);
if (opt_debug)
log_debug ("get_dns_cname(%s)%s%s\n", name,
err ? ": " : " -> ",
err ? gpg_strerror (err) : *r_cname);
return err;
}
diff --git a/dirmngr/http.c b/dirmngr/http.c
index 0f11af750..3adf6e51e 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1,3144 +1,3144 @@
/* http.c - HTTP protocol handler
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2015-2017 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 <https://www.gnu.org/licenses/>.
*/
/* Simple HTTP client implementation. We try to keep the code as
self-contained as possible. There are some contraints however:
- estream is required. We now require estream because it provides a
very useful and portable asprintf implementation and the fopencookie
function.
- stpcpy is required
- fixme: list other requirements.
- With HTTP_USE_NTBTLS or HTTP_USE_GNUTLS support for https is
provided (this also requires estream).
- With HTTP_NO_WSASTARTUP the socket initialization is not done
under Windows. This is useful if the socket layer has already
been initialized elsewhere. This also avoids the installation of
an exit handler to cleanup the socket layer.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <time.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include <npth.h>
#endif
#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
#endif
#ifdef HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif /*HTTP_USE_GNUTLS*/
#include <assuan.h> /* We need the socket wrapper. */
-#include "util.h"
-#include "i18n.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
#include "dns-stuff.h"
#include "http.h"
#include "http-common.h"
#ifdef USE_NPTH
# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) npth_accept ((a), (b), (c))
#else
# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) accept ((a), (b), (c))
#endif
#ifdef HAVE_W32_SYSTEM
#define sock_close(a) closesocket(a)
#else
#define sock_close(a) close(a)
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef INADDR_NONE /* Slowaris is missing that. */
#define INADDR_NONE ((unsigned long)(-1))
#endif /*INADDR_NONE*/
#define HTTP_PROXY_ENV "http_proxy"
#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890@" \
"!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
#if HTTP_USE_NTBTLS
typedef ntbtls_t tls_session_t;
# define USE_TLS 1
#elif HTTP_USE_GNUTLS
typedef gnutls_session_t tls_session_t;
# define USE_TLS 1
#else
typedef void *tls_session_t;
# undef USE_TLS
#endif
static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls);
static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls);
static int remove_escapes (char *string);
static int insert_escapes (char *buffer, const char *string,
const char *special);
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag,strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (int sock, void *buffer, size_t size);
static gpg_error_t write_server (int sock, const char *data, size_t length);
static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
static gpgrt_ssize_t cookie_write (void *cookie,
const void *buffer, size_t size);
static int cookie_close (void *cookie);
#ifdef HAVE_W32_SYSTEM
static gpgrt_ssize_t simple_cookie_read (void *cookie,
void *buffer, size_t size);
static gpgrt_ssize_t simple_cookie_write (void *cookie,
const void *buffer, size_t size);
#endif
/* A socket object used to a allow ref counting of sockets. */
struct my_socket_s
{
assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */
int refcount; /* Number of references to this socket. */
};
typedef struct my_socket_s *my_socket_t;
/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
cookie_write,
NULL,
cookie_close
};
struct cookie_s
{
/* Socket object or NULL if already closed. */
my_socket_t sock;
/* The session object or NULL if not used. */
http_session_t session;
/* True if TLS is to be used. */
int use_tls;
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
};
typedef struct cookie_s *cookie_t;
/* Simple cookie functions. Here the cookie is an int with the
* socket. */
#ifdef HAVE_W32_SYSTEM
static es_cookie_io_functions_t simple_cookie_functions =
{
simple_cookie_read,
simple_cookie_write,
NULL,
NULL
};
#endif
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
#else
# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */
#endif
/* The session object. */
struct http_session_s
{
unsigned long magic;
int refcount; /* Number of references to this object. */
#ifdef HTTP_USE_GNUTLS
gnutls_certificate_credentials_t certcred;
#endif /*HTTP_USE_GNUTLS*/
#ifdef USE_TLS
tls_session_t tls_session;
struct {
int done; /* Verifciation has been done. */
int rc; /* TLS verification return code. */
unsigned int status; /* Verification status. */
} verify;
char *servername; /* Malloced server name. */
#endif /*USE_TLS*/
/* A callback function to log details of TLS certifciates. */
void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
const void **, size_t *);
/* The flags passed to the session object. */
unsigned int flags;
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
};
/* An object to save header lines. */
struct header_s
{
struct header_s *next;
char *value; /* The value of the header (malloced). */
char name[1]; /* The name of the header (canonicalized). */
};
typedef struct header_s *header_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
#else
# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */
#endif
/* Our handle context. */
struct http_context_s
{
unsigned long magic;
unsigned int status_code;
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
void *read_cookie;
http_session_t session;
parsed_uri_t uri;
http_req_t req_type;
char *buffer; /* Line buffer. */
size_t buffer_size;
unsigned int flags;
header_t headers; /* Received headers. */
};
/* Two flags to enable verbose and debug mode. Although currently not
* set-able a value > 1 for OPT_DEBUG enables debugging of the session
* reference counting. */
static int opt_verbose;
static int opt_debug;
/* The global callback for the verification function. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
/* The list of files with trusted CA certificates. */
static strlist_t tls_ca_certlist;
/* The global callback for net activity. */
static void (*netactivity_cb)(void);
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
#if GNUPG_MAJOR_VERSION == 1
#define REQ_WINSOCK_MAJOR 1
#define REQ_WINSOCK_MINOR 1
#else
#define REQ_WINSOCK_MAJOR 2
#define REQ_WINSOCK_MINOR 2
#endif
static void
deinit_sockets (void)
{
WSACleanup();
}
static void
init_sockets (void)
{
static int initialized;
static WSADATA wsdata;
if (initialized)
return;
if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
{
log_error ("error initializing socket library: ec=%d\n",
(int)WSAGetLastError () );
return;
}
if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
|| HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
{
log_error ("socket library version is %x.%x - but %d.%d needed\n",
LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
WSACleanup();
return;
}
atexit ( deinit_sockets );
initialized = 1;
}
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
/* Create a new socket object. Returns NULL and closes FD if not
enough memory is available. */
static my_socket_t
_my_socket_new (int lnr, assuan_fd_t fd)
{
my_socket_t so;
so = xtrymalloc (sizeof *so);
if (!so)
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return NULL;
}
so->fd = fd;
so->refcount = 1;
if (opt_debug)
log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
lnr, so, so->fd);
return so;
}
#define my_socket_new(a) _my_socket_new (__LINE__, (a))
/* Bump up the reference counter for the socket object SO. */
static my_socket_t
_my_socket_ref (int lnr, my_socket_t so)
{
so->refcount++;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
lnr, so, so->fd, so->refcount);
return so;
}
#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
/* Bump down the reference counter for the socket object SO. If SO
has no more references, close the socket and release the
object. */
static void
_my_socket_unref (int lnr, my_socket_t so,
void (*preclose)(void*), void *preclosearg)
{
if (so)
{
so->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
lnr, so, so->fd, so->refcount);
if (!so->refcount)
{
if (preclose)
preclose (preclosearg);
assuan_sock_close (so->fd);
xfree (so);
}
}
}
#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
#ifdef HTTP_USE_GNUTLS
static ssize_t
my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_read (sock->fd, buffer, size);
#else
return read (sock->fd, buffer, size);
#endif
}
static ssize_t
my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_write (sock->fd, buffer, size);
#else
return write (sock->fd, buffer, size);
#endif
}
#endif /*HTTP_USE_GNUTLS*/
#ifdef HTTP_USE_NTBTLS
/* Connect the ntbls callback to our generic callback. */
static gpg_error_t
my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
{
http_t hd = opaque;
(void)verify_flags;
log_assert (hd && hd->session && hd->session->verify_cb);
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
return hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
tls);
}
#endif /*HTTP_USE_NTBTLS*/
/* This notification function is called by estream whenever stream is
closed. Its purpose is to mark the closing in the handle so
that a http_close won't accidentally close the estream. The function
http_close removes this notification so that it won't be called if
http_close was used before an es_fclose. */
static void
fp_onclose_notification (estream_t stream, void *opaque)
{
http_t hd = opaque;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
if (hd->fp_read && hd->fp_read == stream)
hd->fp_read = NULL;
else if (hd->fp_write && hd->fp_write == stream)
hd->fp_write = NULL;
}
/*
* Helper function to create an HTTP header with hex encoded data. A
* new buffer is returned. This buffer is the concatenation of the
* string PREFIX, the hex-encoded DATA of length LEN and the string
* SUFFIX. On error NULL is returned and ERRNO set.
*/
static char *
make_header_line (const char *prefix, const char *suffix,
const void *data, size_t len )
{
static unsigned char bintoasc[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const unsigned char *s = data;
char *buffer, *p;
buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
if (!buffer)
return NULL;
p = stpcpy (buffer, prefix);
for ( ; len >= 3 ; len -= 3, s += 3 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
*p++ = bintoasc[s[2]&077];
*p = 0;
}
if ( len == 2 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[((s[1]<<2)&074)];
*p++ = '=';
}
else if ( len == 1 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(s[0] <<4)&060];
*p++ = '=';
*p++ = '=';
}
*p = 0;
strcpy (p, suffix);
return buffer;
}
/* Set verbosity and debug mode for this module. */
void
http_set_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
always returns NULL. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
tls_callback = cb;
}
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
removed. */
void
http_register_tls_ca (const char *fname)
{
strlist_t sl;
if (!fname)
{
free_strlist (tls_ca_certlist);
tls_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if (access (fname, F_OK))
log_info (_("can't access '%s': %s\n"), fname,
gpg_strerror (gpg_error_from_syserror()));
sl = add_to_strlist (&tls_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a callback which is called every time the HTTP mode has
* made a successful connection to some server. */
void
http_register_netactivity_cb (void (*cb)(void))
{
netactivity_cb = cb;
}
/* Call the netactivity callback if any. */
static void
notify_netactivity (void)
{
if (netactivity_cb)
netactivity_cb ();
}
#ifdef USE_TLS
/* Free the TLS session associated with SESS, if any. */
static void
close_tls_session (http_session_t sess)
{
if (sess->tls_session)
{
# if HTTP_USE_NTBTLS
/* FIXME!!
Possibly, ntbtls_get_transport and close those streams.
Somehow get SOCK to call my_socket_unref.
*/
ntbtls_release (sess->tls_session);
# elif HTTP_USE_GNUTLS
my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
my_socket_unref (sock, NULL, NULL);
gnutls_deinit (sess->tls_session);
if (sess->certcred)
gnutls_certificate_free_credentials (sess->certcred);
# endif /*HTTP_USE_GNUTLS*/
xfree (sess->servername);
sess->tls_session = NULL;
}
}
#endif /*USE_TLS*/
/* Release a session. Take care not to release it while it is being
used by a http context object. */
static void
session_unref (int lnr, http_session_t sess)
{
if (!sess)
return;
log_assert (sess->magic == HTTP_SESSION_MAGIC);
sess->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
lnr, sess, sess->refcount);
if (sess->refcount)
return;
#ifdef USE_TLS
close_tls_session (sess);
#endif /*USE_TLS*/
sess->magic = 0xdeadbeef;
xfree (sess);
}
#define http_session_unref(a) session_unref (__LINE__, (a))
void
http_session_release (http_session_t sess)
{
http_session_unref (sess);
}
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
* HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
* HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
* HTTP_FLAG_NO_CRL - Do not consult CRLs for https.
*/
gpg_error_t
http_session_new (http_session_t *r_session,
const char *intended_hostname, unsigned int flags,
http_verify_cb_t verify_cb, void *verify_cb_value)
{
gpg_error_t err;
http_session_t sess;
*r_session = NULL;
sess = xtrycalloc (1, sizeof *sess);
if (!sess)
return gpg_error_from_syserror ();
sess->magic = HTTP_SESSION_MAGIC;
sess->refcount = 1;
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
#if HTTP_USE_NTBTLS
{
(void)intended_hostname; /* Not needed because we do not preload
* certificates. */
err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
if (err)
{
log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
goto leave;
}
}
#elif HTTP_USE_GNUTLS
{
const char *errpos;
int rc;
strlist_t sl;
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
is_hkps_pool = (intended_hostname
&& !ascii_strcasecmp (intended_hostname,
get_default_keyserver (1)));
/* If the user has not specified a CA list, and they are looking
* for the hkps pool from sks-keyservers.net, then default to
* Kristian's certificate authority: */
if (!tls_ca_certlist && is_hkps_pool)
{
char *pemname = make_filename_try (gnupg_datadir (),
"sks-keyservers.netCA.pem", NULL);
if (!pemname)
{
err = gpg_error_from_syserror ();
log_error ("setting CA from file '%s' failed: %s\n",
pemname, gpg_strerror (err));
}
else
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, pemname, GNUTLS_X509_FMT_PEM);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
pemname, gnutls_strerror (rc));
xfree (pemname);
}
}
/* Add configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_DEF))
{
for (sl = tls_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
if (!tls_ca_certlist && !is_hkps_pool)
add_system_cas = 1;
}
/* Add system certificates to the session. */
if (add_system_cas)
{
#if GNUTLS_VERSION_NUMBER >= 0x030014
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
if (rc < 0)
log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
else if (!shown)
{
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
#endif /* gnutls >= 3.0.20 */
}
rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
if (rc < 0)
{
log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* A new session has the transport ptr set to (void*(-1), we need
it to be NULL. */
gnutls_transport_set_ptr (sess->tls_session, NULL);
rc = gnutls_priority_set_direct (sess->tls_session,
"NORMAL",
&errpos);
if (rc < 0)
{
log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
errpos, gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnutls_credentials_set (sess->tls_session,
GNUTLS_CRD_CERTIFICATE, sess->certcred);
if (rc < 0)
{
log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
{
(void)intended_hostname;
(void)flags;
}
#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
if (opt_debug > 1)
log_debug ("http.c:session_new: sess %p created\n", sess);
err = 0;
#if USE_TLS
leave:
#endif /*USE_TLS*/
if (err)
http_session_unref (sess);
else
*r_session = sess;
return err;
}
/* Increment the reference count for session SESS. Passing NULL for
SESS is allowed. */
http_session_t
http_session_ref (http_session_t sess)
{
if (sess)
{
sess->refcount++;
if (opt_debug > 1)
log_debug ("http.c:session_ref: sess %p ref now %d\n",
sess, sess->refcount);
}
return sess;
}
void
http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *hostname,
const void **certs, size_t *certlens))
{
sess->cert_log_cb = cb;
}
/* Start a HTTP retrieval and on success store at R_HD a context
pointer for completing the request and to wait for the response.
If HTTPHOST is not NULL it is used for the Host header instead of a
Host header derived from the URL. */
gpg_error_t
http_open (http_t *r_hd, http_req_t reqtype, const char *url,
const char *httphost,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
http_t hd;
*r_hd = NULL;
if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = reqtype;
hd->flags = flags;
hd->session = http_session_ref (session);
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (hd, httphost, auth, proxy, srvtag, headers);
if (err)
{
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. */
gpg_error_t
http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
unsigned int flags, const char *srvtag)
{
gpg_error_t err = 0;
http_t hd;
cookie_t cookie;
*r_hd = NULL;
if ((flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
}
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = HTTP_REQ_OPAQUE;
hd->flags = flags;
/* Connect. */
{
assuan_fd_t sock;
err = connect_server (server, port, hd->flags, srvtag, &sock);
if (err)
{
xfree (hd);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (hd);
return err;
}
}
/* Setup estreams for reading and writing. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
leave:
if (err)
{
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
my_socket_unref (hd->sock, NULL, NULL);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
void
http_start_data (http_t hd)
{
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_with_string ("\r\n", "http.c:request-header:");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
}
else
es_fflush (hd->fp_write);
}
gpg_error_t
http_wait_response (http_t hd)
{
gpg_error_t err;
cookie_t cookie;
/* Make sure that we are in the data. */
http_start_data (hd);
/* Close the write stream. Note that the reference counted socket
object keeps the actual system socket open. */
cookie = hd->write_cookie;
if (!cookie)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it to NULL. */
hd->write_cookie = NULL;
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
if ((hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (hd->sock->fd, 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
cookie->sock = my_socket_ref (hd->sock);
cookie->session = http_session_ref (hd->session);
cookie->use_tls = hd->uri->use_tls;
hd->read_cookie = cookie;
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
http_session_unref (cookie->session);
xfree (cookie);
hd->read_cookie = NULL;
return err;
}
err = parse_response (hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
}
/* Convenience function to send a request and wait for the response.
Closes the handle on error. If PROXY is not NULL, this value will
be used as an HTTP proxy and any enabled $http_proxy gets
ignored. */
gpg_error_t
http_open_document (http_t *r_hd, const char *document,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session,
const char *srvtag, strlist_t headers)
{
gpg_error_t err;
err = http_open (r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
proxy, session, srvtag, headers);
if (err)
return err;
err = http_wait_response (*r_hd);
if (err)
http_close (*r_hd, 0);
return err;
}
void
http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
/* First remove the close notifications for the streams. */
if (hd->fp_read)
es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
if (hd->fp_write)
es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
/* Now we can close the streams. */
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
hd->magic = 0xdeadbeef;
http_release_parsed_uri (hd->uri);
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
xfree (hd->buffer);
xfree (hd);
}
estream_t
http_get_read_ptr (http_t hd)
{
return hd?hd->fp_read:NULL;
}
estream_t
http_get_write_ptr (http_t hd)
{
return hd?hd->fp_write:NULL;
}
unsigned int
http_get_status_code (http_t hd)
{
return hd?hd->status_code:0;
}
/* Return information pertaining to TLS. If TLS is not in use for HD,
NULL is returned. WHAT is used ask for specific information:
(NULL) := Only check whether TLS is in use. Returns an
unspecified string if TLS is in use. That string may
even be the empty string.
*/
const char *
http_get_tls_info (http_t hd, const char *what)
{
(void)what;
if (!hd)
return NULL;
return hd->uri->use_tls? "":NULL;
}
static gpg_error_t
parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls)
{
gpg_err_code_t ec;
*ret_uri = xtrycalloc (1, sizeof **ret_uri + strlen (uri));
if (!*ret_uri)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
strcpy ((*ret_uri)->buffer, uri);
ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
if (ec)
{
xfree (*ret_uri);
*ret_uri = NULL;
}
return gpg_err_make (default_errsource, ec);
}
/*
* Parse an URI and put the result into the newly allocated RET_URI.
* On success the caller must use http_release_parsed_uri() to
* releases the resources. If NO_SCHEME_CHECK is set, the function
* tries to parse the URL in the same way it would do for an HTTP
* style URI.
*/
gpg_error_t
http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check)
{
return parse_uri (ret_uri, uri, no_scheme_check, 0);
}
void
http_release_parsed_uri (parsed_uri_t uri)
{
if (uri)
{
uri_tuple_t r, r2;
for (r = uri->query; r; r = r2)
{
r2 = r->next;
xfree (r);
}
xfree (uri);
}
}
static gpg_err_code_t
do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls)
{
uri_tuple_t *tail;
char *p, *p2, *p3, *pp;
int n;
p = uri->buffer;
n = strlen (uri->buffer);
/* Initialize all fields to an empty string or an empty list. */
uri->scheme = uri->host = uri->path = p + n;
uri->port = 0;
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
uri->opaque = 0;
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
/* A quick validity check. */
if (strspn (p, VALID_URI_CHARS) != n)
return GPG_ERR_BAD_URI; /* Invalid characters found. */
if (!only_local_part)
{
/* Find the scheme. */
if (!(p2 = strchr (p, ':')) || p2 == p)
return GPG_ERR_BAD_URI; /* No scheme. */
*p2++ = 0;
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
uri->scheme = p;
if (!strcmp (uri->scheme, "http") && !force_tls)
{
uri->port = 80;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "hkp") && !force_tls)
{
uri->port = 11371;
uri->is_http = 1;
}
#ifdef USE_TLS
else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
|| (force_tls && (!strcmp (uri->scheme, "http")
|| !strcmp (uri->scheme,"hkp"))))
{
uri->port = 443;
uri->is_http = 1;
uri->use_tls = 1;
}
#endif /*USE_TLS*/
else if (!no_scheme_check)
return GPG_ERR_INV_URI; /* Unsupported scheme */
p = p2;
if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
p += 2;
if ((p2 = strchr (p, '/')))
*p2++ = 0;
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
{
uri->auth = p;
*p3++ = '\0';
p = p3;
}
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
/* Handle an IPv6 literal */
if( *p == '[' && (p3=strchr( p, ']' )) )
{
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
uri->v6lit = 1;
p = p3;
}
else
uri->host = p;
if ((p3 = strchr (p, ':')))
{
*p3++ = '\0';
uri->port = atoi (p3);
uri->explicit_port = 1;
}
if ((n = remove_escapes (uri->host)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (uri->host))
return GPG_ERR_BAD_URI; /* Hostname incudes a Nul. */
p = p2 ? p2 : NULL;
}
else if (uri->is_http)
return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */
else
{
uri->opaque = 1;
uri->path = p;
if (is_onion_address (uri->path))
uri->onion = 1;
return 0;
}
} /* End global URI part. */
/* Parse the pathname part if any. */
if (p && *p)
{
/* TODO: Here we have to check params. */
/* Do we have a query part? */
if ((p2 = strchr (p, '?')))
*p2++ = 0;
uri->path = p;
if ((n = remove_escapes (p)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (p))
return GPG_ERR_BAD_URI; /* Path includes a Nul. */
p = p2 ? p2 : NULL;
/* Parse a query string if any. */
if (p && *p)
{
tail = &uri->query;
for (;;)
{
uri_tuple_t elem;
if ((p2 = strchr (p, '&')))
*p2++ = 0;
if (!(elem = parse_tuple (p)))
return GPG_ERR_BAD_URI;
*tail = elem;
tail = &elem->next;
if (!p2)
break; /* Ready. */
p = p2;
}
}
}
if (is_onion_address (uri->host))
uri->onion = 1;
return 0;
}
/*
* Remove all %xx escapes; this is done in-place. Returns: New length
* of the string.
*/
static int
remove_escapes (char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = (unsigned char*)string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
{
s++;
*p = *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
*p <<= 4;
s++;
*p |= *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Make sure to keep a string terminator. */
return n;
}
/* If SPECIAL is NULL this function escapes in forms mode. */
static size_t
escape_data (char *buffer, const void *data, size_t datalen,
const char *special)
{
int forms = !special;
const unsigned char *s;
size_t n = 0;
if (forms)
special = "%;?&=";
for (s = data; datalen; s++, datalen--)
{
if (forms && *s == ' ')
{
if (buffer)
*buffer++ = '+';
n++;
}
else if (forms && *s == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
}
else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
s++;
datalen--;
}
else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
{
if (buffer)
*(unsigned char*)buffer++ = *s;
n++;
}
else
{
if (buffer)
{
snprintf (buffer, 4, "%%%02X", *s);
buffer += 3;
}
n += 3;
}
}
return n;
}
static int
insert_escapes (char *buffer, const char *string,
const char *special)
{
return escape_data (buffer, string, strlen (string), special);
}
/* Allocate a new string from STRING using standard HTTP escaping as
well as escaping of characters given in SPECIALS. A common pattern
for SPECIALS is "%;?&=". However it depends on the needs, for
example "+" and "/: often needs to be escaped too. Returns NULL on
failure and sets ERRNO. If SPECIAL is NULL a dedicated forms
encoding mode is used. */
char *
http_escape_string (const char *string, const char *specials)
{
int n;
char *buf;
n = insert_escapes (NULL, string, specials);
buf = xtrymalloc (n+1);
if (buf)
{
insert_escapes (buf, string, specials);
buf[n] = 0;
}
return buf;
}
/* Allocate a new string from {DATA,DATALEN} using standard HTTP
escaping as well as escaping of characters given in SPECIALS. A
common pattern for SPECIALS is "%;?&=". However it depends on the
needs, for example "+" and "/: often needs to be escaped too.
Returns NULL on failure and sets ERRNO. If SPECIAL is NULL a
dedicated forms encoding mode is used. */
char *
http_escape_data (const void *data, size_t datalen, const char *specials)
{
int n;
char *buf;
n = escape_data (NULL, data, datalen, specials);
buf = xtrymalloc (n+1);
if (buf)
{
escape_data (buf, data, datalen, specials);
buf[n] = 0;
}
return buf;
}
static uri_tuple_t
parse_tuple (char *string)
{
char *p = string;
char *p2;
int n;
uri_tuple_t tuple;
if ((p2 = strchr (p, '=')))
*p2++ = 0;
if ((n = remove_escapes (p)) < 0)
return NULL; /* Bad URI. */
if (n != strlen (p))
return NULL; /* Name with a Nul in it. */
tuple = xtrycalloc (1, sizeof *tuple);
if (!tuple)
return NULL; /* Out of core. */
tuple->name = p;
if (!p2) /* We have only the name, so we assume an empty value string. */
{
tuple->value = p + strlen (p);
tuple->valuelen = 0;
tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
}
else /* Name and value. */
{
if ((n = remove_escapes (p2)) < 0)
{
xfree (tuple);
return NULL; /* Bad URI. */
}
tuple->value = p2;
tuple->valuelen = n;
}
return tuple;
}
/* Return true if STRING is likely "hostname:port" or only "hostname". */
static int
is_hostname_port (const char *string)
{
int colons = 0;
if (!string || !*string)
return 0;
for (; *string; string++)
{
if (*string == ':')
{
if (colons)
return 0;
if (!string[1])
return 0;
colons++;
}
else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
return 0; /* Invalid characters in hostname. */
else if (colons && !digitp (string))
return 0; /* Not a digit in the port. */
}
return 1;
}
/*
* Send a HTTP request to the server
* Returns 0 if the request was successful
*/
static gpg_error_t
send_request (http_t hd, const char *httphost, const char *auth,
const char *proxy, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
const char *server;
char *request, *p;
unsigned short port;
const char *http_proxy = NULL;
char *proxy_authstr = NULL;
char *authstr = NULL;
int sock;
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
#ifdef USE_TLS
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no GNUTLS context available\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
#endif /*USE_TLS*/
if ((hd->flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
}
server = *hd->uri->host ? hd->uri->host : "localhost";
port = hd->uri->port ? hd->uri->port : 80;
/* Try to use SNI. */
#ifdef USE_TLS
if (hd->uri->use_tls)
{
# if HTTP_USE_GNUTLS
int rc;
# endif
xfree (hd->session->servername);
hd->session->servername = xtrystrdup (httphost? httphost : server);
if (!hd->session->servername)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
return err;
}
# if HTTP_USE_NTBTLS
err = ntbtls_set_hostname (hd->session->tls_session,
hd->session->servername);
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
return err;
}
# elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
GNUTLS_NAME_DNS,
hd->session->servername,
strlen (hd->session->servername));
if (rc < 0)
log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
# endif /*HTTP_USE_GNUTLS*/
}
#endif /*USE_TLS*/
if ( (proxy && *proxy)
|| ( (hd->flags & HTTP_FLAG_TRY_PROXY)
&& (http_proxy = getenv (HTTP_PROXY_ENV))
&& *http_proxy ))
{
parsed_uri_t uri;
if (proxy)
http_proxy = proxy;
err = parse_uri (&uri, http_proxy, 0, 0);
if (gpg_err_code (err) == GPG_ERR_INV_URI
&& is_hostname_port (http_proxy))
{
/* Retry assuming a "hostname:port" string. */
char *tmpname = strconcat ("http://", http_proxy, NULL);
if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
err = 0;
xfree (tmpname);
}
if (err)
;
else if (!strcmp (uri->scheme, "http") || !strcmp (uri->scheme, "socks4"))
;
else if (!strcmp (uri->scheme, "socks5h"))
err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
else
err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
if (err)
{
log_error ("invalid HTTP proxy (%s): %s\n",
http_proxy, gpg_strerror (err));
return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
}
if (uri->auth)
{
remove_escapes (uri->auth);
proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
uri->auth, strlen(uri->auth));
if (!proxy_authstr)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
http_release_parsed_uri (uri);
return err;
}
}
err = connect_server (*uri->host ? uri->host : "localhost",
uri->port ? uri->port : 80,
hd->flags, srvtag, &sock);
http_release_parsed_uri (uri);
}
else
{
err = connect_server (server, port, hd->flags, srvtag, &sock);
}
if (err)
{
xfree (proxy_authstr);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
#if HTTP_USE_NTBTLS
if (hd->uri->use_tls)
{
estream_t in, out;
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
* to use es_fopencookie. */
#ifdef HAVE_W32_SYSTEM
in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb",
simple_cookie_functions);
#else
in = es_fdopen_nc (hd->sock->fd, "rb");
#endif
if (!in)
{
err = gpg_error_from_syserror ();
xfree (proxy_authstr);
return err;
}
#ifdef HAVE_W32_SYSTEM
out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb",
simple_cookie_functions);
#else
out = es_fdopen_nc (hd->sock->fd, "wb");
#endif
if (!out)
{
err = gpg_error_from_syserror ();
es_fclose (in);
xfree (proxy_authstr);
return err;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
if (err)
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
#ifdef HTTP_USE_NTBTLS
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
my_ntbtls_verify_cb, hd);
if (err)
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_NTBTLS*/
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
hd->session->verify.done = 0;
/* Try the available verify callbacks until one returns success
* or a real error. Note that NTBTLS does the verification
* during the handshake via */
#ifdef HTTP_USE_NTBTLS
err = 0; /* Fixme check that the CB has been called. */
#else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
if (hd->session->verify_cb
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
hd->session->tls_session);
if (tls_callback
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = tls_callback (hd, hd->session, 0);
if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
#elif HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
int rc;
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
my_gnutls_read);
gnutls_transport_set_push_function (hd->session->tls_session,
my_gnutls_write);
do
{
rc = gnutls_handshake (hd->session->tls_session);
}
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc < 0)
{
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
|| rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
gnutls_alert_description_t alertno;
const char *alertstr;
alertno = gnutls_alert_get (hd->session->tls_session);
alertstr = gnutls_alert_get_name (alertno);
log_info ("TLS handshake failed: %s (alert %d)\n",
alertstr, (int)alertno);
if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
log_info (" (sent server name '%s')\n", server);
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
xfree (proxy_authstr);
return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
}
hd->session->verify.done = 0;
if (tls_callback)
err = tls_callback (hd, hd->session, 0);
else
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_GNUTLS*/
if (auth || hd->uri->auth)
{
char *myauth;
if (auth)
{
myauth = xtrystrdup (auth);
if (!myauth)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
remove_escapes (myauth);
}
else
{
remove_escapes (hd->uri->auth);
myauth = hd->uri->auth;
}
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
if (auth)
xfree (myauth);
if (!authstr)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
}
p = build_rel_path (hd->uri);
if (!p)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (http_proxy && *http_proxy)
{
request = es_bsprintf
("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
hd->uri->use_tls? "https" : "http",
httphost? httphost : server,
port, *p == '/' ? "" : "/", p,
authstr ? authstr : "",
proxy_authstr ? proxy_authstr : "");
}
else
{
char portstr[35];
if (port == (hd->uri->use_tls? 443 : 80))
*portstr = 0;
else
snprintf (portstr, sizeof portstr, ":%u", port);
request = es_bsprintf
("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
*p == '/' ? "" : "/", p,
httphost? httphost : server,
portstr,
authstr? authstr:"");
}
xfree (p);
if (!request)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (authstr);
xfree (proxy_authstr);
return err;
}
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_with_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
cookie->use_tls = hd->uri->use_tls;
cookie->session = http_session_ref (hd->session);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
hd->write_cookie = NULL;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
if (!err)
{
for (;headers; headers=headers->next)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_with_string (headers->d, "http.c:request-header:");
if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
|| (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
break;
}
}
}
}
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
return err;
}
/*
* Build the relative path from the parsed URI. Minimal
* implementation. May return NULL in case of memory failure; errno
* is then set accordingly.
*/
static char *
build_rel_path (parsed_uri_t uri)
{
uri_tuple_t r;
char *rel_path, *p;
int n;
/* Count the needed space. */
n = insert_escapes (NULL, uri->path, "%;?&");
/* TODO: build params. */
for (r = uri->query; r; r = r->next)
{
n++; /* '?'/'&' */
n += insert_escapes (NULL, r->name, "%;?&=");
if (!r->no_value)
{
n++; /* '=' */
n += insert_escapes (NULL, r->value, "%;?&=");
}
}
n++;
/* Now allocate and copy. */
p = rel_path = xtrymalloc (n);
if (!p)
return NULL;
n = insert_escapes (p, uri->path, "%;?&");
p += n;
/* TODO: add params. */
for (r = uri->query; r; r = r->next)
{
*p++ = r == uri->query ? '?' : '&';
n = insert_escapes (p, r->name, "%;?&=");
p += n;
if (!r->no_value)
{
*p++ = '=';
/* TODO: Use valuelen. */
n = insert_escapes (p, r->value, "%;?&=");
p += n;
}
}
*p = 0;
return rel_path;
}
/* Transform a header name into a standard capitalized format; e.g.
"Content-Type". Conversion stops at the colon. As usual we don't
use the localized versions of ctype.h. */
static void
capitalize_header_name (char *name)
{
int first = 1;
for (; *name && *name != ':'; name++)
{
if (*name == '-')
first = 1;
else if (first)
{
if (*name >= 'a' && *name <= 'z')
*name = *name - 'a' + 'A';
first = 0;
}
else if (*name >= 'A' && *name <= 'Z')
*name = *name - 'A' + 'a';
}
}
/* Store an HTTP header line in LINE away. Line continuation is
supported as well as merging of headers with the same name. This
function may modify LINE. */
static gpg_err_code_t
store_header (http_t hd, char *line)
{
size_t n;
char *p, *value;
header_t h;
n = strlen (line);
if (n && line[n-1] == '\n')
{
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
}
if (!n) /* we are never called to hit this. */
return GPG_ERR_BUG;
if (*line == ' ' || *line == '\t')
{
/* Continuation. This won't happen too often as it is not
recommended. We use a straightforward implementaion. */
if (!hd->headers)
return GPG_ERR_PROTOCOL_VIOLATION;
n += strlen (hd->headers->value);
p = xtrymalloc (n+1);
if (!p)
return gpg_err_code_from_syserror ();
strcpy (stpcpy (p, hd->headers->value), line);
xfree (hd->headers->value);
hd->headers->value = p;
return 0;
}
capitalize_header_name (line);
p = strchr (line, ':');
if (!p)
return GPG_ERR_PROTOCOL_VIOLATION;
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, line) )
break;
if (h)
{
/* We have already seen a line with that name. Thus we assume
* it is a comma separated list and merge them. */
p = strconcat (h->value, ",", value, NULL);
if (!p)
return gpg_err_code_from_syserror ();
xfree (h->value);
h->value = p;
return 0;
}
/* Append a new header. */
h = xtrymalloc (sizeof *h + strlen (line));
if (!h)
return gpg_err_code_from_syserror ();
strcpy (h->name, line);
h->value = xtrymalloc (strlen (value)+1);
if (!h->value)
{
xfree (h);
return gpg_err_code_from_syserror ();
}
strcpy (h->value, value);
h->next = hd->headers;
hd->headers = h;
return 0;
}
/* Return the header NAME from the last response. The returned value
is valid as along as HD has not been closed and no other request
has been send. If the header was not found, NULL is returned. NAME
must be canonicalized, that is the first letter of each dash
delimited part must be uppercase and all other letters lowercase. */
const char *
http_get_header (http_t hd, const char *name)
{
header_t h;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, name) )
return h->value;
return NULL;
}
/* Return a newly allocated and NULL terminated array with pointers to
header names. The array must be released with xfree() and its
content is only values as long as no other request has been
send. */
const char **
http_get_header_names (http_t hd)
{
const char **array;
size_t n;
header_t h;
for (n=0, h = hd->headers; h; h = h->next)
n++;
array = xtrycalloc (n+1, sizeof *array);
if (array)
{
for (n=0, h = hd->headers; h; h = h->next)
array[n++] = h->name;
}
return array;
}
/*
* Parse the response from a server.
* Returns: Errorcode and sets some files in the handle
*/
static gpg_err_code_t
parse_response (http_t hd)
{
char *line, *p, *p2;
size_t maxlen, len;
cookie_t cookie = hd->read_cookie;
const char *s;
/* Delete old header lines. */
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
/* Wait for the status line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
if (!maxlen)
return GPG_ERR_TRUNCATED; /* Line has been truncated. */
if (!len)
return GPG_ERR_EOF;
if ((hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_with_string (line, "http.c:response:\n");
}
while (!*line);
if ((p = strchr (line, '/')))
*p++ = 0;
if (!p || strcmp (line, "HTTP"))
return 0; /* Assume http 0.9. */
if ((p2 = strpbrk (p, " \t")))
{
*p2++ = 0;
p2 += strspn (p2, " \t");
}
if (!p2)
return 0; /* Also assume http 0.9. */
p = p2;
/* TODO: Add HTTP version number check. */
if ((p2 = strpbrk (p, " \t")))
*p2++ = 0;
if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
|| !isdigit ((unsigned int)p[2]) || p[3])
{
/* Malformed HTTP status code - assume http 0.9. */
hd->is_http_0_9 = 1;
hd->status_code = 200;
return 0;
}
hd->status_code = atoi (p);
/* Skip all the header lines and wait for the empty line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
/* Note, that we can silently ignore truncated lines. */
if (!len)
return GPG_ERR_EOF;
/* Trim line endings of empty lines. */
if ((*line == '\r' && line[1] == '\n') || *line == '\n')
*line = 0;
if ((hd->flags & HTTP_FLAG_LOG_RESP))
log_info ("http.c:RESP: '%.*s'\n",
(int)strlen(line)-(*line&&line[1]?2:0),line);
if (*line)
{
gpg_err_code_t ec = store_header (hd, line);
if (ec)
return ec;
}
}
while (len && *line);
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
s = http_get_header (hd, "Content-Length");
if (s)
{
cookie->content_length_valid = 1;
cookie->content_length = string_to_u64 (s);
}
}
return 0;
}
#if 0
static int
start_server ()
{
struct sockaddr_in mya;
struct sockaddr_in peer;
int fd, client;
fd_set rfds;
int addrlen;
int i;
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
log_error ("socket() failed: %s\n", strerror (errno));
return -1;
}
i = 1;
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
mya.sin_family = AF_INET;
memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
mya.sin_port = htons (11371);
if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
{
log_error ("bind to port 11371 failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
if (listen (fd, 5))
{
log_error ("listen failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
for (;;)
{
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* ignore any errors */
if (!FD_ISSET (fd, &rfds))
continue;
addrlen = sizeof peer;
client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
if (client == -1)
continue; /* oops */
log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
fflush (stdout);
fflush (stderr);
if (!fork ())
{
int c;
FILE *fp;
fp = fdopen (client, "r");
while ((c = getc (fp)) != EOF)
putchar (c);
fclose (fp);
exit (0);
}
sock_close (client);
}
return 0;
}
#endif
/* Return true if SOCKS shall be used. This is the case if tor_mode
* is enabled and the desired address is not the loopback address.
* This function is basically a copy of the same internal fucntion in
* Libassuan. */
static int
use_socks (struct sockaddr *addr)
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
return 0; /* Not in Tor mode. */
else if (addr->sa_family == AF_INET6)
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
const unsigned char *s;
int i;
s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->sa_family == AF_INET)
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
/* Wrapper around assuan_sock_new which takes the domain from an
* address parameter. */
static assuan_fd_t
my_sock_new_for_addr (struct sockaddr *addr, int type, int proto)
{
int domain;
if (use_socks (addr))
{
/* Libassaun always uses 127.0.0.1 to connect to the socks
* server (i.e. the Tor daemon). */
domain = AF_INET;
}
else
domain = addr->sa_family;
return assuan_sock_new (domain, type, proto);
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at
* R_SOCK. */
static gpg_error_t
connect_server (const char *server, unsigned short port,
unsigned int flags, const char *srvtag, assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
unsigned int srvcount = 0;
int hostfound = 0;
int anyhostaddr = 0;
int srv, connected;
gpg_error_t last_err = 0;
struct srventry *serverlist = NULL;
*r_sock = ASSUAN_INVALID_FD;
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
init_sockets ();
#endif /*Windows*/
/* Onion addresses require special treatment. */
if (is_onion_address (server))
{
#ifdef ASSUAN_SOCK_TOR
if (opt_debug)
log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
server, port);
sock = assuan_sock_connect_byname (server, port, 0, NULL,
ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
(errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
: gpg_err_code_from_syserror ());
log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
return err;
}
notify_netactivity ();
*r_sock = sock;
return 0;
#else /*!ASSUAN_SOCK_TOR*/
err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
return ASSUAN_INVALID_FD;
#endif /*!HASSUAN_SOCK_TOR*/
}
/* Do the SRV thing */
if (srvtag)
{
err = get_dns_srv (server, srvtag, NULL, &serverlist, &srvcount);
if (err)
log_info ("getting '%s' SRV for '%s' failed: %s\n",
srvtag, server, gpg_strerror (err));
/* Note that on error SRVCOUNT is zero. */
err = 0;
}
if (!serverlist)
{
/* Either we're not using SRV, or the SRV lookup failed. Make
up a fake SRV record. */
serverlist = xtrycalloc (1, sizeof *serverlist);
if (!serverlist)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
serverlist->port = port;
strncpy (serverlist->target, server, DIMof (struct srventry, target));
serverlist->target[DIMof (struct srventry, target)-1] = '\0';
srvcount = 1;
}
connected = 0;
for (srv=0; srv < srvcount && !connected; srv++)
{
dns_addrinfo_t aibuf, ai;
if (opt_debug)
log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
serverlist[srv].target, port);
err = resolve_dns_name (serverlist[srv].target, port, 0, SOCK_STREAM,
&aibuf, NULL);
if (err)
{
log_info ("resolving '%s' failed: %s\n",
serverlist[srv].target, gpg_strerror (err));
last_err = err;
continue; /* Not found - try next one. */
}
hostfound = 1;
for (ai = aibuf; ai && !connected; ai = ai->next)
{
if (ai->family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
continue;
if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
continue;
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
log_error ("error creating socket: %s\n", gpg_strerror (err));
free_dns_addrinfo (aibuf);
xfree (serverlist);
return err;
}
anyhostaddr = 1;
if (assuan_sock_connect (sock, ai->addr, ai->addrlen))
{
last_err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
else
{
connected = 1;
notify_netactivity ();
}
}
free_dns_addrinfo (aibuf);
}
xfree (serverlist);
if (!connected)
{
if (!hostfound)
log_error ("can't connect to '%s': %s\n",
server, "host not found");
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
else
{
#ifdef HAVE_W32_SYSTEM
log_error ("can't connect to '%s': ec=%d\n",
server, (int)WSAGetLastError());
#else
log_error ("can't connect to '%s': %s\n",
server, gpg_strerror (last_err));
#endif
}
err = last_err? last_err : gpg_err_make (default_errsource,
GPG_ERR_UNKNOWN_HOST);
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
return err;
}
*r_sock = sock;
return 0;
}
/* Helper to read from a socket. This handles npth things and
* EINTR. */
static gpgrt_ssize_t
read_server (int sock, void *buffer, size_t size)
{
int nread;
do
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nread = recv (sock, buffer, size, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nread = npth_read (sock, buffer, size);
# else
nread = read (sock, buffer, size);
# endif
#endif /*!HAVE_W32_SYSTEM*/
}
while (nread == -1 && errno == EINTR);
return nread;
}
static gpg_error_t
write_server (int sock, const char *data, size_t length)
{
int nleft;
int nwritten;
nleft = length;
while (nleft > 0)
{
#if defined(HAVE_W32_SYSTEM)
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nwritten = send (sock, data, nleft, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
if ( nwritten == SOCKET_ERROR )
{
log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
return gpg_error (GPG_ERR_NETWORK);
}
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nwritten = npth_write (sock, data, nleft);
# else
nwritten = write (sock, data, nleft);
# endif
if (nwritten == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("network write failed: %s\n", strerror (errno));
return gpg_error_from_syserror ();
}
#endif /*!HAVE_W32_SYSTEM*/
nleft -= nwritten;
data += nwritten;
}
return 0;
}
/* Read handler for estream. */
static gpgrt_ssize_t
cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
if (c->content_length_valid)
{
if (!c->content_length)
return 0; /* EOF */
if (c->content_length < size)
size = c->content_length;
}
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
nread = es_fread (buffer, 1, size, in);
if (opt_debug)
log_debug ("TLS network read: %d/%zu\n", nread, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
nread = gnutls_record_recv (c->session->tls_session, buffer, size);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
goto again;
if (nread == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
if (nread == GNUTLS_E_REHANDSHAKE)
goto again; /* A client is allowed to just ignore this request. */
if (nread == GNUTLS_E_PREMATURE_TERMINATION)
{
/* The server terminated the connection. Close the TLS
session, and indicate EOF using a short read. */
close_tls_session (c->session);
return 0;
}
log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
gpg_err_set_errno (EIO);
return -1;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
nread = read_server (c->sock->fd, buffer, size);
}
if (c->content_length_valid && nread > 0)
{
if (nread < c->content_length)
c->content_length -= nread;
else
c->content_length = 0;
}
return (gpgrt_ssize_t)nread;
}
/* Write handler for estream. */
static gpgrt_ssize_t
cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
cookie_t c = cookie;
int nwritten = 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
if (size == 0)
es_fflush (out);
else
nwritten = es_fwrite (buffer, 1, size, out);
if (opt_debug)
log_debug ("TLS network write: %d/%zu\n", nwritten, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
int nleft = size;
while (nleft > 0)
{
nwritten = gnutls_record_send (c->session->tls_session,
buffer, nleft);
if (nwritten <= 0)
{
if (nwritten == GNUTLS_E_INTERRUPTED)
continue;
if (nwritten == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("TLS network write failed: %s\n",
gnutls_strerror (nwritten));
gpg_err_set_errno (EIO);
return -1;
}
nleft -= nwritten;
buffer += nwritten;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
}
return (gpgrt_ssize_t)nwritten;
}
#ifdef HAVE_W32_SYSTEM
static gpgrt_ssize_t
simple_cookie_read (void *cookie, void *buffer, size_t size)
{
int sock = (int)(uintptr_t)cookie;
return read_server (sock, buffer, size);
}
static gpgrt_ssize_t
simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
int sock = (int)(uintptr_t)cookie;
const char *buffer = buffer_arg;
int nwritten;
if (write_server (sock, buffer, size))
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
return (gpgrt_ssize_t)nwritten;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HTTP_USE_GNUTLS
/* Wrapper for gnutls_bye used by my_socket_unref. */
static void
send_gnutls_bye (void *opaque)
{
tls_session_t tls_session = opaque;
int ret;
again:
do
ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
while (ret == GNUTLS_E_INTERRUPTED);
if (ret == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
}
#endif /*HTTP_USE_GNUTLS*/
/* Close handler for estream. */
static int
cookie_close (void *cookie)
{
cookie_t c = cookie;
if (!c)
return 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
/* FIXME!! Possibly call ntbtls_close_notify for close
of write stream. */
my_socket_unref (c->sock, NULL, NULL);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
else
#endif /*HTTP_USE_GNUTLS*/
if (c->sock)
my_socket_unref (c->sock, NULL, NULL);
if (c->session)
http_session_unref (c->session);
xfree (c);
return 0;
}
/* Verify the credentials of the server. Returns 0 on success and
store the result in the session object. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
#if HTTP_USE_GNUTLS
static const char const errprefix[] = "TLS verification of peer failed";
int rc;
unsigned int status;
const char *hostname;
const gnutls_datum_t *certlist;
unsigned int certlistlen;
gnutls_x509_crt_t cert;
gpg_error_t err = 0;
sess->verify.done = 1;
sess->verify.status = 0;
sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
{
log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
return gpg_error (GPG_ERR_GENERAL);
}
rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
if (rc)
{
log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
else if (status)
{
log_error ("%s: status=0x%04x\n", errprefix, status);
#if GNUTLS_VERSION_NUMBER >= 0x030104
{
gnutls_datum_t statusdat;
if (!gnutls_certificate_verification_status_print
(status, GNUTLS_CRT_X509, &statusdat, 0))
{
log_info ("%s: %s\n", errprefix, statusdat.data);
gnutls_free (statusdat.data);
}
}
#endif /*gnutls >= 3.1.4*/
sess->verify.status = status;
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
hostname = sess->servername;
if (!hostname || !strchr (hostname, '.'))
{
log_error ("%s: %s\n", errprefix, "hostname missing");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
if (!certlistlen)
{
log_error ("%s: %s\n", errprefix, "server did not send a certificate");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
/* Need to stop here. */
if (err)
return err;
}
rc = gnutls_x509_crt_init (&cert);
if (rc < 0)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
if (err)
return err;
}
rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
if (rc < 0)
{
log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
if (!gnutls_x509_crt_check_hostname (cert, hostname))
{
log_error ("%s: %s\n", errprefix, "hostname does not match");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
gnutls_x509_crt_deinit (cert);
if (!err)
sess->verify.rc = 0;
if (sess->cert_log_cb)
{
const void *bufarr[10];
size_t buflenarr[10];
size_t n;
for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
{
bufarr[n] = certlist[n].data;
buflenarr[n] = certlist[n].size;
}
bufarr[n] = NULL;
buflenarr[n] = 0;
sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
}
return err;
#else /*!HTTP_USE_GNUTLS*/
(void)sess;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Return the first query variable with the specified key. If there
is no such variable, return NULL. */
struct uri_tuple_s *
uri_query_lookup (parsed_uri_t uri, const char *key)
{
struct uri_tuple_s *t;
for (t = uri->query; t; t = t->next)
if (strcmp (t->name, key) == 0)
return t;
return NULL;
}
diff --git a/dirmngr/ks-engine-finger.c b/dirmngr/ks-engine-finger.c
index 811b72de4..82ef7a500 100644
--- a/dirmngr/ks-engine-finger.c
+++ b/dirmngr/ks-engine-finger.c
@@ -1,126 +1,126 @@
/* ks-engine-finger.c - Finger OpenPGP key access
* Copyright (C) 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
-#include "userids.h"
+#include "../common/userids.h"
#include "ks-engine.h"
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_finger_help (ctrl_t ctrl, parsed_uri_t uri)
{
char const data[] =
"Handler for FINGER:\n"
" finger:<user>@<host>\n"
"Supported methods: fetch\n"
"Example:\n"
" finger:joe@example.org\n";
gpg_error_t err;
if (!uri)
err = ks_print_help (ctrl, " finger");
else if (!strcmp (uri->scheme, "finger"))
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Get the key from URI which is expected to specify a finger scheme.
On success R_FP has an open stream to read the data. */
gpg_error_t
ks_finger_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp)
{
gpg_error_t err;
estream_t fp;
char *server;
char *name;
http_t http;
(void)ctrl;
*r_fp = NULL;
if (strcmp (uri->scheme, "finger") || !uri->opaque || !uri->path)
return gpg_error (GPG_ERR_INV_ARG);
name = xtrystrdup (uri->path);
if (!name)
return gpg_error_from_syserror ();
server = strchr (name, '@');
if (!server)
{
err = gpg_error (GPG_ERR_INV_URI);
xfree (name);
return err;
}
*server++ = 0;
err = http_raw_connect (&http, server, 79,
((dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR : 0)
| (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)),
NULL);
if (err)
{
xfree (name);
return err;
}
fp = http_get_write_ptr (http);
if (!fp)
{
err = gpg_error (GPG_ERR_INTERNAL);
http_close (http, 0);
xfree (name);
return err;
}
if (es_fputs (name, fp) || es_fputs ("\r\n", fp) || es_fflush (fp))
{
err = gpg_error_from_syserror ();
http_close (http, 0);
xfree (name);
return err;
}
xfree (name);
es_fclose (fp);
fp = http_get_read_ptr (http);
if (!fp)
{
err = gpg_error (GPG_ERR_INTERNAL);
http_close (http, 0);
return err;
}
http_close (http, 1 /* Keep read ptr. */);
*r_fp = fp;
return 0;
}
diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c
index b6a06754f..ee7d50675 100644
--- a/dirmngr/ks-engine-hkp.c
+++ b/dirmngr/ks-engine-hkp.c
@@ -1,1677 +1,1677 @@
/* ks-engine-hkp.c - HKP keyserver engine
* Copyright (C) 2011, 2012 Free Software Foundation, Inc.
* Copyright (C) 2011, 2012, 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#include "dirmngr.h"
#include "misc.h"
-#include "userids.h"
+#include "../common/userids.h"
#include "dns-stuff.h"
#include "ks-engine.h"
/* Substitutes for missing Mingw macro. The EAI_SYSTEM mechanism
seems not to be available (probably because there is only one set
of error codes anyway). For now we use WSAEINVAL. */
#ifndef EAI_OVERFLOW
# define EAI_OVERFLOW EAI_FAIL
#endif
#ifdef HAVE_W32_SYSTEM
# ifndef EAI_SYSTEM
# define EAI_SYSTEM WSAEINVAL
# endif
#endif
/* Number of seconds after a host is marked as resurrected. */
#define RESURRECT_INTERVAL (3600*3) /* 3 hours */
/* To match the behaviour of our old gpgkeys helper code we escape
more characters than actually needed. */
#define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
/* How many redirections do we allow. */
#define MAX_REDIRECTS 2
/* Number of retries done for a dead host etc. */
#define SEND_REQUEST_RETRIES 3
/* Objects used to maintain information about hosts. */
struct hostinfo_s;
typedef struct hostinfo_s *hostinfo_t;
struct hostinfo_s
{
time_t lastfail; /* Time we tried to connect and failed. */
time_t lastused; /* Time of last use. */
int *pool; /* A -1 terminated array with indices into
HOSTTABLE or NULL if NAME is not a pool
name. */
int poolidx; /* Index into POOL with the used host. -1 if not set. */
unsigned int v4:1; /* Host supports AF_INET. */
unsigned int v6:1; /* Host supports AF_INET6. */
unsigned int onion:1;/* NAME is an onion (Tor HS) address. */
unsigned int dead:1; /* Host is currently unresponsive. */
unsigned int iporname_valid:1; /* The field IPORNAME below is valid */
/* (but may be NULL) */
time_t died_at; /* The time the host was marked dead. If this is
0 the host has been manually marked dead. */
char *cname; /* Canonical name of the host. Only set if this
is a pool or NAME has a numerical IP address. */
char *iporname; /* Numeric IP address or name for printing. */
unsigned short port; /* The port used by the host, 0 if unknown. */
char name[1]; /* The hostname. */
};
/* An array of hostinfo_t for all hosts requested by the caller or
resolved from a pool name and its allocated size.*/
static hostinfo_t *hosttable;
static int hosttable_size;
/* The number of host slots we initially allocate for HOSTTABLE. */
#define INITIAL_HOSTTABLE_SIZE 10
/* Create a new hostinfo object, fill in NAME and put it into
HOSTTABLE. Return the index into hosttable on success or -1 on
error. */
static int
create_new_hostinfo (const char *name)
{
hostinfo_t hi, *newtable;
int newsize;
int idx, rc;
hi = xtrymalloc (sizeof *hi + strlen (name));
if (!hi)
return -1;
strcpy (hi->name, name);
hi->pool = NULL;
hi->poolidx = -1;
hi->lastused = (time_t)(-1);
hi->lastfail = (time_t)(-1);
hi->v4 = 0;
hi->v6 = 0;
hi->onion = 0;
hi->dead = 0;
hi->iporname_valid = 0;
hi->died_at = 0;
hi->cname = NULL;
hi->iporname = NULL;
hi->port = 0;
/* Add it to the hosttable. */
for (idx=0; idx < hosttable_size; idx++)
if (!hosttable[idx])
{
hosttable[idx] = hi;
return idx;
}
/* Need to extend the hosttable. */
newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE;
newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable);
if (!newtable)
{
xfree (hi);
return -1;
}
hosttable = newtable;
idx = hosttable_size;
hosttable_size = newsize;
rc = idx;
hosttable[idx++] = hi;
while (idx < hosttable_size)
hosttable[idx++] = NULL;
return rc;
}
/* Find the host NAME in our table. Return the index into the
hosttable or -1 if not found. */
static int
find_hostinfo (const char *name)
{
int idx;
for (idx=0; idx < hosttable_size; idx++)
if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name))
return idx;
return -1;
}
static int
sort_hostpool (const void *xa, const void *xb)
{
int a = *(int *)xa;
int b = *(int *)xb;
assert (a >= 0 && a < hosttable_size);
assert (b >= 0 && b < hosttable_size);
assert (hosttable[a]);
assert (hosttable[b]);
return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name);
}
/* Return true if the host with the hosttable index TBLIDX is in POOL. */
static int
host_in_pool_p (int *pool, int tblidx)
{
int i, pidx;
for (i=0; (pidx = pool[i]) != -1; i++)
if (pidx == tblidx && hosttable[pidx])
return 1;
return 0;
}
/* Select a random host. Consult TABLE which indices into the global
hosttable. Returns index into TABLE or -1 if no host could be
selected. */
static int
select_random_host (int *table)
{
int *tbl;
size_t tblsize;
int pidx, idx;
/* We create a new table so that we randomly select only from
currently alive hosts. */
for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
if (hosttable[pidx] && !hosttable[pidx]->dead)
tblsize++;
if (!tblsize)
return -1; /* No hosts. */
tbl = xtrymalloc (tblsize * sizeof *tbl);
if (!tbl)
return -1;
for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
if (hosttable[pidx] && !hosttable[pidx]->dead)
tbl[tblsize++] = pidx;
if (tblsize == 1) /* Save a get_uint_nonce. */
pidx = tbl[0];
else
pidx = tbl[get_uint_nonce () % tblsize];
xfree (tbl);
return pidx;
}
/* Figure out if a set of DNS records looks like a pool. */
static int
arecords_is_pool (dns_addrinfo_t aibuf)
{
dns_addrinfo_t ai;
int n_v6, n_v4;
n_v6 = n_v4 = 0;
for (ai = aibuf; ai; ai = ai->next)
{
if (ai->family == AF_INET6)
n_v6++;
else if (ai->family == AF_INET)
n_v4++;
}
return n_v6 > 1 || n_v4 > 1;
}
/* Print a warninng iff Tor is not running but Tor has been requested.
* Also return true if it is not running. */
static int
tor_not_running_p (ctrl_t ctrl)
{
assuan_fd_t sock;
if (!dirmngr_use_tor ())
return 0;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock != ASSUAN_INVALID_FD)
{
assuan_sock_close (sock);
return 0;
}
log_info ("(it seems Tor is not running)\n");
dirmngr_status (ctrl, "WARNING", "tor_not_running 0",
"Tor is enabled but the local Tor daemon"
" seems to be down", NULL);
return 1;
}
/* Add the host AI under the NAME into the HOSTTABLE. If PORT is not
zero, it specifies which port to use to talk to the host. If NAME
specifies a pool (as indicated by IS_POOL), update the given
reference table accordingly. */
static void
add_host (const char *name, int is_pool,
const dns_addrinfo_t ai, unsigned short port,
int *reftbl, size_t reftblsize, int *refidx)
{
gpg_error_t tmperr;
char *tmphost;
int idx, tmpidx;
int i;
idx = find_hostinfo (name);
if (is_pool)
{
/* For a pool immediately convert the address to a string. */
tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
(DNS_NUMERICHOST | DNS_WITHBRACKET), &tmphost);
}
else if (!is_ip_address (name))
{
/* This is a hostname. Use the name as given without going
* through resolve_dns_addr. */
tmphost = xtrystrdup (name);
if (!tmphost)
tmperr = gpg_error_from_syserror ();
else
tmperr = 0;
}
else
{
/* Do a PTR lookup on AI. If a name was not found the function
* returns the numeric address (with brackets). */
tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
DNS_WITHBRACKET, &tmphost);
}
if (tmperr)
{
log_info ("resolve_dns_addr failed while checking '%s': %s\n",
name, gpg_strerror (tmperr));
}
else if ((*refidx) + 1 >= reftblsize)
{
log_error ("resolve_dns_addr for '%s': '%s'"
" [index table full - ignored]\n", name, tmphost);
}
else
{
if (!is_pool && is_ip_address (name))
/* Update the original entry. */
tmpidx = idx;
else
tmpidx = find_hostinfo (tmphost);
log_info ("resolve_dns_addr for '%s': '%s'%s\n",
name, tmphost,
tmpidx == -1? "" : " [already known]");
if (tmpidx == -1) /* Create a new entry. */
tmpidx = create_new_hostinfo (tmphost);
if (tmpidx == -1)
{
log_error ("map_host for '%s' problem: %s - '%s' [ignored]\n",
name, strerror (errno), tmphost);
}
else /* Set or update the entry. */
{
if (port)
hosttable[tmpidx]->port = port;
if (ai->family == AF_INET6)
{
hosttable[tmpidx]->v6 = 1;
}
else if (ai->family == AF_INET)
{
hosttable[tmpidx]->v4 = 1;
}
else
BUG ();
for (i=0; i < *refidx; i++)
if (reftbl[i] == tmpidx)
break;
if (!(i < *refidx) && tmpidx != idx)
reftbl[(*refidx)++] = tmpidx;
}
}
xfree (tmphost);
}
/* Map the host name NAME to the actual to be used host name. This
* allows us to manage round robin DNS names. We use our own strategy
* to choose one of the hosts. For example we skip those hosts which
* failed for some time and we stick to one host for a time
* independent of DNS retry times. If FORCE_RESELECT is true a new
* host is always selected. If SRVTAG is NULL no service record
* lookup will be done, if it is set that service name is used. The
* selected host is stored as a malloced string at R_HOST; on error
* NULL is stored. If we know the port used by the selected host from
* a service record, a string representation is written to R_PORTSTR,
* otherwise it is left untouched. If R_HTTPFLAGS is not NULL it will
* receive flags which are to be passed to http_open. If R_HTTPHOST
* is not NULL a malloced name of the host is stored there; this might
* be different from R_HOST in case it has been selected from a
* pool. */
static gpg_error_t
map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect,
char **r_host, char *r_portstr,
unsigned int *r_httpflags, char **r_httphost)
{
gpg_error_t err = 0;
hostinfo_t hi;
int idx;
*r_host = NULL;
if (r_httpflags)
*r_httpflags = 0;
if (r_httphost)
*r_httphost = NULL;
/* No hostname means localhost. */
if (!name || !*name)
{
*r_host = xtrystrdup ("localhost");
return *r_host? 0 : gpg_error_from_syserror ();
}
/* See whether the host is in our table. */
idx = find_hostinfo (name);
if (idx == -1 && is_onion_address (name))
{
idx = create_new_hostinfo (name);
if (idx == -1)
return gpg_error_from_syserror ();
hi = hosttable[idx];
hi->onion = 1;
}
else if (idx == -1)
{
/* We never saw this host. Allocate a new entry. */
dns_addrinfo_t aibuf, ai;
int *reftbl;
size_t reftblsize;
int refidx;
int is_pool = 0;
char *cname;
struct srventry *srvs;
unsigned int srvscount;
reftblsize = 100;
reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
if (!reftbl)
return gpg_error_from_syserror ();
refidx = 0;
idx = create_new_hostinfo (name);
if (idx == -1)
{
err = gpg_error_from_syserror ();
xfree (reftbl);
return err;
}
hi = hosttable[idx];
if (srvtag && !is_ip_address (name))
{
/* Check for SRV records. */
err = get_dns_srv (name, srvtag, NULL, &srvs, &srvscount);
if (err)
{
xfree (reftbl);
if (gpg_err_code (err) == GPG_ERR_ECONNREFUSED)
tor_not_running_p (ctrl);
return err;
}
if (srvscount > 0)
{
int i;
is_pool = srvscount > 1;
for (i = 0; i < srvscount; i++)
{
err = resolve_dns_name (srvs[i].target, 0,
AF_UNSPEC, SOCK_STREAM,
&ai, &cname);
if (err)
continue;
dirmngr_tick (ctrl);
add_host (name, is_pool, ai, srvs[i].port,
reftbl, reftblsize, &refidx);
}
xfree (srvs);
}
}
/* Find all A records for this entry and put them into the pool
list - if any. */
err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname);
if (err)
{
log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err));
err = 0;
}
else
{
/* First figure out whether this is a pool. For a pool we
use a different strategy than for a plain server: We use
the canonical name of the pool as the virtual host along
with the IP addresses. If it is not a pool, we use the
specified name. */
if (! is_pool)
is_pool = arecords_is_pool (aibuf);
if (is_pool && cname)
{
hi->cname = cname;
cname = NULL;
}
for (ai = aibuf; ai; ai = ai->next)
{
if (ai->family != AF_INET && ai->family != AF_INET6)
continue;
if (opt.disable_ipv4 && ai->family == AF_INET)
continue;
dirmngr_tick (ctrl);
add_host (name, is_pool, ai, 0, reftbl, reftblsize, &refidx);
}
}
reftbl[refidx] = -1;
xfree (cname);
free_dns_addrinfo (aibuf);
if (refidx && is_pool)
{
assert (!hi->pool);
hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
if (!hi->pool)
{
err = gpg_error_from_syserror ();
log_error ("shrinking index table in map_host failed: %s\n",
gpg_strerror (err));
xfree (reftbl);
return err;
}
qsort (hi->pool, refidx, sizeof *reftbl, sort_hostpool);
}
else
xfree (reftbl);
}
hi = hosttable[idx];
if (hi->pool)
{
/* Deal with the pool name before selecting a host. */
if (r_httphost)
{
*r_httphost = xtrystrdup (hi->cname? hi->cname : hi->name);
if (!*r_httphost)
return gpg_error_from_syserror ();
}
/* If the currently selected host is now marked dead, force a
re-selection . */
if (force_reselect)
hi->poolidx = -1;
else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
&& hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
hi->poolidx = -1;
/* Select a host if needed. */
if (hi->poolidx == -1)
{
hi->poolidx = select_random_host (hi->pool);
if (hi->poolidx == -1)
{
log_error ("no alive host found in pool '%s'\n", name);
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
}
assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
hi = hosttable[hi->poolidx];
assert (hi);
}
else if (r_httphost && is_ip_address (hi->name))
{
/* This is a numerical IP address and not a pool. We want to
* find the canonical name so that it can be used in the HTTP
* Host header. Fixme: We should store that name in the
* hosttable. */
dns_addrinfo_t aibuf, ai;
char *host;
err = resolve_dns_name (hi->name, 0, 0, SOCK_STREAM, &aibuf, NULL);
if (!err)
{
for (ai = aibuf; ai; ai = ai->next)
{
if (ai->family == AF_INET6
|| (!opt.disable_ipv4 && ai->family == AF_INET))
{
err = resolve_dns_addr (ai->addr, ai->addrlen, 0, &host);
if (!err)
{
/* Okay, we return the first found name. */
*r_httphost = host;
break;
}
}
}
}
free_dns_addrinfo (aibuf);
}
if (hi->dead)
{
log_error ("host '%s' marked as dead\n", hi->name);
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
if (r_httpflags)
{
/* If the hosttable does not indicate that a certain host
supports IPv<N>, we explicit set the corresponding http
flags. The reason for this is that a host might be listed in
a pool as not v6 only but actually support v6 when later
the name is resolved by our http layer. */
if (!hi->v4)
*r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
if (!hi->v6)
*r_httpflags |= HTTP_FLAG_IGNORE_IPv6;
/* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion
addresses because the http module detects this itself. This
also allows us to use an onion address without Tor mode being
enabled. */
}
*r_host = xtrystrdup (hi->name);
if (!*r_host)
{
err = gpg_error_from_syserror ();
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return err;
}
if (hi->port)
snprintf (r_portstr, 6 /* five digits and the sentinel */,
"%hu", hi->port);
return 0;
}
/* Mark the host NAME as dead. NAME may be given as an URL. Returns
true if a host was really marked as dead or was already marked dead
(e.g. by a concurrent session). */
static int
mark_host_dead (const char *name)
{
const char *host;
char *host_buffer = NULL;
parsed_uri_t parsed_uri = NULL;
int done = 0;
if (name && *name && !http_parse_uri (&parsed_uri, name, 1))
{
if (parsed_uri->v6lit)
{
host_buffer = strconcat ("[", parsed_uri->host, "]", NULL);
if (!host_buffer)
log_error ("out of core in mark_host_dead");
host = host_buffer;
}
else
host = parsed_uri->host;
}
else
host = name;
if (host && *host && strcmp (host, "localhost"))
{
hostinfo_t hi;
int idx;
idx = find_hostinfo (host);
if (idx != -1)
{
hi = hosttable[idx];
log_info ("marking host '%s' as dead%s\n",
hi->name, hi->dead? " (again)":"");
hi->dead = 1;
hi->died_at = gnupg_get_time ();
if (!hi->died_at)
hi->died_at = 1;
done = 1;
}
}
http_release_parsed_uri (parsed_uri);
xfree (host_buffer);
return done;
}
/* Mark a host in the hosttable as dead or - if ALIVE is true - as
alive. */
gpg_error_t
ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive)
{
gpg_error_t err = 0;
hostinfo_t hi, hi2;
int idx, idx2, idx3, n;
if (!name || !*name || !strcmp (name, "localhost"))
return 0;
idx = find_hostinfo (name);
if (idx == -1)
return gpg_error (GPG_ERR_NOT_FOUND);
hi = hosttable[idx];
if (alive && hi->dead)
{
hi->dead = 0;
err = ks_printf_help (ctrl, "marking '%s' as alive", name);
}
else if (!alive && !hi->dead)
{
hi->dead = 1;
hi->died_at = 0; /* Manually set dead. */
err = ks_printf_help (ctrl, "marking '%s' as dead", name);
}
/* If the host is a pool mark all member hosts. */
if (!err && hi->pool)
{
for (idx2=0; !err && (n=hi->pool[idx2]) != -1; idx2++)
{
assert (n >= 0 && n < hosttable_size);
if (!alive)
{
/* Do not mark a host from a pool dead if it is also a
member in another pool. */
for (idx3=0; idx3 < hosttable_size; idx3++)
{
if (hosttable[idx3]
&& hosttable[idx3]->pool
&& idx3 != idx
&& host_in_pool_p (hosttable[idx3]->pool, n))
break;
}
if (idx3 < hosttable_size)
continue; /* Host is also a member of another pool. */
}
hi2 = hosttable[n];
if (!hi2)
;
else if (alive && hi2->dead)
{
hi2->dead = 0;
err = ks_printf_help (ctrl, "marking '%s' as alive",
hi2->name);
}
else if (!alive && !hi2->dead)
{
hi2->dead = 1;
hi2->died_at = 0; /* Manually set dead. */
err = ks_printf_help (ctrl, "marking '%s' as dead",
hi2->name);
}
}
}
return err;
}
/* Debug function to print the entire hosttable. */
gpg_error_t
ks_hkp_print_hosttable (ctrl_t ctrl)
{
gpg_error_t err;
int idx, idx2;
hostinfo_t hi;
membuf_t mb;
time_t curtime;
char *p, *died;
const char *diedstr;
err = ks_print_help (ctrl, "hosttable (idx, ipv6, ipv4, dead, name, time):");
if (err)
return err;
/* FIXME: We need a lock for the hosttable. */
curtime = gnupg_get_time ();
for (idx=0; idx < hosttable_size; idx++)
if ((hi=hosttable[idx]))
{
if (hi->dead && hi->died_at)
{
died = elapsed_time_string (hi->died_at, curtime);
diedstr = died? died : "error";
}
else
diedstr = died = NULL;
if (!hi->iporname_valid)
{
char *canon = NULL;
xfree (hi->iporname);
hi->iporname = NULL;
/* Do a lookup just for the display purpose. */
if (hi->onion || hi->pool)
;
else if (is_ip_address (hi->name))
{
dns_addrinfo_t aibuf, ai;
/* Turn the numerical IP address string into an AI and
* then do a DNS PTR lookup. */
if (!resolve_dns_name (hi->name, 0, 0,
SOCK_STREAM,
&aibuf, &canon))
{
if (canon && is_ip_address (canon))
{
xfree (canon);
canon = NULL;
}
for (ai = aibuf; !canon && ai; ai = ai->next)
{
resolve_dns_addr (ai->addr, ai->addrlen,
DNS_WITHBRACKET, &canon);
if (canon && is_ip_address (canon))
{
/* We already have the numeric IP - no need to
* display it a second time. */
xfree (canon);
canon = NULL;
}
}
}
free_dns_addrinfo (aibuf);
}
else
{
dns_addrinfo_t aibuf, ai;
/* Get the IP address as a string from a name. Note
* that resolve_dns_addr allocates CANON on success
* and thus terminates the loop. */
if (!resolve_dns_name (hi->name, 0,
hi->v6? AF_INET6 : AF_INET,
SOCK_STREAM,
&aibuf, NULL))
{
for (ai = aibuf; !canon && ai; ai = ai->next)
{
resolve_dns_addr (ai->addr, ai->addrlen,
DNS_NUMERICHOST|DNS_WITHBRACKET,
&canon);
}
}
free_dns_addrinfo (aibuf);
}
hi->iporname = canon;
hi->iporname_valid = 1;
}
err = ks_printf_help (ctrl, "%3d %s %s %s %s%s%s%s%s%s%s\n",
idx,
hi->onion? "O" : hi->v6? "6":" ",
hi->v4? "4":" ",
hi->dead? "d":" ",
hi->name,
hi->iporname? " (":"",
hi->iporname? hi->iporname : "",
hi->iporname? ")":"",
diedstr? " (":"",
diedstr? diedstr:"",
diedstr? ")":"" );
xfree (died);
if (err)
return err;
if (hi->cname)
err = ks_printf_help (ctrl, " . %s", hi->cname);
if (err)
return err;
if (hi->pool)
{
init_membuf (&mb, 256);
put_membuf_printf (&mb, " . -->");
for (idx2=0; hi->pool[idx2] != -1; idx2++)
{
put_membuf_printf (&mb, " %d", hi->pool[idx2]);
if (hi->poolidx == hi->pool[idx2])
put_membuf_printf (&mb, "*");
}
put_membuf( &mb, "", 1);
p = get_membuf (&mb, NULL);
if (!p)
return gpg_error_from_syserror ();
err = ks_print_help (ctrl, p);
xfree (p);
if (err)
return err;
}
}
return 0;
}
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"Handler for HKP URLs:\n"
" hkp://\n"
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
" hkps://\n"
#endif
"Supported methods: search, get, put\n";
gpg_error_t err;
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
const char data2[] = " hkp\n hkps";
#else
const char data2[] = " hkp";
#endif
if (!uri)
err = ks_print_help (ctrl, data2);
else if (uri->is_http && (!strcmp (uri->scheme, "hkp")
|| !strcmp (uri->scheme, "hkps")))
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Build the remote part of the URL from SCHEME, HOST and an optional
* PORT. If NO_SRV is set no SRV record lookup will be done. Returns
* an allocated string at R_HOSTPORT or NULL on failure. If
* R_HTTPHOST is not NULL it receives a malloced string with the
* hostname; this may be different from HOST if HOST is selected from
* a pool. */
static gpg_error_t
make_host_part (ctrl_t ctrl,
const char *scheme, const char *host, unsigned short port,
int force_reselect, int no_srv,
char **r_hostport, unsigned int *r_httpflags, char **r_httphost)
{
gpg_error_t err;
const char *srvtag;
char portstr[10];
char *hostname;
*r_hostport = NULL;
if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https"))
{
scheme = "https";
srvtag = no_srv? NULL : "pgpkey-https";
}
else /* HKP or HTTP. */
{
scheme = "http";
srvtag = no_srv? NULL : "pgpkey-http";
}
portstr[0] = 0;
err = map_host (ctrl, host, srvtag, force_reselect,
&hostname, portstr, r_httpflags, r_httphost);
if (err)
return err;
/* If map_host did not return a port (from a SRV record) but a port
* has been specified (implicitly or explicitly) then use that port.
* In the case that a port was not specified (which is probably a
* bug in https.c) we will set up defaults. */
if (*portstr)
;
else if (!*portstr && port)
snprintf (portstr, sizeof portstr, "%hu", port);
else if (!strcmp (scheme,"https"))
strcpy (portstr, "443");
else
strcpy (portstr, "11371");
if (*hostname != '[' && is_ip_address (hostname) == 6)
*r_hostport = strconcat (scheme, "://[", hostname, "]:", portstr, NULL);
else
*r_hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL);
xfree (hostname);
if (!*r_hostport)
{
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error_from_syserror ();
}
return 0;
}
/* Resolve all known keyserver names and update the hosttable. This
is mainly useful for debugging because the resolving is anyway done
on demand. */
gpg_error_t
ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri)
{
gpg_error_t err;
char *hostport = NULL;
/* NB: With an explicitly given port we do not want to consult a
* service record because that might be in conflict with the port
* from such a service record. */
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
1, uri->explicit_port,
&hostport, NULL, NULL);
if (err)
{
err = ks_printf_help (ctrl, "%s://%s:%hu: resolve failed: %s",
uri->scheme, uri->host, uri->port,
gpg_strerror (err));
}
else
{
err = ks_printf_help (ctrl, "%s", hostport);
xfree (hostport);
}
return err;
}
/* Housekeeping function called from the housekeeping thread. It is
used to mark dead hosts alive so that they may be tried again after
some time. */
void
ks_hkp_housekeeping (time_t curtime)
{
int idx;
hostinfo_t hi;
for (idx=0; idx < hosttable_size; idx++)
{
hi = hosttable[idx];
if (!hi)
continue;
if (!hi->dead)
continue;
if (!hi->died_at)
continue; /* Do not resurrect manually shot hosts. */
if (hi->died_at + RESURRECT_INTERVAL <= curtime
|| hi->died_at > curtime)
{
hi->dead = 0;
log_info ("resurrected host '%s'", hi->name);
}
}
}
/* Reload (SIGHUP) action for this module. We mark all host alive
* even those which have been manually shot. */
void
ks_hkp_reload (void)
{
int idx, count;
hostinfo_t hi;
for (idx=count=0; idx < hosttable_size; idx++)
{
hi = hosttable[idx];
if (!hi)
continue;
hi->iporname_valid = 0;
if (!hi->dead)
continue;
hi->dead = 0;
count++;
}
if (count)
log_info ("number of resurrected hosts: %d", count);
}
/* Send an HTTP request. On success returns an estream object at
R_FP. HOSTPORTSTR is only used for diagnostics. If HTTPHOST is
not NULL it will be used as HTTP "Host" header. If POST_CB is not
NULL a post request is used and that callback is called to allow
writing the post data. If R_HTTP_STATUS is not NULL, the http
status code will be stored there. */
static gpg_error_t
send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
const char *httphost, unsigned int httpflags,
gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
estream_t *r_fp, unsigned int *r_http_status)
{
gpg_error_t err;
http_session_t session = NULL;
http_t http = NULL;
int redirects_left = MAX_REDIRECTS;
estream_t fp = NULL;
char *request_buffer = NULL;
*r_fp = NULL;
err = http_session_new (&session, httphost,
((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0)
| HTTP_FLAG_TRUST_DEF),
gnupg_http_tls_verify_cb, ctrl);
if (err)
goto leave;
http_session_set_log_cb (session, cert_log_cb);
once_more:
err = http_open (&http,
post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
request,
httphost,
/* fixme: AUTH */ NULL,
(httpflags
|(opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
|(dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
|(opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)),
ctrl->http_proxy,
session,
NULL,
/*FIXME curl->srvtag*/NULL);
if (!err)
{
fp = http_get_write_ptr (http);
/* Avoid caches to get the most recent copy of the key. We set
both the Pragma and Cache-Control versions of the header, so
we're good with both HTTP 1.0 and 1.1. */
es_fputs ("Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n", fp);
if (post_cb)
err = post_cb (post_cb_value, http);
if (!err)
{
http_start_data (http);
if (es_ferror (fp))
err = gpg_error_from_syserror ();
}
}
if (err)
{
/* Fixme: After a redirection we show the old host name. */
log_error (_("error connecting to '%s': %s\n"),
hostportstr, gpg_strerror (err));
goto leave;
}
/* Wait for the response. */
dirmngr_tick (ctrl);
err = http_wait_response (http);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
hostportstr, gpg_strerror (err));
goto leave;
}
if (http_get_tls_info (http, NULL))
{
/* Update the httpflags so that a redirect won't fallback to an
unencrypted connection. */
httpflags |= HTTP_FLAG_FORCE_TLS;
}
if (r_http_status)
*r_http_status = http_get_status_code (http);
switch (http_get_status_code (http))
{
case 200:
err = 0;
break; /* Success. */
case 301:
case 302:
case 307:
{
const char *s = http_get_header (http, "Location");
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
request, s?s:"[none]", http_get_status_code (http));
if (s && *s && redirects_left-- )
{
xfree (request_buffer);
request_buffer = xtrystrdup (s);
if (request_buffer)
{
request = request_buffer;
http_close (http, 0);
http = NULL;
goto once_more;
}
err = gpg_error_from_syserror ();
}
else
err = gpg_error (GPG_ERR_NO_DATA);
log_error (_("too many redirections\n"));
}
goto leave;
case 501:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
default:
log_error (_("error accessing '%s': http status %u\n"),
request, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* FIXME: We should register a permanent redirection and whether a
host has ever used TLS so that future calls will always use
TLS. */
fp = http_get_read_ptr (http);
if (!fp)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
/* Return the read stream and close the HTTP context. */
*r_fp = fp;
http_close (http, 1);
http = NULL;
leave:
http_close (http, 0);
http_session_release (session);
xfree (request_buffer);
return err;
}
/* Helper to evaluate the error code ERR from a send_request() call
with REQUEST. The function returns true if the caller shall try
again. TRIES_LEFT points to a variable to track the number of
retries; this function decrements it and won't return true if it is
down to zero. */
static int
handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request,
unsigned int *tries_left)
{
int retry = 0;
/* Fixme: Should we disable all hosts of a protocol family if a
* request for an address of that familiy returned ENETDOWN? */
switch (gpg_err_code (err))
{
case GPG_ERR_ECONNREFUSED:
if (tor_not_running_p (ctrl))
break; /* A retry does not make sense. */
/* Okay: Tor is up or --use-tor is not used. */
/*FALLTHRU*/
case GPG_ERR_ENETUNREACH:
case GPG_ERR_ENETDOWN:
case GPG_ERR_UNKNOWN_HOST:
case GPG_ERR_NETWORK:
if (mark_host_dead (request) && *tries_left)
retry = 1;
break;
case GPG_ERR_ETIMEDOUT:
if (*tries_left)
{
log_info ("selecting a different host due to a timeout\n");
retry = 1;
}
break;
case GPG_ERR_EACCES:
if (dirmngr_use_tor ())
{
log_info ("(Tor configuration problem)\n");
dirmngr_status (ctrl, "WARNING", "tor_config_problem 0",
"Please check that the \"SocksPort\" flag "
"\"IPv6Traffic\" is set in torrc", NULL);
}
break;
default:
break;
}
if (*tries_left)
--*tries_left;
return retry;
}
/* Search the keyserver identified by URI for keys matching PATTERN.
On success R_FP has an open stream to read the data. If
R_HTTP_STATUS is not NULL, the http status code will be stored
there. */
gpg_error_t
ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
estream_t *r_fp, unsigned int *r_http_status)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char fprbuf[2+40+1];
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
int reselect;
unsigned int httpflags;
char *httphost = NULL;
unsigned int tries = SEND_REQUEST_RETRIES;
*r_fp = NULL;
/* Remove search type indicator and adjust PATTERN accordingly.
Note that HKP keyservers like the 0x to be present when searching
by keyid. We need to re-format the fingerprint and keyids so to
remove the gpg specific force-use-of-this-key flag ("!"). */
err = classify_user_id (pattern, &desc, 1);
if (err)
return err;
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
pattern = desc.u.name;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
pattern = fprbuf;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX",
(ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
pattern = fprbuf;
break;
case KEYDB_SEARCH_MODE_FPR16:
fprbuf[0] = '0';
fprbuf[1] = 'x';
bin2hex (desc.u.fpr, 16, fprbuf+2);
pattern = fprbuf;
break;
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
fprbuf[0] = '0';
fprbuf[1] = 'x';
bin2hex (desc.u.fpr, 20, fprbuf+2);
pattern = fprbuf;
break;
default:
return gpg_error (GPG_ERR_INV_USER_ID);
}
/* Build the request string. */
reselect = 0;
again:
{
char *searchkey;
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
if (!searchkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (request);
request = strconcat (hostport,
"/pks/lookup?op=index&options=mr&search=",
searchkey,
NULL);
xfree (searchkey);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, httpflags,
NULL, NULL, &fp, r_http_status);
if (handle_send_request_error (ctrl, err, request, &tries))
{
reselect = 1;
goto again;
}
if (err)
goto leave;
err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
if (err)
goto leave;
/* Peek at the response. */
{
int c = es_getc (fp);
if (c == -1)
{
err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
log_error ("error reading response: %s\n", gpg_strerror (err));
goto leave;
}
if (c == '<')
{
/* The document begins with a '<': Assume a HTML response,
which we don't support. */
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto leave;
}
es_ungetc (c, fp);
}
/* Return the read stream. */
*r_fp = fp;
fp = NULL;
leave:
es_fclose (fp);
xfree (request);
xfree (hostport);
xfree (httphost);
return err;
}
/* Get the key described key the KEYSPEC string from the keyserver
identified by URI. On success R_FP has an open stream to read the
data. The data will be provided in a format GnuPG can import
(either a binary OpenPGP message or an armored one). */
gpg_error_t
ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char kidbuf[2+40+1];
const char *exactname = NULL;
char *searchkey = NULL;
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
int reselect;
char *httphost = NULL;
unsigned int httpflags;
unsigned int tries = SEND_REQUEST_RETRIES;
*r_fp = NULL;
/* Remove search type indicator and adjust PATTERN accordingly.
Note that HKP keyservers like the 0x to be present when searching
by keyid. We need to re-format the fingerprint and keyids so to
remove the gpg specific force-use-of-this-key flag ("!"). */
err = classify_user_id (keyspec, &desc, 1);
if (err)
return err;
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (kidbuf, sizeof kidbuf, "0x%08lX", (ulong)desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX",
(ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
/* This is a v4 fingerprint. */
kidbuf[0] = '0';
kidbuf[1] = 'x';
bin2hex (desc.u.fpr, 20, kidbuf+2);
break;
case KEYDB_SEARCH_MODE_EXACT:
exactname = desc.u.name;
break;
case KEYDB_SEARCH_MODE_FPR16:
log_error ("HKP keyservers do not support v3 fingerprints\n");
default:
return gpg_error (GPG_ERR_INV_USER_ID);
}
searchkey = http_escape_string (exactname? exactname : kidbuf,
EXTRA_ESCAPE_CHARS);
if (!searchkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
reselect = 0;
again:
/* Build the request string. */
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
xfree (request);
request = strconcat (hostport,
"/pks/lookup?op=get&options=mr&search=",
searchkey,
exactname? "&exact=on":"",
NULL);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, httpflags,
NULL, NULL, &fp, NULL);
if (handle_send_request_error (ctrl, err, request, &tries))
{
reselect = 1;
goto again;
}
if (err)
goto leave;
err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
if (err)
goto leave;
/* Return the read stream and close the HTTP context. */
*r_fp = fp;
fp = NULL;
leave:
es_fclose (fp);
xfree (request);
xfree (hostport);
xfree (httphost);
xfree (searchkey);
return err;
}
/* Callback parameters for put_post_cb. */
struct put_post_parm_s
{
char *datastring;
};
/* Helper for ks_hkp_put. */
static gpg_error_t
put_post_cb (void *opaque, http_t http)
{
struct put_post_parm_s *parm = opaque;
gpg_error_t err = 0;
estream_t fp;
size_t len;
fp = http_get_write_ptr (http);
len = strlen (parm->datastring);
es_fprintf (fp,
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
http_start_data (http);
if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
err = gpg_error_from_syserror ();
return err;
}
/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */
gpg_error_t
ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
{
gpg_error_t err;
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
struct put_post_parm_s parm;
char *armored = NULL;
int reselect;
char *httphost = NULL;
unsigned int httpflags;
unsigned int tries = SEND_REQUEST_RETRIES;
parm.datastring = NULL;
err = armor_data (&armored, data, datalen);
if (err)
goto leave;
parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
if (!parm.datastring)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (armored);
armored = NULL;
/* Build the request string. */
reselect = 0;
again:
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
xfree (request);
request = strconcat (hostport, "/pks/add", NULL);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, 0,
put_post_cb, &parm, &fp, NULL);
if (handle_send_request_error (ctrl, err, request, &tries))
{
reselect = 1;
goto again;
}
if (err)
goto leave;
leave:
es_fclose (fp);
xfree (parm.datastring);
xfree (armored);
xfree (request);
xfree (hostport);
xfree (httphost);
return err;
}
diff --git a/dirmngr/ks-engine-kdns.c b/dirmngr/ks-engine-kdns.c
index 9bb0d420a..71463fea1 100644
--- a/dirmngr/ks-engine-kdns.c
+++ b/dirmngr/ks-engine-kdns.c
@@ -1,79 +1,79 @@
/* ks-engine-kdns.c - KDNS OpenPGP key access
* Copyright (C) 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
-#include "userids.h"
+#include "../common/userids.h"
#include "ks-engine.h"
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"This keyserver engine accepts URLs of the form:\n"
" kdns://[NAMESERVER]/[ROOT][?at=STRING]\n"
"with\n"
" NAMESERVER used for queries (default: system standard)\n"
" ROOT a DNS name appended to the query (default: none)\n"
" STRING a string to replace the '@' (default: \".\")\n"
"If a long answer is expected add the parameter \"usevc=1\".\n"
"Supported methods: fetch\n"
"Example:\n"
"A query for \"hacker@gnupg.org\" with\n"
" kdns://10.0.0.1/example.net?at=_key_&usevc=1\n"
"setup as --auto-key-lookup in gpg does a CERT record query\n"
"with type PGP on the nameserver 10.0.0.1 for\n"
" hacker._key_.gnupg.org.example.net";
gpg_error_t err;
if (!uri)
err = ks_print_help (ctrl, " kdns");
else if (!strcmp (uri->scheme, "kdns"))
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Get the key from URI which is expected to specify a kdns scheme.
On success R_FP has an open stream to read the data. */
gpg_error_t
ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp)
{
gpg_error_t err;
(void)ctrl;
*r_fp = NULL;
if (strcmp (uri->scheme, "kdns"))
return gpg_error (GPG_ERR_INV_ARG);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return err;
}
diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c
index f664655b3..f50ba50d0 100644
--- a/dirmngr/ks-engine-ldap.c
+++ b/dirmngr/ks-engine-ldap.c
@@ -1,2101 +1,2101 @@
/* ks-engine-ldap.c - talk to a LDAP keyserver
* Copyright (C) 2001, 2002, 2004, 2005, 2006
* 2007 Free Software Foundation, Inc.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#ifdef _WIN32
# include <winsock2.h>
# include <winldap.h>
#else
# ifdef NEED_LBER_H
# include <lber.h>
# endif
/* For OpenLDAP, to enable the API that we're using. */
# define LDAP_DEPRECATED 1
# include <ldap.h>
#endif
#include "dirmngr.h"
#include "misc.h"
-#include "userids.h"
+#include "../common/userids.h"
#include "ks-engine.h"
#include "ldap-parse-uri.h"
#ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm);
#endif
/* Convert an LDAP error to a GPG error. */
static int
ldap_err_to_gpg_err (int code)
{
gpg_err_code_t ec;
switch (code)
{
#ifdef LDAP_X_CONNECTING
case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break;
#endif
case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break;
case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break;
case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break;
case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break;
case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break;
case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break;
case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break;
case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break;
case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break;
case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break;
case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break;
case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break;
case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break;
case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break;
case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break;
case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break;
case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break;
case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break;
case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break;
case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break;
case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break;
case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break;
case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break;
case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break;
case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break;
case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break;
case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break;
#ifdef LDAP_ADMINLIMIT_EXCEEDED
case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break;
#endif
#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION
case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break;
#endif
case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break;
case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break;
case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break;
case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break;
case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break;
case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break;
#ifdef LDAP_TYPE_OR_VALUE_EXISTS
case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break;
#endif
case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break;
case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break;
case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break;
case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break;
case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break;
case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break;
#ifdef LDAP_X_PROXY_AUTHZ_FAILURE
case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break;
#endif
case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break;
case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break;
#ifdef LDAP_INSUFFICIENT_ACCESS
case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break;
#endif
case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break;
case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break;
case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break;
case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break;
case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break;
case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break;
case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break;
case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break;
case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break;
case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break;
case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break;
case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break;
#ifdef LDAP_VLV_ERROR
case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break;
#endif
case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break;
#ifdef LDAP_CUP_RESOURCES_EXHAUSTED
case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break;
case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break;
case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break;
case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break;
case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break;
#endif
#ifdef LDAP_CANCELLED
case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break;
#endif
#ifdef LDAP_NO_SUCH_OPERATION
case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break;
#endif
#ifdef LDAP_TOO_LATE
case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break;
#endif
#ifdef LDAP_CANNOT_CANCEL
case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break;
#endif
#ifdef LDAP_ASSERTION_FAILED
case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break;
#endif
#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED
case LDAP_PROXIED_AUTHORIZATION_DENIED:
ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break;
#endif
default:
#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR)
if (LDAP_E_ERROR (code))
ec = GPG_ERR_LDAP_E_GENERAL;
else if (LDAP_X_ERROR (code))
ec = GPG_ERR_LDAP_X_GENERAL;
else
#endif
ec = GPG_ERR_LDAP_GENERAL;
break;
}
return ec;
}
/* Retrieve an LDAP error and return it's GPG equivalent. */
static int
ldap_to_gpg_err (LDAP *ld)
{
#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
int err;
if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0)
return ldap_err_to_gpg_err (err);
else
return GPG_ERR_GENERAL;
#elif defined(HAVE_LDAP_LD_ERRNO)
return ldap_err_to_gpg_err (ld->ld_errno);
#else
/* We should never get here since the LDAP library should always
have either ldap_get_option or ld_errno, but just in case... */
return GPG_ERR_INTERNAL;
#endif
}
static time_t
ldap2epochtime (const char *timestr)
{
struct tm pgptime;
time_t answer;
memset (&pgptime, 0, sizeof(pgptime));
/* YYYYMMDDHHmmssZ */
sscanf (timestr, "%4d%2d%2d%2d%2d%2d",
&pgptime.tm_year,
&pgptime.tm_mon,
&pgptime.tm_mday,
&pgptime.tm_hour,
&pgptime.tm_min,
&pgptime.tm_sec);
pgptime.tm_year -= 1900;
pgptime.tm_isdst = -1;
pgptime.tm_mon--;
/* mktime() takes the timezone into account, so we use timegm() */
answer = timegm (&pgptime);
return answer;
}
/* Caller must free the result. */
static char *
tm2ldaptime (struct tm *tm)
{
struct tm tmp = *tm;
char buf[16];
/* YYYYMMDDHHmmssZ */
tmp.tm_year += 1900;
tmp.tm_mon ++;
snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ",
tmp.tm_year,
tmp.tm_mon,
tmp.tm_mday,
tmp.tm_hour,
tmp.tm_min,
tmp.tm_sec);
return xstrdup (buf);
}
#if 0
/* Caller must free */
static char *
epoch2ldaptime (time_t stamp)
{
struct tm tm;
if (gmtime_r (&stamp, &tm))
return tm2ldaptime (&tm);
else
return xstrdup ("INVALID TIME");
}
#endif
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"Handler for LDAP URLs:\n"
" ldap://host:port/[BASEDN]???[bindname=BINDNAME,password=PASSWORD]\n"
"\n"
"Note: basedn, bindname and password need to be percent escaped. In\n"
"particular, spaces need to be replaced with %20 and commas with %2c.\n"
"bindname will typically be of the form:\n"
"\n"
" uid=user%2cou=PGP%20Users%2cdc=EXAMPLE%2cdc=ORG\n"
"\n"
"The ldaps:// and ldapi:// schemes are also supported. If ldaps is used\n"
"then the server's certificate will be checked. If it is not valid, any\n"
"operation will be aborted.\n"
"\n"
"Supported methods: search, get, put\n";
gpg_error_t err;
if(!uri)
err = ks_print_help (ctrl, " ldap");
else if (strcmp (uri->scheme, "ldap") == 0
|| strcmp (uri->scheme, "ldaps") == 0
|| strcmp (uri->scheme, "ldapi") == 0)
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Convert a keyspec to a filter. Return an error if the keyspec is
bad or is not supported. The filter is escaped and returned in
*filter. It is the caller's responsibility to free *filter.
*filter is only set if this function returns success (i.e., 0). */
static gpg_error_t
keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
{
/* Remove search type indicator and adjust PATTERN accordingly.
Note: don't include a preceding 0x when searching by keyid. */
/* XXX: Should we include disabled / revoke options? */
KEYDB_SEARCH_DESC desc;
char *f = NULL;
char *freeme = NULL;
gpg_error_t err = classify_user_id (keyspec, &desc, 1);
if (err)
return err;
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_EXACT:
f = xasprintf ("(pgpUserID=%s)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_SUBSTR:
if (! only_exact)
f = xasprintf ("(pgpUserID=*%s*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_MAIL:
if (! only_exact)
f = xasprintf ("(pgpUserID=*<%s>*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_MAILSUB:
if (! only_exact)
f = xasprintf ("(pgpUserID=*<*%s*>*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_MAILEND:
if (! only_exact)
f = xasprintf ("(pgpUserID=*<*%s>*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
f = xasprintf ("(pgpKeyID=%08lX)", (ulong) desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
f = xasprintf ("(pgpCertID=%08lX%08lX)",
(ulong) desc.u.kid[0], (ulong) desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR16:
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
case KEYDB_SEARCH_MODE_ISSUER:
case KEYDB_SEARCH_MODE_ISSUER_SN:
case KEYDB_SEARCH_MODE_SN:
case KEYDB_SEARCH_MODE_SUBJECT:
case KEYDB_SEARCH_MODE_KEYGRIP:
case KEYDB_SEARCH_MODE_WORDS:
case KEYDB_SEARCH_MODE_FIRST:
case KEYDB_SEARCH_MODE_NEXT:
default:
break;
}
xfree (freeme);
if (! f)
{
log_error ("Unsupported search mode.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
*filter = f;
return 0;
}
/* Connect to an LDAP server and interrogate it.
- uri describes the server to connect to and various options
including whether to use TLS and the username and password (see
ldap_parse_uri for a description of the various fields).
This function returns:
- The ldap connection handle in *LDAP_CONNP.
- The base DN for the PGP key space by querying the
pgpBaseKeySpaceDN attribute (This is normally
'ou=PGP Keys,dc=EXAMPLE,dc=ORG').
- The attribute to lookup to find the pgp key. This is either
'pgpKey' or 'pgpKeyV2'.
- Whether this is a real ldap server. (It's unclear what this
exactly means.)
The values are returned in the passed variables. If you pass NULL,
then the value won't be returned. It is the caller's
responsibility to release *LDAP_CONNP with ldap_unbind and xfree
*BASEDNP and *PGPKEYATTRP.
If this function successfully interrogated the server, it returns
0. If there was an LDAP error, it returns the LDAP error code. If
an error occurred, *basednp, etc., are undefined (and don't need to
be freed.)
If no LDAP error occurred, you still need to check that *basednp is
valid. If it is NULL, then the server does not appear to be an
OpenPGP Keyserver. In this case, you also do not need to xfree
*pgpkeyattrp. */
static int
my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
char **basednp, char **pgpkeyattrp, int *real_ldapp)
{
int err = 0;
LDAP *ldap_conn = NULL;
char *user = uri->auth;
struct uri_tuple_s *password_param = uri_query_lookup (uri, "password");
char *password = password_param ? password_param->value : NULL;
char *basedn = NULL;
/* Whether to look for the pgpKey or pgpKeyv2 attribute. */
char *pgpkeyattr = "pgpKey";
int real_ldap = 0;
log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s)\n",
uri->host, uri->port,
uri->path ?: "",
uri->auth ? "bindname=" : "", uri->auth ?: "",
uri->auth && password ? "," : "",
password ? "password=" : "", password ?: "");
/* If the uri specifies a secure connection and we don't support
TLS, then fail; don't silently revert to an insecure
connection. */
if (uri->use_tls)
{
#ifndef HAVE_LDAP_START_TLS_S
log_error ("Can't use LDAP to connect to the server: no TLS support.");
err = GPG_ERR_LDAP_NOT_SUPPORTED;
goto out;
#endif
}
ldap_conn = ldap_init (uri->host, uri->port);
if (! ldap_conn)
{
err = gpg_err_code_from_syserror ();
log_error ("Failed to open connection to LDAP server (%s://%s:%d)\n",
uri->scheme, uri->host, uri->port);
goto out;
}
#ifdef HAVE_LDAP_SET_OPTION
{
int ver = LDAP_VERSION3;
err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
if (err != LDAP_SUCCESS)
{
log_error ("gpgkeys: unable to go to LDAP 3: %s\n",
ldap_err2string (err));
goto out;
}
}
#endif
/* XXX: It would be nice to have an option to provide the server's
certificate. */
#if 0
#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION)
err = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
if (err)
{
log_error ("unable to set ca-cert-file to '%s': %s\n",
ca_cert_file, ldap_err2string (err));
goto out;
}
#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */
#endif
#ifdef HAVE_LDAP_START_TLS_S
if (uri->use_tls)
{
/* XXX: We need an option to determine whether to abort if the
certificate is bad or not. Right now we conservatively
default to checking the certificate and aborting. */
#ifndef HAVE_W32_SYSTEM
int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */
err = ldap_set_option (ldap_conn,
LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
if (err)
{
log_error ("Failed to set TLS option on LDAP connection.\n");
goto out;
}
#else
/* On Windows, the certificates are checked by default. If the
option to disable checking mentioned above is ever
implemented, the way to do that on Windows is to install a
callback routine using ldap_set_option (..,
LDAP_OPT_SERVER_CERTIFICATE, ..); */
#endif
err = ldap_start_tls_s (ldap_conn,
#ifdef HAVE_W32_SYSTEM
/* ServerReturnValue, result */
NULL, NULL,
#endif
/* ServerControls, ClientControls */
NULL, NULL);
if (err)
{
log_error ("Failed to connect to LDAP server with TLS.\n");
goto out;
}
}
#endif
/* By default we don't bind as there is usually no need to. */
if (uri->auth)
{
log_debug ("LDAP bind to %s, password %s\n",
user, password ? ">not shown<" : ">none<");
err = ldap_simple_bind_s (ldap_conn, user, password);
if (err != LDAP_SUCCESS)
{
log_error ("Internal LDAP bind error: %s\n",
ldap_err2string (err));
goto out;
}
}
if (uri->path && *uri->path)
/* User specified base DN. */
{
basedn = xstrdup (uri->path);
/* If the user specifies a base DN, then we know the server is a
real LDAP server. */
real_ldap = 1;
}
else
{
LDAPMessage *res = NULL;
/* Look for namingContexts. */
char *attr[] = { "namingContexts", NULL };
err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
"(objectClass=*)", attr, 0, &res);
if (err == LDAP_SUCCESS)
{
char **context = ldap_get_values (ldap_conn, res, "namingContexts");
if (context)
/* We found some, so try each namingContext as the search
base and look for pgpBaseKeySpaceDN. Because we found
this, we know we're talking to a regular-ish LDAP
server and not an LDAP keyserver. */
{
int i;
char *attr2[] =
{ "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
real_ldap = 1;
for (i = 0; context[i] && ! basedn; i++)
{
char **vals;
LDAPMessage *si_res;
{
char *object = xasprintf ("cn=pgpServerInfo,%s",
context[i]);
err = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
"(objectClass=*)", attr2, 0, &si_res);
xfree (object);
}
if (err == LDAP_SUCCESS)
{
vals = ldap_get_values (ldap_conn, si_res,
"pgpBaseKeySpaceDN");
if (vals)
{
basedn = xtrystrdup (vals[0]);
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, si_res,
"pgpSoftware");
if (vals)
{
log_debug ("Server: \t%s\n", vals[0]);
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, si_res,
"pgpVersion");
if (vals)
{
log_debug ("Version:\t%s\n", vals[0]);
ldap_value_free (vals);
}
}
/* From man ldap_search_s: "res parameter of
ldap_search_ext_s() and ldap_search_s() should be
freed with ldap_msgfree() regardless of return
value of these functions. */
ldap_msgfree (si_res);
}
ldap_value_free (context);
}
}
else
{
/* We don't have an answer yet, which means the server might
be an LDAP keyserver. */
char **vals;
LDAPMessage *si_res = NULL;
char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL };
err = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
"(objectClass=*)", attr2, 0, &si_res);
if (err == LDAP_SUCCESS)
{
/* For the LDAP keyserver, this is always
"OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
in the future. */
vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN");
if (vals)
{
basedn = xtrystrdup (vals[0]);
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, si_res, "software");
if (vals)
{
log_debug ("ldap: Server: \t%s\n", vals[0]);
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, si_res, "version");
if (vals)
{
log_debug ("ldap: Version:\t%s\n", vals[0]);
/* If the version is high enough, use the new
pgpKeyV2 attribute. This design is iffy at best,
but it matches how PGP does it. I figure the NAI
folks assumed that there would never be an LDAP
keyserver vendor with a different numbering
scheme. */
if (atoi (vals[0]) > 1)
pgpkeyattr = "pgpKeyV2";
ldap_value_free (vals);
}
}
ldap_msgfree (si_res);
}
/* From man ldap_search_s: "res parameter of ldap_search_ext_s()
and ldap_search_s() should be freed with ldap_msgfree()
regardless of return value of these functions. */
ldap_msgfree (res);
}
out:
if (! err)
{
log_debug ("ldap_conn: %p\n", ldap_conn);
log_debug ("real_ldap: %d\n", real_ldap);
log_debug ("basedn: %s\n", basedn);
log_debug ("pgpkeyattr: %s\n", pgpkeyattr);
}
if (! err && real_ldapp)
*real_ldapp = real_ldap;
if (err)
xfree (basedn);
else
{
if (pgpkeyattrp)
{
if (basedn)
*pgpkeyattrp = xstrdup (pgpkeyattr);
else
*pgpkeyattrp = NULL;
}
if (basednp)
*basednp = basedn;
else
xfree (basedn);
}
if (err)
{
if (ldap_conn)
ldap_unbind (ldap_conn);
}
else
*ldap_connp = ldap_conn;
return err;
}
/* Extract keys from an LDAP reply and write them out to the output
stream OUTPUT in a format GnuPG can import (either the OpenPGP
binary format or armored format). */
static void
extract_keys (estream_t output,
LDAP *ldap_conn, const char *certid, LDAPMessage *message)
{
char **vals;
es_fprintf (output, "INFO %s BEGIN\n", certid);
es_fprintf (output, "pub:%s:", certid);
/* Note: ldap_get_values returns a NULL terminates array of
strings. */
vals = ldap_get_values (ldap_conn, message, "pgpkeytype");
if (vals && vals[0])
{
if (strcmp (vals[0], "RSA") == 0)
es_fprintf (output, "1");
else if (strcmp (vals[0],"DSS/DH") == 0)
es_fprintf (output, "17");
ldap_value_free (vals);
}
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeysize");
if (vals && vals[0])
{
int v = atoi (vals[0]);
if (v > 0)
es_fprintf (output, "%d", v);
ldap_value_free (vals);
}
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeycreatetime");
if (vals && vals[0])
{
if (strlen (vals[0]) == 15)
es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
ldap_value_free (vals);
}
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeyexpiretime");
if (vals && vals[0])
{
if (strlen (vals[0]) == 15)
es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
ldap_value_free (vals);
}
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgprevoked");
if (vals && vals[0])
{
if (atoi (vals[0]) == 1)
es_fprintf (output, "r");
ldap_value_free (vals);
}
es_fprintf (output, "\n");
vals = ldap_get_values (ldap_conn, message, "pgpuserid");
if (vals && vals[0])
{
int i;
for (i = 0; vals[i]; i++)
es_fprintf (output, "uid:%s\n", vals[i]);
ldap_value_free (vals);
}
es_fprintf (output, "INFO %s END\n", certid);
}
/* Get the key described key the KEYSPEC string from the keyserver
identified by URI. On success R_FP has an open stream to read the
data. */
gpg_error_t
ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
estream_t *r_fp)
{
gpg_error_t err = 0;
int ldap_err;
char *filter = NULL;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
char *pgpkeyattr = NULL;
estream_t fp = NULL;
LDAPMessage *message = NULL;
(void) ctrl;
if (dirmngr_use_tor ())
{
/* For now we do not support LDAP over Tor. */
log_error (_("LDAP access not possible due to Tor mode\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* Before connecting to the server, make sure we have a sane
keyspec. If not, there is no need to establish a network
connection. */
err = keyspec_to_ldap_filter (keyspec, &filter, 1);
if (err)
return (err);
/* Make sure we are talking to an OpenPGP LDAP server. */
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &pgpkeyattr, NULL);
if (ldap_err || !basedn)
{
if (ldap_err)
err = ldap_err_to_gpg_err (ldap_err);
else
err = GPG_ERR_GENERAL;
goto out;
}
{
/* The ordering is significant. Specifically, "pgpcertid" needs
to be the second item in the list, since everything after it
may be discarded we aren't in verbose mode. */
char *attrs[] =
{
pgpkeyattr,
"pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
"pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype",
NULL
};
/* 1 if we want just attribute types; 0 if we want both attribute
types and values. */
int attrsonly = 0;
int count;
ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE,
filter, attrs, attrsonly, &message);
if (ldap_err)
{
err = ldap_err_to_gpg_err (ldap_err);
log_error ("gpgkeys: LDAP search error: %s\n",
ldap_err2string (ldap_err));
goto out;
}
count = ldap_count_entries (ldap_conn, message);
if (count < 1)
{
log_error ("gpgkeys: key %s not found on keyserver\n", keyspec);
if (count == -1)
err = ldap_to_gpg_err (ldap_conn);
else
err = gpg_error (GPG_ERR_NO_DATA);
goto out;
}
{
/* There may be more than one unique result for a given keyID,
so we should fetch them all (test this by fetching short key
id 0xDEADBEEF). */
/* The set of entries that we've seen. */
strlist_t seen = NULL;
LDAPMessage *each;
for (each = ldap_first_entry (ldap_conn, message);
each;
each = ldap_next_entry (ldap_conn, each))
{
char **vals;
char **certid;
/* Use the long keyid to remove duplicates. The LDAP
server returns the same keyid more than once if there
are multiple user IDs on the key. Note that this does
NOT mean that a keyid that exists multiple times on the
keyserver will not be fetched. It means that each KEY,
no matter how many user IDs share its keyid, will be
fetched only once. If a keyid that belongs to more
than one key is fetched, the server quite properly
responds with all matching keys. -ds */
certid = ldap_get_values (ldap_conn, each, "pgpcertid");
if (certid && certid[0])
{
if (! strlist_find (seen, certid[0]))
{
/* It's not a duplicate, add it */
add_to_strlist (&seen, certid[0]);
if (! fp)
fp = es_fopenmem(0, "rw");
extract_keys (fp, ldap_conn, certid[0], each);
vals = ldap_get_values (ldap_conn, each, pgpkeyattr);
if (! vals)
{
err = ldap_to_gpg_err (ldap_conn);
log_error("gpgkeys: unable to retrieve key %s "
"from keyserver\n", certid[0]);
goto out;
}
else
{
/* We should strip the new lines. */
es_fprintf (fp, "KEY 0x%s BEGIN\n", certid[0]);
es_fputs (vals[0], fp);
es_fprintf (fp, "\nKEY 0x%s END\n", certid[0]);
ldap_value_free (vals);
}
}
}
ldap_value_free (certid);
}
free_strlist (seen);
if (! fp)
err = gpg_error (GPG_ERR_NO_DATA);
}
}
out:
if (message)
ldap_msgfree (message);
if (err)
{
if (fp)
es_fclose (fp);
}
else
{
if (fp)
es_fseek (fp, 0, SEEK_SET);
*r_fp = fp;
}
xfree (pgpkeyattr);
xfree (basedn);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (filter);
return err;
}
/* Search the keyserver identified by URI for keys matching PATTERN.
On success R_FP has an open stream to read the data. */
gpg_error_t
ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
estream_t *r_fp)
{
gpg_error_t err;
int ldap_err;
char *filter = NULL;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
estream_t fp = NULL;
(void) ctrl;
if (dirmngr_use_tor ())
{
/* For now we do not support LDAP over Tor. */
log_error (_("LDAP access not possible due to Tor mode\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* Before connecting to the server, make sure we have a sane
keyspec. If not, there is no need to establish a network
connection. */
err = keyspec_to_ldap_filter (pattern, &filter, 0);
if (err)
{
log_error ("Bad search pattern: '%s'\n", pattern);
return (err);
}
/* Make sure we are talking to an OpenPGP LDAP server. */
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL);
if (ldap_err || !basedn)
{
if (ldap_err)
err = ldap_err_to_gpg_err (ldap_err);
else
err = GPG_ERR_GENERAL;
goto out;
}
/* Even if we have no results, we want to return a stream. */
fp = es_fopenmem(0, "rw");
if (!fp)
{
err = gpg_error_from_syserror ();
goto out;
}
{
char **vals;
LDAPMessage *res, *each;
int count = 0;
strlist_t dupelist = NULL;
/* The maximum size of the search, including the optional stuff
and the trailing \0 */
char *attrs[] =
{
"pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled",
"pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp",
"pgpkeysize", "pgpkeytype", NULL
};
log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
ldap_err = ldap_search_s (ldap_conn, basedn,
LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
xfree (filter);
filter = NULL;
if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED)
{
err = ldap_err_to_gpg_err (ldap_err);
log_error ("SEARCH %s FAILED %d\n", pattern, err);
log_error ("gpgkeys: LDAP search error: %s\n",
ldap_err2string (err));
goto out;
}
/* The LDAP server doesn't return a real count of unique keys, so we
can't use ldap_count_entries here. */
for (each = ldap_first_entry (ldap_conn, res);
each;
each = ldap_next_entry (ldap_conn, each))
{
char **certid = ldap_get_values (ldap_conn, each, "pgpcertid");
if (certid && certid[0] && ! strlist_find (dupelist, certid[0]))
{
add_to_strlist (&dupelist, certid[0]);
count++;
}
}
if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
{
if (count == 1)
log_error ("gpgkeys: search results exceeded server limit."
" First 1 result shown.\n");
else
log_error ("gpgkeys: search results exceeded server limit."
" First %d results shown.\n", count);
}
free_strlist (dupelist);
dupelist = NULL;
if (count < 1)
es_fputs ("info:1:0\n", fp);
else
{
es_fprintf (fp, "info:1:%d\n", count);
for (each = ldap_first_entry (ldap_conn, res);
each;
each = ldap_next_entry (ldap_conn, each))
{
char **certid;
LDAPMessage *uids;
certid = ldap_get_values (ldap_conn, each, "pgpcertid");
if (! certid || ! certid[0])
continue;
/* Have we seen this certid before? */
if (! strlist_find (dupelist, certid[0]))
{
add_to_strlist (&dupelist, certid[0]);
es_fprintf (fp, "pub:%s:",certid[0]);
vals = ldap_get_values (ldap_conn, each, "pgpkeytype");
if (vals)
{
/* The LDAP server doesn't exactly handle this
well. */
if (strcasecmp (vals[0], "RSA") == 0)
es_fputs ("1", fp);
else if (strcasecmp (vals[0], "DSS/DH") == 0)
es_fputs ("17", fp);
ldap_value_free (vals);
}
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgpkeysize");
if (vals)
{
/* Not sure why, but some keys are listed with a
key size of 0. Treat that like an unknown. */
if (atoi (vals[0]) > 0)
es_fprintf (fp, "%d", atoi (vals[0]));
ldap_value_free (vals);
}
es_fputc (':', fp);
/* YYYYMMDDHHmmssZ */
vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime");
if(vals && strlen (vals[0]) == 15)
{
es_fprintf (fp, "%u",
(unsigned int) ldap2epochtime(vals[0]));
ldap_value_free (vals);
}
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime");
if (vals && strlen (vals[0]) == 15)
{
es_fprintf (fp, "%u",
(unsigned int) ldap2epochtime (vals[0]));
ldap_value_free (vals);
}
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgprevoked");
if (vals)
{
if (atoi (vals[0]) == 1)
es_fprintf (fp, "r");
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, each, "pgpdisabled");
if (vals)
{
if (atoi (vals[0]) ==1)
es_fprintf (fp, "d");
ldap_value_free (vals);
}
#if 0
/* This is not yet specified in the keyserver
protocol, but may be someday. */
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "modifytimestamp");
if(vals && strlen (vals[0]) == 15)
{
es_fprintf (fp, "%u",
(unsigned int) ldap2epochtime (vals[0]));
ldap_value_free (vals);
}
#endif
es_fprintf (fp, "\n");
/* Now print all the uids that have this certid */
for (uids = ldap_first_entry (ldap_conn, res);
uids;
uids = ldap_next_entry (ldap_conn, uids))
{
vals = ldap_get_values (ldap_conn, uids, "pgpcertid");
if (! vals)
continue;
if (strcasecmp (certid[0], vals[0]) == 0)
{
char **uidvals;
es_fprintf (fp, "uid:");
uidvals = ldap_get_values (ldap_conn,
uids, "pgpuserid");
if (uidvals)
{
/* Need to escape any colons */
char *quoted = percent_escape (uidvals[0], NULL);
es_fputs (quoted, fp);
xfree (quoted);
ldap_value_free (uidvals);
}
es_fprintf (fp, "\n");
}
ldap_value_free(vals);
}
}
ldap_value_free (certid);
}
}
ldap_msgfree (res);
free_strlist (dupelist);
}
log_debug ("SEARCH %s END\n", pattern);
out:
if (err)
{
if (fp)
es_fclose (fp);
}
else
{
/* Return the read stream. */
if (fp)
es_fseek (fp, 0, SEEK_SET);
*r_fp = fp;
}
xfree (basedn);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (filter);
return err;
}
/* A modlist describes a set of changes to an LDAP entry. (An entry
consists of 1 or more attributes. Attributes are <name, value>
pairs. Note: an attribute may be multi-valued in which case
multiple values are associated with a single name.)
A modlist is a NULL terminated array of struct LDAPMod's.
Thus, if we have:
LDAPMod **modlist;
Then:
modlist[i]
Is the ith modification.
Each LDAPMod describes a change to a single attribute. Further,
there is one modification for each attribute that we want to
change. The attribute's new value is stored in LDAPMod.mod_values.
If the attribute is multi-valued, we still only use a single
LDAPMod structure: mod_values is a NULL-terminated array of
strings. To delete an attribute from an entry, we set mod_values
to NULL.
Thus, if:
modlist[i]->mod_values == NULL
then we remove the attribute.
(Using LDAP_MOD_DELETE doesn't work here as we don't know if the
attribute in question exists or not.)
Note: this function does NOT copy or free ATTR. It does copy
VALUE. */
static void
modlist_add (LDAPMod ***modlistp, char *attr, const char *value)
{
LDAPMod **modlist = *modlistp;
LDAPMod **m;
int nummods = 0;
/* Search modlist for the attribute we're playing with. If modlist
is NULL, then the list is empty. Recall: modlist is a NULL
terminated array. */
for (m = modlist; m && *m; m++, nummods ++)
{
/* The attribute is already on the list. */
char **ptr;
int numvalues = 0;
if (strcasecmp ((*m)->mod_type, attr) != 0)
continue;
/* We have this attribute already, so when the REPLACE happens,
the server attributes will be replaced anyway. */
if (! value)
return;
/* Attributes can be multi-valued. See if the value is already
present. mod_values is a NULL terminated array of pointers.
Note: mod_values can be NULL. */
for (ptr = (*m)->mod_values; ptr && *ptr; ptr++)
{
if (strcmp (*ptr, value) == 0)
/* Duplicate value, we're done. */
return;
numvalues ++;
}
/* Append the value. */
ptr = xrealloc ((*m)->mod_values, sizeof (char *) * (numvalues + 2));
(*m)->mod_values = ptr;
ptr[numvalues] = xstrdup (value);
ptr[numvalues + 1] = NULL;
return;
}
/* We didn't find the attr, so make one and add it to the end */
/* Like attribute values, the list of attributes is NULL terminated
array of pointers. */
modlist = xrealloc (modlist, sizeof (LDAPMod *) * (nummods + 2));
*modlistp = modlist;
modlist[nummods] = xmalloc (sizeof (LDAPMod));
modlist[nummods]->mod_op = LDAP_MOD_REPLACE;
modlist[nummods]->mod_type = attr;
if (value)
{
modlist[nummods]->mod_values = xmalloc (sizeof(char *) * 2);
modlist[nummods]->mod_values[0] = xstrdup (value);
modlist[nummods]->mod_values[1] = NULL;
}
else
modlist[nummods]->mod_values = NULL;
modlist[nummods + 1] = NULL;
return;
}
/* Look up the value of an attribute in the specified modlist. If the
attribute is not on the mod list, returns NULL. The result is a
NULL-terminated array of strings. Don't change it. */
static char **
modlist_lookup (LDAPMod **modlist, const char *attr)
{
LDAPMod **m;
for (m = modlist; m && *m; m++)
{
if (strcasecmp ((*m)->mod_type, attr) != 0)
continue;
return (*m)->mod_values;
}
return NULL;
}
/* Dump a modlist to a file. This is useful for debugging. */
static estream_t modlist_dump (LDAPMod **modlist, estream_t output)
GPGRT_ATTR_USED;
static estream_t
modlist_dump (LDAPMod **modlist, estream_t output)
{
LDAPMod **m;
int opened = 0;
if (! output)
{
output = es_fopenmem (0, "rw");
if (!output)
return NULL;
opened = 1;
}
for (m = modlist; m && *m; m++)
{
es_fprintf (output, " %s:", (*m)->mod_type);
if (! (*m)->mod_values)
es_fprintf(output, " delete.\n");
else
{
char **ptr;
int i;
int multi = 0;
if ((*m)->mod_values[0] && (*m)->mod_values[1])
/* Have at least 2. */
multi = 1;
if (multi)
es_fprintf (output, "\n");
for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++)
{
/* Assuming terminals are about 80 characters wide,
display at most about 10 lines of debugging
output. If we do trim the buffer, append '...' to
the end. */
const int max_len = 10 * 70;
size_t value_len = strlen (*ptr);
int elide = value_len > max_len;
if (multi)
es_fprintf (output, " %d. ", i);
es_fprintf (output, "`%.*s", max_len, *ptr);
if (elide)
es_fprintf (output, "...' (%zd bytes elided)",
value_len - max_len);
else
es_fprintf (output, "'");
es_fprintf (output, "\n");
}
}
}
if (opened)
es_fseek (output, 0, SEEK_SET);
return output;
}
/* Free all of the memory allocated by the mod list. This assumes
that the attribute names don't have to be freed, but the attributes
values do. (Which is what modlist_add does.) */
static void
modlist_free (LDAPMod **modlist)
{
LDAPMod **ml;
if (! modlist)
return;
/* Unwind and free the whole modlist structure */
/* The modlist is a NULL terminated array of pointers. */
for (ml = modlist; *ml; ml++)
{
LDAPMod *mod = *ml;
char **ptr;
/* The list of values is a NULL termianted array of pointers.
If the list is NULL, there are no values. */
if (mod->mod_values)
{
for (ptr = mod->mod_values; *ptr; ptr++)
xfree (*ptr);
xfree (mod->mod_values);
}
xfree (mod);
}
xfree (modlist);
}
/* Append two onto the end of one. Two is not freed, but its pointers
are now part of one. Make sure you don't free them both!
As long as you don't add anything to ONE, TWO is still valid.
After that all bets are off. */
static void
modlists_join (LDAPMod ***one, LDAPMod **two)
{
int i, one_count = 0, two_count = 0;
LDAPMod **grow;
if (!*two)
/* two is empty. Nothing to do. */
return;
if (!*one)
/* one is empty. Just set it equal to *two. */
{
*one = two;
return;
}
for (grow = *one; *grow; grow++)
one_count ++;
for (grow = two; *grow; grow++)
two_count ++;
grow = xrealloc (*one, sizeof(LDAPMod *) * (one_count + two_count + 1));
for (i = 0; i < two_count; i++)
grow[one_count + i] = two[i];
grow[one_count + i] = NULL;
*one = grow;
}
/* Given a string, unescape C escapes. In particular, \xXX. This
modifies the string in place. */
static void
uncescape (char *str)
{
size_t r = 0;
size_t w = 0;
char *first = strchr (str, '\\');
if (! first)
/* No backslashes => no escaping. We're done. */
return;
/* Start at the first '\\'. */
r = w = (uintptr_t) first - (uintptr_t) str;
while (str[r])
{
/* XXX: What to do about bad escapes?
XXX: hextobyte already checks the string thus the hexdigitp
could be removed. */
if (str[r] == '\\' && str[r + 1] == 'x'
&& str[r+2] && str[r+3]
&& hexdigitp (str + r + 2)
&& hexdigitp (str + r + 3))
{
int x = hextobyte (&str[r + 2]);
assert (0 <= x && x <= 0xff);
str[w] = x;
/* We consumed 4 characters and wrote 1. */
r += 4;
w ++;
}
else
str[w ++] = str[r ++];
}
str[w] = '\0';
}
/* Given one line from an info block (`gpg --list-{keys,sigs}
--with-colons KEYID'), pull it apart and fill in the modlist with
the relevant (for the LDAP schema) attributes. */
static void
extract_attributes (LDAPMod ***modlist, char *line)
{
int field_count;
char **fields;
char *keyid;
int is_pub, is_sub, is_uid, is_sig;
/* Remove trailing whitespace */
trim_trailing_spaces (line);
fields = strsplit (line, ':', '\0', &field_count);
if (field_count == 1)
/* We only have a single field. There is definitely nothing to
do. */
goto out;
if (field_count < 7)
goto out;
is_pub = strcasecmp ("pub", fields[0]) == 0;
is_sub = strcasecmp ("sub", fields[0]) == 0;
is_uid = strcasecmp ("uid", fields[0]) == 0;
is_sig = strcasecmp ("sig", fields[0]) == 0;
if (!is_pub && !is_sub && !is_uid && !is_sig)
/* Not a relevant line. */
goto out;
keyid = fields[4];
if (is_uid && strlen (keyid) == 0)
/* The uid record type can have an empty keyid. */
;
else if (strlen (keyid) == 16
&& strspn (keyid, "0123456789aAbBcCdDeEfF") == 16)
/* Otherwise, we expect exactly 16 hex characters. */
;
else
{
log_error ("malformed record!\n");
goto out;
}
if (is_pub)
{
int disabled = 0;
int revoked = 0;
char *flags;
for (flags = fields[1]; *flags; flags ++)
switch (*flags)
{
case 'r':
case 'R':
revoked = 1;
break;
case 'd':
case 'D':
disabled = 1;
break;
}
/* Note: we always create the pgpDisabled and pgpRevoked
attributes, regardless of whether the key is disabled/revoked
or not. This is because a very common search is like
"(&(pgpUserID=*isabella*)(pgpDisabled=0))" */
if (is_pub)
{
modlist_add (modlist,"pgpDisabled", disabled ? "1" : "0");
modlist_add (modlist,"pgpRevoked", revoked ? "1" : "0");
}
}
if (is_pub || is_sub)
{
char *size = fields[2];
int val = atoi (size);
size = NULL;
if (val > 0)
{
/* We zero pad this on the left to make PGP happy. */
char padded[6];
if (val < 99999 && val > 0)
{
snprintf (padded, sizeof padded, "%05u", val);
size = padded;
}
}
if (size)
{
if (is_pub || is_sub)
modlist_add (modlist, "pgpKeySize", size);
}
}
if (is_pub)
{
char *algo = fields[3];
int val = atoi (algo);
switch (val)
{
case 1:
algo = "RSA";
break;
case 17:
algo = "DSS/DH";
break;
default:
algo = NULL;
break;
}
if (algo)
{
if (is_pub)
modlist_add (modlist, "pgpKeyType", algo);
}
}
if (is_pub || is_sub || is_sig)
{
if (is_pub)
{
modlist_add (modlist, "pgpCertID", keyid);
modlist_add (modlist, "pgpKeyID", &keyid[8]);
}
if (is_sub)
modlist_add (modlist, "pgpSubKeyID", keyid);
if (is_sig)
modlist_add (modlist, "pgpSignerID", keyid);
}
if (is_pub)
{
char *create_time = fields[5];
if (strlen (create_time) == 0)
create_time = NULL;
else
{
char *create_time_orig = create_time;
struct tm tm;
time_t t;
char *end;
memset (&tm, 0, sizeof (tm));
/* parse_timestamp handles both seconds fromt he epoch and
ISO 8601 format. We also need to handle YYYY-MM-DD
format (as generated by gpg1 --with-colons --list-key).
Check that first and then if it fails, then try
parse_timestamp. */
if (!isodate_human_to_tm (create_time, &tm))
create_time = tm2ldaptime (&tm);
else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1
&& *end == '\0')
{
if (!gnupg_gmtime (&t, &tm))
create_time = NULL;
else
create_time = tm2ldaptime (&tm);
}
else
create_time = NULL;
if (! create_time)
/* Failed to parse string. */
log_error ("Failed to parse creation time ('%s')",
create_time_orig);
}
if (create_time)
{
modlist_add (modlist, "pgpKeyCreateTime", create_time);
xfree (create_time);
}
}
if (is_pub)
{
char *expire_time = fields[6];
if (strlen (expire_time) == 0)
expire_time = NULL;
else
{
char *expire_time_orig = expire_time;
struct tm tm;
time_t t;
char *end;
memset (&tm, 0, sizeof (tm));
/* parse_timestamp handles both seconds fromt he epoch and
ISO 8601 format. We also need to handle YYYY-MM-DD
format (as generated by gpg1 --with-colons --list-key).
Check that first and then if it fails, then try
parse_timestamp. */
if (!isodate_human_to_tm (expire_time, &tm))
expire_time = tm2ldaptime (&tm);
else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1
&& *end == '\0')
{
if (!gnupg_gmtime (&t, &tm))
expire_time = NULL;
else
expire_time = tm2ldaptime (&tm);
}
else
expire_time = NULL;
if (! expire_time)
/* Failed to parse string. */
log_error ("Failed to parse creation time ('%s')",
expire_time_orig);
}
if (expire_time)
{
modlist_add (modlist, "pgpKeyExpireTime", expire_time);
xfree (expire_time);
}
}
if ((is_uid || is_pub) && field_count >= 10)
{
char *uid = fields[9];
if (is_pub && strlen (uid) == 0)
/* When using gpg --list-keys, the uid is included. When
passed via gpg, it is not. It is important to process it
when it is present, because gpg 1 won't print a UID record
if there is only one key. */
;
else
{
uncescape (uid);
modlist_add (modlist, "pgpUserID", uid);
}
}
out:
free (fields);
}
/* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to
the keyserver identified by URI. See server.c:cmd_ks_put for the
format of the data and metadata. */
gpg_error_t
ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
void *data, size_t datalen,
void *info, size_t infolen)
{
gpg_error_t err = 0;
int ldap_err;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
char *pgpkeyattr = NULL;
int real_ldap;
LDAPMod **modlist = NULL;
LDAPMod **addlist = NULL;
char *data_armored = NULL;
/* The last byte of the info block. */
const char *infoend = (const char *) info + infolen - 1;
/* Enable this code to dump the modlist to /tmp/modlist.txt. */
#if 0
# warning Disable debug code before checking in.
const int dump_modlist = 1;
#else
const int dump_modlist = 0;
#endif
estream_t dump = NULL;
/* Elide a warning. */
(void) ctrl;
if (dirmngr_use_tor ())
{
/* For now we do not support LDAP over Tor. */
log_error (_("LDAP access not possible due to Tor mode\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
ldap_err = my_ldap_connect (uri,
&ldap_conn, &basedn, &pgpkeyattr, &real_ldap);
if (ldap_err || !basedn)
{
if (ldap_err)
err = ldap_err_to_gpg_err (ldap_err);
else
err = GPG_ERR_GENERAL;
goto out;
}
if (! real_ldap)
/* We appear to have an OpenPGP Keyserver, which can unpack the key
on its own (not just a dumb LDAP server). */
{
LDAPMod mod, *attrs[2];
char *key[] = { data, NULL };
char *dn;
memset (&mod, 0, sizeof (mod));
mod.mod_op = LDAP_MOD_ADD;
mod.mod_type = pgpkeyattr;
mod.mod_values = key;
attrs[0] = &mod;
attrs[1] = NULL;
dn = xasprintf ("pgpCertid=virtual,%s", basedn);
ldap_err = ldap_add_s (ldap_conn, dn, attrs);
xfree (dn);
if (ldap_err != LDAP_SUCCESS)
{
err = ldap_err_to_gpg_err (err);
goto out;
}
goto out;
}
modlist = xmalloc (sizeof (LDAPMod *));
*modlist = NULL;
if (dump_modlist)
{
dump = es_fopen("/tmp/modlist.txt", "w");
if (! dump)
log_error ("Failed to open /tmp/modlist.txt: %s\n",
strerror (errno));
if (dump)
{
es_fprintf(dump, "data (%zd bytes)\n", datalen);
es_fprintf(dump, "info (%zd bytes): '\n", infolen);
es_fwrite(info, infolen, 1, dump);
es_fprintf(dump, "'\n");
}
}
/* Start by nulling out all attributes. We try and do a modify
operation first, so this ensures that we don't leave old
attributes lying around. */
modlist_add (&modlist, "pgpDisabled", NULL);
modlist_add (&modlist, "pgpKeyID", NULL);
modlist_add (&modlist, "pgpKeyType", NULL);
modlist_add (&modlist, "pgpUserID", NULL);
modlist_add (&modlist, "pgpKeyCreateTime", NULL);
modlist_add (&modlist, "pgpSignerID", NULL);
modlist_add (&modlist, "pgpRevoked", NULL);
modlist_add (&modlist, "pgpSubKeyID", NULL);
modlist_add (&modlist, "pgpKeySize", NULL);
modlist_add (&modlist, "pgpKeyExpireTime", NULL);
modlist_add (&modlist, "pgpCertID", NULL);
/* Assemble the INFO stuff into LDAP attributes */
while (infolen > 0)
{
char *temp = NULL;
char *newline = memchr (info, '\n', infolen);
if (! newline)
/* The last line is not \n terminated! Make a copy so we can
add a NUL terminator. */
{
temp = xmalloc (infolen + 1);
memcpy (temp, info, infolen);
info = temp;
newline = (char *) info + infolen;
}
*newline = '\0';
extract_attributes (&modlist, info);
infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1);
info = newline + 1;
/* Sanity check. */
if (! temp)
assert ((char *) info + infolen - 1 == infoend);
else
{
assert (infolen == -1);
xfree (temp);
}
}
modlist_add (&addlist, "objectClass", "pgpKeyInfo");
err = armor_data (&data_armored, data, datalen);
if (err)
goto out;
modlist_add (&addlist, pgpkeyattr, data_armored);
/* Now append addlist onto modlist. */
modlists_join (&modlist, addlist);
if (dump)
{
estream_t input = modlist_dump (modlist, NULL);
if (input)
{
copy_stream (input, dump);
es_fclose (input);
}
}
/* Going on the assumption that modify operations are more frequent
than adds, we try a modify first. If it's not there, we just
turn around and send an add command for the same key. Otherwise,
the modify brings the server copy into compliance with our copy.
Note that unlike the LDAP keyserver (and really, any other
keyserver) this does NOT merge signatures, but replaces the whole
key. This should make some people very happy. */
{
char **certid;
char *dn;
certid = modlist_lookup (modlist, "pgpCertID");
if (/* We should have a value. */
! certid
/* Exactly one. */
|| !(certid[0] && !certid[1]))
{
log_error ("Bad certid.\n");
err = GPG_ERR_GENERAL;
goto out;
}
dn = xasprintf ("pgpCertID=%s,%s", certid[0], basedn);
err = ldap_modify_s (ldap_conn, dn, modlist);
if (err == LDAP_NO_SUCH_OBJECT)
err = ldap_add_s (ldap_conn, dn, addlist);
xfree (dn);
if (err != LDAP_SUCCESS)
{
log_error ("gpgkeys: error adding key to keyserver: %s\n",
ldap_err2string (err));
err = ldap_err_to_gpg_err (err);
}
}
out:
if (dump)
es_fclose (dump);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (basedn);
xfree (pgpkeyattr);
modlist_free (modlist);
xfree (addlist);
xfree (data_armored);
return err;
}
diff --git a/dirmngr/ldap-parse-uri.c b/dirmngr/ldap-parse-uri.c
index 967149694..94d4efd38 100644
--- a/dirmngr/ldap-parse-uri.c
+++ b/dirmngr/ldap-parse-uri.c
@@ -1,246 +1,246 @@
/* ldap-parse-uri.c - Parse an LDAP URI.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <gpg-error.h>
#ifdef HAVE_W32_SYSTEM
# include "ldap-url.h"
#else
# include <ldap.h>
#endif
-#include "util.h"
+#include "../common/util.h"
#include "http.h"
/* Returns 1 if the string is an LDAP URL (begins with ldap:, ldaps:
or ldapi:). */
int
ldap_uri_p (const char *url)
{
char *colon = strchr (url, ':');
if (! colon)
return 0;
else
{
int offset = (uintptr_t) colon - (uintptr_t) url;
if (/* All lower case. */
(offset == 4 && memcmp (url, "ldap", 4) == 0)
|| (offset == 5
&& (memcmp (url, "ldaps", 5) == 0
&& memcmp (url, "ldapi", 5) == 0))
/* Mixed case. */
|| ((url[0] == 'l' || url[0] == 'L')
&& (url[1] == 'd' || url[1] == 'D')
&& (url[2] == 'a' || url[2] == 'A')
&& (url[3] == 'p' || url[3] == 'P')
&& (url[4] == ':'
|| ((url[4] == 's' || url[4] == 'S'
|| url[4] == 'i' || url[4] == 'i')
&& url[5] == ':'))))
return 1;
return 0;
}
}
/* Parse a URI and put the result into *purip. On success the
caller must use http_release_parsed_uri() to releases the resources.
uri->path is the base DN (or NULL for the default).
uri->auth is the bindname (or NULL for none).
The uri->query variable "password" is the password.
Note: any specified scope, any attributes, any filter and any
unknown extensions are simply ignored. */
gpg_error_t
ldap_parse_uri (parsed_uri_t *purip, const char *uri)
{
gpg_err_code_t err = 0;
parsed_uri_t puri = NULL;
int result;
LDAPURLDesc *lud = NULL;
char *scheme = NULL;
char *host = NULL;
char *dn = NULL;
char *bindname = NULL;
char *password = NULL;
char **s;
char *buffer;
int len;
result = ldap_url_parse (uri, &lud);
if (result != 0)
{
log_error ("Unable to parse LDAP uri '%s'\n", uri);
err = GPG_ERR_GENERAL;
goto out;
}
scheme = lud->lud_scheme;
host = lud->lud_host;
dn = lud->lud_dn;
for (s = lud->lud_exts; s && *s; s ++)
{
if (strncmp (*s, "bindname=", 9) == 0)
{
if (bindname)
log_error ("bindname given multiple times in URL '%s', ignoring.\n",
uri);
else
bindname = *s + 9;
}
else if (strncmp (*s, "password=", 9) == 0)
{
if (password)
log_error ("password given multiple times in URL '%s', ignoring.\n",
uri);
else
password = *s + 9;
}
else
log_error ("Unhandled extension (%s) in URL '%s', ignoring.",
*s, uri);
}
len = 0;
#define add(s) do { if (s) len += strlen (s) + 1; } while (0)
add (scheme);
add (host);
add (dn);
add (bindname);
add (password);
puri = xtrycalloc (1, sizeof *puri + len);
if (! puri)
{
err = gpg_err_code_from_syserror ();
goto out;
}
buffer = puri->buffer;
#define copy(to, s) \
do \
{ \
if (s) \
{ \
to = buffer; \
buffer = stpcpy (buffer, s) + 1; \
} \
} \
while (0)
copy (puri->scheme, scheme);
/* Make sure the scheme is lower case. */
ascii_strlwr (puri->scheme);
copy (puri->host, host);
copy (puri->path, dn);
copy (puri->auth, bindname);
if (password)
{
puri->query = calloc (sizeof (*puri->query), 1);
if (!puri->query)
{
err = gpg_err_code_from_syserror ();
goto out;
}
puri->query->name = "password";
copy (puri->query->value, password);
puri->query->valuelen = strlen (password) + 1;
}
puri->use_tls = strcmp (puri->scheme, "ldaps") == 0;
puri->port = lud->lud_port;
out:
if (lud)
ldap_free_urldesc (lud);
if (err)
{
if (puri)
http_release_parsed_uri (puri);
}
else
*purip = puri;
return gpg_err_make (default_errsource, err);
}
/* The following characters need to be escaped to be part of an LDAP
filter: *, (, ), \, NUL and /. Note: we don't handle NUL, since a
NUL can't be part of a C string.
This function always allocates a new string on success. It is the
caller's responsibility to free it.
*/
char *
ldap_escape_filter (const char *filter)
{
int l = strcspn (filter, "*()\\/");
if (l == strlen (filter))
/* Nothing to escape. */
return xstrdup (filter);
{
/* In the worst case we need to escape every letter. */
char *escaped = xmalloc (1 + 3 * strlen (filter));
/* Indices into filter and escaped. */
int filter_i = 0;
int escaped_i = 0;
for (filter_i = 0; filter_i < strlen (filter); filter_i ++)
{
switch (filter[filter_i])
{
case '*':
case '(':
case ')':
case '\\':
case '/':
snprintf (&escaped[escaped_i], 4, "%%%02x",
((const unsigned char *)filter)[filter_i]);
escaped_i += 3;
break;
default:
escaped[escaped_i ++] = filter[filter_i];
break;
}
}
/* NUL terminate it. */
escaped[escaped_i] = 0;
/* We could shrink escaped to be just escaped_i bytes, but the
result will probably be freed very quickly anyways. */
return escaped;
}
}
diff --git a/dirmngr/ldap-parse-uri.h b/dirmngr/ldap-parse-uri.h
index bdbb6c3fc..e9a3f95ad 100644
--- a/dirmngr/ldap-parse-uri.h
+++ b/dirmngr/ldap-parse-uri.h
@@ -1,33 +1,33 @@
/* ldap-parse-uri.h - Parse an LDAP URI.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#ifndef DIRMNGR_LDAP_PARSE_URI_H
#define DIRMNGR_LDAP_PARSE_URI_H
-#include "util.h"
+#include "../common/util.h"
#include "http.h"
extern int ldap_uri_p (const char *url);
extern gpg_error_t ldap_parse_uri (parsed_uri_t *ret_uri, const char *uri);
extern char *ldap_escape_filter (const char *filter);
#endif
diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c
index b313848e1..ac4964a55 100644
--- a/dirmngr/ldap-wrapper.c
+++ b/dirmngr/ldap-wrapper.c
@@ -1,782 +1,782 @@
/* ldap-wrapper.c - LDAP access via a wrapper process
* Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
We can't use LDAP directly for these reasons:
1. On some systems the LDAP library uses (indirectly) pthreads and
that is not compatible with PTh.
2. It is huge library in particular if TLS comes into play. So
problems with unfreed memory might turn up and we don't want
this in a long running daemon.
3. There is no easy way for timeouts. In particular the timeout
value does not work for DNS lookups (well, this is usual) and it
seems not to work while loading a large attribute like a
CRL. Having a separate process allows us to either tell the
process to commit suicide or have our own housekepping function
kill it after some time. The latter also allows proper
cancellation of a query at any point of time.
4. Given that we are going out to the network and usually get back
a long response, the fork/exec overhead is acceptable.
Note that under WindowsCE the number of processes is strongly
limited (32 processes including the kernel processes) and thus we
don't use the process approach but implement a different wrapper in
ldap-wrapper-ce.c.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <npth.h>
#include "dirmngr.h"
-#include "exechelp.h"
+#include "../common/exechelp.h"
#include "misc.h"
#include "ldap-wrapper.h"
#ifdef HAVE_W32_SYSTEM
#define setenv(a,b,c) SetEnvironmentVariable ((a),(b))
#else
#define pth_close(fd) close(fd)
#endif
#ifndef USE_LDAPWRAPPER
# error This module is not expected to be build.
#endif
/* In case sysconf does not return a value we need to have a limit. */
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */
#define TIMERTICK_INTERVAL 2
/* To keep track of the LDAP wrapper state we use this structure. */
struct wrapper_context_s
{
struct wrapper_context_s *next;
pid_t pid; /* The pid of the wrapper process. */
int printable_pid; /* Helper to print diagnostics after the process has
been cleaned up. */
int fd; /* Connected with stdout of the ldap wrapper. */
gpg_error_t fd_error; /* Set to the gpg_error of the last read error
if any. */
int log_fd; /* Connected with stderr of the ldap wrapper. */
ctrl_t ctrl; /* Connection data. */
int ready; /* Internally used to mark to be removed contexts. */
ksba_reader_t reader; /* The ksba reader object or NULL. */
char *line; /* Used to print the log lines (malloced). */
size_t linesize;/* Allocated size of LINE. */
size_t linelen; /* Use size of LINE. */
time_t stamp; /* The last time we noticed ativity. */
};
/* We keep a global list of spawed wrapper process. A separate thread
makes use of this list to log error messages and to watch out for
finished processes. */
static struct wrapper_context_s *wrapper_list;
/* We need to know whether we are shutting down the process. */
static int shutting_down;
/* Close the pth file descriptor FD and set it to -1. */
#define SAFE_CLOSE(fd) \
do { int _fd = fd; if (_fd != -1) { close (_fd); fd = -1;} } while (0)
/* Read a fixed amount of data from READER into BUFFER. */
static gpg_error_t
read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
{
gpg_error_t err;
size_t nread;
while (count)
{
err = ksba_reader_read (reader, buffer, count, &nread);
if (err)
return err;
buffer += nread;
count -= nread;
}
return 0;
}
/* Release the wrapper context and kill a running wrapper process. */
static void
destroy_wrapper (struct wrapper_context_s *ctx)
{
if (ctx->pid != (pid_t)(-1))
{
gnupg_kill_process (ctx->pid);
gnupg_release_process (ctx->pid);
}
ksba_reader_release (ctx->reader);
SAFE_CLOSE (ctx->fd);
SAFE_CLOSE (ctx->log_fd);
xfree (ctx->line);
xfree (ctx);
}
/* Print the content of LINE to thye log stream but make sure to only
print complete lines. Using NULL for LINE will flush any pending
output. LINE may be modified by this function. */
static void
print_log_line (struct wrapper_context_s *ctx, char *line)
{
char *s;
size_t n;
if (!line)
{
if (ctx->line && ctx->linelen)
{
log_info ("%s\n", ctx->line);
ctx->linelen = 0;
}
return;
}
while ((s = strchr (line, '\n')))
{
*s = 0;
if (ctx->line && ctx->linelen)
{
log_info ("%s", ctx->line);
ctx->linelen = 0;
log_printf ("%s\n", line);
}
else
log_info ("%s\n", line);
line = s + 1;
}
n = strlen (line);
if (n)
{
if (ctx->linelen + n + 1 >= ctx->linesize)
{
char *tmp;
size_t newsize;
newsize = ctx->linesize + ((n + 255) & ~255) + 1;
tmp = (ctx->line ? xtryrealloc (ctx->line, newsize)
: xtrymalloc (newsize));
if (!tmp)
{
log_error (_("error printing log line: %s\n"), strerror (errno));
return;
}
ctx->line = tmp;
ctx->linesize = newsize;
}
memcpy (ctx->line + ctx->linelen, line, n);
ctx->linelen += n;
ctx->line[ctx->linelen] = 0;
}
}
/* Read data from the log stream. Returns true if the log stream
indicated EOF or error. */
static int
read_log_data (struct wrapper_context_s *ctx)
{
int n;
char line[256];
/* We must use the npth_read function for pipes, always. */
do
n = npth_read (ctx->log_fd, line, sizeof line - 1);
while (n < 0 && errno == EINTR);
if (n <= 0) /* EOF or error. */
{
if (n < 0)
log_error (_("error reading log from ldap wrapper %d: %s\n"),
(int)ctx->pid, strerror (errno));
print_log_line (ctx, NULL);
SAFE_CLOSE (ctx->log_fd);
return 1;
}
line[n] = 0;
print_log_line (ctx, line);
if (ctx->stamp != (time_t)(-1))
ctx->stamp = time (NULL);
return 0;
}
/* This function is run by a separate thread to maintain the list of
wrappers and to log error messages from these wrappers. */
void *
ldap_wrapper_thread (void *dummy)
{
int nfds;
struct wrapper_context_s *ctx;
struct wrapper_context_s *ctx_prev;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
fd_set fdset;
int ret;
time_t exptime;
(void)dummy;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
int any_action = 0;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Inactivity is checked below. Nothing else to do. */
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
FD_ZERO (&fdset);
nfds = -1;
for (ctx = wrapper_list; ctx; ctx = ctx->next)
{
if (ctx->log_fd != -1)
{
FD_SET (ctx->log_fd, &fdset);
if (ctx->log_fd > nfds)
nfds = ctx->log_fd;
}
}
nfds++;
/* FIXME: For Windows, we have to use a reader thread on the
pipe that signals an event (and a npth_select_ev variant). */
ret = npth_pselect (nfds + 1, &fdset, NULL, NULL, &timeout, NULL);
if (ret == -1)
{
if (errno != EINTR)
{
log_error (_("npth_select failed: %s - waiting 1s\n"),
strerror (errno));
npth_sleep (1);
}
continue;
}
/* All timestamps before exptime should be considered expired. */
exptime = time (NULL);
if (exptime > INACTIVITY_TIMEOUT)
exptime -= INACTIVITY_TIMEOUT;
/* Note that there is no need to lock the list because we always
add entries at the head (with a pending event status) and
thus traversing the list will even work if we have a context
switch in waitpid (which should anyway only happen with Pth's
hard system call mapping). */
for (ctx = wrapper_list; ctx; ctx = ctx->next)
{
/* Check whether there is any logging to be done. */
if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &fdset))
{
if (read_log_data (ctx))
{
SAFE_CLOSE (ctx->log_fd);
any_action = 1;
}
}
/* Check whether the process is still running. */
if (ctx->pid != (pid_t)(-1))
{
gpg_error_t err;
int status;
err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0,
&status);
if (!err)
{
log_info (_("ldap wrapper %d ready"), (int)ctx->pid);
ctx->ready = 1;
gnupg_release_process (ctx->pid);
ctx->pid = (pid_t)(-1);
any_action = 1;
}
else if (gpg_err_code (err) == GPG_ERR_GENERAL)
{
if (status == 10)
log_info (_("ldap wrapper %d ready: timeout\n"),
(int)ctx->pid);
else
log_info (_("ldap wrapper %d ready: exitcode=%d\n"),
(int)ctx->pid, status);
ctx->ready = 1;
gnupg_release_process (ctx->pid);
ctx->pid = (pid_t)(-1);
any_action = 1;
}
else if (gpg_err_code (err) != GPG_ERR_TIMEOUT)
{
log_error (_("waiting for ldap wrapper %d failed: %s\n"),
(int)ctx->pid, gpg_strerror (err));
any_action = 1;
}
}
/* Check whether we should terminate the process. */
if (ctx->pid != (pid_t)(-1)
&& ctx->stamp != (time_t)(-1) && ctx->stamp < exptime)
{
gnupg_kill_process (ctx->pid);
ctx->stamp = (time_t)(-1);
log_info (_("ldap wrapper %d stalled - killing\n"),
(int)ctx->pid);
/* We need to close the log fd because the cleanup loop
waits for it. */
SAFE_CLOSE (ctx->log_fd);
any_action = 1;
}
}
/* If something has been printed to the log file or we got an
EOF from a wrapper, we now print the list of active
wrappers. */
if (any_action && DBG_LOOKUP)
{
log_info ("ldap worker stati:\n");
for (ctx = wrapper_list; ctx; ctx = ctx->next)
log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n",
ctx,
(int)ctx->pid, (int)ctx->printable_pid,
ctx->reader,
ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0,
(unsigned long)ctx->stamp, ctx->ready);
}
/* Use a separate loop to check whether ready marked wrappers
may be removed. We may only do so if the ksba reader object
is not anymore in use or we are in shutdown state. */
again:
for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next)
if (ctx->ready
&& ((ctx->log_fd == -1 && !ctx->reader) || shutting_down))
{
if (ctx_prev)
ctx_prev->next = ctx->next;
else
wrapper_list = ctx->next;
destroy_wrapper (ctx);
/* We need to restart because destroy_wrapper might have
done a context switch. */
goto again;
}
}
/*NOTREACHED*/
return NULL; /* Make the compiler happy. */
}
/* Start the reaper thread for the ldap wrapper. */
void
ldap_wrapper_launch_thread (void)
{
static int done;
npth_attr_t tattr;
npth_t thread;
int err;
if (done)
return;
done = 1;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, ldap_wrapper_thread, NULL);
if (err)
{
log_error (_("error spawning ldap wrapper reaper thread: %s\n"),
strerror (err) );
dirmngr_exit (1);
}
npth_setname_np (thread, "ldap-reaper");
npth_attr_destroy (&tattr);
}
/* Wait until all ldap wrappers have terminated. We assume that the
kill has already been sent to all of them. */
void
ldap_wrapper_wait_connections ()
{
shutting_down = 1;
/* FIXME: This is a busy wait. */
while (wrapper_list)
npth_usleep (200);
}
/* This function is to be used to release a context associated with the
given reader object. */
void
ldap_wrapper_release_context (ksba_reader_t reader)
{
struct wrapper_context_s *ctx;
if (!reader )
return;
for (ctx=wrapper_list; ctx; ctx=ctx->next)
if (ctx->reader == reader)
{
if (DBG_LOOKUP)
log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n",
ctx,
(int)ctx->pid, (int)ctx->printable_pid,
ctx->reader,
ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0);
ctx->reader = NULL;
SAFE_CLOSE (ctx->fd);
if (ctx->ctrl)
{
ctx->ctrl->refcount--;
ctx->ctrl = NULL;
}
if (ctx->fd_error)
log_info (_("reading from ldap wrapper %d failed: %s\n"),
ctx->printable_pid, gpg_strerror (ctx->fd_error));
break;
}
}
/* Cleanup all resources held by the connection associated with
CTRL. This is used after a cancel to kill running wrappers. */
void
ldap_wrapper_connection_cleanup (ctrl_t ctrl)
{
struct wrapper_context_s *ctx;
for (ctx=wrapper_list; ctx; ctx=ctx->next)
if (ctx->ctrl && ctx->ctrl == ctrl)
{
ctx->ctrl->refcount--;
ctx->ctrl = NULL;
if (ctx->pid != (pid_t)(-1))
gnupg_kill_process (ctx->pid);
if (ctx->fd_error)
log_info (_("reading from ldap wrapper %d failed: %s\n"),
ctx->printable_pid, gpg_strerror (ctx->fd_error));
}
}
/* This is the callback used by the ldap wrapper to feed the ksba
reader with the wrappers stdout. See the description of
ksba_reader_set_cb for details. */
static int
reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread)
{
struct wrapper_context_s *ctx = cb_value;
size_t nleft = count;
int nfds;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
fd_set fdset, read_fdset;
int ret;
/* FIXME: We might want to add some internal buffering because the
ksba code does not do any buffering for itself (because a ksba
reader may be detached from another stream to read other data and
the it would be cumbersome to get back already buffered
stuff). */
if (!buffer && !count && !nread)
return -1; /* Rewind is not supported. */
/* If we ever encountered a read error, don't continue (we don't want to
possibly overwrite the last error cause). Bail out also if the
file descriptor has been closed. */
if (ctx->fd_error || ctx->fd == -1)
{
*nread = 0;
return -1;
}
FD_ZERO (&fdset);
FD_SET (ctx->fd, &fdset);
nfds = ctx->fd + 1;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
while (nleft > 0)
{
int n;
gpg_error_t err;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
err = dirmngr_tick (ctx->ctrl);
if (err)
{
ctx->fd_error = err;
SAFE_CLOSE (ctx->fd);
return -1;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
read_fdset = fdset;
ret = npth_pselect (nfds, &read_fdset, NULL, NULL, &timeout, NULL);
saved_errno = errno;
if (ret == -1 && saved_errno != EINTR)
{
ctx->fd_error = gpg_error_from_errno (errno);
SAFE_CLOSE (ctx->fd);
return -1;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
/* This should not block now that select returned with a file
descriptor. So it shouldn't be necessary to use npth_read
(and it is slightly dangerous in the sense that a concurrent
thread might (accidentially?) change the status of ctx->fd
before we read. FIXME: Set ctx->fd to nonblocking? */
n = read (ctx->fd, buffer, nleft);
if (n < 0)
{
ctx->fd_error = gpg_error_from_errno (errno);
SAFE_CLOSE (ctx->fd);
return -1;
}
else if (!n)
{
if (nleft == count)
return -1; /* EOF. */
break;
}
nleft -= n;
buffer += n;
if (n > 0 && ctx->stamp != (time_t)(-1))
ctx->stamp = time (NULL);
}
*nread = count - nleft;
return 0;
}
/* Fork and exec the LDAP wrapper and return a new libksba reader
object at READER. ARGV is a NULL terminated list of arguments for
the wrapper. The function returns 0 on success or an error code.
Special hack to avoid passing a password through the command line
which is globally visible: If the first element of ARGV is "--pass"
it will be removed and instead the environment variable
DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern
OSes the environment is not visible to other users. For those old
systems where it can't be avoided, we don't want to go into the
hassle of passing the password via stdin; it's just too complicated
and an LDAP password used for public directory lookups should not
be that confidential. */
gpg_error_t
ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
{
gpg_error_t err;
pid_t pid;
struct wrapper_context_s *ctx;
int i;
int j;
const char **arg_list;
const char *pgmname;
int outpipe[2], errpipe[2];
/* It would be too simple to connect stderr just to our logging
stream. The problem is that if we are running multi-threaded
everything gets intermixed. Clearly we don't want this. So the
only viable solutions are either to have another thread
responsible for logging the messages or to add an option to the
wrapper module to do the logging on its own. Given that we anyway
need a way to reap the child process and this is best done using a
general reaping thread, that thread can do the logging too. */
ldap_wrapper_launch_thread ();
*reader = NULL;
/* Files: We need to prepare stdin and stdout. We get stderr from
the function. */
if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program)
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP);
else
pgmname = opt.ldap_wrapper_program;
/* Create command line argument array. */
for (i = 0; argv[i]; i++)
;
arg_list = xtrycalloc (i + 2, sizeof *arg_list);
if (!arg_list)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating memory: %s\n"), strerror (errno));
return err;
}
for (i = j = 0; argv[i]; i++, j++)
if (!i && argv[i + 1] && !strcmp (*argv, "--pass"))
{
arg_list[j] = "--env-pass";
setenv ("DIRMNGR_LDAP_PASS", argv[1], 1);
i++;
}
else
arg_list[j] = (char*) argv[i];
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating memory: %s\n"), strerror (errno));
xfree (arg_list);
return err;
}
err = gnupg_create_inbound_pipe (outpipe, NULL, 0);
if (!err)
{
err = gnupg_create_inbound_pipe (errpipe, NULL, 0);
if (err)
{
close (outpipe[0]);
close (outpipe[1]);
}
}
if (err)
{
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
xfree (arg_list);
xfree (ctx);
return err;
}
err = gnupg_spawn_process_fd (pgmname, arg_list,
-1, outpipe[1], errpipe[1], &pid);
xfree (arg_list);
close (outpipe[1]);
close (errpipe[1]);
if (err)
{
close (outpipe[0]);
close (errpipe[0]);
xfree (ctx);
return err;
}
ctx->pid = pid;
ctx->printable_pid = (int) pid;
ctx->fd = outpipe[0];
ctx->log_fd = errpipe[0];
ctx->ctrl = ctrl;
ctrl->refcount++;
ctx->stamp = time (NULL);
err = ksba_reader_new (reader);
if (!err)
err = ksba_reader_set_cb (*reader, reader_callback, ctx);
if (err)
{
log_error (_("error initializing reader object: %s\n"),
gpg_strerror (err));
destroy_wrapper (ctx);
ksba_reader_release (*reader);
*reader = NULL;
return err;
}
/* Hook the context into our list of running wrappers. */
ctx->reader = *reader;
ctx->next = wrapper_list;
wrapper_list = ctx;
if (opt.verbose)
log_info ("ldap wrapper %d started (reader %p)\n",
(int)ctx->pid, ctx->reader);
/* Need to wait for the first byte so we are able to detect an empty
output and not let the consumer see an EOF without further error
indications. The CRL loading logic assumes that after return
from this function, a failed search (e.g. host not found ) is
indicated right away. */
{
unsigned char c;
err = read_buffer (*reader, &c, 1);
if (err)
{
ldap_wrapper_release_context (*reader);
ksba_reader_release (*reader);
*reader = NULL;
if (gpg_err_code (err) == GPG_ERR_EOF)
return gpg_error (GPG_ERR_NO_DATA);
else
return err;
}
ksba_reader_unread (*reader, &c, 1);
}
return 0;
}
diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c
index a037f5d69..d661a6825 100644
--- a/dirmngr/ldap.c
+++ b/dirmngr/ldap.c
@@ -1,865 +1,865 @@
/* ldap.c - LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <npth.h>
#include "dirmngr.h"
-#include "exechelp.h"
+#include "../common/exechelp.h"
#include "crlfetch.h"
#include "ldapserver.h"
#include "misc.h"
#include "ldap-wrapper.h"
-#include "host2net.h"
+#include "../common/host2net.h"
#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890" \
"$-_.+!*'(),"
#define USERCERTIFICATE "userCertificate"
#define CACERTIFICATE "caCertificate"
#define X509CACERT "x509caCert"
#define USERSMIMECERTIFICATE "userSMIMECertificate"
/* Definition for the context of the cert fetch functions. */
struct cert_fetch_context_s
{
ksba_reader_t reader; /* The reader used (shallow copy). */
unsigned char *tmpbuf; /* Helper buffer. */
size_t tmpbufsize; /* Allocated size of tmpbuf. */
int truncated; /* Flag to indicate a truncated output. */
};
/* Add HOST and PORT to our list of LDAP servers. Fixme: We should
better use an extra list of servers. */
static void
add_server_to_servers (const char *host, int port)
{
ldap_server_t server;
ldap_server_t last = NULL;
const char *s;
if (!port)
port = 389;
for (server=opt.ldapservers; server; server = server->next)
{
if (!strcmp (server->host, host) && server->port == port)
return; /* already in list... */
last = server;
}
/* We assume that the host names are all supplied by our
configuration files and thus are sane. To keep this assumption
we must reject all invalid host names. */
for (s=host; *s; s++)
if (!strchr ("abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"01234567890.-", *s))
{
log_error (_("invalid char 0x%02x in host name - not added\n"), *s);
return;
}
log_info (_("adding '%s:%d' to the ldap server list\n"), host, port);
server = xtrycalloc (1, sizeof *s);
if (!server)
log_error (_("malloc failed: %s\n"), strerror (errno));
else
{
server->host = xstrdup (host);
server->port = port;
if (last)
last->next = server;
else
opt.ldapservers = server;
}
}
/* Perform an LDAP query. Returns an gpg error code or 0 on success.
The function returns a new reader object at READER. */
static gpg_error_t
run_ldap_wrapper (ctrl_t ctrl,
int ignore_timeout,
int multi_mode,
const char *proxy,
const char *host, int port,
const char *user, const char *pass,
const char *dn, const char *filter, const char *attr,
const char *url,
ksba_reader_t *reader)
{
const char *argv[40];
int argc;
char portbuf[30], timeoutbuf[30];
*reader = NULL;
argc = 0;
if (pass) /* Note, that the password must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
}
if (opt.verbose)
argv[argc++] = "-vv";
argv[argc++] = "--log-with-pid";
if (multi_mode)
argv[argc++] = "--multi";
if (opt.ldaptimeout)
{
sprintf (timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
if (ignore_timeout)
argv[argc++] = "--only-search-timeout";
}
if (proxy)
{
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host)
{
argv[argc++] = "--host";
argv[argc++] = host;
}
if (port)
{
sprintf (portbuf, "%d", port);
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
if (dn)
{
argv[argc++] = "--dn";
argv[argc++] = dn;
}
if (filter)
{
argv[argc++] = "--filter";
argv[argc++] = filter;
}
if (attr)
{
argv[argc++] = "--attr";
argv[argc++] = attr;
}
argv[argc++] = url? url : "ldap://";
argv[argc] = NULL;
return ldap_wrapper (ctrl, reader, argv);
}
/* Perform a LDAP query using a given URL. On success a new ksba
reader is returned. If HOST or PORT are not 0, they are used to
override the values from the URL. */
gpg_error_t
url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port,
ksba_reader_t *reader)
{
gpg_error_t err;
err = run_ldap_wrapper (ctrl,
1, /* Ignore explicit timeout because CRLs
might be very large. */
0,
opt.ldap_proxy,
host, port,
NULL, NULL,
NULL, NULL, NULL, url,
reader);
/* FIXME: This option might be used for DoS attacks. Because it
will enlarge the list of servers to consult without a limit and
all LDAP queries w/o a host are will then try each host in
turn. */
if (!err && opt.add_new_ldapservers && !opt.ldap_proxy)
{
if (host)
add_server_to_servers (host, port);
else if (url)
{
char *tmp = host_and_port_from_url (url, &port);
if (tmp)
{
add_server_to_servers (tmp, port);
xfree (tmp);
}
}
}
/* If the lookup failed and we are not only using the proxy, we try
again using our default list of servers. */
if (err && !(opt.ldap_proxy && opt.only_ldap_proxy))
{
struct ldapserver_iter iter;
if (DBG_LOOKUP)
log_debug ("no hostname in URL or query failed; "
"trying all default hostnames\n");
for (ldapserver_iter_begin (&iter, ctrl);
err && ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
err = run_ldap_wrapper (ctrl,
0,
0,
NULL,
server->host, server->port,
NULL, NULL,
NULL, NULL, NULL, url,
reader);
if (!err)
break;
}
}
return err;
}
/* Perform an LDAP query on all configured servers. On error the
error code of the last try is returned. */
gpg_error_t
attr_fetch_ldap (ctrl_t ctrl,
const char *dn, const char *attr, ksba_reader_t *reader)
{
gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION);
struct ldapserver_iter iter;
*reader = NULL;
/* FIXME; we might want to look at the Base SN to try matching
servers first. */
for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
err = run_ldap_wrapper (ctrl,
0,
0,
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn, "objectClass=*", attr, NULL,
reader);
if (!err)
break; /* Probably found a result. Ready. */
}
return err;
}
/* Parse PATTERN and return a new strlist to be used for the actual
LDAP query. Bit 0 of the flags field is set if that pattern is
actually a base specification. Caller must release the returned
strlist. NULL is returned on error.
* Possible patterns:
*
* KeyID
* Fingerprint
* OpenPGP userid
* x Email address Indicated by a left angle bracket.
* Exact word match in user id or subj. name
* x Subj. DN indicated bu a leading slash
* Issuer DN
* Serial number + subj. DN
* x Substring match indicated by a leading '*; is also the default.
*/
strlist_t
parse_one_pattern (const char *pattern)
{
strlist_t result = NULL;
char *p;
switch (*pattern)
{
case '<': /* Email. */
{
pattern++;
result = xmalloc (sizeof *result + 5 + strlen (pattern));
result->next = NULL;
result->flags = 0;
p = stpcpy (stpcpy (result->d, "mail="), pattern);
if (p[-1] == '>')
*--p = 0;
if (!*result->d) /* Error. */
{
xfree (result);
result = NULL;
}
break;
}
case '/': /* Subject DN. */
pattern++;
if (*pattern)
{
result = xmalloc (sizeof *result + strlen (pattern));
result->next = NULL;
result->flags = 1; /* Base spec. */
strcpy (result->d, pattern);
}
break;
case '#': /* Issuer DN. */
pattern++;
if (*pattern == '/') /* Just issuer DN. */
{
pattern++;
}
else /* Serial number + issuer DN */
{
}
break;
case '*':
pattern++;
default: /* Take as substring match. */
{
const char format[] = "(|(sn=*%s*)(|(cn=*%s*)(mail=*%s*)))";
if (*pattern)
{
result = xmalloc (sizeof *result
+ strlen (format) + 3 * strlen (pattern));
result->next = NULL;
result->flags = 0;
sprintf (result->d, format, pattern, pattern, pattern);
}
}
break;
}
return result;
}
/* Take the string STRING and escape it according to the URL rules.
Retun a newly allocated string. */
static char *
escape4url (const char *string)
{
const char *s;
char *buf, *p;
size_t n;
if (!string)
string = "";
for (s=string,n=0; *s; s++)
if (strchr (UNENCODED_URL_CHARS, *s))
n++;
else
n += 3;
buf = malloc (n+1);
if (!buf)
return NULL;
for (s=string,p=buf; *s; s++)
if (strchr (UNENCODED_URL_CHARS, *s))
*p++ = *s;
else
{
sprintf (p, "%%%02X", *(const unsigned char *)s);
p += 3;
}
*p = 0;
return buf;
}
/* Create a LDAP URL from DN and FILTER and return it in URL. We don't
need the host and port because this will be specified using the
override options. */
static gpg_error_t
make_url (char **url, const char *dn, const char *filter)
{
gpg_error_t err;
char *u_dn, *u_filter;
char const attrs[] = (USERCERTIFICATE ","
/* USERSMIMECERTIFICATE "," */
CACERTIFICATE ","
X509CACERT );
*url = NULL;
u_dn = escape4url (dn);
if (!u_dn)
return gpg_error_from_errno (errno);
u_filter = escape4url (filter);
if (!u_filter)
{
err = gpg_error_from_errno (errno);
xfree (u_dn);
return err;
}
*url = strconcat ("ldap:///", u_dn, "?", attrs, "?sub?", u_filter, NULL);
if (!*url)
err = gpg_error_from_syserror ();
else
err = 0;
xfree (u_dn);
xfree (u_filter);
return err;
}
/* Prepare an LDAP query to return the attribute ATTR for the DN. All
configured default servers are queried until one responds. This
function returns an error code or 0 and a CONTEXT on success. */
gpg_error_t
start_default_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context,
const char *dn, const char *attr)
{
gpg_error_t err;
struct ldapserver_iter iter;
*context = xtrycalloc (1, sizeof **context);
if (!*context)
return gpg_error_from_errno (errno);
/* FIXME; we might want to look at the Base SN to try matching
servers first. */
err = gpg_error (GPG_ERR_CONFIGURATION);
for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
err = run_ldap_wrapper (ctrl,
0,
1,
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn, "objectClass=*", attr, NULL,
&(*context)->reader);
if (!err)
break; /* Probably found a result. */
}
if (err)
{
xfree (*context);
*context = NULL;
}
return err;
}
/* Prepare an LDAP query to return certificates matching PATTERNS using
the SERVER. This function returns an error code or 0 and a CONTEXT
on success. */
gpg_error_t
start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context,
strlist_t patterns, const ldap_server_t server)
{
gpg_error_t err;
char *proxy = NULL;
char *host = NULL;
int port;
char *user = NULL;
char *pass = NULL;
const char *base;
char *argv[50];
int argc = 0;
int argc_malloced = 0;
char portbuf[30], timeoutbuf[30];
*context = NULL;
if (opt.ldap_proxy && !(proxy = xtrystrdup (opt.ldap_proxy)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (server)
{
if (server->host && !(host = xtrystrdup (server->host)))
{
err = gpg_error_from_syserror ();
goto leave;
}
port = server->port;
if (server->user && !(user = xtrystrdup (server->user)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (server->pass && !(pass = xtrystrdup (server->pass)))
{
err = gpg_error_from_syserror ();
goto leave;
}
base = server->base;
}
else /* Use a default server. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if (!base)
base = "";
if (pass) /* Note: Must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
}
if (opt.verbose)
argv[argc++] = "-vv";
argv[argc++] = "--log-with-pid";
argv[argc++] = "--multi";
if (opt.ldaptimeout)
{
snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
}
if (opt.ldap_proxy)
{
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host)
{
argv[argc++] = "--host";
argv[argc++] = host;
}
if (port)
{
snprintf (portbuf, sizeof portbuf, "%d", port);
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
/* All entries in argv from this index on are malloc'ed. */
argc_malloced = argc;
for (; patterns; patterns = patterns->next)
{
strlist_t sl;
char *url;
if (argc >= DIM (argv) - 1)
{
/* Too many patterns. It does not make sense to allow an
arbitrary number of patters because the length of the
command line is limited anyway. */
/* fixme: cleanup. */
return gpg_error (GPG_ERR_RESOURCE_LIMIT);
}
sl = parse_one_pattern (patterns->d);
if (!sl)
{
log_error (_("start_cert_fetch: invalid pattern '%s'\n"),
patterns->d);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
if ((sl->flags & 1))
err = make_url (&url, sl->d, "objectClass=*");
else
err = make_url (&url, base, sl->d);
free_strlist (sl);
if (err)
goto leave;
argv[argc++] = url;
}
argv[argc] = NULL;
*context = xtrycalloc (1, sizeof **context);
if (!*context)
{
err = gpg_error_from_errno (errno);
goto leave;
}
err = ldap_wrapper (ctrl, &(*context)->reader, (const char**)argv);
if (err)
{
xfree (*context);
*context = NULL;
}
leave:
for (; argc_malloced < argc; argc_malloced++)
xfree (argv[argc_malloced]);
xfree (proxy);
xfree (host);
xfree (user);
xfree (pass);
return err;
}
/* Read a fixed amount of data from READER into BUFFER. */
static gpg_error_t
read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
{
gpg_error_t err;
size_t nread;
while (count)
{
err = ksba_reader_read (reader, buffer, count, &nread);
if (err)
return err;
buffer += nread;
count -= nread;
}
return 0;
}
/* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF if no
(more) certificates are available or any other error
code. GPG_ERR_TRUNCATED may be returned to indicate that the result
has been truncated. */
gpg_error_t
fetch_next_cert_ldap (cert_fetch_context_t context,
unsigned char **value, size_t *valuelen)
{
gpg_error_t err;
unsigned char hdr[5];
char *p, *pend;
unsigned long n;
int okay = 0;
/* int is_cms = 0; */
*value = NULL;
*valuelen = 0;
err = 0;
while (!err)
{
err = read_buffer (context->reader, hdr, 5);
if (err)
break;
n = buf32_to_ulong (hdr+1);
if (*hdr == 'V' && okay)
{
#if 0 /* That code is not yet ready. */
if (is_cms)
{
/* The certificate needs to be parsed from CMS data. */
ksba_cms_t cms;
ksba_stop_reason_t stopreason;
int i;
err = ksba_cms_new (&cms);
if (err)
goto leave;
err = ksba_cms_set_reader_writer (cms, context->reader, NULL);
if (err)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
goto leave;
}
do
{
err = ksba_cms_parse (cms, &stopreason);
if (err)
{
log_error ("ksba_cms_parse failed: %s\n",
gpg_strerror (err));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
log_error ("userSMIMECertificate is not "
"a certs-only message\n");
}
while (stopreason != KSBA_SR_READY);
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
check_and_store (ctrl, stats, cert, 0);
ksba_cert_release (cert);
cert = NULL;
}
if (!i)
log_error ("no certificate found\n");
else
any = 1;
}
else
#endif
{
*value = xtrymalloc (n);
if (!*value)
return gpg_error_from_errno (errno);
*valuelen = n;
err = read_buffer (context->reader, *value, n);
break; /* Ready or error. */
}
}
else if (!n && *hdr == 'A')
okay = 0;
else if (n)
{
if (n > context->tmpbufsize)
{
xfree (context->tmpbuf);
context->tmpbufsize = 0;
context->tmpbuf = xtrymalloc (n+1);
if (!context->tmpbuf)
return gpg_error_from_errno (errno);
context->tmpbufsize = n;
}
err = read_buffer (context->reader, context->tmpbuf, n);
if (err)
break;
if (*hdr == 'A')
{
p = context->tmpbuf;
p[n] = 0; /*(we allocated one extra byte for this.)*/
/* fixme: is_cms = 0; */
if ( (pend = strchr (p, ';')) )
*pend = 0; /* Strip off the extension. */
if (!ascii_strcasecmp (p, USERCERTIFICATE))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
USERCERTIFICATE);
okay = 1;
}
else if (!ascii_strcasecmp (p, CACERTIFICATE))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
CACERTIFICATE);
okay = 1;
}
else if (!ascii_strcasecmp (p, X509CACERT))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
CACERTIFICATE);
okay = 1;
}
/* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */
/* { */
/* if (DBG_LOOKUP) */
/* log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", */
/* USERSMIMECERTIFICATE); */
/* okay = 1; */
/* is_cms = 1; */
/* } */
else
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'"
" - ignored\n", p);
okay = 0;
}
}
else if (*hdr == 'E')
{
p = context->tmpbuf;
p[n] = 0; /*(we allocated one extra byte for this.)*/
if (!strcmp (p, "truncated"))
{
context->truncated = 1;
log_info (_("ldap_search hit the size limit of"
" the server\n"));
}
}
}
}
if (err)
{
xfree (*value);
*value = NULL;
*valuelen = 0;
if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated)
{
context->truncated = 0; /* So that the next call would return EOF. */
err = gpg_error (GPG_ERR_TRUNCATED);
}
}
return err;
}
void
end_cert_fetch_ldap (cert_fetch_context_t context)
{
if (context)
{
ksba_reader_t reader = context->reader;
xfree (context->tmpbuf);
xfree (context);
ldap_wrapper_release_context (reader);
ksba_reader_release (reader);
}
}
diff --git a/dirmngr/misc.c b/dirmngr/misc.c
index 6d7c963db..1716141a6 100644
--- a/dirmngr/misc.c
+++ b/dirmngr/misc.c
@@ -1,655 +1,655 @@
/* misc.c - miscellaneous
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2002, 2003, 2004, 2010 Free Software Foundation, Inc.
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "dirmngr.h"
-#include "util.h"
+#include "../common/util.h"
#include "misc.h"
/* Convert the hex encoded STRING back into binary and store the
result into the provided buffer RESULT. The actual size of that
buffer will be returned. The caller should provide RESULT of at
least strlen(STRING)/2 bytes. There is no error detection, the
parsing stops at the first non hex character. With RESULT given as
NULL, the function does only return the size of the buffer which
would be needed. */
size_t
unhexify (unsigned char *result, const char *string)
{
const char *s;
size_t n;
for (s=string,n=0; hexdigitp (s) && hexdigitp(s+1); s += 2)
{
if (result)
result[n] = xtoi_2 (s);
n++;
}
return n;
}
char*
hashify_data( const char* data, size_t len )
{
unsigned char buf[20];
gcry_md_hash_buffer (GCRY_MD_SHA1, buf, data, len);
return hexify_data (buf, 20, 0);
}
/* FIXME: Replace this by hextobin. */
char*
hexify_data (const unsigned char* data, size_t len, int with_prefix)
{
int i;
char *result = xmalloc (2*len + (with_prefix?2:0) + 1);
char *p;
if (with_prefix)
p = stpcpy (result, "0x");
else
p = result;
for (i = 0; i < 2*len; i+=2 )
snprintf (p+i, 3, "%02X", *data++);
return result;
}
char *
serial_hex (ksba_sexp_t serial )
{
unsigned char* p = serial;
char *endp;
unsigned long n;
char *certid;
if (!p)
return NULL;
else {
p++; /* ignore initial '(' */
n = strtoul (p, (char**)&endp, 10);
p = endp;
if (*p!=':')
return NULL;
else {
int i = 0;
certid = xmalloc( sizeof( char )*(2*n + 1 ) );
for (p++; n; n--, p++) {
sprintf ( certid+i , "%02X", *p);
i += 2;
}
}
}
return certid;
}
/* Take an S-Expression encoded blob and return a pointer to the
actual data as well as its length. Return NULL for an invalid
S-Expression.*/
const unsigned char *
serial_to_buffer (const ksba_sexp_t serial, size_t *length)
{
unsigned char *p = serial;
char *endp;
unsigned long n;
if (!p || *p != '(')
return NULL;
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p != ':')
return NULL;
p++;
*length = n;
return p;
}
/* Do an in-place percent unescaping of STRING. Returns STRING. Note
that this function does not do a '+'-to-space unescaping.*/
char *
unpercent_string (char *string)
{
char *s = string;
char *d = string;
while (*s)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d++ = xtoi_2 ( s);
s += 2;
}
else
*d++ = *s++;
}
*d = 0;
return string;
}
/* Convert a canonical encoded S-expression in CANON into the GCRY
type. */
gpg_error_t
canon_sexp_to_gcry (const unsigned char *canon, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
size_t n;
gcry_sexp_t sexp;
*r_sexp = NULL;
n = gcry_sexp_canon_len (canon, 0, NULL, NULL);
if (!n)
{
log_error (_("invalid canonical S-expression found\n"));
err = gpg_error (GPG_ERR_INV_SEXP);
}
else if ((err = gcry_sexp_sscan (&sexp, NULL, canon, n)))
log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err));
else
*r_sexp = sexp;
return err;
}
/* Return an allocated buffer with the formatted fingerprint as one
large hexnumber */
char *
get_fingerprint_hexstring (ksba_cert_t cert)
{
unsigned char digest[20];
gcry_md_hd_t md;
int rc;
char *buf;
int i;
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (rc)
log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc));
rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (rc)
{
log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc));
memset (digest, 0xff, 20); /* Use a dummy value. */
}
else
{
gcry_md_final (md);
memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
}
gcry_md_close (md);
buf = xmalloc (41);
*buf = 0;
for (i=0; i < 20; i++ )
sprintf (buf+strlen(buf), "%02X", digest[i]);
return buf;
}
/* Return an allocated buffer with the formatted fingerprint as one
large hexnumber. This version inserts the usual colons. */
char *
get_fingerprint_hexstring_colon (ksba_cert_t cert)
{
unsigned char digest[20];
gcry_md_hd_t md;
int rc;
char *buf;
int i;
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (rc)
log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc));
rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (rc)
{
log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc));
memset (digest, 0xff, 20); /* Use a dummy value. */
}
else
{
gcry_md_final (md);
memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
}
gcry_md_close (md);
buf = xmalloc (61);
*buf = 0;
for (i=0; i < 20; i++ )
sprintf (buf+strlen(buf), "%02X:", digest[i]);
buf[strlen(buf)-1] = 0; /* Remove railing colon. */
return buf;
}
/* Dump the serial number SERIALNO to the log stream. */
void
dump_serial (ksba_sexp_t serialno)
{
char *p;
p = serial_hex (serialno);
log_printf ("%s", p?p:"?");
xfree (p);
}
/* Dump STRING to the log file but choose the best readable
format. */
void
dump_string (const char *string)
{
if (!string)
log_printf ("[error]");
else
{
const unsigned char *s;
for (s=string; *s; s++)
{
if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
break;
}
if (!*s && *string != '[')
log_printf ("%s", string);
else
{
log_printf ( "[ ");
log_printhex (NULL, string, strlen (string));
log_printf ( " ]");
}
}
}
/* Dump an KSBA cert object to the log stream. Prefix the output with
TEXT. This is used for debugging. */
void
dump_cert (const char *text, ksba_cert_t cert)
{
ksba_sexp_t sexp;
char *p;
ksba_isotime_t t;
int idx;
log_debug ("BEGIN Certificate '%s':\n", text? text:"");
if (cert)
{
sexp = ksba_cert_get_serial (cert);
p = serial_hex (sexp);
log_debug (" serial: %s\n", p?p:"?");
xfree (p);
ksba_free (sexp);
ksba_cert_get_validity (cert, 0, t);
log_debug (" notBefore: ");
dump_isotime (t);
log_printf ("\n");
ksba_cert_get_validity (cert, 1, t);
log_debug (" notAfter: ");
dump_isotime (t);
log_printf ("\n");
p = ksba_cert_get_issuer (cert, 0);
log_debug (" issuer: ");
dump_string (p);
ksba_free (p);
log_printf ("\n");
p = ksba_cert_get_subject (cert, 0);
log_debug (" subject: ");
dump_string (p);
ksba_free (p);
log_printf ("\n");
for (idx=1; (p = ksba_cert_get_subject (cert, idx)); idx++)
{
log_debug (" aka: ");
dump_string (p);
ksba_free (p);
log_printf ("\n");
}
log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert));
p = get_fingerprint_hexstring (cert);
log_debug (" SHA1 fingerprint: %s\n", p);
xfree (p);
}
log_debug ("END Certificate\n");
}
/* Log the certificate's name in "#SN/ISSUERDN" format along with
TEXT. */
void
cert_log_name (const char *text, ksba_cert_t cert)
{
log_info ("%s", text? text:"certificate" );
if (cert)
{
ksba_sexp_t sn;
char *p;
p = ksba_cert_get_issuer (cert, 0);
sn = ksba_cert_get_serial (cert);
if (p && sn)
{
log_printf (" #");
dump_serial (sn);
log_printf ("/");
dump_string (p);
}
else
log_printf (" [invalid]");
ksba_free (sn);
xfree (p);
}
log_printf ("\n");
}
/* Log the certificate's subject DN along with TEXT. */
void
cert_log_subject (const char *text, ksba_cert_t cert)
{
log_info ("%s", text? text:"subject" );
if (cert)
{
char *p;
p = ksba_cert_get_subject (cert, 0);
if (p)
{
log_printf (" /");
dump_string (p);
xfree (p);
}
else
log_printf (" [invalid]");
}
log_printf ("\n");
}
/* Callback to print infos about the TLS certificates. */
void
cert_log_cb (http_session_t sess, gpg_error_t err,
const char *hostname, const void **certs, size_t *certlens)
{
ksba_cert_t cert;
size_t n;
(void)sess;
if (!err)
return; /* No error - no need to log anything */
log_debug ("expected hostname: %s\n", hostname);
for (n=0; certs[n]; n++)
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, certs[n], certlens[n]);
if (err)
log_error ("error parsing cert for logging: %s\n", gpg_strerror (err));
else
{
char textbuf[20];
snprintf (textbuf, sizeof textbuf, "server[%u]", (unsigned int)n);
dump_cert (textbuf, cert);
}
ksba_cert_release (cert);
}
}
/****************
* Remove all %xx escapes; this is done inplace.
* Returns: New length of the string.
*/
static int
remove_percent_escapes (unsigned char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
{
s++;
*p = xtoi_2 (s);
s++;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Always keep a string terminator. */
return n;
}
/* Return the host name and the port (0 if none was given) from the
URL. Return NULL on error or if host is not included in the
URL. */
char *
host_and_port_from_url (const char *url, int *port)
{
const char *s, *s2;
char *buf, *p;
int n;
s = url;
*port = 0;
/* Find the scheme */
if ( !(s2 = strchr (s, ':')) || s2 == s )
return NULL; /* No scheme given. */
s = s2+1;
/* Find the hostname */
if (*s != '/')
return NULL; /* Does not start with a slash. */
s++;
if (*s != '/')
return NULL; /* No host name. */
s++;
buf = xtrystrdup (s);
if (!buf)
{
log_error (_("malloc failed: %s\n"), strerror (errno));
return NULL;
}
if ((p = strchr (buf, '/')))
*p++ = 0;
strlwr (buf);
if ((p = strchr (p, ':')))
{
*p++ = 0;
*port = atoi (p);
}
/* Remove quotes and make sure that no Nul has been encoded. */
if ((n = remove_percent_escapes (buf)) < 0
|| n != strlen (buf) )
{
log_error (_("bad URL encoding detected\n"));
xfree (buf);
return NULL;
}
return buf;
}
/* A KSBA reader callback to read from an estream. */
static int
my_estream_ksba_reader_cb (void *cb_value, char *buffer, size_t count,
size_t *r_nread)
{
estream_t fp = cb_value;
if (!fp)
return gpg_error (GPG_ERR_INV_VALUE);
if (!buffer && !count && !r_nread)
{
es_rewind (fp);
return 0;
}
*r_nread = es_fread (buffer, 1, count, fp);
if (!*r_nread)
return -1; /* EOF or error. */
return 0; /* Success. */
}
/* Create a KSBA reader object and connect it to the estream FP. */
gpg_error_t
create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp)
{
gpg_error_t err;
ksba_reader_t reader;
*r_reader = NULL;
err = ksba_reader_new (&reader);
if (!err)
err = ksba_reader_set_cb (reader, my_estream_ksba_reader_cb, fp);
if (err)
{
log_error (_("error initializing reader object: %s\n"),
gpg_strerror (err));
ksba_reader_release (reader);
return err;
}
*r_reader = reader;
return 0;
}
gpg_error_t
armor_data (char **r_string, const void *data, size_t datalen)
{
gpg_error_t err;
struct b64state b64state;
estream_t fp;
long length;
char *buffer;
size_t nread;
*r_string = NULL;
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
return gpg_error_from_syserror ();
if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK"))
|| (err=b64enc_write (&b64state, data, datalen))
|| (err = b64enc_finish (&b64state)))
{
es_fclose (fp);
return err;
}
/* FIXME: To avoid the extra buffer allocation estream should
provide a function to snatch the internal allocated memory from
such a memory stream. */
length = es_ftell (fp);
if (length < 0)
{
err = gpg_error_from_syserror ();
es_fclose (fp);
return err;
}
buffer = xtrymalloc (length+1);
if (!buffer)
{
err = gpg_error_from_syserror ();
es_fclose (fp);
return err;
}
es_rewind (fp);
if (es_read (fp, buffer, length, &nread))
{
err = gpg_error_from_syserror ();
es_fclose (fp);
return err;
}
buffer[nread] = 0;
es_fclose (fp);
*r_string = buffer;
return 0;
}
/* Copy all data from IN to OUT. */
gpg_error_t
copy_stream (estream_t in, estream_t out)
{
char buffer[512];
size_t nread;
while (!es_read (in, buffer, sizeof buffer, &nread))
{
if (!nread)
return 0; /* EOF */
if (es_write (out, buffer, nread, NULL))
break;
}
return gpg_error_from_syserror ();
}
diff --git a/dirmngr/server.c b/dirmngr/server.c
index 9fa8229ef..f4aeadbc4 100644
--- a/dirmngr/server.c
+++ b/dirmngr/server.c
@@ -1,2802 +1,2802 @@
/* server.c - LDAP and Keyserver access server
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH
* Copyright (C) 2014, 2015, 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "dirmngr.h"
#include <assuan.h>
#include "crlcache.h"
#include "crlfetch.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "ocsp.h"
#include "certcache.h"
#include "validate.h"
#include "misc.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "ks-action.h"
#include "ks-engine.h" /* (ks_hkp_print_hosttable) */
#if USE_LDAP
# include "ldap-parse-uri.h"
#endif
#include "dns-stuff.h"
-#include "mbox-util.h"
-#include "zb32.h"
-#include "server-help.h"
+#include "../common/mbox-util.h"
+#include "../common/zb32.h"
+#include "../common/server-help.h"
/* To avoid DoS attacks we limit the size of a certificate to
something reasonable. The DoS was actually only an issue back when
Dirmngr was a system service and not a user service. */
#define MAX_CERT_LENGTH (16*1024)
/* The limit for the CERTLIST inquiry. We allow for up to 20
* certificates but also take PEM encoding into account. */
#define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3)
/* The same goes for OpenPGP keyblocks, but here we need to allow for
much longer blocks; a 200k keyblock is not too unusual for keys
with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even
has 770 KiB as of 2015-08-23. To avoid adding a runtime option we
now use 20MiB which should really be enough. Well, a key with
several pictures could be larger (the parser as a 18MiB limit for
attribute packets) but it won't be nice to the keyservers to send
them such large blobs. */
#define MAX_KEYBLOCK_LENGTH (20*1024*1024)
#define PARM_ERROR(t) assuan_set_error (ctx, \
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Control structure per connection. */
struct server_local_s
{
/* Data used to associate an Assuan context with local server data */
assuan_context_t assuan_ctx;
/* Per-session LDAP servers. */
ldap_server_t ldapservers;
/* Per-session list of keyservers. */
uri_item_t keyservers;
/* If this flag is set to true this dirmngr process will be
terminated after the end of this session. */
int stopme;
/* State variable private to is_tor_running. */
int tor_state;
/* If the first both flags are set the assuan logging of data lines
* is suppressed. The count variable is used to show the number of
* non-logged bytes. */
size_t inhibit_data_logging_count;
unsigned int inhibit_data_logging : 1;
unsigned int inhibit_data_logging_now : 1;
};
/* Cookie definition for assuan data line output. */
static gpgrt_ssize_t data_line_cookie_write (void *cookie,
const void *buffer, size_t size);
static int data_line_cookie_close (void *cookie);
static es_cookie_io_functions_t data_line_cookie_functions =
{
NULL,
data_line_cookie_write,
NULL,
data_line_cookie_close
};
/* Accessor for the local ldapservers variable. */
ldap_server_t
get_ldapservers_from_ctrl (ctrl_t ctrl)
{
if (ctrl && ctrl->server_local)
return ctrl->server_local->ldapservers;
else
return NULL;
}
/* Release an uri_item_t list. */
static void
release_uri_item_list (uri_item_t list)
{
while (list)
{
uri_item_t tmp = list->next;
http_release_parsed_uri (list->parsed_uri);
xfree (list);
list = tmp;
}
}
/* Release all configured keyserver info from CTRL. */
void
release_ctrl_keyservers (ctrl_t ctrl)
{
if (! ctrl->server_local)
return;
release_uri_item_list (ctrl->server_local->keyservers);
ctrl->server_local->keyservers = NULL;
}
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* This is a wrapper around assuan_send_data which makes debugging the
output in verbose mode easier. */
static gpg_error_t
data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
const char *buffer = buffer_arg;
gpg_error_t err;
/* If we do not want logging, enable it here. */
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
ctrl->server_local->inhibit_data_logging_now = 1;
if (opt.verbose && buffer && size)
{
/* Ease reading of output by sending a physical line at each LF. */
const char *p;
size_t n, nbytes;
nbytes = size;
do
{
p = memchr (buffer, '\n', nbytes);
n = p ? (p - buffer) + 1 : nbytes;
err = assuan_send_data (ctx, buffer, n);
if (err)
{
gpg_err_set_errno (EIO);
goto leave;
}
buffer += n;
nbytes -= n;
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
{
gpg_err_set_errno (EIO);
goto leave;
}
}
while (nbytes);
}
else
{
err = assuan_send_data (ctx, buffer, size);
if (err)
{
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
goto leave;
}
}
leave:
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
{
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count += size;
}
return err;
}
/* A write handler used by es_fopencookie to write assuan data
lines. */
static gpgrt_ssize_t
data_line_cookie_write (void *cookie, const void *buffer, size_t size)
{
assuan_context_t ctx = cookie;
if (data_line_write (ctx, buffer, size))
return -1;
return (gpgrt_ssize_t)size;
}
static int
data_line_cookie_close (void *cookie)
{
assuan_context_t ctx = cookie;
if (DBG_IPC)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl && ctrl->server_local
&& ctrl->server_local->inhibit_data_logging
&& ctrl->server_local->inhibit_data_logging_count)
log_debug ("(%zu bytes sent via D lines not shown)\n",
ctrl->server_local->inhibit_data_logging_count);
}
if (assuan_send_data (ctx, NULL, 0))
{
gpg_err_set_errno (EIO);
return -1;
}
return 0;
}
/* Copy the % and + escaped string S into the buffer D and replace the
escape sequences. Note, that it is sufficient to allocate the
target string D as long as the source string S, i.e.: strlen(s)+1.
Note further that if S contains an escaped binary Nul the resulting
string D will contain the 0 as well as all other characters but it
will be impossible to know whether this is the original EOS or a
copied Nul. */
static void
strcpy_escaped_plus (char *d, const unsigned char *s)
{
while (*s)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d++ = xtoi_2 ( s);
s += 2;
}
else if (*s == '+')
*d++ = ' ', s++;
else
*d++ = *s++;
}
*d = 0;
}
/* This function returns true if a Tor server is running. The sattus
is cached for the current connection. */
static int
is_tor_running (ctrl_t ctrl)
{
/* Check whether we can connect to the proxy. */
if (!ctrl || !ctrl->server_local)
return 0; /* Ooops. */
if (!ctrl->server_local->tor_state)
{
assuan_fd_t sock;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
ctrl->server_local->tor_state = -1; /* Not running. */
else
{
assuan_sock_close (sock);
ctrl->server_local->tor_state = 1; /* Running. */
}
}
return (ctrl->server_local->tor_state > 0);
}
/* Return an error if the assuan context does not belong to the owner
of the process or to root. On error FAILTEXT is set as Assuan
error string. */
static gpg_error_t
check_owner_permission (assuan_context_t ctx, const char *failtext)
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows the dirmngr is always run under the control of the
user. */
(void)ctx;
(void)failtext;
#else
gpg_err_code_t ec;
assuan_peercred_t cred;
ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
if (!ec && cred->uid && cred->uid != getuid ())
ec = GPG_ERR_EPERM;
if (ec)
return set_error (ec, failtext);
#endif
return 0;
}
/* Common code for get_cert_local and get_issuer_cert_local. */
static ksba_cert_t
do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
{
unsigned char *value;
size_t valuelen;
int rc;
char *buf;
ksba_cert_t cert;
buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command);
if (!buf)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
&value, &valuelen, MAX_CERT_LENGTH);
xfree (buf);
}
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"),
command, gpg_strerror (rc));
return NULL;
}
if (!valuelen)
{
xfree (value);
return NULL;
}
rc = ksba_cert_new (&cert);
if (!rc)
{
rc = ksba_cert_init_from_mem (cert, value, valuelen);
if (rc)
{
ksba_cert_release (cert);
cert = NULL;
}
}
xfree (value);
return cert;
}
/* Ask back to return a certificate for NAME, given as a regular gpgsm
* certificate identifier (e.g. fingerprint or one of the other
* methods). Alternatively, NULL may be used for NAME to return the
* current target certificate. Either return the certificate in a
* KSBA object or NULL if it is not available. */
ksba_cert_t
get_cert_local (ctrl_t ctrl, const char *name)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_cert_local called w/o context\n");
return NULL;
}
return do_get_cert_local (ctrl, name, "SENDCERT");
}
/* Ask back to return the issuing certificate for NAME, given as a
* regular gpgsm certificate identifier (e.g. fingerprint or one
* of the other methods). Alternatively, NULL may be used for NAME to
* return the current target certificate. Either return the certificate
* in a KSBA object or NULL if it is not available. */
ksba_cert_t
get_issuing_cert_local (ctrl_t ctrl, const char *name)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_issuing_cert_local called w/o context\n");
return NULL;
}
return do_get_cert_local (ctrl, name, "SENDISSUERCERT");
}
/* Ask back to return a certificate with subject NAME and a
* subjectKeyIdentifier of KEYID. */
ksba_cert_t
get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
{
unsigned char *value;
size_t valuelen;
int rc;
char *buf;
ksba_cert_t cert;
char *hexkeyid;
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_cert_local_ski called w/o context\n");
return NULL;
}
if (!name || !keyid)
{
log_debug ("get_cert_local_ski called with insufficient arguments\n");
return NULL;
}
hexkeyid = serial_hex (keyid);
if (!hexkeyid)
{
log_debug ("serial_hex() failed\n");
return NULL;
}
buf = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
xfree (hexkeyid);
return NULL;
}
xfree (hexkeyid);
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
&value, &valuelen, MAX_CERT_LENGTH);
xfree (buf);
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI",
gpg_strerror (rc));
return NULL;
}
if (!valuelen)
{
xfree (value);
return NULL;
}
rc = ksba_cert_new (&cert);
if (!rc)
{
rc = ksba_cert_init_from_mem (cert, value, valuelen);
if (rc)
{
ksba_cert_release (cert);
cert = NULL;
}
}
xfree (value);
return cert;
}
/* Ask the client via an inquiry to check the istrusted status of the
certificate specified by the hexified fingerprint HEXFPR. Returns
0 if the certificate is trusted by the client or an error code. */
gpg_error_t
get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr)
{
unsigned char *value;
size_t valuelen;
int rc;
char request[100];
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx
|| !hexfpr)
return gpg_error (GPG_ERR_INV_ARG);
snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr);
rc = assuan_inquire (ctrl->server_local->assuan_ctx, request,
&value, &valuelen, 100);
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"),
request, gpg_strerror (rc));
return rc;
}
/* The expected data is: "1" or "1 cruft" (not a C-string). */
if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1)))
rc = 0;
else
rc = gpg_error (GPG_ERR_NOT_TRUSTED);
xfree (value);
return rc;
}
/* Ask the client to return the certificate associated with the
current command. This is sometimes needed because the client usually
sends us just the cert ID, assuming that the request can be
satisfied from the cache, where the cert ID is used as key. */
static int
inquire_cert_and_load_crl (assuan_context_t ctx)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
ksba_cert_t cert = NULL;
err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
if (err)
return err;
/* { */
/* FILE *fp = fopen ("foo.der", "r"); */
/* value = xmalloc (2000); */
/* valuelen = fread (value, 1, 2000, fp); */
/* fclose (fp); */
/* } */
if (!valuelen) /* No data returned; return a comprehensible error. */
return gpg_error (GPG_ERR_MISSING_CERT);
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, value, valuelen);
if(err)
goto leave;
xfree (value); value = NULL;
err = crl_cache_reload_crl (ctrl, cert);
leave:
ksba_cert_release (cert);
xfree (value);
return err;
}
/* Handle OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "force-crl-refresh"))
{
int i = *value? atoi (value) : 0;
ctrl->force_crl_refresh = i;
}
else if (!strcmp (key, "audit-events"))
{
int i = *value? atoi (value) : 0;
ctrl->audit_events = i;
}
else if (!strcmp (key, "http-proxy"))
{
xfree (ctrl->http_proxy);
if (!*value || !strcmp (value, "none"))
ctrl->http_proxy = NULL;
else if (!(ctrl->http_proxy = xtrystrdup (value)))
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "honor-keyserver-url-used"))
{
/* Return an error if we are running in Tor mode. */
if (dirmngr_use_tor ())
err = gpg_error (GPG_ERR_FORBIDDEN);
}
else if (!strcmp (key, "http-crl"))
{
int i = *value? atoi (value) : 0;
ctrl->http_no_crl = !i;
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static const char hlp_dns_cert[] =
"DNS_CERT <subtype> <name>\n"
"DNS_CERT --pka <user_id>\n"
"DNS_CERT --dane <user_id>\n"
"\n"
"Return the CERT record for <name>. <subtype> is one of\n"
" * Return the first record of any supported subtype\n"
" PGP Return the first record of subtype PGP (3)\n"
" IPGP Return the first record of subtype IPGP (6)\n"
"If the content of a certificate is available (PGP) it is returned\n"
"by data lines. Fingerprints and URLs are returned via status lines.\n"
"In --pka mode the fingerprint and if available an URL is returned.\n"
"In --dane mode the key is returned from RR type 61";
static gpg_error_t
cmd_dns_cert (assuan_context_t ctx, char *line)
{
/* ctrl_t ctrl = assuan_get_pointer (ctx); */
gpg_error_t err = 0;
int pka_mode, dane_mode;
char *mbox = NULL;
char *namebuf = NULL;
char *encodedhash = NULL;
const char *name;
int certtype;
char *p;
void *key = NULL;
size_t keylen;
unsigned char *fpr = NULL;
size_t fprlen;
char *url = NULL;
pka_mode = has_option (line, "--pka");
dane_mode = has_option (line, "--dane");
line = skip_options (line);
if (pka_mode && dane_mode)
{
err = PARM_ERROR ("either --pka or --dane may be given");
goto leave;
}
if (pka_mode || dane_mode)
; /* No need to parse here - we do this later. */
else
{
p = strchr (line, ' ');
if (!p)
{
err = PARM_ERROR ("missing arguments");
goto leave;
}
*p++ = 0;
if (!strcmp (line, "*"))
certtype = DNS_CERTTYPE_ANY;
else if (!strcmp (line, "IPGP"))
certtype = DNS_CERTTYPE_IPGP;
else if (!strcmp (line, "PGP"))
certtype = DNS_CERTTYPE_PGP;
else
{
err = PARM_ERROR ("unknown subtype");
goto leave;
}
while (spacep (p))
p++;
line = p;
if (!*line)
{
err = PARM_ERROR ("name missing");
goto leave;
}
}
if (pka_mode || dane_mode)
{
char *domain; /* Points to mbox. */
char hashbuf[32]; /* For SHA-1 and SHA-256. */
/* We lowercase ascii characters but the DANE I-D does not allow
this. FIXME: Check after the release of the RFC whether to
change this. */
mbox = mailbox_from_userid (line);
if (!mbox || !(domain = strchr (mbox, '@')))
{
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
goto leave;
}
*domain++ = 0;
if (pka_mode)
{
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
encodedhash = zb32_encode (hashbuf, 8*20);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
namebuf = strconcat (encodedhash, "._pka.", domain, NULL);
if (!namebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
name = namebuf;
certtype = DNS_CERTTYPE_IPGP;
}
else
{
/* Note: The hash is truncated to 28 bytes and we lowercase
the result only for aesthetic reasons. */
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
encodedhash = bin2hex (hashbuf, 28, NULL);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (encodedhash);
namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL);
if (!namebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
name = namebuf;
certtype = DNS_CERTTYPE_RR61;
}
}
else
name = line;
err = get_dns_cert (name, certtype, &key, &keylen, &fpr, &fprlen, &url);
if (err)
goto leave;
if (key)
{
err = data_line_write (ctx, key, keylen);
if (err)
goto leave;
}
if (fpr)
{
char *tmpstr;
tmpstr = bin2hex (fpr, fprlen, NULL);
if (!tmpstr)
err = gpg_error_from_syserror ();
else
{
err = assuan_write_status (ctx, "FPR", tmpstr);
xfree (tmpstr);
}
if (err)
goto leave;
}
if (url)
{
err = assuan_write_status (ctx, "URL", url);
if (err)
goto leave;
}
leave:
xfree (key);
xfree (fpr);
xfree (url);
xfree (mbox);
xfree (namebuf);
xfree (encodedhash);
return leave_cmd (ctx, err);
}
static const char hlp_wkd_get[] =
"WKD_GET [--submission-address|--policy-flags] <user_id>\n"
"\n"
"Return the key or other info for <user_id>\n"
"from the Web Key Directory.";
static gpg_error_t
cmd_wkd_get (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *mbox = NULL;
char *domainbuf = NULL;
char *domain; /* Points to mbox or domainbuf. */
char sha1buf[20];
char *uri = NULL;
char *encodedhash = NULL;
int opt_submission_addr;
int opt_policy_flags;
int no_log = 0;
char portstr[20] = { 0 };
opt_submission_addr = has_option (line, "--submission-address");
opt_policy_flags = has_option (line, "--policy-flags");
line = skip_options (line);
mbox = mailbox_from_userid (line);
if (!mbox || !(domain = strchr (mbox, '@')))
{
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
goto leave;
}
*domain++ = 0;
/* Check for SRV records. */
if (1)
{
struct srventry *srvs;
unsigned int srvscount;
size_t domainlen, targetlen;
int i;
err = get_dns_srv (domain, "openpgpkey", NULL, &srvs, &srvscount);
if (err)
goto leave;
/* Find the first target which also ends in DOMAIN or is equal
* to DOMAIN. */
domainlen = strlen (domain);
for (i = 0; i < srvscount; i++)
{
log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port);
targetlen = strlen (srvs[i].target);
if ((targetlen > domainlen + 1
&& srvs[i].target[targetlen - domainlen - 1] == '.'
&& !ascii_strcasecmp (srvs[i].target + targetlen - domainlen,
domain))
|| (targetlen == domainlen
&& !ascii_strcasecmp (srvs[i].target, domain)))
{
/* found. */
domainbuf = xtrystrdup (srvs[i].target);
if (!domainbuf)
{
err = gpg_error_from_syserror ();
xfree (srvs);
goto leave;
}
domain = domainbuf;
if (srvs[i].port)
snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port);
break;
}
}
xfree (srvs);
log_debug ("srv: got '%s%s'\n", domain, portstr);
}
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox));
encodedhash = zb32_encode (sha1buf, 8*20);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt_submission_addr)
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/submission-address",
NULL);
}
else if (opt_policy_flags)
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/policy",
NULL);
}
else
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/hu/",
encodedhash,
NULL);
no_log = 1;
}
if (!uri)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Setup an output stream and perform the get. */
{
estream_t outfp;
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
else
{
if (no_log)
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_fetch (ctrl, uri, outfp);
es_fclose (outfp);
ctrl->server_local->inhibit_data_logging = 0;
}
}
leave:
xfree (uri);
xfree (encodedhash);
xfree (mbox);
xfree (domainbuf);
return leave_cmd (ctx, err);
}
static const char hlp_ldapserver[] =
"LDAPSERVER <data>\n"
"\n"
"Add a new LDAP server to the list of configured LDAP servers.\n"
"DATA is in the same format as expected in the configure file.";
static gpg_error_t
cmd_ldapserver (assuan_context_t ctx, char *line)
{
#if USE_LDAP
ctrl_t ctrl = assuan_get_pointer (ctx);
ldap_server_t server;
ldap_server_t *last_next_p;
while (spacep (line))
line++;
if (*line == '\0')
return leave_cmd (ctx, PARM_ERROR (_("ldapserver missing")));
server = ldapserver_parse_one (line, "", 0);
if (! server)
return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG));
last_next_p = &ctrl->server_local->ldapservers;
while (*last_next_p)
last_next_p = &(*last_next_p)->next;
*last_next_p = server;
return leave_cmd (ctx, 0);
#else
(void)line;
return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED));
#endif
}
static const char hlp_isvalid[] =
"ISVALID [--only-ocsp] [--force-default-responder]"
" <certificate_id>|<certificate_fpr>\n"
"\n"
"This command checks whether the certificate identified by the\n"
"certificate_id is valid. This is done by consulting CRLs or\n"
"whatever has been configured. Note, that the returned error codes\n"
"are from gpg-error.h. The command may callback using the inquire\n"
"function. See the manual for details.\n"
"\n"
"The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n"
"delimited by a single dot. The first part is the SHA-1 hash of the\n"
"issuer name and the second part the serial number.\n"
"\n"
"Alternatively the certificate's fingerprint may be given in which\n"
"case an OCSP request is done before consulting the CRL.\n"
"\n"
"If the option --only-ocsp is given, no fallback to a CRL check will\n"
"be used.\n"
"\n"
"If the option --force-default-responder is given, only the default\n"
"OCSP responder will be used and any other methods of obtaining an\n"
"OCSP responder URL won't be used.";
static gpg_error_t
cmd_isvalid (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *issuerhash, *serialno;
gpg_error_t err;
int did_inquire = 0;
int ocsp_mode = 0;
int only_ocsp;
int force_default_responder;
only_ocsp = has_option (line, "--only-ocsp");
force_default_responder = has_option (line, "--force-default-responder");
line = skip_options (line);
issuerhash = xstrdup (line); /* We need to work on a copy of the
line because that same Assuan
context may be used for an inquiry.
That is because Assuan reuses its
line buffer.
*/
serialno = strchr (issuerhash, '.');
if (serialno)
*serialno++ = 0;
else
{
char *endp = strchr (issuerhash, ' ');
if (endp)
*endp = 0;
if (strlen (issuerhash) != 40)
{
xfree (issuerhash);
return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
}
ocsp_mode = 1;
}
again:
if (ocsp_mode)
{
/* Note, that we ignore the given issuer hash and instead rely
on the current certificate semantics used with this
command. */
if (!opt.allow_ocsp)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder);
/* Fixme: If we got no ocsp response and --only-ocsp is not used
we should fall back to CRL mode. Thus we need to clear
OCSP_MODE, get the issuerhash and the serialno from the
current certificate and jump to again. */
}
else if (only_ocsp)
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else
{
switch (crl_cache_isvalid (ctrl,
issuerhash, serialno,
ctrl->force_crl_refresh))
{
case CRL_CACHE_VALID:
err = 0;
break;
case CRL_CACHE_INVALID:
err = gpg_error (GPG_ERR_CERT_REVOKED);
break;
case CRL_CACHE_DONTKNOW:
if (did_inquire)
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (!(err = inquire_cert_and_load_crl (ctx)))
{
did_inquire = 1;
goto again;
}
break;
case CRL_CACHE_CANTUSE:
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
break;
default:
log_fatal ("crl_cache_isvalid returned invalid code\n");
}
}
xfree (issuerhash);
return leave_cmd (ctx, err);
}
/* If the line contains a SHA-1 fingerprint as the first argument,
return the FPR vuffer on success. The function checks that the
fingerprint consists of valid characters and prints and error
message if it does not and returns NULL. Fingerprints are
considered optional and thus no explicit error is returned. NULL is
also returned if there is no fingerprint at all available.
FPR must be a caller provided buffer of at least 20 bytes.
Note that colons within the fingerprint are allowed to separate 2
hex digits; this allows for easier cutting and pasting using the
usual fingerprint rendering.
*/
static unsigned char *
get_fingerprint_from_line (const char *line, unsigned char *fpr)
{
const char *s;
int i;
for (s=line, i=0; *s && *s != ' '; s++ )
{
if ( hexdigitp (s) && hexdigitp (s+1) )
{
if ( i >= 20 )
return NULL; /* Fingerprint too long. */
fpr[i++] = xtoi_2 (s);
s++;
}
else if ( *s != ':' )
return NULL; /* Invalid. */
}
if ( i != 20 )
return NULL; /* Fingerprint to short. */
return fpr;
}
static const char hlp_checkcrl[] =
"CHECKCRL [<fingerprint>]\n"
"\n"
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
"entire X.509 certificate blob) is valid or not by consulting the\n"
"CRL responsible for this certificate. If the fingerprint has not\n"
"been given or the certificate is not known, the function \n"
"inquires the certificate using an\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request (which should match FINGERPRINT) as a binary blob.\n"
"Processing then takes place without further interaction; in\n"
"particular dirmngr tries to locate other required certificate by\n"
"its own mechanism which includes a local certificate store as well\n"
"as a list of trusted root certificates.\n"
"\n"
"The return value is the usual gpg-error code or 0 for ducesss;\n"
"i.e. the certificate validity has been confirmed by a valid CRL.";
static gpg_error_t
cmd_checkcrl (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char fprbuffer[20], *fpr;
ksba_cert_t cert;
fpr = get_fingerprint_from_line (line, fprbuffer);
cert = fpr? get_cert_byfpr (fpr) : NULL;
if (!cert)
{
/* We do not have this certificate yet or the fingerprint has
not been given. Inquire it from the client. */
unsigned char *value = NULL;
size_t valuelen;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
}
assert (cert);
err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh);
if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
{
err = crl_cache_reload_crl (ctrl, cert);
if (!err)
err = crl_cache_cert_isvalid (ctrl, cert, 0);
}
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static const char hlp_checkocsp[] =
"CHECKOCSP [--force-default-responder] [<fingerprint>]\n"
"\n"
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
"entire X.509 certificate blob) is valid or not by asking an OCSP\n"
"responder responsible for this certificate. The optional\n"
"fingerprint may be used for a quick check in case an OCSP check has\n"
"been done for this certificate recently (we always cache OCSP\n"
"responses for a couple of minutes). If the fingerprint has not been\n"
"given or there is no cached result, the function inquires the\n"
"certificate using an\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request (which should match FINGERPRINT) as a binary blob.\n"
"Processing then takes place without further interaction; in\n"
"particular dirmngr tries to locate other required certificates by\n"
"its own mechanism which includes a local certificate store as well\n"
"as a list of trusted root certificates.\n"
"\n"
"If the option --force-default-responder is given, only the default\n"
"OCSP responder will be used and any other methods of obtaining an\n"
"OCSP responder URL won't be used.\n"
"\n"
"The return value is the usual gpg-error code or 0 for ducesss;\n"
"i.e. the certificate validity has been confirmed by a valid CRL.";
static gpg_error_t
cmd_checkocsp (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char fprbuffer[20], *fpr;
ksba_cert_t cert;
int force_default_responder;
force_default_responder = has_option (line, "--force-default-responder");
line = skip_options (line);
fpr = get_fingerprint_from_line (line, fprbuffer);
cert = fpr? get_cert_byfpr (fpr) : NULL;
if (!cert)
{
/* We do not have this certificate yet or the fingerprint has
not been given. Inquire it from the client. */
unsigned char *value = NULL;
size_t valuelen;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
}
assert (cert);
if (!opt.allow_ocsp)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder);
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static int
lookup_cert_by_url (assuan_context_t ctx, const char *url)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *value = NULL;
size_t valuelen;
/* Fetch single certificate given it's URL. */
err = fetch_cert_by_url (ctrl, url, &value, &valuelen);
if (err)
{
log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Send the data, flush the buffer and then send an END. */
err = assuan_send_data (ctx, value, valuelen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
if (err)
{
log_error (_("error sending data: %s\n"), gpg_strerror (err));
goto leave;
}
leave:
return err;
}
/* Send the certificate, flush the buffer and then send an END. */
static gpg_error_t
return_one_cert (void *opaque, ksba_cert_t cert)
{
assuan_context_t ctx = opaque;
gpg_error_t err;
const unsigned char *der;
size_t derlen;
der = ksba_cert_get_image (cert, &derlen);
if (!der)
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
else
{
err = assuan_send_data (ctx, der, derlen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
}
if (err)
log_error (_("error sending data: %s\n"), gpg_strerror (err));
return err;
}
/* Lookup certificates from the internal cache or using the ldap
servers. */
static int
lookup_cert_by_pattern (assuan_context_t ctx, char *line,
int single, int cache_only)
{
gpg_error_t err = 0;
char *p;
strlist_t sl, list = NULL;
int truncated = 0, truncation_forced = 0;
int count = 0;
int local_count = 0;
#if USE_LDAP
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value = NULL;
size_t valuelen;
struct ldapserver_iter ldapserver_iter;
cert_fetch_context_t fetch_context;
#endif /*USE_LDAP*/
int any_no_data = 0;
/* Break the line down into an STRLIST */
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_errno (errno);
goto leave;
}
memset (sl, 0, sizeof *sl);
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
/* First look through the internal cache. The certifcates returned
here are not counted towards the truncation limit. */
if (single && !cache_only)
; /* Do not read from the local cache in this case. */
else
{
for (sl=list; sl; sl = sl->next)
{
err = get_certs_bypattern (sl->d, return_one_cert, ctx);
if (!err)
local_count++;
if (!err && single)
goto ready;
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
err = 0;
if (cache_only)
any_no_data = 1;
}
else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only)
{
/* No real fault because the internal pattern lookup
can't yet cope with all types of pattern. */
err = 0;
}
if (err)
goto ready;
}
}
/* Loop over all configured servers unless we want only the
certificates from the cache. */
#if USE_LDAP
for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
!cache_only && !ldapserver_iter_end_p (&ldapserver_iter)
&& ldapserver_iter.server->host && !truncation_forced;
ldapserver_iter_next (&ldapserver_iter))
{
ldap_server_t ldapserver = ldapserver_iter.server;
if (DBG_LOOKUP)
log_debug ("cmd_lookup: trying %s:%d base=%s\n",
ldapserver->host, ldapserver->port,
ldapserver->base?ldapserver->base : "[default]");
/* Fetch certificates matching pattern */
err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver);
if ( gpg_err_code (err) == GPG_ERR_NO_DATA )
{
if (DBG_LOOKUP)
log_debug ("cmd_lookup: no data\n");
err = 0;
any_no_data = 1;
continue;
}
if (err)
{
log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Fetch the certificates for this query. */
while (!truncation_forced)
{
xfree (value); value = NULL;
err = fetch_next_cert (fetch_context, &value, &valuelen);
if (gpg_err_code (err) == GPG_ERR_NO_DATA )
{
err = 0;
any_no_data = 1;
break; /* Ready. */
}
if (gpg_err_code (err) == GPG_ERR_TRUNCATED)
{
truncated = 1;
err = 0;
break; /* Ready. */
}
if (gpg_err_code (err) == GPG_ERR_EOF)
{
err = 0;
break; /* Ready. */
}
if (!err && !value)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
if (err)
{
log_error (_("fetch_next_cert failed: %s\n"),
gpg_strerror (err));
end_cert_fetch (fetch_context);
goto leave;
}
if (DBG_LOOKUP)
log_debug ("cmd_lookup: returning one cert%s\n",
truncated? " (truncated)":"");
/* Send the data, flush the buffer and then send an END line
as a certificate delimiter. */
err = assuan_send_data (ctx, value, valuelen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
if (err)
{
log_error (_("error sending data: %s\n"), gpg_strerror (err));
end_cert_fetch (fetch_context);
goto leave;
}
if (++count >= opt.max_replies )
{
truncation_forced = 1;
log_info (_("max_replies %d exceeded\n"), opt.max_replies );
}
if (single)
break;
}
end_cert_fetch (fetch_context);
}
#endif /*USE_LDAP*/
ready:
if (truncated || truncation_forced)
{
char str[50];
sprintf (str, "%d", count);
assuan_write_status (ctx, "TRUNCATED", str);
}
if (!err && !count && !local_count && any_no_data)
err = gpg_error (GPG_ERR_NO_DATA);
leave:
free_strlist (list);
return err;
}
static const char hlp_lookup[] =
"LOOKUP [--url] [--single] [--cache-only] <pattern>\n"
"\n"
"Lookup certificates matching PATTERN. With --url the pattern is\n"
"expected to be one URL.\n"
"\n"
"If --url is not given: To allow for multiple patterns (which are ORed)\n"
"quoting is required: Spaces are translated to \"+\" or \"%20\";\n"
"obviously this requires that the usual escape quoting rules are applied.\n"
"\n"
"If --url is given no special escaping is required because URLs are\n"
"already escaped this way.\n"
"\n"
"If --single is given the first and only the first match will be\n"
"returned. If --cache-only is _not_ given, no local query will be\n"
"done.\n"
"\n"
"If --cache-only is given no external lookup is done so that only\n"
"certificates from the cache may get returned.";
static gpg_error_t
cmd_lookup (assuan_context_t ctx, char *line)
{
gpg_error_t err;
int lookup_url, single, cache_only;
lookup_url = has_leading_option (line, "--url");
single = has_leading_option (line, "--single");
cache_only = has_leading_option (line, "--cache-only");
line = skip_options (line);
if (lookup_url && cache_only)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (lookup_url && single)
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
else if (lookup_url)
err = lookup_cert_by_url (ctx, line);
else
err = lookup_cert_by_pattern (ctx, line, single, cache_only);
return leave_cmd (ctx, err);
}
static const char hlp_loadcrl[] =
"LOADCRL [--url] <filename|url>\n"
"\n"
"Load the CRL in the file with name FILENAME into our cache. Note\n"
"that FILENAME should be given with an absolute path because\n"
"Dirmngrs cwd is not known. With --url the CRL is directly loaded\n"
"from the given URL.\n"
"\n"
"This command is usually used by gpgsm using the invocation \"gpgsm\n"
"--call-dirmngr loadcrl <filename>\". A direct invocation of Dirmngr\n"
"is not useful because gpgsm might need to callback gpgsm to ask for\n"
"the CA's certificate.";
static gpg_error_t
cmd_loadcrl (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int use_url = has_leading_option (line, "--url");
line = skip_options (line);
if (use_url)
{
ksba_reader_t reader;
err = crl_fetch (ctrl, line, &reader);
if (err)
log_error (_("fetching CRL from '%s' failed: %s\n"),
line, gpg_strerror (err));
else
{
err = crl_cache_insert (ctrl, line, reader);
if (err)
log_error (_("processing CRL from '%s' failed: %s\n"),
line, gpg_strerror (err));
crl_close_reader (reader);
}
}
else
{
char *buf;
buf = xtrymalloc (strlen (line)+1);
if (!buf)
err = gpg_error_from_syserror ();
else
{
strcpy_escaped_plus (buf, line);
err = crl_cache_load (ctrl, buf);
xfree (buf);
}
}
return leave_cmd (ctx, err);
}
static const char hlp_listcrls[] =
"LISTCRLS\n"
"\n"
"List the content of all CRLs in a readable format. This command is\n"
"usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n"
"listcrls\". It may also be used directly using \"dirmngr\n"
"--list-crls\".";
static gpg_error_t
cmd_listcrls (assuan_context_t ctx, char *line)
{
gpg_error_t err;
estream_t fp;
(void)line;
fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!fp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
err = crl_cache_list (fp);
es_fclose (fp);
}
return leave_cmd (ctx, err);
}
static const char hlp_cachecert[] =
"CACHECERT\n"
"\n"
"Put a certificate into the internal cache. This command might be\n"
"useful if a client knows in advance certificates required for a\n"
"test and wants to make sure they get added to the internal cache.\n"
"It is also helpful for debugging. To get the actual certificate,\n"
"this command immediately inquires it using\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request as a binary blob.";
static gpg_error_t
cmd_cachecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
unsigned char *value = NULL;
size_t valuelen;
(void)line;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
err = cache_cert (cert);
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static const char hlp_validate[] =
"VALIDATE [--systrust] [--tls] [--no-crl]\n"
"\n"
"Validate a certificate using the certificate validation function\n"
"used internally by dirmngr. This command is only useful for\n"
"debugging. To get the actual certificate, this command immediately\n"
"inquires it using\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request as a binary blob. The option --tls modifies this by asking\n"
"for list of certificates with\n"
"\n"
" INQUIRE CERTLIST\n"
"\n"
"Here the first certificate is the target certificate, the remaining\n"
"certificates are suggested intermediary certificates. All certifciates\n"
"need to be PEM encoded.\n"
"\n"
"The option --systrust changes the behaviour to include the system\n"
"provided root certificates as trust anchors. The option --no-crl\n"
"skips CRL checks";
static gpg_error_t
cmd_validate (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
certlist_t certlist = NULL;
unsigned char *value = NULL;
size_t valuelen;
int systrust_mode, tls_mode, no_crl;
systrust_mode = has_option (line, "--systrust");
tls_mode = has_option (line, "--tls");
no_crl = has_option (line, "--no-crl");
line = skip_options (line);
if (tls_mode)
err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST",
&value, &valuelen, MAX_CERTLIST_LENGTH);
else
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else if (tls_mode)
{
estream_t fp;
fp = es_fopenmem_init (0, "rb", value, valuelen);
if (!fp)
err = gpg_error_from_syserror ();
else
{
err = read_certlist_from_stream (&certlist, fp);
es_fclose (fp);
if (!err && !certlist)
err = gpg_error (GPG_ERR_MISSING_CERT);
if (!err)
{
/* Extraxt the first certificate from the list. */
cert = certlist->cert;
ksba_cert_ref (cert);
}
}
}
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
if (!tls_mode)
{
/* If we have this certificate already in our cache, use the
* cached version for validation because this will take care of
* any cached results. We don't need to do this in tls mode
* because this has already been done for certificate in a
* certlist_t. */
unsigned char fpr[20];
ksba_cert_t tmpcert;
cert_compute_fpr (cert, fpr);
tmpcert = get_cert_byfpr (fpr);
if (tmpcert)
{
ksba_cert_release (cert);
cert = tmpcert;
}
}
/* Quick hack to make verification work by inserting the supplied
* certs into the cache. */
if (tls_mode && certlist)
{
certlist_t cl;
for (cl = certlist->next; cl; cl = cl->next)
cache_cert (cl->cert);
}
err = validate_cert_chain (ctrl, cert, NULL,
(VALIDATE_FLAG_TRUST_CONFIG
| (tls_mode ? VALIDATE_FLAG_TLS : 0)
| (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0)
| (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)),
NULL);
leave:
ksba_cert_release (cert);
release_certlist (certlist);
return leave_cmd (ctx, err);
}
/* Parse an keyserver URI and store it in a new uri item which is
returned at R_ITEM. On error return an error code. */
static gpg_error_t
make_keyserver_item (const char *uri, uri_item_t *r_item)
{
gpg_error_t err;
uri_item_t item;
*r_item = NULL;
item = xtrymalloc (sizeof *item + strlen (uri));
if (!item)
return gpg_error_from_syserror ();
item->next = NULL;
item->parsed_uri = NULL;
strcpy (item->uri, uri);
#if USE_LDAP
if (ldap_uri_p (item->uri))
err = ldap_parse_uri (&item->parsed_uri, uri);
else
#endif
{
err = http_parse_uri (&item->parsed_uri, uri, 1);
}
if (err)
xfree (item);
else
*r_item = item;
return err;
}
/* If no keyserver is stored in CTRL but a global keyserver has been
set, put that global keyserver into CTRL. We need use this
function to help migrate from the old gpg based keyserver
configuration to the new dirmngr based configuration. */
static gpg_error_t
ensure_keyserver (ctrl_t ctrl)
{
gpg_error_t err;
uri_item_t item;
uri_item_t onion_items = NULL;
uri_item_t plain_items = NULL;
uri_item_t ui;
strlist_t sl;
if (ctrl->server_local->keyservers)
return 0; /* Already set for this session. */
if (!opt.keyserver)
{
/* No global option set. Fall back to default: */
return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER,
&ctrl->server_local->keyservers);
}
for (sl = opt.keyserver; sl; sl = sl->next)
{
err = make_keyserver_item (sl->d, &item);
if (err)
goto leave;
if (item->parsed_uri->onion)
{
item->next = onion_items;
onion_items = item;
}
else
{
item->next = plain_items;
plain_items = item;
}
}
/* Decide which to use. Note that the sesssion has no keyservers
yet set. */
if (onion_items && !onion_items->next && plain_items && !plain_items->next)
{
/* If there is just one onion and one plain keyserver given, we take
only one depending on whether Tor is running or not. */
if (is_tor_running (ctrl))
{
ctrl->server_local->keyservers = onion_items;
onion_items = NULL;
}
else
{
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
}
else if (!is_tor_running (ctrl))
{
/* Tor is not running. It does not make sense to add Onion
addresses. */
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
else
{
/* In all other cases add all keyservers. */
ctrl->server_local->keyservers = onion_items;
onion_items = NULL;
for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next)
;
if (ui)
ui->next = plain_items;
else
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
leave:
release_uri_item_list (onion_items);
release_uri_item_list (plain_items);
return err;
}
static const char hlp_keyserver[] =
"KEYSERVER [<options>] [<uri>|<host>]\n"
"Options are:\n"
" --help\n"
" --clear Remove all configured keyservers\n"
" --resolve Resolve HKP host names and rotate\n"
" --hosttable Print table of known hosts and pools\n"
" --dead Mark <host> as dead\n"
" --alive Mark <host> as alive\n"
"\n"
"If called without arguments list all configured keyserver URLs.\n"
"If called with an URI add this as keyserver. Note that keyservers\n"
"are configured on a per-session base. A default keyserver may already be\n"
"present, thus the \"--clear\" option must be used to get full control.\n"
"If \"--clear\" and an URI are used together the clear command is\n"
"obviously executed first. A RESET command does not change the list\n"
"of configured keyservers.";
static gpg_error_t
cmd_keyserver (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clear_flag, add_flag, help_flag, host_flag, resolve_flag;
int dead_flag, alive_flag;
uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it
is always initialized. */
clear_flag = has_option (line, "--clear");
help_flag = has_option (line, "--help");
resolve_flag = has_option (line, "--resolve");
host_flag = has_option (line, "--hosttable");
dead_flag = has_option (line, "--dead");
alive_flag = has_option (line, "--alive");
line = skip_options (line);
add_flag = !!*line;
if (help_flag)
{
err = ks_action_help (ctrl, line);
goto leave;
}
if (resolve_flag)
{
err = ensure_keyserver (ctrl);
if (!err)
err = ks_action_resolve (ctrl, ctrl->server_local->keyservers);
if (err)
goto leave;
}
if (alive_flag && dead_flag)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies");
goto leave;
}
if (dead_flag)
{
err = check_owner_permission (ctx, "no permission to use --dead");
if (err)
goto leave;
}
if (alive_flag || dead_flag)
{
if (!*line)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing");
goto leave;
}
err = ks_hkp_mark_host (ctrl, line, alive_flag);
if (err)
goto leave;
}
if (host_flag)
{
err = ks_hkp_print_hosttable (ctrl);
if (err)
goto leave;
}
if (resolve_flag || host_flag || alive_flag || dead_flag)
goto leave;
if (add_flag)
{
err = make_keyserver_item (line, &item);
if (err)
goto leave;
}
if (clear_flag)
release_ctrl_keyservers (ctrl);
if (add_flag)
{
item->next = ctrl->server_local->keyservers;
ctrl->server_local->keyservers = item;
}
if (!add_flag && !clear_flag && !help_flag)
{
/* List configured keyservers. However, we first add a global
keyserver. */
uri_item_t u;
err = ensure_keyserver (ctrl);
if (err)
{
assuan_set_error (ctx, err,
"Bad keyserver configuration in dirmngr.conf");
goto leave;
}
for (u=ctrl->server_local->keyservers; u; u = u->next)
dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL);
}
err = 0;
leave:
return leave_cmd (ctx, err);
}
static const char hlp_ks_search[] =
"KS_SEARCH {<pattern>}\n"
"\n"
"Search the configured OpenPGP keyservers (see command KEYSERVER)\n"
"for keys matching PATTERN";
static gpg_error_t
cmd_ks_search (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
strlist_t list, sl;
char *p;
estream_t outfp;
/* No options for now. */
line = skip_options (line);
/* Break the line down into an strlist. Each pattern is
percent-plus escaped. */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_syserror ();
goto leave;
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Setup an output stream and perform the search. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
err = ks_action_search (ctrl, ctrl->server_local->keyservers,
list, outfp);
es_fclose (outfp);
}
leave:
free_strlist (list);
return leave_cmd (ctx, err);
}
static const char hlp_ks_get[] =
"KS_GET {<pattern>}\n"
"\n"
"Get the keys matching PATTERN from the configured OpenPGP keyservers\n"
"(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n"
"or an exact name indicated by the '=' prefix.";
static gpg_error_t
cmd_ks_get (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
strlist_t list, sl;
char *p;
estream_t outfp;
/* No options for now. */
line = skip_options (line);
/* Break the line into a strlist. Each pattern is by
definition percent-plus escaped. However we only support keyids
and fingerprints and thus the client has no need to apply the
escaping. */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_syserror ();
goto leave;
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Setup an output stream and perform the get. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_get (ctrl, ctrl->server_local->keyservers, list, outfp);
es_fclose (outfp);
ctrl->server_local->inhibit_data_logging = 0;
}
leave:
free_strlist (list);
return leave_cmd (ctx, err);
}
static const char hlp_ks_fetch[] =
"KS_FETCH <URL>\n"
"\n"
"Get the key(s) from URL.";
static gpg_error_t
cmd_ks_fetch (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
estream_t outfp;
/* No options for now. */
line = skip_options (line);
err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */
if (err)
goto leave;
/* Setup an output stream and perform the get. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_fetch (ctrl, line, outfp);
es_fclose (outfp);
ctrl->server_local->inhibit_data_logging = 0;
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_ks_put[] =
"KS_PUT\n"
"\n"
"Send a key to the configured OpenPGP keyservers. The actual key material\n"
"is then requested by Dirmngr using\n"
"\n"
" INQUIRE KEYBLOCK\n"
"\n"
"The client shall respond with a binary version of the keyblock (e.g.,\n"
"the output of `gpg --export KEYID'). For LDAP\n"
"keyservers Dirmngr may ask for meta information of the provided keyblock\n"
"using:\n"
"\n"
" INQUIRE KEYBLOCK_INFO\n"
"\n"
"The client shall respond with a colon delimited info lines (the output\n"
"of 'for x in keys sigs; do gpg --list-$x --with-colons KEYID; done').\n";
static gpg_error_t
cmd_ks_put (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *info = NULL;
size_t infolen;
/* No options for now. */
line = skip_options (line);
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Ask for the key material. */
err = assuan_inquire (ctx, "KEYBLOCK",
&value, &valuelen, MAX_KEYBLOCK_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
{
err = gpg_error (GPG_ERR_MISSING_CERT);
goto leave;
}
/* Ask for the key meta data. Not actually needed for HKP servers
but we do it anyway to test the client implementaion. */
err = assuan_inquire (ctx, "KEYBLOCK_INFO",
&info, &infolen, MAX_KEYBLOCK_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Send the key. */
err = ks_action_put (ctrl, ctrl->server_local->keyservers,
value, valuelen, info, infolen);
leave:
xfree (info);
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_loadswdb[] =
"LOADSWDB [--force]\n"
"\n"
"Load and verify the swdb.lst from the Net.";
static gpg_error_t
cmd_loadswdb (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
err = dirmngr_load_swdb (ctrl, has_option (line, "--force"));
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
"version - Return the version of the program.\n"
"pid - Return the process id of the server.\n"
"tor - Return OK if running in Tor mode\n"
"dnsinfo - Return info about the DNS resolver\n"
"socket_name - Return the name of the socket.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = dirmngr_get_current_socket_name ();
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "tor"))
{
int use_tor;
use_tor = dirmngr_use_tor ();
if (use_tor)
{
if (!is_tor_running (ctrl))
err = assuan_write_status (ctx, "NO_TOR", "Tor not running");
else
err = 0;
if (!err)
assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled"
/**/ : "- Tor mode is enforced");
}
else
err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled");
}
else if (!strcmp (line, "dnsinfo"))
{
if (standard_resolver_p ())
assuan_set_okay_line
(ctx, "- Forced use of System resolver (w/o Tor support)");
else
{
#ifdef USE_LIBDNS
assuan_set_okay_line (ctx, (recursive_resolver_p ()
? "- Libdns recursive resolver"
: "- Libdns stub resolver"));
#else
assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)");
#endif
}
err = 0;
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
static const char hlp_killdirmngr[] =
"KILLDIRMNGR\n"
"\n"
"This command allows a user - given sufficient permissions -\n"
"to kill this dirmngr process.\n";
static gpg_error_t
cmd_killdirmngr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return gpg_error (GPG_ERR_EOF);
}
static const char hlp_reloaddirmngr[] =
"RELOADDIRMNGR\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloaddirmngr (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
dirmngr_sighup_action ();
return 0;
}
/* Tell the assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "DNS_CERT", cmd_dns_cert, hlp_dns_cert },
{ "WKD_GET", cmd_wkd_get, hlp_wkd_get },
{ "LDAPSERVER", cmd_ldapserver, hlp_ldapserver },
{ "ISVALID", cmd_isvalid, hlp_isvalid },
{ "CHECKCRL", cmd_checkcrl, hlp_checkcrl },
{ "CHECKOCSP", cmd_checkocsp, hlp_checkocsp },
{ "LOOKUP", cmd_lookup, hlp_lookup },
{ "LOADCRL", cmd_loadcrl, hlp_loadcrl },
{ "LISTCRLS", cmd_listcrls, hlp_listcrls },
{ "CACHECERT", cmd_cachecert, hlp_cachecert },
{ "VALIDATE", cmd_validate, hlp_validate },
{ "KEYSERVER", cmd_keyserver, hlp_keyserver },
{ "KS_SEARCH", cmd_ks_search, hlp_ks_search },
{ "KS_GET", cmd_ks_get, hlp_ks_get },
{ "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "LOADSWDB", cmd_loadswdb, hlp_loadswdb },
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
{ "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
{ NULL, NULL }
};
int i, j, rc;
for (i=j=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Note that we do not reset the list of configured keyservers. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
#if USE_LDAP
ldapserver_list_free (ctrl->server_local->ldapservers);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
return 0;
}
/* This function is called by our assuan log handler to test whether a
* log message shall really be printed. The function must return
* false to inhibit the logging of MSG. CAT gives the requested log
* category. MSG might be NULL. */
int
dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)cat;
(void)msg;
if (!ctrl || !ctrl->server_local)
return 1; /* Can't decide - allow logging. */
if (!ctrl->server_local->inhibit_data_logging)
return 1; /* Not requested - allow logging. */
/* Disallow logging if *_now is true. */
return !ctrl->server_local->inhibit_data_logging_now;
}
/* Startup the server and run the main command loop. With FD = -1,
use stdin/stdout. */
void
start_command_handler (assuan_fd_t fd)
{
static const char hello[] = "Dirmngr " VERSION " at your service";
static char *hello_line;
int rc;
assuan_context_t ctx;
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (ctrl)
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl || !ctrl->server_local)
{
log_error (_("can't allocate control structure: %s\n"),
strerror (errno));
xfree (ctrl);
return;
}
dirmngr_init_default_ctrl (ctrl);
rc = assuan_new (&ctx);
if (rc)
{
log_error (_("failed to allocate assuan context: %s\n"),
gpg_strerror (rc));
dirmngr_exit (2);
}
if (fd == ASSUAN_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
assuan_release (ctx);
log_error (_("failed to initialize the server: %s\n"),
gpg_strerror(rc));
dirmngr_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error (_("failed to the register commands with Assuan: %s\n"),
gpg_strerror(rc));
dirmngr_exit (2);
}
if (!hello_line)
{
hello_line = xtryasprintf
("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename? opt.config_filename : "[none]",
hello);
}
ctrl->server_local->assuan_ctx = ctx;
assuan_set_pointer (ctx, ctrl);
assuan_set_hello_line (ctx, hello_line);
assuan_register_option_handler (ctx, option_handler);
assuan_register_reset_notify (ctx, reset_notify);
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
break;
if (rc)
{
log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
break;
}
#ifndef HAVE_W32_SYSTEM
if (opt.verbose)
{
assuan_peercred_t peercred;
if (!assuan_get_peercred (ctx, &peercred))
log_info ("connection from process %ld (%ld:%ld)\n",
(long)peercred->pid, (long)peercred->uid,
(long)peercred->gid);
}
#endif
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
#if USE_LDAP
ldap_wrapper_connection_cleanup (ctrl);
ldapserver_list_free (ctrl->server_local->ldapservers);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
release_ctrl_keyservers (ctrl);
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);
if (ctrl->server_local->stopme)
dirmngr_exit (0);
if (ctrl->refcount)
log_error ("oops: connection control structure still referenced (%d)\n",
ctrl->refcount);
else
{
release_ctrl_ocsp_certs (ctrl);
xfree (ctrl->server_local);
dirmngr_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
}
/* Send a status line back to the client. KEYWORD is the status
keyword, the optional string arguments are blank separated added to
the line, the last argument must be a NULL. */
gpg_error_t
dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, keyword);
if (ctrl->server_local)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
char buf[950], *p;
size_t n;
p = buf;
n = 0;
while ( (text = va_arg (arg_ptr, const char *)) )
{
if (n)
{
*p++ = ' ';
n++;
}
for ( ; *text && n < DIM (buf)-2; n++)
*p++ = *text++;
}
*p = 0;
err = assuan_write_status (ctx, keyword, buf);
}
va_end (arg_ptr);
return err;
}
/* Print a help status line. TEXTLEN gives the length of the text
from TEXT to be printed. The function splits text at LFs. */
gpg_error_t
dirmngr_status_help (ctrl_t ctrl, const char *text)
{
gpg_error_t err = 0;
if (ctrl->server_local)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
char buf[950], *p;
size_t n;
do
{
p = buf;
n = 0;
for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++)
*p++ = *text++;
if (*text == '\n')
text++;
*p = 0;
err = assuan_write_status (ctx, "#", buf);
}
while (!err && *text);
}
return err;
}
/* Send a tick progress indicator back. Fixme: This is only done for
the currently active channel. */
gpg_error_t
dirmngr_tick (ctrl_t ctrl)
{
static time_t next_tick = 0;
gpg_error_t err = 0;
time_t now = time (NULL);
if (!next_tick)
{
next_tick = now + 1;
}
else if ( now > next_tick )
{
if (ctrl)
{
err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL);
if (err)
{
/* Take this as in indication for a cancel request. */
err = gpg_error (GPG_ERR_CANCELED);
}
now = time (NULL);
}
next_tick = now + 1;
}
return err;
}
diff --git a/dirmngr/t-dns-stuff.c b/dirmngr/t-dns-stuff.c
index 23c0c6aa0..5a3ede15b 100644
--- a/dirmngr/t-dns-stuff.c
+++ b/dirmngr/t-dns-stuff.c
@@ -1,313 +1,313 @@
/* t-dns-cert.c - Module test for dns-stuff.c
* Copyright (C) 2011 Free Software Foundation, Inc.
* Copyright (C) 2011, 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
-#include "util.h"
+#include "../common/util.h"
#include "dns-stuff.h"
#define PGM "t-dns-stuff"
static int verbose;
static int debug;
static void
init_sockets (void)
{
#ifdef HAVE_W32_SYSTEM
WSADATA wsadat;
WSAStartup (0x202, &wsadat);
#endif
}
int
main (int argc, char **argv)
{
int last_argc = -1;
gpg_error_t err;
int any_options = 0;
int opt_tor = 0;
int opt_cert = 0;
int opt_srv = 0;
int opt_bracket = 0;
int opt_cname = 0;
char const *name = NULL;
gpgrt_init ();
log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX);
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 " [HOST]\n"
"Options:\n"
" --verbose print timings etc.\n"
" --debug flyswatter\n"
" --standard-resolver use the system's resolver\n"
" --use-tor use Tor\n"
" --new-circuit use a new Tor circuit\n"
" --bracket enclose v6 addresses in brackets\n"
" --cert lookup a CERT RR\n"
" --srv lookup a SRV RR\n"
" --cname lookup a CNAME RR\n"
" --timeout SECONDS timeout after SECONDS\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, "--use-tor"))
{
opt_tor = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--standard-resolver"))
{
enable_standard_resolver (1);
argc--; argv++;
}
else if (!strcmp (*argv, "--recursive-resolver"))
{
enable_recursive_resolver (1);
argc--; argv++;
}
else if (!strcmp (*argv, "--bracket"))
{
opt_bracket = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--cert"))
{
any_options = opt_cert = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--srv"))
{
any_options = opt_srv = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--cname"))
{
any_options = opt_cname = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--timeout"))
{
argc--; argv++;
if (argc)
{
set_dns_timeout (atoi (*argv));
argc--; argv++;
}
}
else if (!strncmp (*argv, "--", 2))
{
fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
exit (1);
}
}
if (!argc && !any_options)
{
opt_cert = 1;
name = "simon.josefsson.org";
}
else if (argc == 1)
name = *argv;
else
{
fprintf (stderr, PGM ": none or too many host names given\n");
exit (1);
}
set_dns_verbose (verbose, debug);
init_sockets ();
if (opt_tor)
enable_dns_tormode (0);
if (opt_cert)
{
unsigned char *fpr;
size_t fpr_len;
char *url;
void *key;
size_t keylen;
if (verbose || any_options)
printf ("CERT lookup on '%s'\n", name);
err = get_dns_cert (name, DNS_CERTTYPE_ANY, &key, &keylen,
&fpr, &fpr_len, &url);
if (err)
printf ("get_dns_cert failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else if (key)
{
if (verbose || any_options)
printf ("Key found (%u bytes)\n", (unsigned int)keylen);
}
else
{
if (fpr)
{
int i;
printf ("Fingerprint found (%d bytes): ", (int)fpr_len);
for (i = 0; i < fpr_len; i++)
printf ("%02X", fpr[i]);
putchar ('\n');
}
else
printf ("No fingerprint found\n");
if (url)
printf ("URL found: %s\n", url);
else
printf ("No URL found\n");
}
xfree (key);
xfree (fpr);
xfree (url);
}
else if (opt_cname)
{
char *cname;
printf ("CNAME lookup on '%s'\n", name);
err = get_dns_cname (name, &cname);
if (err)
printf ("get_dns_cname failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
{
printf ("CNAME found: '%s'\n", cname);
}
xfree (cname);
}
else if (opt_srv)
{
struct srventry *srv;
unsigned int count;
int i;
err = get_dns_srv (name? name : "_hkp._tcp.wwwkeys.pgp.net",
NULL, NULL, &srv, &count);
if (err)
printf ("get_dns_srv failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
{
printf ("count=%u\n",count);
for (i=0; i < count; i++)
{
printf("priority=%-8hu ",srv[i].priority);
printf("weight=%-8hu ",srv[i].weight);
printf("port=%-5hu ",srv[i].port);
printf("target=%s\n",srv[i].target);
}
xfree(srv);
}
}
else /* Standard lookup. */
{
char *cname;
dns_addrinfo_t aibuf, ai;
char *host;
printf ("Lookup on '%s'\n", name);
err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname);
if (err)
{
fprintf (stderr, PGM": resolving '%s' failed: %s\n",
name, gpg_strerror (err));
exit (1);
}
if (cname)
printf ("cname: %s\n", cname);
for (ai = aibuf; ai; ai = ai->next)
{
printf ("%s %3d %3d ",
ai->family == AF_INET6? "inet6" :
ai->family == AF_INET? "inet4" : "? ",
ai->socktype, ai->protocol);
err = resolve_dns_addr (ai->addr, ai->addrlen,
(DNS_NUMERICHOST
| (opt_bracket? DNS_WITHBRACKET:0)),
&host);
if (err)
printf ("[resolve_dns_addr failed: %s]", gpg_strerror (err));
else
{
printf ("%s", host);
xfree (host);
}
err = resolve_dns_addr (ai->addr, ai->addrlen,
(opt_bracket? DNS_WITHBRACKET:0),
&host);
if (err)
printf (" [resolve_dns_addr failed (2): %s]", gpg_strerror (err));
else
{
if (!is_ip_address (host))
printf (" (%s)", host);
xfree (host);
}
putchar ('\n');
}
xfree (cname);
free_dns_addrinfo (aibuf);
}
reload_dns_stuff (1); /* Release objects. */
return 0;
}
diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c
index 35f59478b..622dce54f 100644
--- a/dirmngr/t-http.c
+++ b/dirmngr/t-http.c
@@ -1,462 +1,462 @@
/* t-http.c
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assuan.h>
-#include "util.h"
-#include "logging.h"
+#include "../common/util.h"
+#include "../common/logging.h"
#include "http.h"
#include <ksba.h>
#if HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h> /* For init, logging, and deinit. */
#endif /*HTTP_USE_GNUTLS*/
#define PGM "t-http"
static int verbose;
static int debug;
static int no_verify;
/* static void */
/* read_dh_params (const char *fname) */
/* { */
/* gpg_error_t err; */
/* int rc; */
/* FILE *fp; */
/* struct stat st; */
/* char *buf; */
/* size_t buflen; */
/* gnutls_datum_t datum; */
/* fp = fopen (fname, "rb"); */
/* if (!fp) */
/* { */
/* err = gpg_error_from_syserror (); */
/* log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err)); */
/* } */
/* if (fstat (fileno(fp), &st)) */
/* { */
/* err = gpg_error_from_syserror (); */
/* log_fatal ("can't stat '%s': %s\n", fname, gpg_strerror (err)); */
/* } */
/* buflen = st.st_size; */
/* buf = xmalloc (buflen+1); */
/* if (fread (buf, buflen, 1, fp) != 1) */
/* { */
/* err = gpg_error_from_syserror (); */
/* log_fatal ("error reading '%s': %s\n", fname, gpg_strerror (err)); */
/* } */
/* fclose (fp); */
/* datum.size = buflen; */
/* datum.data = buf; */
/* rc = gnutls_dh_params_import_pkcs3 (dh_params, &datum, GNUTLS_X509_FMT_PEM); */
/* if (rc < 0) */
/* log_fatal ("gnutls_dh_param_import failed: %s\n", gnutls_strerror (rc)); */
/* xfree (buf); */
/* } */
#if HTTP_USE_GNUTLS
static gpg_error_t
verify_callback (http_t hd, http_session_t session, int reserved)
{
(void)hd;
(void)reserved;
return no_verify? 0 : http_verify_server_credentials (session);
}
#endif
#if HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
fprintf (stderr, "gnutls:L%d: %s", level, text);
}
#endif
#if HTTP_USE_NTBTLS
static gpg_error_t
my_http_tls_verify_cb (void *opaque,
http_t http,
http_session_t session,
unsigned int http_flags,
void *tls_context)
{
gpg_error_t err;
int idx;
ksba_cert_t cert;
ksba_cert_t hostcert = NULL;
(void)opaque;
(void)http;
(void)session;
(void)http_flags;
/* Get the peer's certs fron ntbtls. */
for (idx = 0;
(cert = ntbtls_x509_get_peer_cert (tls_context, idx)); idx++)
{
if (!idx)
{
log_info ("Received host certificate\n");
hostcert = cert;
}
else
{
log_info ("Received additional certificate\n");
ksba_cert_release (cert);
}
}
if (!idx)
{
err = gpg_error (GPG_ERR_MISSING_CERT);
goto leave;
}
err = 0;
leave:
ksba_cert_release (hostcert);
log_info ("my_http_tls_verify_cb returns: %s\n", gpg_strerror (err));
return err;
}
#endif /*HTTP_USE_NTBTLS*/
/* 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;
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;
}
int
main (int argc, char **argv)
{
int last_argc = -1;
gpg_error_t err;
int rc; parsed_uri_t uri;
uri_tuple_t r;
http_t hd;
int c;
unsigned int my_http_flags = 0;
int no_out = 0;
int tls_dbg = 0;
int no_crl = 0;
const char *cafile = NULL;
http_session_t session = NULL;
gpgrt_init ();
log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
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 " URL\n"
"Options:\n"
" --verbose print timings etc.\n"
" --debug flyswatter\n"
" --tls-debug N use TLS debug level N\n"
" --cacert FNAME expect CA certificate in file FNAME\n"
" --no-verify do not verify the certificate\n"
" --force-tls use HTTP_FLAG_FORCE_TLS\n"
" --force-tor use HTTP_FLAG_FORCE_TOR\n"
" --no-out do not print the content\n"
" --no-crl do not consuilt a CRL\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, "--tls-debug"))
{
argc--; argv++;
if (argc)
{
tls_dbg = atoi (*argv);
argc--; argv++;
}
}
else if (!strcmp (*argv, "--cacert"))
{
argc--; argv++;
if (argc)
{
cafile = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "--no-verify"))
{
no_verify = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--force-tls"))
{
my_http_flags |= HTTP_FLAG_FORCE_TLS;
argc--; argv++;
}
else if (!strcmp (*argv, "--force-tor"))
{
my_http_flags |= HTTP_FLAG_FORCE_TOR;
argc--; argv++;
}
else if (!strcmp (*argv, "--no-out"))
{
no_out = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--no-crl"))
{
no_crl = 1;
argc--; argv++;
}
else if (!strncmp (*argv, "--", 2))
{
fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
exit (1);
}
}
if (argc != 1)
{
fprintf (stderr, PGM ": no or too many URLS given\n");
exit (1);
}
if (!cafile)
cafile = prepend_srcdir ("tls-ca.pem");
/* http.c makes use of the assuan socket wrapper. */
assuan_sock_init ();
#if HTTP_USE_NTBTLS
log_info ("new session.\n");
err = http_session_new (&session, NULL,
((no_crl? HTTP_FLAG_NO_CRL : 0)
| HTTP_FLAG_TRUST_DEF),
my_http_tls_verify_cb, NULL);
if (err)
log_error ("http_session_new failed: %s\n", gpg_strerror (err));
ntbtls_set_debug (tls_dbg, NULL, NULL);
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
http_register_tls_callback (verify_callback);
http_register_tls_ca (cafile);
err = http_session_new (&session, NULL,
((no_crl? HTTP_FLAG_NO_CRL : 0)
| HTTP_FLAG_TRUST_DEF),
NULL, NULL);
if (err)
log_error ("http_session_new failed: %s\n", gpg_strerror (err));
/* rc = gnutls_dh_params_init(&dh_params); */
/* if (rc) */
/* log_error ("gnutls_dh_params_init failed: %s\n", gnutls_strerror (rc)); */
/* read_dh_params ("dh_param.pem"); */
/* rc = gnutls_certificate_set_x509_trust_file */
/* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */
/* if (rc) */
/* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */
/* gnutls_strerror (rc)); */
/* gnutls_certificate_set_dh_params (certcred, dh_params); */
gnutls_global_set_log_function (my_gnutls_log);
if (tls_dbg)
gnutls_global_set_log_level (tls_dbg);
#endif /*HTTP_USE_GNUTLS*/
rc = http_parse_uri (&uri, *argv, 1);
if (rc)
{
log_error ("'%s': %s\n", *argv, gpg_strerror (rc));
return 1;
}
printf ("Scheme: %s\n", uri->scheme);
if (uri->opaque)
printf ("Value : %s\n", uri->path);
else
{
printf ("Auth : %s\n", uri->auth? uri->auth:"[none]");
printf ("Host : %s\n", uri->host);
printf ("Port : %u\n", uri->port);
printf ("Path : %s\n", uri->path);
for (r = uri->params; r; r = r->next)
{
printf ("Params: %s", r->name);
if (!r->no_value)
{
printf ("=%s", r->value);
if (strlen (r->value) != r->valuelen)
printf (" [real length=%d]", (int) r->valuelen);
}
putchar ('\n');
}
for (r = uri->query; r; r = r->next)
{
printf ("Query : %s", r->name);
if (!r->no_value)
{
printf ("=%s", r->value);
if (strlen (r->value) != r->valuelen)
printf (" [real length=%d]", (int) r->valuelen);
}
putchar ('\n');
}
printf ("Flags :%s%s%s%s\n",
uri->is_http? " http":"",
uri->opaque? " opaque":"",
uri->v6lit? " v6lit":"",
uri->onion? " onion":"");
printf ("TLS : %s\n",
uri->use_tls? "yes":
(my_http_flags&HTTP_FLAG_FORCE_TLS)? "forced" : "no");
printf ("Tor : %s\n",
(my_http_flags&HTTP_FLAG_FORCE_TOR)? "yes" : "no");
}
fflush (stdout);
http_release_parsed_uri (uri);
uri = NULL;
rc = http_open_document (&hd, *argv, NULL, my_http_flags,
NULL, session, NULL, NULL);
if (rc)
{
log_error ("can't get '%s': %s\n", *argv, gpg_strerror (rc));
return 1;
}
log_info ("open_http_document succeeded; status=%u\n",
http_get_status_code (hd));
{
const char **names;
int i;
names = http_get_header_names (hd);
if (!names)
log_fatal ("http_get_header_names failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
for (i = 0; names[i]; i++)
printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i]));
xfree (names);
}
fflush (stdout);
switch (http_get_status_code (hd))
{
case 200:
case 400:
case 401:
case 403:
case 404:
{
unsigned long count = 0;
while ((c = es_getc (http_get_read_ptr (hd))) != EOF)
{
count++;
if (!no_out)
putchar (c);
}
log_info ("Received bytes: %lu\n", count);
}
break;
case 301:
case 302:
case 307:
log_info ("Redirected to: %s\n", http_get_header (hd, "Location"));
break;
}
http_close (hd, 0);
http_session_release (session);
#ifdef HTTP_USE_GNUTLS
gnutls_global_deinit ();
#endif /*HTTP_USE_GNUTLS*/
return 0;
}
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 604be93d5..f1d2d1734 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -1,259 +1,259 @@
# Copyright (C) 1998, 1999, 2000, 2001, 2002,
# 2003, 2006, 2010 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = options.skel dirmngr-conf.skel distsigkey.gpg \
ChangeLog-2011 gpg-w32info.rc \
gpg.w32-manifest.in test.c t-keydb-keyring.kbx \
t-keydb-get-keyblock.gpg t-stutter-data.asc
-AM_CPPFLAGS = -I$(top_srcdir)/common
+AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
$(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
needed_libs = ../kbx/libkeybox.a $(libcommon)
# Because there are no program specific transform macros we need to
# work around that to allow installing gpg as gpg2.
gpg2_hack_list = gpg gpgv
if USE_GPG2_HACK
gpg2_hack_uninst = gpg2 gpgv2
use_gpg2_hack = yes
else
gpg2_hack_uninst = $(gpg2_hack_list)
use_gpg2_hack = no
endif
# NB: We use noinst_ for gpg and gpgv so that we can install them with
# the install-hook target under the name gpg2/gpgv2.
noinst_PROGRAMS = gpg
if !HAVE_W32CE_SYSTEM
noinst_PROGRAMS += gpgv
endif
if MAINTAINER_MODE
noinst_PROGRAMS += gpgcompose
endif
noinst_PROGRAMS += $(module_tests)
TESTS = $(module_tests)
if ENABLE_BZIP2_SUPPORT
bzip2_source = compress-bz2.c
else
bzip2_source =
endif
if ENABLE_CARD_SUPPORT
card_source = card-util.c
else
card_source =
endif
if NO_TRUST_MODELS
trust_source =
else
trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h
endif
if USE_TOFU
tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h sqrtu32.c sqrtu32.h
else
tofu_source =
endif
if HAVE_W32_SYSTEM
resource_objs += gpg-w32info.o
gpg-w32info.o : gpg.w32-manifest
endif
common_source = \
gpg.h \
dek.h \
build-packet.c \
compress.c \
$(bzip2_source) \
filter.h \
free-packet.c \
getkey.c \
keydb.c keydb.h \
keyring.c keyring.h \
seskey.c \
kbnode.c \
main.h \
mainproc.c \
armor.c \
mdfilter.c \
textfilter.c \
progress.c \
misc.c \
rmd160.c rmd160.h \
options.h \
openfile.c \
keyid.c \
packet.h \
parse-packet.c \
cpr.c \
plaintext.c \
sig-check.c \
keylist.c \
pkglue.c pkglue.h \
ecdh.c
gpg_sources = server.c \
$(common_source) \
pkclist.c \
skclist.c \
pubkey-enc.c \
passphrase.c \
decrypt.c \
decrypt-data.c \
cipher.c \
encrypt.c \
sign.c \
verify.c \
revoke.c \
dearmor.c \
import.c \
export.c \
migrate.c \
delkey.c \
keygen.c \
helptext.c \
keyserver.c \
keyserver-internal.h \
call-dirmngr.c call-dirmngr.h \
photoid.c photoid.h \
call-agent.c call-agent.h \
trust.c $(trust_source) $(tofu_source) \
$(card_source) \
exec.c exec.h
gpg_SOURCES = gpg.c \
keyedit.c \
$(gpg_sources)
gpgcompose_SOURCES = gpgcompose.c $(gpg_sources)
gpgv_SOURCES = gpgv.c \
$(common_source) \
verify.c
#gpgd_SOURCES = gpgd.c \
# ks-proto.h \
# ks-proto.c \
# ks-db.c \
# ks-db.h \
# $(common_source)
LDADD = $(needed_libs) ../common/libgpgrl.a \
$(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
gpg_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpg_LDFLAGS = $(extra_bin_ldflags)
gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
$(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgv_LDFLAGS = $(extra_bin_ldflags)
gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgcompose_LDFLAGS = $(extra_bin_ldflags)
t_common_ldadd =
module_tests = t-rmd160 t-keydb t-keydb-get-keyblock t-stutter
t_rmd160_SOURCES = t-rmd160.c rmd160.c
t_rmd160_LDADD = $(t_common_ldadd)
t_keydb_SOURCES = t-keydb.c test-stubs.c $(common_source)
t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_keydb_get_keyblock_SOURCES = t-keydb-get-keyblock.c test-stubs.c \
$(common_source)
t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_stutter_SOURCES = t-stutter.c test-stubs.c \
$(common_source)
t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
$(PROGRAMS): $(needed_libs) ../common/libgpgrl.a
# NB: To install gpg and gpgv we use this -hook. This code has to
# duplicate most of the automake generated install-binPROGRAMS target
# so that directories are created and the transform feature works.
install-exec-hook:
@echo "running install-exec-hook"; \
echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
$(MKDIR_P) "$(DESTDIR)$(bindir)"; \
for p in $(gpg2_hack_list); do \
echo "$$p$(EXEEXT) $$p$(EXEEXT)"; done | \
sed 's/$(EXEEXT)$$//' | \
while read p p1; do if test -f $$p \
; then echo "$$p"; echo "$$p"; else :; fi; \
done | \
sed -e 'p;s,.*/,,;n;h' \
-e 's|.*|.|' \
-e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
sed 'N;N;N;s,\n, ,g' | \
$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
{ d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
if ($$2 == $$4) files[d] = files[d] " " $$1; \
else { print "f", $$3 "/" $$4, $$1; } } \
END { for (d in files) print "f", d, files[d] }' | \
while read type dir files; do \
for f in $$files; do \
if test $(use_gpg2_hack) = yes ; \
then f2=`echo "$${f}" | sed 's/$(EXEEXT)$$//'`2$(EXEEXT); \
else f2="$${f}" ;\
fi ; \
echo "$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \
$${f} '$(DESTDIR)$(bindir)/$${f2}'"; \
$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \
$${f} "$(DESTDIR)$(bindir)/$${f2}"; \
done; \
done
install-data-local:
$(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
$(INSTALL_DATA) $(srcdir)/options.skel \
$(DESTDIR)$(pkgdatadir)/gpg-conf.skel
$(INSTALL_DATA) $(srcdir)/dirmngr-conf.skel \
$(DESTDIR)$(pkgdatadir)/dirmngr-conf.skel
$(INSTALL_DATA) $(srcdir)/distsigkey.gpg \
$(DESTDIR)$(pkgdatadir)/distsigkey.gpg
# NB: For uninstalling gpg and gpgv we use -local because there is
# no need for a specific order the targets need to be run.
uninstall-local:
-@rm $(DESTDIR)$(pkgdatadir)/gpg-conf.skel
-@rm $(DESTDIR)$(pkgdatadir)/dirmngr-conf.skel
-@rm $(DESTDIR)$(pkgdatadir)/distsigkey.gpg
-@files=`for p in $(gpg2_hack_uninst); do echo "$$p"; done | \
sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
-e 's/$$/$(EXEEXT)/' \
`; \
echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
cd "$(DESTDIR)$(bindir)" && rm -f $$files
diff --git a/g10/armor.c b/g10/armor.c
index 6b7c3f7ab..7fb9a89ee 100644
--- a/g10/armor.c
+++ b/g10/armor.c
@@ -1,1571 +1,1570 @@
/* armor.c - Armor flter
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "filter.h"
#include "packet.h"
#include "options.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#define MAX_LINELEN 20000
#define CRCINIT 0xB704CE
#define CRCPOLY 0X864CFB
#define CRCUPDATE(a,c) do { \
a = ((a) << 8) ^ crc_table[((a)&0xff >> 16) ^ (c)]; \
a &= 0x00ffffff; \
} while(0)
static u32 crc_table[256];
static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static byte asctobin[256]; /* runtime initialized */
static int is_initialized;
typedef enum {
fhdrHASArmor = 0,
fhdrNOArmor,
fhdrINIT,
fhdrINITCont,
fhdrINITSkip,
fhdrCHECKBegin,
fhdrWAITHeader,
fhdrWAITClearsig,
fhdrSKIPHeader,
fhdrCLEARSIG,
fhdrREADClearsig,
fhdrNullClearsig,
fhdrEMPTYClearsig,
fhdrCHECKClearsig,
fhdrCHECKClearsig2,
fhdrCHECKDashEscaped,
fhdrCHECKDashEscaped2,
fhdrCHECKDashEscaped3,
fhdrREADClearsigNext,
fhdrENDClearsig,
fhdrENDClearsigHelp,
fhdrTESTSpaces,
fhdrCLEARSIGSimple,
fhdrCLEARSIGSimpleNext,
fhdrTEXT,
fhdrTEXTSimple,
fhdrERROR,
fhdrERRORShow,
fhdrEOF
} fhdr_state_t;
/* if we encounter this armor string with this index, go
* into a mode which fakes packets and wait for the next armor */
#define BEGIN_SIGNATURE 2
#define BEGIN_SIGNED_MSG_IDX 3
static char *head_strings[] = {
"BEGIN PGP MESSAGE",
"BEGIN PGP PUBLIC KEY BLOCK",
"BEGIN PGP SIGNATURE",
"BEGIN PGP SIGNED MESSAGE",
"BEGIN PGP ARMORED FILE", /* gnupg extension */
"BEGIN PGP PRIVATE KEY BLOCK",
"BEGIN PGP SECRET KEY BLOCK", /* only used by pgp2 */
NULL
};
static char *tail_strings[] = {
"END PGP MESSAGE",
"END PGP PUBLIC KEY BLOCK",
"END PGP SIGNATURE",
"END dummy",
"END PGP ARMORED FILE",
"END PGP PRIVATE KEY BLOCK",
"END PGP SECRET KEY BLOCK",
NULL
};
static int armor_filter ( void *opaque, int control,
iobuf_t chain, byte *buf, size_t *ret_len);
/* Create a new context for armor filters. */
armor_filter_context_t *
new_armor_context (void)
{
armor_filter_context_t *afx;
afx = xcalloc (1, sizeof *afx);
afx->refcount = 1;
return afx;
}
/* Release an armor filter context. Passing NULL is explicitly
allowed and a no-op. */
void
release_armor_context (armor_filter_context_t *afx)
{
if (!afx)
return;
log_assert (afx->refcount);
if ( --afx->refcount )
return;
xfree (afx);
}
/* Push the armor filter onto the iobuf stream IOBUF. */
int
push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf)
{
int rc;
afx->refcount++;
rc = iobuf_push_filter (iobuf, armor_filter, afx);
if (rc)
afx->refcount--;
return rc;
}
static void
initialize(void)
{
int i, j;
u32 t;
byte *s;
/* init the crc lookup table */
crc_table[0] = 0;
for(i=j=0; j < 128; j++ ) {
t = crc_table[j];
if( t & 0x00800000 ) {
t <<= 1;
crc_table[i++] = t ^ CRCPOLY;
crc_table[i++] = t;
}
else {
t <<= 1;
crc_table[i++] = t;
crc_table[i++] = t ^ CRCPOLY;
}
}
/* build the helptable for radix64 to bin conversion */
for(i=0; i < 256; i++ )
asctobin[i] = 255; /* used to detect invalid characters */
for(s=bintoasc,i=0; *s; s++,i++ )
asctobin[*s] = i;
is_initialized=1;
}
/*
* Check whether this is an armored file. See also
* parse-packet.c for details on this code.
*
* Note that the buffer BUF needs to be at least 2 bytes long. If in
* doubt that the second byte to 0.
*
* Returns: True if it seems to be armored
*/
static int
is_armored (const byte *buf)
{
int ctb, pkttype;
int indeterminate_length_allowed;
ctb = *buf;
if( !(ctb & 0x80) )
/* The most significant bit of the CTB must be set. Since it is
cleared, this is not a binary OpenPGP message. Assume it is
armored. */
return 1;
pkttype = ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf);
switch( pkttype ) {
case PKT_PUBKEY_ENC:
case PKT_SIGNATURE:
case PKT_SYMKEY_ENC:
case PKT_ONEPASS_SIG:
case PKT_SECRET_KEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_MARKER:
case PKT_RING_TRUST:
case PKT_USER_ID:
case PKT_PUBLIC_SUBKEY:
case PKT_ATTRIBUTE:
case PKT_MDC:
indeterminate_length_allowed = 0;
break;
case PKT_COMPRESSED:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_PLAINTEXT:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
indeterminate_length_allowed = 1;
break;
default:
/* Invalid packet type. */
return 1;
}
if (! indeterminate_length_allowed)
/* It is only legal to use an indeterminate length with a few
packet types. If a packet uses an indeterminate length, but
that is not allowed, then the data is not valid binary
OpenPGP data. */
{
int new_format;
int indeterminate_length;
new_format = !! (ctb & (1 << 6));
if (new_format)
indeterminate_length = (buf[1] >= 224 && buf[1] < 255);
else
indeterminate_length = (ctb & 3) == 3;
if (indeterminate_length)
return 1;
}
/* The first CTB seems legit. It is probably not armored
data. */
return 0;
}
/****************
* Try to check whether the iobuf is armored
* Returns true if this may be the case; the caller should use the
* filter to do further processing.
*/
int
use_armor_filter( IOBUF a )
{
byte buf[2];
int n;
/* fixme: there might be a problem with iobuf_peek */
n = iobuf_peek (a, buf, 2);
if( n == -1 )
return 0; /* EOF, doesn't matter whether armored or not */
if( !n )
return 1; /* can't check it: try armored */
if (n != 2)
return 0; /* short buffer */
return is_armored(buf);
}
static void
invalid_armor(void)
{
write_status(STATUS_BADARMOR);
g10_exit(1); /* stop here */
}
/****************
* check whether the armor header is valid on a signed message.
* this is for security reasons: the header lines are not included in the
* hash and by using some creative formatting rules, Mallory could fake
* any text at the beginning of a document; assuming it is read with
* a simple viewer. We only allow the Hash Header.
*/
static int
parse_hash_header( const char *line )
{
const char *s, *s2;
unsigned found = 0;
if( strlen(line) < 6 || strlen(line) > 60 )
return 0; /* too short or too long */
if( memcmp( line, "Hash:", 5 ) )
return 0; /* invalid header */
for(s=line+5;;s=s2) {
for(; *s && (*s==' ' || *s == '\t'); s++ )
;
if( !*s )
break;
for(s2=s+1; *s2 && *s2!=' ' && *s2 != '\t' && *s2 != ','; s2++ )
;
if( !strncmp( s, "RIPEMD160", s2-s ) )
found |= 1;
else if( !strncmp( s, "SHA1", s2-s ) )
found |= 2;
else if( !strncmp( s, "SHA224", s2-s ) )
found |= 8;
else if( !strncmp( s, "SHA256", s2-s ) )
found |= 16;
else if( !strncmp( s, "SHA384", s2-s ) )
found |= 32;
else if( !strncmp( s, "SHA512", s2-s ) )
found |= 64;
else
return 0;
for(; *s2 && (*s2==' ' || *s2 == '\t'); s2++ )
;
if( *s2 && *s2 != ',' )
return 0;
if( *s2 )
s2++;
}
return found;
}
/* Returns true if this is a valid armor tag as per RFC-2440bis-21. */
static int
is_armor_tag(const char *line)
{
if(strncmp(line,"Version",7)==0
|| strncmp(line,"Comment",7)==0
|| strncmp(line,"MessageID",9)==0
|| strncmp(line,"Hash",4)==0
|| strncmp(line,"Charset",7)==0)
return 1;
return 0;
}
/****************
* Check whether this is a armor line.
* returns: -1 if it is not a armor header or the index number of the
* armor header.
*/
static int
is_armor_header( byte *line, unsigned len )
{
const char *s;
byte *save_p, *p;
int save_c;
int i;
if( len < 15 )
return -1; /* too short */
if( memcmp( line, "-----", 5 ) )
return -1; /* no */
p = strstr( line+5, "-----");
if( !p )
return -1;
save_p = p;
p += 5;
/* Some Windows environments seem to add whitespace to the end of
the line, so we strip it here. This becomes strict if
--rfc2440 is set since 2440 reads "The header lines, therefore,
MUST start at the beginning of a line, and MUST NOT have text
following them on the same line." It is unclear whether "text"
refers to all text or just non-whitespace text. 4880 clarified
this was only non-whitespace text. */
if(RFC2440)
{
if( *p == '\r' )
p++;
if( *p == '\n' )
p++;
}
else
while(*p==' ' || *p=='\r' || *p=='\n' || *p=='\t')
p++;
if( *p )
return -1; /* garbage after dashes */
save_c = *save_p; *save_p = 0;
p = line+5;
for(i=0; (s=head_strings[i]); i++ )
if( !strcmp(s, p) )
break;
*save_p = save_c;
if( !s )
return -1; /* unknown armor line */
if( opt.verbose > 1 )
log_info(_("armor: %s\n"), head_strings[i]);
return i;
}
/****************
* Parse a header lines
* Return 0: Empty line (end of header lines)
* -1: invalid header line
* >0: Good header line
*/
static int
parse_header_line( armor_filter_context_t *afx, byte *line, unsigned int len )
{
byte *p;
int hashes=0;
unsigned int len2;
len2 = length_sans_trailing_ws ( line, len );
if( !len2 ) {
afx->buffer_pos = len2; /* (it is not the fine way to do it here) */
return 0; /* WS only: same as empty line */
}
/*
This is fussy. The spec says that a header line is delimited
with a colon-space pair. This means that a line such as
"Comment: " (with nothing else) is actually legal as an empty
string comment. However, email and cut-and-paste being what it
is, that trailing space may go away. Therefore, we accept empty
headers delimited with only a colon. --rfc2440, as always,
makes this strict and enforces the colon-space pair. -dms
*/
p = strchr( line, ':');
if( !p || (RFC2440 && p[1]!=' ')
|| (!RFC2440 && p[1]!=' ' && p[1]!='\n' && p[1]!='\r'))
{
log_error (_("invalid armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
return -1;
}
/* Chop off the whitespace we detected before */
len=len2;
line[len2]='\0';
if( opt.verbose ) {
log_info(_("armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
if( afx->in_cleartext )
{
if( (hashes=parse_hash_header( line )) )
afx->hashes |= hashes;
else if( strlen(line) > 15 && !memcmp( line, "NotDashEscaped:", 15 ) )
afx->not_dash_escaped = 1;
else
{
log_error(_("invalid clearsig header\n"));
return -1;
}
}
else if(!is_armor_tag(line))
{
/* Section 6.2: "Unknown keys should be reported to the user,
but OpenPGP should continue to process the message." Note
that in a clearsigned message this applies to the signature
part (i.e. "BEGIN PGP SIGNATURE") and not the signed data
("BEGIN PGP SIGNED MESSAGE"). The only key allowed in the
signed data section is "Hash". */
log_info(_("unknown armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
return 1;
}
/* figure out whether the data is armored or not */
static int
check_input( armor_filter_context_t *afx, IOBUF a )
{
int rc = 0;
int i;
byte *line;
unsigned len;
unsigned maxlen;
int hdr_line = -1;
/* read the first line to see whether this is armored data */
maxlen = MAX_LINELEN;
len = afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
if( !maxlen ) {
/* line has been truncated: assume not armored */
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
if( !len ) {
return -1; /* eof */
}
/* (the line is always a C string but maybe longer) */
if( *line == '\n' || ( len && (*line == '\r' && line[1]=='\n') ) )
;
else if (len >= 2 && !is_armored (line)) {
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
/* find the armor header */
while(len) {
i = is_armor_header( line, len );
if( i >= 0 && !(afx->only_keyblocks && i != 1 && i != 5 && i != 6 )) {
hdr_line = i;
if( hdr_line == BEGIN_SIGNED_MSG_IDX ) {
if( afx->in_cleartext ) {
log_error(_("nested clear text signatures\n"));
rc = gpg_error (GPG_ERR_INV_ARMOR);
}
afx->in_cleartext = 1;
}
break;
}
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
}
/* Parse the header lines. */
while(len) {
/* Read the next line (skip all truncated lines). */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
i = parse_header_line( afx, line, len );
if( i <= 0 ) {
if (i && RFC2440)
rc = GPG_ERR_INV_ARMOR;
break;
}
}
if( rc )
invalid_armor();
else if( afx->in_cleartext )
afx->faked = 1;
else {
afx->inp_checked = 1;
afx->crc = CRCINIT;
afx->idx = 0;
afx->radbuf[0] = 0;
}
return rc;
}
#define PARTIAL_CHUNK 512
#define PARTIAL_POW 9
/****************
* Fake a literal data packet and wait for the next armor line
* fixme: empty line handling and null length clear text signature are
* not implemented/checked.
*/
static int
fake_packet( armor_filter_context_t *afx, IOBUF a,
size_t *retn, byte *buf, size_t size )
{
int rc = 0;
size_t len = 0;
int lastline = 0;
unsigned maxlen, n;
byte *p;
byte tempbuf[PARTIAL_CHUNK];
size_t tempbuf_len=0;
while( !rc && size-len>=(PARTIAL_CHUNK+1)) {
/* copy what we have in the line buffer */
if( afx->faked == 1 )
afx->faked++; /* skip the first (empty) line */
else
{
/* It's full, so write this partial chunk */
if(tempbuf_len==PARTIAL_CHUNK)
{
buf[len++]=0xE0+PARTIAL_POW;
memcpy(&buf[len],tempbuf,PARTIAL_CHUNK);
len+=PARTIAL_CHUNK;
tempbuf_len=0;
continue;
}
while( tempbuf_len < PARTIAL_CHUNK
&& afx->buffer_pos < afx->buffer_len )
tempbuf[tempbuf_len++] = afx->buffer[afx->buffer_pos++];
if( tempbuf_len==PARTIAL_CHUNK )
continue;
}
/* read the next line */
maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !afx->buffer_len ) {
rc = -1; /* eof (should not happen) */
continue;
}
if( !maxlen )
afx->truncated++;
p = afx->buffer;
n = afx->buffer_len;
/* Armor header or dash-escaped line? */
if(p[0]=='-')
{
/* 2440bis-10: When reversing dash-escaping, an
implementation MUST strip the string "- " if it occurs
at the beginning of a line, and SHOULD warn on "-" and
any character other than a space at the beginning of a
line. */
if(p[1]==' ' && !afx->not_dash_escaped)
{
/* It's a dash-escaped line, so skip over the
escape. */
afx->buffer_pos = 2;
}
else if(p[1]=='-' && p[2]=='-' && p[3]=='-' && p[4]=='-')
{
/* Five dashes in a row mean it's probably armor
header. */
int type = is_armor_header( p, n );
if( afx->not_dash_escaped && type != BEGIN_SIGNATURE )
; /* this is okay */
else
{
if( type != BEGIN_SIGNATURE )
{
log_info(_("unexpected armor: "));
es_write_sanitized (log_get_stream (), p, n,
NULL, NULL);
log_printf ("\n");
}
lastline = 1;
rc = -1;
}
}
else if(!afx->not_dash_escaped)
{
/* Bad dash-escaping. */
log_info (_("invalid dash escaped line: "));
es_write_sanitized (log_get_stream (), p, n, NULL, NULL);
log_printf ("\n");
}
}
/* Now handle the end-of-line canonicalization */
if( !afx->not_dash_escaped )
{
int crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n';
afx->buffer_len=
trim_trailing_chars( &p[afx->buffer_pos], n-afx->buffer_pos,
" \t\r\n");
afx->buffer_len+=afx->buffer_pos;
/* the buffer is always allocated with enough space to append
* the removed [CR], LF and a Nul
* The reason for this complicated procedure is to keep at least
* the original type of lineending - handling of the removed
* trailing spaces seems to be impossible in our method
* of faking a packet; either we have to use a temporary file
* or calculate the hash here in this module and somehow find
* a way to send the hash down the processing line (well, a special
* faked packet could do the job).
*/
if( crlf )
afx->buffer[afx->buffer_len++] = '\r';
afx->buffer[afx->buffer_len++] = '\n';
afx->buffer[afx->buffer_len] = '\0';
}
}
if( lastline ) { /* write last (ending) length header */
if(tempbuf_len<192)
buf[len++]=tempbuf_len;
else
{
buf[len++]=((tempbuf_len-192)/256) + 192;
buf[len++]=(tempbuf_len-192) % 256;
}
memcpy(&buf[len],tempbuf,tempbuf_len);
len+=tempbuf_len;
rc = 0;
afx->faked = 0;
afx->in_cleartext = 0;
/* and now read the header lines */
afx->buffer_pos = 0;
for(;;) {
int i;
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
} while( !maxlen );
p = afx->buffer;
n = afx->buffer_len;
if( !n ) {
rc = -1;
break; /* eof */
}
i = parse_header_line( afx, p , n );
if( i <= 0 ) {
if( i )
invalid_armor();
break;
}
}
afx->inp_checked = 1;
afx->crc = CRCINIT;
afx->idx = 0;
afx->radbuf[0] = 0;
}
*retn = len;
return rc;
}
static int
invalid_crc(void)
{
if ( opt.ignore_crc_error )
return 0;
log_inc_errorcount();
return gpg_error (GPG_ERR_INV_ARMOR);
}
static int
radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
byte *buf, size_t size )
{
byte val;
int c=0, c2; /*init c because gcc is not clever enough for the continue*/
int checkcrc=0;
int rc = 0;
size_t n = 0;
int idx, i, onlypad=0;
u32 crc;
crc = afx->crc;
idx = afx->idx;
val = afx->radbuf[0];
for( n=0; n < size; ) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
again:
if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
continue;
else if( c == '=' ) { /* pad character: stop */
/* some mailers leave quoted-printable encoded characters
* so we try to workaround this */
if( afx->buffer_pos+2 < afx->buffer_len ) {
int cc1, cc2, cc3;
cc1 = afx->buffer[afx->buffer_pos];
cc2 = afx->buffer[afx->buffer_pos+1];
cc3 = afx->buffer[afx->buffer_pos+2];
if( isxdigit(cc1) && isxdigit(cc2)
&& strchr( "=\n\r\t ", cc3 )) {
/* well it seems to be the case - adjust */
c = isdigit(cc1)? (cc1 - '0'): (ascii_toupper(cc1)-'A'+10);
c <<= 4;
c |= isdigit(cc2)? (cc2 - '0'): (ascii_toupper(cc2)-'A'+10);
afx->buffer_pos += 2;
afx->qp_detected = 1;
goto again;
}
}
/* Occasionally a bug MTA will leave the = escaped as
=3D. If the 4 characters following that are valid
Radix64 characters and they are following by a new
line, assume that this is the case and skip the
3D. */
if (afx->buffer_pos + 6 < afx->buffer_len
&& afx->buffer[afx->buffer_pos + 0] == '3'
&& afx->buffer[afx->buffer_pos + 1] == 'D'
&& asctobin[afx->buffer[afx->buffer_pos + 2]] != 255
&& asctobin[afx->buffer[afx->buffer_pos + 3]] != 255
&& asctobin[afx->buffer[afx->buffer_pos + 4]] != 255
&& asctobin[afx->buffer[afx->buffer_pos + 5]] != 255
&& afx->buffer[afx->buffer_pos + 6] == '\n')
{
afx->buffer_pos += 2;
afx->qp_detected = 1;
}
if (!n)
onlypad = 1;
if( idx == 1 )
buf[n++] = val;
checkcrc++;
break;
}
else if( (c = asctobin[(c2=c)]) == 255 ) {
log_error(_("invalid radix64 character %02X skipped\n"), c2);
continue;
}
switch(idx) {
case 0: val = c << 2; break;
case 1: val |= (c>>4)&3; buf[n++]=val;val=(c<<4)&0xf0;break;
case 2: val |= (c>>2)&15; buf[n++]=val;val=(c<<6)&0xc0;break;
case 3: val |= c&0x3f; buf[n++] = val; break;
}
idx = (idx+1) % 4;
}
for(i=0; i < n; i++ )
crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]];
crc &= 0x00ffffff;
afx->crc = crc;
afx->idx = idx;
afx->radbuf[0] = val;
if( checkcrc ) {
afx->any_data = 1;
afx->inp_checked=0;
afx->faked = 0;
for(;;) { /* skip lf and pad characters */
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
if( c == '\n' || c == ' ' || c == '\r'
|| c == '\t' || c == '=' )
continue;
break;
}
if( c == -1 )
log_error(_("premature eof (no CRC)\n"));
else {
u32 mycrc = 0;
idx = 0;
do {
if( (c = asctobin[c]) == 255 )
break;
switch(idx) {
case 0: val = c << 2; break;
case 1: val |= (c>>4)&3; mycrc |= val << 16;val=(c<<4)&0xf0;break;
case 2: val |= (c>>2)&15; mycrc |= val << 8;val=(c<<6)&0xc0;break;
case 3: val |= c&0x3f; mycrc |= val; break;
}
for(;;) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size,
&maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
break;
}
if( !afx->buffer_len )
break; /* eof */
} while( ++idx < 4 );
if( c == -1 ) {
log_info(_("premature eof (in CRC)\n"));
rc = invalid_crc();
}
else if( idx == 0 ) {
/* No CRC at all is legal ("MAY") */
rc=0;
}
else if( idx != 4 ) {
log_info(_("malformed CRC\n"));
rc = invalid_crc();
}
else if( mycrc != afx->crc ) {
log_info (_("CRC error; %06lX - %06lX\n"),
(ulong)afx->crc, (ulong)mycrc);
rc = invalid_crc();
}
else {
rc = 0;
/* FIXME: Here we should emit another control packet,
* so that we know in mainproc that we are processing
* a clearsign message */
#if 0
for(rc=0;!rc;) {
rc = 0 /*check_trailer( &fhdr, c )*/;
if( !rc ) {
if( (c=iobuf_get(a)) == -1 )
rc = 2;
}
}
if( rc == -1 )
rc = 0;
else if( rc == 2 ) {
log_error(_("premature eof (in trailer)\n"));
rc = GPG_ERR_INVALID_ARMOR;
}
else {
log_error(_("error in trailer line\n"));
rc = GPG_ERR_INVALID_ARMOR;
}
#endif
}
}
}
if( !n && !onlypad )
rc = -1;
*retn = n;
return rc;
}
/****************
* This filter is used to handle the armor stuff
*/
static int
armor_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
armor_filter_context_t *afx = opaque;
int rc=0, i, c;
byte radbuf[3];
int idx, idx2;
size_t n=0;
u32 crc;
#if 0
static FILE *fp ;
if( !fp ) {
fp = fopen("armor.out", "w");
assert(fp);
}
#endif
if( DBG_FILTER )
log_debug("armor-filter: control: %d\n", control );
if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) {
n = 0;
if( afx->buffer_len ) {
/* Copy the data from AFX->BUFFER to BUF. */
for(; n < size && afx->buffer_pos < afx->buffer_len; n++ )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
}
/* If there is still space in BUF, read directly into it. */
for(; n < size; n++ ) {
if( (c=iobuf_get(a)) == -1 )
break;
buf[n] = c & 0xff;
}
if( !n )
/* We didn't get any data. EOF. */
rc = -1;
*ret_len = n;
}
else if( control == IOBUFCTRL_UNDERFLOW ) {
/* We need some space for the faked packet. The minmum
* required size is the PARTIAL_CHUNK size plus a byte for the
* length itself */
if( size < PARTIAL_CHUNK+1 )
BUG(); /* supplied buffer too short */
if( afx->faked )
rc = fake_packet( afx, a, &n, buf, size );
else if( !afx->inp_checked ) {
rc = check_input( afx, a );
if( afx->inp_bypass ) {
for(n=0; n < size && afx->buffer_pos < afx->buffer_len; )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
if( !n )
rc = -1;
}
else if( afx->faked ) {
unsigned int hashes = afx->hashes;
const byte *sesmark;
size_t sesmarklen;
sesmark = get_session_marker( &sesmarklen );
if ( sesmarklen > 20 )
BUG();
/* the buffer is at least 15+n*15 bytes long, so it
* is easy to construct the packets */
hashes &= 1|2|8|16|32|64;
if( !hashes ) {
hashes |= 2; /* Default to SHA-1. */
}
n=0;
/* First a gpg control packet... */
buf[n++] = 0xff; /* new format, type 63, 1 length byte */
n++; /* see below */
memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen;
buf[n++] = CTRLPKT_CLEARSIGN_START;
buf[n++] = afx->not_dash_escaped? 0:1; /* sigclass */
if( hashes & 1 )
buf[n++] = DIGEST_ALGO_RMD160;
if( hashes & 2 )
buf[n++] = DIGEST_ALGO_SHA1;
if( hashes & 8 )
buf[n++] = DIGEST_ALGO_SHA224;
if( hashes & 16 )
buf[n++] = DIGEST_ALGO_SHA256;
if( hashes & 32 )
buf[n++] = DIGEST_ALGO_SHA384;
if( hashes & 64 )
buf[n++] = DIGEST_ALGO_SHA512;
buf[1] = n - 2;
/* ...followed by an invented plaintext packet.
Amusingly enough, this packet is not compliant with
2440 as the initial partial length is less than 512
bytes. Of course, we'll accept it anyway ;) */
buf[n++] = 0xCB; /* new packet format, type 11 */
buf[n++] = 0xE1; /* 2^1 == 2 bytes */
buf[n++] = 't'; /* canonical text mode */
buf[n++] = 0; /* namelength */
buf[n++] = 0xE2; /* 2^2 == 4 more bytes */
memset(buf+n, 0, 4); /* timestamp */
n += 4;
}
else if( !rc )
rc = radix64_read( afx, a, &n, buf, size );
}
else
rc = radix64_read( afx, a, &n, buf, size );
#if 0
if( n )
if( fwrite(buf, n, 1, fp ) != 1 )
BUG();
#endif
*ret_len = n;
}
else if( control == IOBUFCTRL_FLUSH && !afx->cancel ) {
if( !afx->status ) { /* write the header line */
const char *s;
strlist_t comment=opt.comments;
if( afx->what >= DIM(head_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, head_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
if (opt.emit_version)
{
iobuf_writestr (a, "Version: "GNUPG_NAME" v");
for (s=VERSION; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 1 && *s)
{
iobuf_writebyte (a, *s++);
for (; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 2)
{
for (; *s && *s != '-' && !spacep (s); s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 3)
iobuf_writestr (a, " (" PRINTABLE_OS_NAME ")");
}
}
iobuf_writestr(a,afx->eol);
}
/* write the comment strings */
for(s=comment->d;comment;comment=comment->next,s=comment->d)
{
iobuf_writestr(a, "Comment: " );
for( ; *s; s++ )
{
if( *s == '\n' )
iobuf_writestr(a, "\\n" );
else if( *s == '\r' )
iobuf_writestr(a, "\\r" );
else if( *s == '\v' )
iobuf_writestr(a, "\\v" );
else
iobuf_put(a, *s );
}
iobuf_writestr(a,afx->eol);
}
if ( afx->hdrlines ) {
for ( s = afx->hdrlines; *s; s++ ) {
#ifdef HAVE_DOSISH_SYSTEM
if ( *s == '\n' )
iobuf_put( a, '\r');
#endif
iobuf_put(a, *s );
}
}
iobuf_writestr(a,afx->eol);
afx->status++;
afx->idx = 0;
afx->idx2 = 0;
afx->crc = CRCINIT;
}
crc = afx->crc;
idx = afx->idx;
idx2 = afx->idx2;
for(i=0; i < idx; i++ )
radbuf[i] = afx->radbuf[i];
for(i=0; i < size; i++ )
crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]];
crc &= 0x00ffffff;
for( ; size; buf++, size-- ) {
radbuf[idx++] = *buf;
if( idx > 2 ) {
idx = 0;
c = bintoasc[(*radbuf >> 2) & 077];
iobuf_put(a, c);
c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
iobuf_put(a, c);
c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
iobuf_put(a, c);
c = bintoasc[radbuf[2]&077];
iobuf_put(a, c);
if( ++idx2 >= (64/4) )
{ /* pgp doesn't like 72 here */
iobuf_writestr(a,afx->eol);
idx2=0;
}
}
}
for(i=0; i < idx; i++ )
afx->radbuf[i] = radbuf[i];
afx->idx = idx;
afx->idx2 = idx2;
afx->crc = crc;
}
else if( control == IOBUFCTRL_INIT )
{
if( !is_initialized )
initialize();
/* Figure out what we're using for line endings if the caller
didn't specify. */
if(afx->eol[0]==0)
{
#ifdef HAVE_DOSISH_SYSTEM
afx->eol[0]='\r';
afx->eol[1]='\n';
#else
afx->eol[0]='\n';
#endif
}
}
else if( control == IOBUFCTRL_CANCEL ) {
afx->cancel = 1;
}
else if( control == IOBUFCTRL_FREE ) {
if( afx->cancel )
;
else if( afx->status ) { /* pad, write cecksum, and bottom line */
crc = afx->crc;
idx = afx->idx;
idx2 = afx->idx2;
if( idx ) {
c = bintoasc[(afx->radbuf[0]>>2)&077];
iobuf_put(a, c);
if( idx == 1 ) {
c = bintoasc[((afx->radbuf[0] << 4) & 060) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
iobuf_put(a, '=');
}
else { /* 2 */
c = bintoasc[(((afx->radbuf[0]<<4)&060)
|((afx->radbuf[1]>>4)&017))&077];
iobuf_put(a, c);
c = bintoasc[((afx->radbuf[1] << 2) & 074) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
}
if( ++idx2 >= (64/4) )
{ /* pgp doesn't like 72 here */
iobuf_writestr(a,afx->eol);
idx2=0;
}
}
/* may need a linefeed */
if( idx2 )
iobuf_writestr(a,afx->eol);
/* write the CRC */
iobuf_put(a, '=');
radbuf[0] = crc >>16;
radbuf[1] = crc >> 8;
radbuf[2] = crc;
c = bintoasc[(*radbuf >> 2) & 077];
iobuf_put(a, c);
c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
iobuf_put(a, c);
c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
iobuf_put(a, c);
c = bintoasc[radbuf[2]&077];
iobuf_put(a, c);
iobuf_writestr(a,afx->eol);
/* and the trailer */
if( afx->what >= DIM(tail_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, tail_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
}
else if( !afx->any_data && !afx->inp_bypass ) {
log_error(_("no valid OpenPGP data found.\n"));
afx->no_openpgp_data = 1;
write_status_text( STATUS_NODATA, "1" );
}
if( afx->truncated )
log_info(_("invalid armor: line longer than %d characters\n"),
MAX_LINELEN );
/* issue an error to enforce dissemination of correct software */
if( afx->qp_detected )
log_error(_("quoted printable character in armor - "
"probably a buggy MTA has been used\n") );
xfree( afx->buffer );
afx->buffer = NULL;
release_armor_context (afx);
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "armor_filter", *ret_len);
return rc;
}
/****************
* create a radix64 encoded string.
*/
char *
make_radix64_string( const byte *data, size_t len )
{
char *buffer, *p;
buffer = p = xmalloc( (len+2)/3*4 + 1 );
for( ; len >= 3 ; len -= 3, data += 3 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077];
*p++ = bintoasc[data[2]&077];
}
if( len == 2 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[((data[1]<<2)&074)];
}
else if( len == 1 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(data[0] <<4)&060];
}
*p = 0;
return buffer;
}
/***********************************************
* For the pipemode command we can't use the armor filter for various
* reasons, so we use this new unarmor_pump stuff to remove the armor
*/
enum unarmor_state_e {
STA_init = 0,
STA_bypass,
STA_wait_newline,
STA_wait_dash,
STA_first_dash,
STA_compare_header,
STA_found_header_wait_newline,
STA_skip_header_lines,
STA_skip_header_lines_non_ws,
STA_read_data,
STA_wait_crc,
STA_read_crc,
STA_ready
};
struct unarmor_pump_s {
enum unarmor_state_e state;
byte val;
int checkcrc;
int pos; /* counts from 0..3 */
u32 crc;
u32 mycrc; /* the one store in the data */
};
UnarmorPump
unarmor_pump_new (void)
{
UnarmorPump x;
if( !is_initialized )
initialize();
x = xmalloc_clear (sizeof *x);
return x;
}
void
unarmor_pump_release (UnarmorPump x)
{
xfree (x);
}
/*
* Get the next character from the ascii armor taken from the IOBUF
* created earlier by unarmor_pump_new().
* Return: c = Character
* 256 = ignore this value
* -1 = End of current armor
* -2 = Premature EOF (not used)
* -3 = Invalid armor
*/
int
unarmor_pump (UnarmorPump x, int c)
{
int rval = 256; /* default is to ignore the return value */
switch (x->state) {
case STA_init:
{
byte tmp[2];
tmp[0] = c;
tmp[1] = 0;
if ( is_armored (tmp) )
x->state = c == '-'? STA_first_dash : STA_wait_newline;
else {
x->state = STA_bypass;
return c;
}
}
break;
case STA_bypass:
return c; /* return here to avoid crc calculation */
case STA_wait_newline:
if (c == '\n')
x->state = STA_wait_dash;
break;
case STA_wait_dash:
x->state = c == '-'? STA_first_dash : STA_wait_newline;
break;
case STA_first_dash: /* just need for initialization */
x->pos = 0;
x->state = STA_compare_header;
case STA_compare_header:
if ( "-----BEGIN PGP SIGNATURE-----"[++x->pos] == c ) {
if ( x->pos == 28 )
x->state = STA_found_header_wait_newline;
}
else
x->state = c == '\n'? STA_wait_dash : STA_wait_newline;
break;
case STA_found_header_wait_newline:
/* to make CR,LF issues easier we simply allow for white space
behind the 5 dashes */
if ( c == '\n' )
x->state = STA_skip_header_lines;
else if ( c != '\r' && c != ' ' && c != '\t' )
x->state = STA_wait_dash; /* garbage after the header line */
break;
case STA_skip_header_lines:
/* i.e. wait for one empty line */
if ( c == '\n' ) {
x->state = STA_read_data;
x->crc = CRCINIT;
x->val = 0;
x->pos = 0;
}
else if ( c != '\r' && c != ' ' && c != '\t' )
x->state = STA_skip_header_lines_non_ws;
break;
case STA_skip_header_lines_non_ws:
/* like above but we already encountered non white space */
if ( c == '\n' )
x->state = STA_skip_header_lines;
break;
case STA_read_data:
/* fixme: we don't check for the trailing dash lines but rely
* on the armor stop characters */
if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
break; /* skip all kind of white space */
if( c == '=' ) { /* pad character: stop */
if( x->pos == 1 ) /* in this case val has some value */
rval = x->val;
x->state = STA_wait_crc;
break;
}
{
int c2;
if( (c = asctobin[(c2=c)]) == 255 ) {
log_error(_("invalid radix64 character %02X skipped\n"), c2);
break;
}
}
switch(x->pos) {
case 0:
x->val = c << 2;
break;
case 1:
x->val |= (c>>4)&3;
rval = x->val;
x->val = (c<<4)&0xf0;
break;
case 2:
x->val |= (c>>2)&15;
rval = x->val;
x->val = (c<<6)&0xc0;
break;
case 3:
x->val |= c&0x3f;
rval = x->val;
break;
}
x->pos = (x->pos+1) % 4;
break;
case STA_wait_crc:
if( c == '\n' || c == ' ' || c == '\r' || c == '\t' || c == '=' )
break; /* skip ws and pad characters */
/* assume that we are at the next line */
x->state = STA_read_crc;
x->pos = 0;
x->mycrc = 0;
case STA_read_crc:
if( (c = asctobin[c]) == 255 ) {
rval = -1; /* ready */
if( x->crc != x->mycrc ) {
log_info (_("CRC error; %06lX - %06lX\n"),
(ulong)x->crc, (ulong)x->mycrc);
if ( invalid_crc() )
rval = -3;
}
x->state = STA_ready; /* not sure whether this is correct */
break;
}
switch(x->pos) {
case 0:
x->val = c << 2;
break;
case 1:
x->val |= (c>>4)&3;
x->mycrc |= x->val << 16;
x->val = (c<<4)&0xf0;
break;
case 2:
x->val |= (c>>2)&15;
x->mycrc |= x->val << 8;
x->val = (c<<6)&0xc0;
break;
case 3:
x->val |= c&0x3f;
x->mycrc |= x->val;
break;
}
x->pos = (x->pos+1) % 4;
break;
case STA_ready:
rval = -1;
break;
}
if ( !(rval & ~255) ) { /* compute the CRC */
x->crc = (x->crc << 8) ^ crc_table[((x->crc >> 16)&0xff) ^ rval];
x->crc &= 0x00ffffff;
}
return rval;
}
diff --git a/g10/build-packet.c b/g10/build-packet.c
index 489fadd85..c81c1ab2e 100644
--- a/g10/build-packet.c
+++ b/g10/build-packet.c
@@ -1,1696 +1,1696 @@
/* build-packet.c - assemble packets and write them
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2010, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/i18n.h"
#include "options.h"
-#include "host2net.h"
+#include "../common/host2net.h"
static int do_user_id( IOBUF out, int ctb, PKT_user_id *uid );
static int do_key (iobuf_t out, int ctb, PKT_public_key *pk);
static int do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc );
static int do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc );
static u32 calc_plaintext( PKT_plaintext *pt );
static int do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt );
static int do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed );
static int do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed );
static int do_compressed( IOBUF out, int ctb, PKT_compressed *cd );
static int do_signature( IOBUF out, int ctb, PKT_signature *sig );
static int do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops );
static int calc_header_length( u32 len, int new_ctb );
static int write_16(IOBUF inp, u16 a);
static int write_32(IOBUF inp, u32 a);
static int write_header( IOBUF out, int ctb, u32 len );
static int write_sign_packet_header( IOBUF out, int ctb, u32 len );
static int write_header2( IOBUF out, int ctb, u32 len, int hdrlen );
static int write_new_header( IOBUF out, int ctb, u32 len, int hdrlen );
/* Returns 1 if CTB is a new format ctb and 0 if CTB is an old format
ctb. */
static int
ctb_new_format_p (int ctb)
{
/* Bit 7 must always be set. */
log_assert ((ctb & (1 << 7)));
/* Bit 6 indicates whether the packet is a new format packet. */
return (ctb & (1 << 6));
}
/* Extract the packet type from a CTB. */
static int
ctb_pkttype (int ctb)
{
if (ctb_new_format_p (ctb))
/* Bits 0 through 5 are the packet type. */
return (ctb & ((1 << 6) - 1));
else
/* Bits 2 through 5 are the packet type. */
return (ctb & ((1 << 6) - 1)) >> 2;
}
/****************
* Build a packet and write it to INP
* Returns: 0 := okay
* >0 := error
* Note: Caller must free the packet
*/
int
build_packet( IOBUF out, PACKET *pkt )
{
int new_ctb=0, rc=0, ctb;
int pkttype;
if( DBG_PACKET )
log_debug("build_packet() type=%d\n", pkt->pkttype );
log_assert( pkt->pkt.generic );
switch ((pkttype = pkt->pkttype))
{
case PKT_PUBLIC_KEY:
if (pkt->pkt.public_key->seckey_info)
pkttype = PKT_SECRET_KEY;
break;
case PKT_PUBLIC_SUBKEY:
if (pkt->pkt.public_key->seckey_info)
pkttype = PKT_SECRET_SUBKEY;
break;
case PKT_PLAINTEXT: new_ctb = pkt->pkt.plaintext->new_ctb; break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC: new_ctb = pkt->pkt.encrypted->new_ctb; break;
case PKT_COMPRESSED:new_ctb = pkt->pkt.compressed->new_ctb; break;
case PKT_USER_ID:
if( pkt->pkt.user_id->attrib_data )
pkttype = PKT_ATTRIBUTE;
break;
default: break;
}
if( new_ctb || pkttype > 15 ) /* new format */
ctb = 0xc0 | (pkttype & 0x3f);
else
ctb = 0x80 | ((pkttype & 15)<<2);
switch( pkttype )
{
case PKT_ATTRIBUTE:
case PKT_USER_ID:
rc = do_user_id( out, ctb, pkt->pkt.user_id );
break;
case PKT_OLD_COMMENT:
case PKT_COMMENT:
/*
Ignore these. Theoretically, this will never be called as
we have no way to output comment packets any longer, but
just in case there is some code path that would end up
outputting a comment that was written before comments were
dropped (in the public key?) this is a no-op.
*/
break;
case PKT_PUBLIC_SUBKEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SECRET_KEY:
rc = do_key (out, ctb, pkt->pkt.public_key);
break;
case PKT_SYMKEY_ENC:
rc = do_symkey_enc( out, ctb, pkt->pkt.symkey_enc );
break;
case PKT_PUBKEY_ENC:
rc = do_pubkey_enc( out, ctb, pkt->pkt.pubkey_enc );
break;
case PKT_PLAINTEXT:
rc = do_plaintext( out, ctb, pkt->pkt.plaintext );
break;
case PKT_ENCRYPTED:
rc = do_encrypted( out, ctb, pkt->pkt.encrypted );
break;
case PKT_ENCRYPTED_MDC:
rc = do_encrypted_mdc( out, ctb, pkt->pkt.encrypted );
break;
case PKT_COMPRESSED:
rc = do_compressed( out, ctb, pkt->pkt.compressed );
break;
case PKT_SIGNATURE:
rc = do_signature( out, ctb, pkt->pkt.signature );
break;
case PKT_ONEPASS_SIG:
rc = do_onepass_sig( out, ctb, pkt->pkt.onepass_sig );
break;
case PKT_RING_TRUST:
break; /* ignore it (keyring.c does write it directly)*/
case PKT_MDC: /* we write it directly, so we should never see it here. */
default:
log_bug("invalid packet type in build_packet()\n");
break;
}
return rc;
}
/*
* Write the mpi A to OUT.
*/
gpg_error_t
gpg_mpi_write (iobuf_t out, gcry_mpi_t a)
{
int rc;
if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const unsigned char *p;
unsigned char lenhdr[2];
/* gcry_log_debugmpi ("a", a); */
p = gcry_mpi_get_opaque (a, &nbits);
if (p)
{
/* Strip leading zero bits. */
for (; nbits >= 8 && !*p; p++, nbits -= 8)
;
if (nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
}
/* gcry_log_debug (" [%u bit]\n", nbits); */
/* gcry_log_debughex (" ", p, (nbits+7)/8); */
lenhdr[0] = nbits >> 8;
lenhdr[1] = nbits;
rc = iobuf_write (out, lenhdr, 2);
if (!rc && p)
rc = iobuf_write (out, p, (nbits+7)/8);
}
else
{
char buffer[(MAX_EXTERN_MPI_BITS+7)/8+2]; /* 2 is for the mpi length. */
size_t nbytes;
nbytes = DIM(buffer);
rc = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a );
if( !rc )
rc = iobuf_write( out, buffer, nbytes );
else if (gpg_err_code(rc) == GPG_ERR_TOO_SHORT )
{
log_info ("mpi too large (%u bits)\n", gcry_mpi_get_nbits (a));
/* The buffer was too small. We better tell the user about the MPI. */
rc = gpg_error (GPG_ERR_TOO_LARGE);
}
}
return rc;
}
/*
* Write an opaque MPI to the output stream without length info.
*/
gpg_error_t
gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a)
{
int rc;
if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const void *p;
p = gcry_mpi_get_opaque (a, &nbits);
rc = p ? iobuf_write (out, p, (nbits+7)/8) : 0;
}
else
rc = gpg_error (GPG_ERR_BAD_MPI);
return rc;
}
/* Calculate the length of a packet described by PKT. */
u32
calc_packet_length( PACKET *pkt )
{
u32 n=0;
int new_ctb = 0;
log_assert (pkt->pkt.generic);
switch( pkt->pkttype ) {
case PKT_PLAINTEXT:
n = calc_plaintext( pkt->pkt.plaintext );
new_ctb = pkt->pkt.plaintext->new_ctb;
break;
case PKT_ATTRIBUTE:
case PKT_USER_ID:
case PKT_COMMENT:
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_SYMKEY_ENC:
case PKT_PUBKEY_ENC:
case PKT_ENCRYPTED:
case PKT_SIGNATURE:
case PKT_ONEPASS_SIG:
case PKT_RING_TRUST:
case PKT_COMPRESSED:
default:
log_bug("invalid packet type in calc_packet_length()");
break;
}
n += calc_header_length(n, new_ctb);
return n;
}
static gpg_error_t
write_fake_data (IOBUF out, gcry_mpi_t a)
{
unsigned int n;
void *p;
if (!a)
return 0;
if (!gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0; /* e.g. due to generating a key with wrong usage. */
p = gcry_mpi_get_opaque ( a, &n);
if (!p)
return 0; /* For example due to a read error in
parse-packet.c:read_rest. */
return iobuf_write (out, p, (n+7)/8 );
}
/* Serialize the user id (RFC 4880, Section 5.11) or the user
attribute UID (Section 5.12) and write it to OUT.
CTB is the serialization's CTB. It specifies the header format and
the packet's type. The header length must not be set. */
static int
do_user_id( IOBUF out, int ctb, PKT_user_id *uid )
{
int rc;
log_assert (ctb_pkttype (ctb) == PKT_USER_ID
|| ctb_pkttype (ctb) == PKT_ATTRIBUTE);
if (uid->attrib_data)
{
write_header(out, ctb, uid->attrib_len);
rc = iobuf_write( out, uid->attrib_data, uid->attrib_len );
}
else
{
write_header2( out, ctb, uid->len, 0 );
rc = iobuf_write( out, uid->name, uid->len );
}
return rc;
}
/* Serialize the key (RFC 4880, Section 5.5) described by PK and write
it to OUT.
This function serializes both primary keys and subkeys with or
without a secret part.
CTB is the serialization's CTB. It specifies the header format and
the packet's type. The header length must not be set.
PK->VERSION specifies the serialization format. A value of 0 means
to use the default version. Currently, only version 4 packets are
supported.
*/
static int
do_key (iobuf_t out, int ctb, PKT_public_key *pk)
{
gpg_error_t err = 0;
/* The length of the body is stored in the packet's header, which
occurs before the body. Unfortunately, we don't know the length
of the packet's body until we've written all of the data! To
work around this, we first write the data into this temporary
buffer, then generate the header, and finally copy the contents
of this buffer to OUT. */
iobuf_t a = iobuf_temp();
int i, nskey, npkey;
log_assert (pk->version == 0 || pk->version == 4);
log_assert (ctb_pkttype (ctb) == PKT_PUBLIC_KEY
|| ctb_pkttype (ctb) == PKT_PUBLIC_SUBKEY
|| ctb_pkttype (ctb) == PKT_SECRET_KEY
|| ctb_pkttype (ctb) == PKT_SECRET_SUBKEY);
/* Write the version number - if none is specified, use 4 */
if ( !pk->version )
iobuf_put ( a, 4 );
else
iobuf_put ( a, pk->version );
write_32 (a, pk->timestamp );
iobuf_put (a, pk->pubkey_algo );
/* Get number of secret and public parameters. They are held in one
array: the public ones followed by the secret ones. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
npkey = pubkey_get_npkey (pk->pubkey_algo);
/* If we don't have any public parameters - which is for example the
case if we don't know the algorithm used - the parameters are
stored as one blob in a faked (opaque) MPI. */
if (!npkey)
{
write_fake_data (a, pk->pkey[0]);
goto leave;
}
log_assert (npkey < nskey);
for (i=0; i < npkey; i++ )
{
if ( (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && (i == 0))
|| (pk->pubkey_algo == PUBKEY_ALGO_EDDSA && (i == 0))
|| (pk->pubkey_algo == PUBKEY_ALGO_ECDH && (i == 0 || i == 2)))
err = gpg_mpi_write_nohdr (a, pk->pkey[i]);
else
err = gpg_mpi_write (a, pk->pkey[i]);
if (err)
goto leave;
}
if (pk->seckey_info)
{
/* This is a secret key packet. */
struct seckey_info *ski = pk->seckey_info;
/* Build the header for protected (encrypted) secret parameters. */
if (ski->is_protected)
{
/* OpenPGP protection according to rfc2440. */
iobuf_put (a, ski->sha1chk? 0xfe : 0xff);
iobuf_put (a, ski->algo);
if (ski->s2k.mode >= 1000)
{
/* These modes are not possible in OpenPGP, we use them
to implement our extensions, 101 can be viewed as a
private/experimental extension (this is not specified
in rfc2440 but the same scheme is used for all other
algorithm identifiers). */
iobuf_put (a, 101);
iobuf_put (a, ski->s2k.hash_algo);
iobuf_write (a, "GNU", 3 );
iobuf_put (a, ski->s2k.mode - 1000);
}
else
{
iobuf_put (a, ski->s2k.mode);
iobuf_put (a, ski->s2k.hash_algo);
}
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
iobuf_write (a, ski->s2k.salt, 8);
if (ski->s2k.mode == 3)
iobuf_put (a, ski->s2k.count);
/* For our special modes 1001, 1002 we do not need an IV. */
if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002)
iobuf_write (a, ski->iv, ski->ivlen);
}
else /* Not protected. */
iobuf_put (a, 0 );
if (ski->s2k.mode == 1001)
; /* GnuPG extension - don't write a secret key at all. */
else if (ski->s2k.mode == 1002)
{
/* GnuPG extension - divert to OpenPGP smartcard. */
/* Length of the serial number or 0 for no serial number. */
iobuf_put (a, ski->ivlen );
/* The serial number gets stored in the IV field. */
iobuf_write (a, ski->iv, ski->ivlen);
}
else if (ski->is_protected)
{
/* The secret key is protected - write it out as it is. */
byte *p;
unsigned int ndatabits;
log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE));
p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits);
if (p)
iobuf_write (a, p, (ndatabits+7)/8 );
}
else
{
/* Non-protected key. */
for ( ; i < nskey; i++ )
if ( (err = gpg_mpi_write (a, pk->pkey[i])))
goto leave;
write_16 (a, ski->csum );
}
}
leave:
if (!err)
{
/* Build the header of the packet - which we must do after
writing all the other stuff, so that we know the length of
the packet */
write_header2 (out, ctb, iobuf_get_temp_length(a), 0);
/* And finally write it out to the real stream. */
err = iobuf_write_temp (out, a);
}
iobuf_close (a); /* Close the temporary buffer */
return err;
}
/* Serialize the symmetric-key encrypted session key packet (RFC 4880,
5.3) described by ENC and write it to OUT.
CTB is the serialization's CTB. It specifies the header format and
the packet's type. The header length must not be set. */
static int
do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc )
{
int rc = 0;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_SYMKEY_ENC);
/* The only acceptable version. */
log_assert( enc->version == 4 );
/* RFC 4880, Section 3.7. */
switch( enc->s2k.mode )
{
/* Simple S2K. */
case 0:
/* Salted S2K. */
case 1:
/* Iterated and salted S2K. */
case 3:
/* Reasonable values. */
break;
default:
log_bug("do_symkey_enc: s2k=%d\n", enc->s2k.mode );
}
iobuf_put( a, enc->version );
iobuf_put( a, enc->cipher_algo );
iobuf_put( a, enc->s2k.mode );
iobuf_put( a, enc->s2k.hash_algo );
if( enc->s2k.mode == 1 || enc->s2k.mode == 3 ) {
iobuf_write(a, enc->s2k.salt, 8 );
if( enc->s2k.mode == 3 )
iobuf_put(a, enc->s2k.count);
}
if( enc->seskeylen )
iobuf_write(a, enc->seskey, enc->seskeylen );
write_header(out, ctb, iobuf_get_temp_length(a) );
rc = iobuf_write_temp( out, a );
iobuf_close(a);
return rc;
}
/* Serialize the public-key encrypted session key packet (RFC 4880,
5.1) described by ENC and write it to OUT.
CTB is the serialization's CTB. It specifies the header format and
the packet's type. The header length must not be set. */
static int
do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc )
{
int rc = 0;
int n, i;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_PUBKEY_ENC);
iobuf_put (a, 3); /* Version. */
if ( enc->throw_keyid )
{
write_32(a, 0 ); /* Don't tell Eve who can decrypt the message. */
write_32(a, 0 );
}
else
{
write_32(a, enc->keyid[0] );
write_32(a, enc->keyid[1] );
}
iobuf_put(a,enc->pubkey_algo );
n = pubkey_get_nenc( enc->pubkey_algo );
if ( !n )
write_fake_data( a, enc->data[0] );
for (i=0; i < n && !rc ; i++ )
{
if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1)
rc = gpg_mpi_write_nohdr (a, enc->data[i]);
else
rc = gpg_mpi_write (a, enc->data[i]);
}
if (!rc)
{
write_header (out, ctb, iobuf_get_temp_length(a) );
rc = iobuf_write_temp (out, a);
}
iobuf_close(a);
return rc;
}
/* Calculate the length of the serialized plaintext packet PT (RFC
4480, Section 5.9). */
static u32
calc_plaintext( PKT_plaintext *pt )
{
/* Truncate namelen to the maximum 255 characters. Note this means
that a function that calls build_packet with an illegal literal
packet will get it back legalized. */
if(pt->namelen>255)
pt->namelen=255;
return pt->len? (1 + 1 + pt->namelen + 4 + pt->len) : 0;
}
/* Serialize the plaintext packet (RFC 4880, 5.9) described by PT and
write it to OUT.
The body of the message is stored in PT->BUF. The amount of data
to write is PT->LEN. (PT->BUF should be configured to return EOF
after this much data has been read.) If PT->LEN is 0 and CTB
indicates that this is a new format packet, then partial block mode
is assumed to have been enabled on OUT. On success, partial block
mode is disabled.
If PT->BUF is NULL, the caller must write out the data. In
this case, if PT->LEN was 0, then partial body length mode was
enabled and the caller must disable it by calling
iobuf_set_partial_body_length_mode (out, 0). */
static int
do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt )
{
int rc = 0;
size_t nbytes;
log_assert (ctb_pkttype (ctb) == PKT_PLAINTEXT);
write_header(out, ctb, calc_plaintext( pt ) );
log_assert (pt->mode == 'b' || pt->mode == 't' || pt->mode == 'u'
|| pt->mode == 'm'
|| pt->mode == 'l' || pt->mode == '1');
iobuf_put(out, pt->mode );
iobuf_put(out, pt->namelen );
iobuf_write (out, pt->name, pt->namelen);
rc = write_32(out, pt->timestamp );
if (rc)
return rc;
if (pt->buf)
{
nbytes = iobuf_copy (out, pt->buf);
if(ctb_new_format_p (ctb) && !pt->len)
/* Turn off partial body length mode. */
iobuf_set_partial_body_length_mode (out, 0);
if( pt->len && nbytes != pt->len )
log_error("do_plaintext(): wrote %lu bytes but expected %lu bytes\n",
(ulong)nbytes, (ulong)pt->len );
}
return rc;
}
/* Serialize the symmetrically encrypted data packet (RFC 4880,
Section 5.7) described by ED and write it to OUT.
Note: this only writes the packets header! The call must then
follow up and write the initial random data and the body to OUT.
(If you use the encryption iobuf filter (cipher_filter), then this
is done automatically.) */
static int
do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed )
{
int rc = 0;
u32 n;
log_assert (! ed->mdc_method);
log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED);
n = ed->len ? (ed->len + ed->extralen) : 0;
write_header(out, ctb, n );
/* This is all. The caller has to write the real data */
return rc;
}
/* Serialize the symmetrically encrypted integrity protected data
packet (RFC 4880, Section 5.13) described by ED and write it to
OUT.
Note: this only writes the packet's header! The caller must then
follow up and write the initial random data, the body and the MDC
packet to OUT. (If you use the encryption iobuf filter
(cipher_filter), then this is done automatically.) */
static int
do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed )
{
int rc = 0;
u32 n;
log_assert (ed->mdc_method);
log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED_MDC);
/* Take version number and the following MDC packet in account. */
n = ed->len ? (ed->len + ed->extralen + 1 + 22) : 0;
write_header(out, ctb, n );
iobuf_put(out, 1 ); /* version */
/* This is all. The caller has to write the real data */
return rc;
}
/* Serialize the compressed packet (RFC 4880, Section 5.6) described
by CD and write it to OUT.
Note: this only writes the packet's header! The caller must then
follow up and write the body to OUT. */
static int
do_compressed( IOBUF out, int ctb, PKT_compressed *cd )
{
int rc = 0;
log_assert (ctb_pkttype (ctb) == PKT_COMPRESSED);
/* We must use the old convention and don't use blockmode for the
sake of PGP 2 compatibility. However if the new_ctb flag was
set, CTB is already formatted as new style and write_header2
does create a partial length encoding using new the new
style. */
write_header2(out, ctb, 0, 0);
iobuf_put(out, cd->algorithm );
/* This is all. The caller has to write the real data */
return rc;
}
/****************
* Delete all subpackets of type REQTYPE and return a bool whether a packet
* was deleted.
*/
int
delete_sig_subpkt (subpktarea_t *area, sigsubpkttype_t reqtype )
{
int buflen;
sigsubpkttype_t type;
byte *buffer, *bufstart;
size_t n;
size_t unused = 0;
int okay = 0;
if( !area )
return 0;
buflen = area->len;
buffer = area->data;
for(;;) {
if( !buflen ) {
okay = 1;
break;
}
bufstart = buffer;
n = *buffer++; buflen--;
if( n == 255 ) {
if( buflen < 4 )
break;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if( n >= 192 ) {
if( buflen < 2 )
break;
n = (( n - 192 ) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if( buflen < n )
break;
type = *buffer & 0x7f;
if( type == reqtype ) {
buffer++;
buflen--;
n--;
if( n > buflen )
break;
buffer += n; /* point to next subpkt */
buflen -= n;
memmove (bufstart, buffer, buflen); /* shift */
unused += buffer - bufstart;
buffer = bufstart;
}
else {
buffer += n; buflen -=n;
}
}
if (!okay)
log_error ("delete_subpkt: buffer shorter than subpacket\n");
log_assert (unused <= area->len);
area->len -= unused;
return !!unused;
}
/****************
* Create or update a signature subpacket for SIG of TYPE. This
* functions knows where to put the data (hashed or unhashed). The
* function may move data from the unhashed part to the hashed one.
* Note: All pointers into sig->[un]hashed (e.g. returned by
* parse_sig_subpkt) are not valid after a call to this function. The
* data to put into the subpaket should be in a buffer with a length
* of buflen.
*/
void
build_sig_subpkt (PKT_signature *sig, sigsubpkttype_t type,
const byte *buffer, size_t buflen )
{
byte *p;
int critical, hashed;
subpktarea_t *oldarea, *newarea;
size_t nlen, n, n0;
critical = (type & SIGSUBPKT_FLAG_CRITICAL);
type &= ~SIGSUBPKT_FLAG_CRITICAL;
/* Sanity check buffer sizes */
if(parse_one_sig_subpkt(buffer,buflen,type)<0)
BUG();
switch(type)
{
case SIGSUBPKT_NOTATION:
case SIGSUBPKT_POLICY:
case SIGSUBPKT_REV_KEY:
case SIGSUBPKT_SIGNATURE:
/* we do allow multiple subpackets */
break;
default:
/* we don't allow multiple subpackets */
delete_sig_subpkt(sig->hashed,type);
delete_sig_subpkt(sig->unhashed,type);
break;
}
/* Any special magic that needs to be done for this type so the
packet doesn't need to be reparsed? */
switch(type)
{
case SIGSUBPKT_NOTATION:
sig->flags.notation=1;
break;
case SIGSUBPKT_POLICY:
sig->flags.policy_url=1;
break;
case SIGSUBPKT_PREF_KS:
sig->flags.pref_ks=1;
break;
case SIGSUBPKT_EXPORTABLE:
if(buffer[0])
sig->flags.exportable=1;
else
sig->flags.exportable=0;
break;
case SIGSUBPKT_REVOCABLE:
if(buffer[0])
sig->flags.revocable=1;
else
sig->flags.revocable=0;
break;
case SIGSUBPKT_TRUST:
sig->trust_depth=buffer[0];
sig->trust_value=buffer[1];
break;
case SIGSUBPKT_REGEXP:
sig->trust_regexp=buffer;
break;
/* This should never happen since we don't currently allow
creating such a subpacket, but just in case... */
case SIGSUBPKT_SIG_EXPIRE:
if(buf32_to_u32(buffer)+sig->timestamp<=make_timestamp())
sig->flags.expired=1;
else
sig->flags.expired=0;
break;
default:
break;
}
if( (buflen+1) >= 8384 )
nlen = 5; /* write 5 byte length header */
else if( (buflen+1) >= 192 )
nlen = 2; /* write 2 byte length header */
else
nlen = 1; /* just a 1 byte length header */
switch( type )
{
/* The issuer being unhashed is a historical oddity. It
should work equally as well hashed. Of course, if even an
unhashed issuer is tampered with, it makes it awfully hard
to verify the sig... */
case SIGSUBPKT_ISSUER:
case SIGSUBPKT_SIGNATURE:
hashed = 0;
break;
default:
hashed = 1;
break;
}
if( critical )
type |= SIGSUBPKT_FLAG_CRITICAL;
oldarea = hashed? sig->hashed : sig->unhashed;
/* Calculate new size of the area and allocate */
n0 = oldarea? oldarea->len : 0;
n = n0 + nlen + 1 + buflen; /* length, type, buffer */
if (oldarea && n <= oldarea->size) { /* fits into the unused space */
newarea = oldarea;
/*log_debug ("updating area for type %d\n", type );*/
}
else if (oldarea) {
newarea = xrealloc (oldarea, sizeof (*newarea) + n - 1);
newarea->size = n;
/*log_debug ("reallocating area for type %d\n", type );*/
}
else {
newarea = xmalloc (sizeof (*newarea) + n - 1);
newarea->size = n;
/*log_debug ("allocating area for type %d\n", type );*/
}
newarea->len = n;
p = newarea->data + n0;
if (nlen == 5) {
*p++ = 255;
*p++ = (buflen+1) >> 24;
*p++ = (buflen+1) >> 16;
*p++ = (buflen+1) >> 8;
*p++ = (buflen+1);
*p++ = type;
memcpy (p, buffer, buflen);
}
else if (nlen == 2) {
*p++ = (buflen+1-192) / 256 + 192;
*p++ = (buflen+1-192) % 256;
*p++ = type;
memcpy (p, buffer, buflen);
}
else {
*p++ = buflen+1;
*p++ = type;
memcpy (p, buffer, buflen);
}
if (hashed)
sig->hashed = newarea;
else
sig->unhashed = newarea;
}
/*
* Put all the required stuff from SIG into subpackets of sig.
* PKSK is the signing key.
* Hmmm, should we delete those subpackets which are in a wrong area?
*/
void
build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk)
{
u32 u;
byte buf[1+MAX_FINGERPRINT_LEN];
size_t fprlen;
/* For v4 keys we need to write the ISSUER subpacket. We do not
* want that for a future v5 format. */
if (pksk->version < 5)
{
u = sig->keyid[0];
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
u = sig->keyid[1];
buf[4] = (u >> 24) & 0xff;
buf[5] = (u >> 16) & 0xff;
buf[6] = (u >> 8) & 0xff;
buf[7] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_ISSUER, buf, 8);
}
/* Write the new ISSUER_FPR subpacket. */
fingerprint_from_pk (pksk, buf+1, &fprlen);
if (fprlen == 20)
{
buf[0] = pksk->version;
build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, 21);
}
/* Write the timestamp. */
u = sig->timestamp;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt( sig, SIGSUBPKT_SIG_CREATED, buf, 4 );
if(sig->expiredate)
{
if(sig->expiredate>sig->timestamp)
u=sig->expiredate-sig->timestamp;
else
u=1; /* A 1-second expiration time is the shortest one
OpenPGP has */
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
/* Mark this CRITICAL, so if any implementation doesn't
understand sigs that can expire, it'll just disregard this
sig altogether. */
build_sig_subpkt( sig, SIGSUBPKT_SIG_EXPIRE | SIGSUBPKT_FLAG_CRITICAL,
buf, 4 );
}
}
void
build_attribute_subpkt(PKT_user_id *uid,byte type,
const void *buf,u32 buflen,
const void *header,u32 headerlen)
{
byte *attrib;
int idx;
if(1+headerlen+buflen>8383)
idx=5;
else if(1+headerlen+buflen>191)
idx=2;
else
idx=1;
/* realloc uid->attrib_data to the right size */
uid->attrib_data=xrealloc(uid->attrib_data,
uid->attrib_len+idx+1+headerlen+buflen);
attrib=&uid->attrib_data[uid->attrib_len];
if(idx==5)
{
attrib[0]=255;
attrib[1]=(1+headerlen+buflen) >> 24;
attrib[2]=(1+headerlen+buflen) >> 16;
attrib[3]=(1+headerlen+buflen) >> 8;
attrib[4]=1+headerlen+buflen;
}
else if(idx==2)
{
attrib[0]=(1+headerlen+buflen-192) / 256 + 192;
attrib[1]=(1+headerlen+buflen-192) % 256;
}
else
attrib[0]=1+headerlen+buflen; /* Good luck finding a JPEG this small! */
attrib[idx++]=type;
/* Tack on our data at the end */
if(headerlen>0)
memcpy(&attrib[idx],header,headerlen);
memcpy(&attrib[idx+headerlen],buf,buflen);
uid->attrib_len+=idx+headerlen+buflen;
}
/* Returns a human-readable string corresponding to the notation.
This ignores notation->value. The caller must free the result. */
static char *
notation_value_to_human_readable_string (struct notation *notation)
{
if(notation->bdat)
/* Binary data. */
{
size_t len = notation->blen;
int i;
char preview[20];
for (i = 0; i < len && i < sizeof (preview) - 1; i ++)
if (isprint (notation->bdat[i]))
preview[i] = notation->bdat[i];
else
preview[i] = '?';
preview[i] = 0;
return xasprintf (_("[ not human readable (%zu bytes: %s%s) ]"),
len, preview, i < len ? "..." : "");
}
else
/* The value is human-readable. */
return xstrdup (notation->value);
}
/* Turn the notation described by the string STRING into a notation.
STRING has the form:
- -name - Delete the notation.
- name@domain.name=value - Normal notation
- !name@domain.name=value - Notation with critical bit set.
The caller must free the result using free_notation(). */
struct notation *
string_to_notation(const char *string,int is_utf8)
{
const char *s;
int saw_at=0;
struct notation *notation;
notation=xmalloc_clear(sizeof(*notation));
if(*string=='-')
{
notation->flags.ignore=1;
string++;
}
if(*string=='!')
{
notation->flags.critical=1;
string++;
}
/* If and when the IETF assigns some official name tags, we'll have
to add them here. */
for( s=string ; *s != '='; s++ )
{
if( *s=='@')
saw_at++;
/* -notationname is legal without an = sign */
if(!*s && notation->flags.ignore)
break;
if( !*s || !isascii (*s) || (!isgraph(*s) && !isspace(*s)) )
{
log_error(_("a notation name must have only printable characters"
" or spaces, and end with an '='\n") );
goto fail;
}
}
notation->name=xmalloc((s-string)+1);
strncpy(notation->name,string,s-string);
notation->name[s-string]='\0';
if(!saw_at && !opt.expert)
{
log_error(_("a user notation name must contain the '@' character\n"));
goto fail;
}
if (saw_at > 1)
{
log_error(_("a notation name must not contain more than"
" one '@' character\n"));
goto fail;
}
if(*s)
{
const char *i=s+1;
int highbit=0;
/* we only support printable text - therefore we enforce the use
of only printable characters (an empty value is valid) */
for(s++; *s ; s++ )
{
if ( !isascii (*s) )
highbit=1;
else if (iscntrl(*s))
{
log_error(_("a notation value must not use any"
" control characters\n"));
goto fail;
}
}
if(!highbit || is_utf8)
notation->value=xstrdup(i);
else
notation->value=native_to_utf8(i);
}
return notation;
fail:
free_notation(notation);
return NULL;
}
/* Like string_to_notation, but store opaque data rather than human
readable data. */
struct notation *
blob_to_notation(const char *name, const char *data, size_t len)
{
const char *s;
int saw_at=0;
struct notation *notation;
notation=xmalloc_clear(sizeof(*notation));
if(*name=='-')
{
notation->flags.ignore=1;
name++;
}
if(*name=='!')
{
notation->flags.critical=1;
name++;
}
/* If and when the IETF assigns some official name tags, we'll have
to add them here. */
for( s=name ; *s; s++ )
{
if( *s=='@')
saw_at++;
/* -notationname is legal without an = sign */
if(!*s && notation->flags.ignore)
break;
if (*s == '=')
{
log_error(_("a notation name may not contain an '=' character\n"));
goto fail;
}
if (!isascii (*s) || (!isgraph(*s) && !isspace(*s)))
{
log_error(_("a notation name must have only printable characters"
" or spaces\n") );
goto fail;
}
}
notation->name=xstrdup (name);
if(!saw_at && !opt.expert)
{
log_error(_("a user notation name must contain the '@' character\n"));
goto fail;
}
if (saw_at > 1)
{
log_error(_("a notation name must not contain more than"
" one '@' character\n"));
goto fail;
}
notation->bdat = xmalloc (len);
memcpy (notation->bdat, data, len);
notation->blen = len;
notation->value = notation_value_to_human_readable_string (notation);
return notation;
fail:
free_notation(notation);
return NULL;
}
struct notation *
sig_to_notation(PKT_signature *sig)
{
const byte *p;
size_t len;
int seq = 0;
int crit;
notation_t list = NULL;
/* See RFC 4880, 5.2.3.16 for the format of notation data. In
short, a notation has:
- 4 bytes of flags
- 2 byte name length (n1)
- 2 byte value length (n2)
- n1 bytes of name data
- n2 bytes of value data
*/
while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION,&len,&seq,&crit)))
{
int n1,n2;
struct notation *n=NULL;
if(len<8)
{
log_info(_("WARNING: invalid notation data found\n"));
continue;
}
/* name length. */
n1=(p[4]<<8)|p[5];
/* value length. */
n2=(p[6]<<8)|p[7];
if(8+n1+n2!=len)
{
log_info(_("WARNING: invalid notation data found\n"));
continue;
}
n=xmalloc_clear(sizeof(*n));
n->name=xmalloc(n1+1);
memcpy(n->name,&p[8],n1);
n->name[n1]='\0';
if(p[0]&0x80)
/* The value is human-readable. */
{
n->value=xmalloc(n2+1);
memcpy(n->value,&p[8+n1],n2);
n->value[n2]='\0';
n->flags.human = 1;
}
else
/* Binary data. */
{
n->bdat=xmalloc(n2);
n->blen=n2;
memcpy(n->bdat,&p[8+n1],n2);
n->value = notation_value_to_human_readable_string (n);
}
n->flags.critical=crit;
n->next=list;
list=n;
}
return list;
}
/* Release the resources associated with the *list* of notations. To
release a single notation, make sure that notation->next is
NULL. */
void
free_notation(struct notation *notation)
{
while(notation)
{
struct notation *n=notation;
xfree(n->name);
xfree(n->value);
xfree(n->altvalue);
xfree(n->bdat);
notation=n->next;
xfree(n);
}
}
/* Serialize the signature packet (RFC 4880, Section 5.2) described by
SIG and write it to OUT. */
static int
do_signature( IOBUF out, int ctb, PKT_signature *sig )
{
int rc = 0;
int n, i;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_SIGNATURE);
if ( !sig->version || sig->version == 3)
{
iobuf_put( a, 3 );
/* Version 3 packets don't support subpackets. */
log_assert (! sig->hashed);
log_assert (! sig->unhashed);
}
else
iobuf_put( a, sig->version );
if ( sig->version < 4 )
iobuf_put (a, 5 ); /* Constant */
iobuf_put (a, sig->sig_class );
if ( sig->version < 4 )
{
write_32(a, sig->timestamp );
write_32(a, sig->keyid[0] );
write_32(a, sig->keyid[1] );
}
iobuf_put(a, sig->pubkey_algo );
iobuf_put(a, sig->digest_algo );
if ( sig->version >= 4 )
{
size_t nn;
/* Timestamp and keyid must have been packed into the subpackets
prior to the call of this function, because these subpackets
are hashed. */
nn = sig->hashed? sig->hashed->len : 0;
write_16(a, nn);
if (nn)
iobuf_write( a, sig->hashed->data, nn );
nn = sig->unhashed? sig->unhashed->len : 0;
write_16(a, nn);
if (nn)
iobuf_write( a, sig->unhashed->data, nn );
}
iobuf_put(a, sig->digest_start[0] );
iobuf_put(a, sig->digest_start[1] );
n = pubkey_get_nsig( sig->pubkey_algo );
if ( !n )
write_fake_data( a, sig->data[0] );
for (i=0; i < n && !rc ; i++ )
rc = gpg_mpi_write (a, sig->data[i] );
if (!rc)
{
if ( is_RSA(sig->pubkey_algo) && sig->version < 4 )
write_sign_packet_header(out, ctb, iobuf_get_temp_length(a) );
else
write_header(out, ctb, iobuf_get_temp_length(a) );
rc = iobuf_write_temp( out, a );
}
iobuf_close(a);
return rc;
}
/* Serialize the one-pass signature packet (RFC 4880, Section 5.4)
described by OPS and write it to OUT. */
static int
do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops )
{
log_assert (ctb_pkttype (ctb) == PKT_ONEPASS_SIG);
write_header(out, ctb, 4 + 8 + 1);
iobuf_put (out, 3); /* Version. */
iobuf_put(out, ops->sig_class );
iobuf_put(out, ops->digest_algo );
iobuf_put(out, ops->pubkey_algo );
write_32(out, ops->keyid[0] );
write_32(out, ops->keyid[1] );
iobuf_put(out, ops->last );
return 0;
}
/* Write a 16-bit quantity to OUT in big endian order. */
static int
write_16(IOBUF out, u16 a)
{
iobuf_put(out, a>>8);
if( iobuf_put(out,a) )
return -1;
return 0;
}
/* Write a 32-bit quantity to OUT in big endian order. */
static int
write_32(IOBUF out, u32 a)
{
iobuf_put(out, a>> 24);
iobuf_put(out, a>> 16);
iobuf_put(out, a>> 8);
return iobuf_put(out, a);
}
/****************
* calculate the length of a header.
*
* LEN is the length of the packet's body. NEW_CTB is whether we are
* using a new or old format packet.
*
* This function does not handle indeterminate lengths or partial body
* lengths. (If you pass LEN as 0, then this function assumes you
* really mean an empty body.)
*/
static int
calc_header_length( u32 len, int new_ctb )
{
if( new_ctb ) {
if( len < 192 )
return 2;
if( len < 8384 )
return 3;
else
return 6;
}
if( len < 256 )
return 2;
if( len < 65536 )
return 3;
return 5;
}
/****************
* Write the CTB and the packet length
*/
static int
write_header( IOBUF out, int ctb, u32 len )
{
return write_header2( out, ctb, len, 0 );
}
static int
write_sign_packet_header (IOBUF out, int ctb, u32 len)
{
(void)ctb;
/* Work around a bug in the pgp read function for signature packets,
which are not correctly coded and silently assume at some point 2
byte length headers.*/
iobuf_put (out, 0x89 );
iobuf_put (out, len >> 8 );
return iobuf_put (out, len) == -1 ? -1:0;
}
/****************
* Write a packet header to OUT.
*
* CTB is the ctb. It determines whether a new or old format packet
* header should be written. The length field is adjusted, but the
* CTB is otherwise written out as is.
*
* LEN is the length of the packet's body.
*
* If HDRLEN is set, then we don't necessarily use the most efficient
* encoding to store LEN, but the specified length. (If this is not
* possible, this is a bug.) In this case, LEN=0 means a 0 length
* packet. Note: setting HDRLEN is only supported for old format
* packets!
*
* If HDRLEN is not set, then the shortest encoding is used. In this
* case, LEN=0 means the body has an indeterminate length and a
* partial body length header (if a new format packet) or an
* indeterminate length header (if an old format packet) is written
* out. Further, if using partial body lengths, this enables partial
* body length mode on OUT.
*/
static int
write_header2( IOBUF out, int ctb, u32 len, int hdrlen )
{
if (ctb_new_format_p (ctb))
return write_new_header( out, ctb, len, hdrlen );
/* An old format packet. Refer to RFC 4880, Section 4.2.1 to
understand how lengths are encoded in this case. */
/* The length encoding is stored in the two least significant bits.
Make sure they are cleared. */
log_assert ((ctb & 3) == 0);
log_assert (hdrlen == 0 || hdrlen == 2 || hdrlen == 3 || hdrlen == 5);
if (hdrlen)
/* Header length is given. */
{
if( hdrlen == 2 && len < 256 )
/* 00 => 1 byte length. */
;
else if( hdrlen == 3 && len < 65536 )
/* 01 => 2 byte length. If len < 256, this is not the most
compact encoding, but it is a correct encoding. */
ctb |= 1;
else if (hdrlen == 5)
/* 10 => 4 byte length. If len < 65536, this is not the most
compact encoding, but it is a correct encoding. */
ctb |= 2;
else
log_bug ("Can't encode length=%d in a %d byte header!\n",
len, hdrlen);
}
else
{
if( !len )
/* 11 => Indeterminate length. */
ctb |= 3;
else if( len < 256 )
/* 00 => 1 byte length. */
;
else if( len < 65536 )
/* 01 => 2 byte length. */
ctb |= 1;
else
/* 10 => 4 byte length. */
ctb |= 2;
}
if( iobuf_put(out, ctb ) )
return -1;
if( len || hdrlen )
{
if( ctb & 2 )
{
if(iobuf_put(out, len >> 24 ))
return -1;
if(iobuf_put(out, len >> 16 ))
return -1;
}
if( ctb & 3 )
if(iobuf_put(out, len >> 8 ))
return -1;
if( iobuf_put(out, len ) )
return -1;
}
return 0;
}
/* Write a new format header to OUT.
CTB is the ctb.
LEN is the length of the packet's body. If LEN is 0, then enables
partial body length mode (i.e., the body is of an indeterminant
length) on OUT. Note: this function cannot be used to generate a
header for a zero length packet.
HDRLEN is the length of the packet's header. If HDRLEN is 0, the
shortest encoding is chosen based on the length of the packet's
body. Currently, values other than 0 are not supported.
Returns 0 on success. */
static int
write_new_header( IOBUF out, int ctb, u32 len, int hdrlen )
{
if( hdrlen )
log_bug("can't cope with hdrlen yet\n");
if( iobuf_put(out, ctb ) )
return -1;
if( !len ) {
iobuf_set_partial_body_length_mode(out, 512 );
}
else {
if( len < 192 ) {
if( iobuf_put(out, len ) )
return -1;
}
else if( len < 8384 ) {
len -= 192;
if( iobuf_put( out, (len / 256) + 192) )
return -1;
if( iobuf_put( out, (len % 256) ) )
return -1;
}
else {
if( iobuf_put( out, 0xff ) )
return -1;
if( iobuf_put( out, (len >> 24)&0xff ) )
return -1;
if( iobuf_put( out, (len >> 16)&0xff ) )
return -1;
if( iobuf_put( out, (len >> 8)&0xff ) )
return -1;
if( iobuf_put( out, len & 0xff ) )
return -1;
}
}
return 0;
}
diff --git a/g10/call-agent.c b/g10/call-agent.c
index 7c30bdbff..7d627bb36 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -1,2310 +1,2310 @@
/* call-agent.c - Divert GPG operations to the agent.
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
-#include "util.h"
-#include "membuf.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
#include "options.h"
-#include "i18n.h"
-#include "asshelp.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/sysutils.h"
#include "call-agent.h"
-#include "status.h"
+#include "../common/status.h"
#include "../common/shareddefs.h"
-#include "host2net.h"
+#include "../common/host2net.h"
#define CONTROL_D ('D' - 'A' + 1)
static assuan_context_t agent_ctx = NULL;
static int did_early_card_test;
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* If RC is not 0, write an appropriate status message. */
static void
status_sc_op_failure (int rc)
{
switch (gpg_err_code (rc))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
}
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpg_proxy_pinentry_notify (parm->ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
if (have_static_passphrase ())
{
const char *s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
{
char *pw;
char buf[32];
if (parm->keyinfo.keyid)
emit_status_need_passphrase (parm->keyinfo.keyid,
parm->keyinfo.mainkeyid,
parm->keyinfo.pubkey_algo);
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: "));
cpr_kill_prompt ();
if (*pw == CONTROL_D && !pw[1])
err = gpg_error (GPG_ERR_CANCELED);
else
err = assuan_send_data (parm->ctx, pw, strlen (pw));
xfree (pw);
}
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
" ", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl, int for_card)
{
int rc;
(void)ctrl; /* Not yet used. */
/* Fixme: We need a context for each thread or serialize the access
to the agent. */
if (agent_ctx)
rc = 0;
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc
&& !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
write_status_error ("set_pinentry_mode", rc);
}
}
}
}
if (!rc && for_card && !did_early_card_test)
{
/* Request the serial number of the card for an early test. */
struct agent_card_info_s info;
memset (&info, 0, sizeof info);
rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!rc)
rc = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (rc)
{
switch (gpg_err_code (rc))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
write_status_text (STATUS_CARDCTRL, "6");
break;
case GPG_ERR_OBJ_TERM_STATE:
write_status_text (STATUS_CARDCTRL, "7");
break;
default:
write_status_text (STATUS_CARDCTRL, "4");
log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
break;
}
}
if (!rc && is_status_enabled () && info.serialno)
{
char *buf;
buf = xasprintf ("3 %s", info.serialno);
write_status_text (STATUS_CARDCTRL, buf);
xfree (buf);
}
agent_release_card_info (&info);
if (!rc)
did_early_card_test = 1;
}
return rc;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 byte hexencoded string and put it into the provided
20 byte buffer FPR in binary format. */
static int
unhexify_fpr (const char *hexstr, unsigned char *fpr)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if (*s || (n != 40))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s; s += 2, n++)
fpr[n] = xtoi_2 (s);
return 1; /* okay */
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Release the card info structure INFO. */
void
agent_release_card_info (struct agent_card_info_s *info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->apptype); info->apptype = NULL;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0;
info->fpr1valid = info->fpr2valid = info->fpr3valid = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct agent_card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& xtoi_2 (parm->serialno+12) >= 2 );
}
else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptype);
parm->apptype = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
{
while (spacep (p))
p++;
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvretry[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
xfree (buf);
}
}
else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1valid = unhexify_fpr (line, parm->fpr1);
else if (no == 2)
parm->fpr2valid = unhexify_fpr (line, parm->fpr2);
else if (no == 3)
parm->fpr3valid = unhexify_fpr (line, parm->fpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
{
int no = atoi (line);
while (* line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1time = strtoul (line, NULL, 10);
else if (no == 2)
parm->fpr2time = strtoul (line, NULL, 10);
else if (no == 3)
parm->fpr3time = strtoul (line, NULL, 10);
}
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1);
else if (no == 2)
parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2);
else if (no == 3)
parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = PUBKEY_ALGO_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
return 0;
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
NULL, NULL);
}
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
return 0;
}
/* Call the scdaemon to learn about a smartcard */
int
agent_scd_learn (struct agent_card_info_s *info, int force)
{
int rc;
struct default_inq_parm_s parm;
struct agent_card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
/* Send the serialno command to initialize the connection. We don't
care about the data returned. If the card has already been
initialized, this is a very fast command. The main reason we
need to do this here is to handle a card removed case so that an
"l" command in --edit-card can be used to show ta newly inserted
card. We request the openpgp card because that is what we
expect. */
rc = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx,
force ? "LEARN --sendinfo --force" : "LEARN --sendinfo",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get the key attributes. */
if (!rc)
agent_scd_getattr ("KEY-ATTR", info);
if (info == &dummyinfo)
agent_release_card_info (info);
return rc;
}
/* Send an APDU to the current card. On success the status word is
stored at R_SW. With HEXAPDU being NULL only a RESET command is
send to scd. With HEXAPDU being the string "undefined" the command
"SERIALNO undefined" is send to scd. */
gpg_error_t
agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
{
gpg_error_t err;
/* Start the agent but not with the card flag so that we do not
autoselect the openpgp application. */
err = start_agent (NULL, 0);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
*r_sw = buf16_to_uint (data+datalen-2);
}
xfree (data);
}
}
return err;
}
int
agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s",
force?"--force ": "", hexgrip, serialno, keyno, timestamp);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
if (rc)
return rc;
return rc;
}
/* Call the agent to retrieve a data object. This function returns
the data in the same structure as used by the learn command. It is
allowed to update such a structure using this commmand. */
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return rc;
}
/* Send an setattr command to the SCdaemon. SERIALNO is not actually
used here but required by gpg 1.4's implementation of this code in
cardglue.c. */
int
agent_scd_setattr (const char *name,
const unsigned char *value, size_t valuelen,
const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *p;
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
(void)serialno;
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
*p++ = ' ';
for (; valuelen; value++, valuelen--)
{
if (p >= line + DIM(line)-5 )
return gpg_error (GPG_ERR_TOO_LARGE);
if (*value < ' ' || *value == '+' || *value == '%')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
*p = 0;
rc = start_agent (NULL, 1);
if (!rc)
{
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
}
status_sc_op_failure (rc);
return rc;
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END
command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
int rc;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
rc = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Send a WRITECERT command to the SCdaemon. */
int
agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return rc;
}
/* Handle a KEYDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_writekey_parms (void *opaque, const char *line)
{
int rc;
struct writekey_parm_s *parm = opaque;
if (has_leading_keyword (line, "KEYDATA"))
{
rc = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Send a WRITEKEY command to the SCdaemon. */
int
agent_scd_writekey (int keyno, const char *serialno,
const unsigned char *keydata, size_t keydatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writekey_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITEKEY --force OPENPGP.%d", keyno);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.keydata = keydata;
parms.keydatalen = keydatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writekey_parms, &parms, NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
write_status_text (STATUS_PROGRESS, line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
the value will be passed to SCDAEMON with --timestamp option so that
the key is created with this. Otherwise, timestamp was generated by
SCDEAMON. On success, creation time is stored back to
CREATETIME. */
int
agent_scd_genkey (int keyno, int force, u32 *createtime)
{
int rc;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
if (*createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
keyno);
dfltparm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
status_sc_op_failure (rc);
return rc;
}
/* Issue an SCD SERIALNO openpgp command and if SERIALNO is not NULL
ask the user to insert the requested card. */
gpg_error_t
select_openpgp (const char *serialno)
{
gpg_error_t err;
/* Send the serialno command to initialize the connection. Without
a given S/N we don't care about the data returned. If the card
has already been initialized, this is a very fast command. We
request the openpgp card because that is what we expect.
Note that an opt.limit_card_insert_tries of 1 means: No tries at
all whereas 0 means do not limit the number of tries. Due to the
sue of a pinentry prompt with a cancel option we use it here in a
boolean sense. */
if (!serialno || opt.limit_card_insert_tries == 1)
err = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
NULL, NULL, NULL, NULL, NULL, NULL);
else
{
char *this_sn = NULL;
char *desc;
int ask;
char *want_sn;
char *p;
want_sn = xtrystrdup (serialno);
if (!want_sn)
return gpg_error_from_syserror ();
p = strchr (want_sn, '/');
if (p)
*p = 0;
do
{
ask = 0;
err = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
NULL, NULL, NULL, NULL,
get_serialno_cb, &this_sn);
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
ask = 1;
else if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
ask = 2;
else if (err)
;
else if (this_sn)
{
if (strcmp (want_sn, this_sn))
ask = 2;
}
xfree (this_sn);
this_sn = NULL;
if (ask)
{
char *formatted = NULL;
char *ocodeset = i18n_switchto_utf8 ();
if (!strncmp (want_sn, "D27600012401", 12)
&& strlen (want_sn) == 32 )
formatted = xtryasprintf ("(%.4s) %.8s",
want_sn + 16, want_sn + 20);
err = 0;
desc = xtryasprintf
("%s:\n\n"
" \"%s\"",
ask == 1
? _("Please insert the card with serial number")
: _("Please remove the current card and "
"insert the one with serial number"),
formatted? formatted : want_sn);
if (!desc)
err = gpg_error_from_syserror ();
xfree (formatted);
i18n_switchback (ocodeset);
if (!err)
err = gpg_agent_get_confirmation (desc);
xfree (desc);
}
}
while (ask && !err);
xfree (want_sn);
}
return err;
}
/* Send a READCERT command to the SCdaemon. */
int
agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, DIM(line), "SCD READCERT %s", certidstr);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error (GPG_ERR_ENOMEM);
return 0;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
CHVNO 1: Change the PIN
2: For v1 cards: Same as 1.
For v2 cards: Reset the PIN using the Reset Code.
3: Change the admin PIN
101: Set a new PIN and reset the retry counter
102: For v1 cars: Same as 101.
For v2 cards: Set a new Reset Code.
SERIALNO is not used.
*/
int
agent_scd_change_pin (int chvno, const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
const char *reset = "";
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
if (chvno >= 100)
reset = "--reset";
chvno %= 100;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
number of the card - optionally followed by the fingerprint;
however the fingerprint is ignored here. */
int
agent_scd_checkpin (const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Dummy function, only used by the gpg 1.4 implementation. */
void
agent_clear_pin_cache (const char *sn)
{
(void)sn;
}
/* Note: All strings shall be UTF-8. On success the caller needs to
free the string stored at R_PASSPHRASE. On error NULL will be
stored at R_PASSPHRASE and an appropriate fpf error code
returned. */
gpg_error_t
agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int repeat,
int check,
char **r_passphrase)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *arg1 = NULL;
char *arg2 = NULL;
char *arg3 = NULL;
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_passphrase = NULL;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent understands the repeat option. */
if (assuan_transact (agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE repeat",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (cache_id && *cache_id)
if (!(arg1 = percent_plus_escape (cache_id)))
goto no_mem;
if (err_msg && *err_msg)
if (!(arg2 = percent_plus_escape (err_msg)))
goto no_mem;
if (prompt && *prompt)
if (!(arg3 = percent_plus_escape (prompt)))
goto no_mem;
if (desc_msg && *desc_msg)
if (!(arg4 = percent_plus_escape (desc_msg)))
goto no_mem;
snprintf (line, DIM(line),
"GET_PASSPHRASE --data --repeat=%d%s -- %s %s %s %s",
repeat,
check? " --check --qualitybar":"",
arg1? arg1:"X",
arg2? arg2:"X",
arg3? arg3:"X",
arg4? arg4:"X");
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
init_membuf_secure (&data, 64);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
rc = gpg_error_from_syserror ();
}
return rc;
no_mem:
rc = gpg_error_from_syserror ();
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
return rc;
}
gpg_error_t
agent_clear_passphrase (const char *cache_id)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
if (!cache_id || !*cache_id)
return 0;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
return assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpg_agent_get_confirmation (const char *desc)
{
int rc;
char *tmp;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
tmp = percent_plus_escape (desc);
if (!tmp)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp);
xfree (tmp);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return rc;
}
/* Return the S2K iteration count as computed by gpg-agent. */
gpg_error_t
agent_get_s2k_count (unsigned long *r_count)
{
gpg_error_t err;
membuf_t data;
char *buf;
*r_count = 0;
err = start_agent (NULL, 0);
if (err)
return err;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
*r_count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
return err;
}
/* Ask the agent whether a secret key for the given public key is
available. Returns 0 if available. */
gpg_error_t
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *hexgrip;
err = start_agent (ctrl, 0);
if (err)
return err;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
return err;
snprintf (line, sizeof line, "HAVEKEY %s", hexgrip);
xfree (hexgrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *p;
kbnode_t kbctx, node;
int nkeys;
unsigned char grip[20];
err = start_agent (ctrl, 0);
if (err)
return err;
err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
found in KEYBLOCK. */
p = stpcpy (line, "HAVEKEY");
for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2))
{
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
break; /* Seckey available or unexpected error - ready. */
p = stpcpy (line, "HAVEKEY");
nkeys = 0;
}
err = keygrip_from_pk (node->pkt->pkt.public_key, grip);
if (err)
return err;
*p++ = ' ';
bin2hex (grip, 20, p);
p += 40;
nkeys++;
}
if (!err && nkeys)
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
struct keyinfo_data_parm_s
{
char *serialno;
int cleartext;
};
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
struct keyinfo_data_parm_s *data = opaque;
int is_smartcard;
char *s;
if ((s = has_leading_keyword (line, "KEYINFO")) && data)
{
/* Parse the arguments:
* 0 1 2 3 4 5
* <keygrip> <type> <serialno> <idstr> <cached> <protection>
*/
char *fields[6];
if (split_fields (s, fields, DIM (fields)) == 6)
{
is_smartcard = (fields[1][0] == 'T');
if (is_smartcard && !data->serialno && strcmp (fields[2], "-"))
data->serialno = xtrystrdup (fields[2]);
/* 'P' for protected, 'C' for clear */
data->cleartext = (fields[5][0] == 'C');
}
}
return 0;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO.
if r_cleartext is not NULL, the referenced int will be set to 1 if
the agent's copy of the key is stored in the clear, or 0 otherwise
*/
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct keyinfo_data_parm_s keyinfo;
memset (&keyinfo, 0,sizeof keyinfo);
*r_serialno = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
if (!err && keyinfo.serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (keyinfo.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (keyinfo.serialno);
else
{
*r_serialno = keyinfo.serialno;
if (r_cleartext)
*r_cleartext = keyinfo.cleartext;
}
return err;
}
/* Status callback for agent_import_key, agent_export_key and
agent_genkey. */
static gpg_error_t
cache_nonce_status_cb (void *opaque, const char *line)
{
struct cache_nonce_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "CACHE_NONCE")))
{
if (parm->cache_nonce_addr)
{
xfree (*parm->cache_nonce_addr);
*parm->cache_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PASSWD_NONCE")))
{
if (parm->passwd_nonce_addr)
{
xfree (*parm->passwd_nonce_addr);
*parm->passwd_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (opt.enable_progress_filter)
write_status_text (STATUS_PROGRESS, s);
}
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYPARAM"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->keyparms, strlen (parm->keyparms));
}
else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase)
{
err = assuan_send_data (parm->dflt->ctx,
parm->passphrase, strlen (parm->passphrase));
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to generate a new key. KEYPARMS is the usual
S-expression giving the parameters of the key. gpg-agent passes it
gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not
to protect the generated key. If NO_PROTECTION is not set and
PASSPHRASE is not NULL the agent is requested to protect the key
with that passphrase instead of asking for one. */
gpg_error_t
agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, gcry_sexp_t *r_pubkey)
{
gpg_error_t err;
struct genkey_parm_s gk_parm;
struct cache_nonce_parm_s cn_parm;
struct default_inq_parm_s dfltparm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (passwd_nonce_addr && *passwd_nonce_addr)
; /* A RESET would flush the passwd nonce cache. */
else
{
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf (&data, 1024);
gk_parm.dflt = &dfltparm;
gk_parm.keyparms = keyparms;
gk_parm.passphrase = passphrase;
snprintf (line, sizeof line, "GENKEY%s%s%s%s%s",
no_protection? " --no-protection" :
passphrase ? " --inq-passwd" :
/* */ "",
passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_pubkey, NULL, buf, len);
xfree (buf);
}
return err;
}
/* Call the agent to read the public key part for a given keygrip. If
FROMCARD is true, the key is directly read from the current
smartcard. In this case HEXKEYGRIP should be the keyID
(e.g. OPENPGP.3). */
gpg_error_t
agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"",
hexkeygrip);
init_membuf (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. DESC is a description of the key to be
displayed if the agent needs to ask for the PIN. DIGEST and
DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
used to compute the digest. If CACHE_NONCE is used the agent is
advised to first try a passphrase associated with that nonce. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen, int digestalgo,
gcry_sexp_t *r_sigval)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_sigval = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
bin2hex (digest, digestlen, line + strlen (line));
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, sizeof line, "PKSIGN%s%s",
cache_nonce? " -- ":"",
cache_nonce? cache_nonce:"");
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
unsigned char *buf;
size_t len;
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_sigval, NULL, buf, len);
xfree (buf);
}
}
return err;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END. */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->dflt->ctx,
parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Check whether there is any padding info from the agent. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Call the agent to do a decrypt operation using the key identified
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
success the decoded value is stored verbatim at R_BUF and its
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
and PUBKEY_ALGO are used to construct additional promots or status
messages. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t n, len;
char *p, *buf, *endp;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
if (!keygrip || strlen(keygrip) != 40
|| !s_ciphertext || !r_buf || !r_buflen || !r_padding)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
*r_padding = -1;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, sizeof line, "SETKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf_secure (&data, 1024);
{
struct cipher_parm_s parm;
parm.dflt = &dfltparm;
parm.ctx = agent_ctx;
err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
if (err)
return err;
err = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &parm,
padding_info_cb, r_padding);
xfree (parm.ciphertext);
}
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
log_assert (len); /* (we forced Nul termination.) */
if (*buf != '(')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
len -= 10; /* Count only the data of the second part. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
endp++;
if (endp-p+n > len)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
}
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be used for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_kek = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
const void *key, size_t keylen, int unattended, int force)
{
gpg_error_t err;
struct import_key_parm_s parm;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
parm.dflt = &dfltparm;
parm.key = key;
parm.keylen = keylen;
snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s",
unattended? " --unattended":"",
force? " --force":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
inq_import_key_parms, &parm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Receive a secret key from the agent. HEXKEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). if OPENPGP_PROTECTED
is not zero, ensure that the key material is returned in RFC
4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is
not NULL the agent is advised to first try a passphrase associated
with that nonce. On success the key is stored as a canonical
S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int openpgp_protected, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_result = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s",
openpgp_protected ? "--openpgp ":"",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
init_membuf_secure (&data, 1024);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC
is not NULL, display DESC instead of the default description
message. If FORCE is true the agent is advised not to ask for
confirmation. */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int force)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "DELETE_KEY%s %s",
force? " --force":"", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return err;
}
/* Ask the agent to change the passphrase of the key identified by
* HEXKEYGRIP. If DESC is not NULL, display DESC instead of the
* default description message. If CACHE_NONCE_ADDR is not NULL the
* agent is advised to first try a passphrase associated with that
* nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use
* the passphrase associated with that nonce for the new passphrase.
* If VERIFY is true the passphrase is only verified. */
gpg_error_t
agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
if (verify)
snprintf (line, DIM(line), "PASSWD %s%s --verify %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
else
snprintf (line, DIM(line), "PASSWD %s%s %s%s %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
hexkeygrip);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = passwd_nonce_addr;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Return the version reported by gpg-agent. */
gpg_error_t
agent_get_version (ctrl_t ctrl, char **r_version)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
return err;
err = get_assuan_server_version (agent_ctx, 0, r_version);
return err;
}
diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c
index 2f2ba982e..a33cdc7d7 100644
--- a/g10/call-dirmngr.c
+++ b/g10/call-dirmngr.c
@@ -1,1372 +1,1372 @@
/* call-dirmngr.c - GPG operations to the Dirmngr.
* Copyright (C) 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
-#include "util.h"
-#include "membuf.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
#include "options.h"
-#include "i18n.h"
-#include "asshelp.h"
-#include "keyserver.h"
-#include "status.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/keyserver.h"
+#include "../common/status.h"
#include "call-dirmngr.h"
/* Parameter structure used to gather status info. */
struct ks_status_parm_s
{
const char *keyword; /* Look for this keyword or NULL for "SOURCE". */
char *source;
};
/* Parameter structure used with the KS_SEARCH command. */
struct ks_search_parm_s
{
gpg_error_t lasterr; /* Last error code. */
membuf_t saveddata; /* Buffer to build complete lines. */
char *helpbuf; /* NULL or malloced buffer. */
size_t helpbufsize; /* Allocated size of HELPBUF. */
gpg_error_t (*data_cb)(void*, int, char*); /* Callback. */
void *data_cb_value; /* First argument for DATA_CB. */
struct ks_status_parm_s *stparm; /* Link to the status parameter. */
};
/* Parameter structure used with the KS_GET command. */
struct ks_get_parm_s
{
estream_t memfp;
};
/* Parameter structure used with the KS_PUT command. */
struct ks_put_parm_s
{
assuan_context_t ctx;
kbnode_t keyblock; /* The optional keyblock. */
const void *data; /* The key in OpenPGP binary format. */
size_t datalen; /* The length of DATA. */
};
/* Parameter structure used with the DNS_CERT command. */
struct dns_cert_parm_s
{
estream_t memfp;
unsigned char *fpr;
size_t fprlen;
char *url;
};
/* Data used to associate an session with dirmngr contexts. We can't
use a simple one to one mapping because we sometimes need two
connections to the dirmngr; for example while doing a listing and
being in a data callback we may want to retrieve a key. The local
dirmngr data takes care of this. At the end of the session the
function dirmngr_deinit_session_data is called by gpg.c to cleanup
these resources. Note that gpg.h defines a typedef dirmngr_local_t
for this structure. */
struct dirmngr_local_s
{
/* Link to other contexts which are used simultaneously. */
struct dirmngr_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* Flag set when the keyserver names have been send. */
int set_keyservers_done;
/* Flag set to true while an operation is running on CTX. */
int is_active;
};
/* Deinitialize all session data of dirmngr pertaining to CTRL. */
void
gpg_dirmngr_deinit_session_data (ctrl_t ctrl)
{
dirmngr_local_t dml;
while ((dml = ctrl->dirmngr_local))
{
ctrl->dirmngr_local = dml->next;
if (dml->is_active)
log_error ("oops: trying to cleanup an active dirmngr context\n");
else
assuan_release (dml->ctx);
xfree (dml);
}
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, 0, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
" ", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Try to connect to the Dirmngr via a socket or spawn it if possible.
Handle the server's initial greeting and set global options. */
static gpg_error_t
create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program,
opt.autostart, opt.verbose, DBG_IPC,
NULL /*gpg_status2*/, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no dirmngr running in this session\n"));
}
}
else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME)))
{
char *line;
/* Tell the dirmngr that we want to collect audit event. */
/* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */
/* NULL, NULL, NULL, NULL, NULL, NULL); */
if (opt.keyserver_options.http_proxy)
{
line = xtryasprintf ("OPTION http-proxy=%s",
opt.keyserver_options.http_proxy);
if (!line)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
xfree (line);
}
}
if (err)
;
else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL))
{
/* Tell the dirmngr that this possibly privacy invading
option is in use. If Dirmngr is running in Tor mode, it
will return an error. */
err = assuan_transact (ctx, "OPTION honor-keyserver-url-used",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_FORBIDDEN)
log_error (_("keyserver option \"honor-keyserver-url\""
" may not be used in Tor mode\n"));
else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* Old dirmngr versions do not support this option. */
}
}
if (err)
assuan_release (ctx);
else
{
/* audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); */
*r_ctx = ctx;
}
return err;
}
/* Get a context for accessing dirmngr. If no context is available a
new one is created and - if required - dirmngr started. On success
an assuan context is stored at R_CTX. This context may only be
released by means of close_context. Note that NULL is stored at
R_CTX on error. */
static gpg_error_t
open_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
dirmngr_local_t dml;
*r_ctx = NULL;
for (;;)
{
for (dml = ctrl->dirmngr_local; dml && dml->is_active; dml = dml->next)
;
if (dml)
{
/* Found an inactive local session - return that. */
log_assert (!dml->is_active);
/* But first do the per session init if not yet done. */
if (!dml->set_keyservers_done)
{
keyserver_spec_t ksi;
/* Set all configured keyservers. We clear existing
keyservers so that any keyserver configured in GPG
overrides keyservers possibly still configured in Dirmngr
for the session (Note that the keyserver list of a
session in Dirmngr survives a RESET. */
for (ksi = opt.keyserver; ksi; ksi = ksi->next)
{
char *line;
line = xtryasprintf
("KEYSERVER%s %s",
ksi == opt.keyserver? " --clear":"", ksi->uri);
if (!line)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (dml->ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
xfree (line);
}
if (err)
return err;
}
dml->set_keyservers_done = 1;
}
dml->is_active = 1;
*r_ctx = dml->ctx;
return 0;
}
dml = xtrycalloc (1, sizeof *dml);
if (!dml)
return gpg_error_from_syserror ();
err = create_context (ctrl, &dml->ctx);
if (err)
{
xfree (dml);
return err;
}
/* To be on the nPth thread safe site we need to add it to a
list; this is far easier than to have a lock for this
function. It should not happen anyway but the code is free
because we need it for the is_active check above. */
dml->next = ctrl->dirmngr_local;
ctrl->dirmngr_local = dml;
}
}
/* Close the assuan context CTX or return it to a pool of unused
contexts. If CTX is NULL, the function does nothing. */
static void
close_context (ctrl_t ctrl, assuan_context_t ctx)
{
dirmngr_local_t dml;
if (!ctx)
return;
for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
{
if (dml->ctx == ctx)
{
if (!dml->is_active)
log_fatal ("closing inactive dirmngr context %p\n", ctx);
dml->is_active = 0;
return;
}
}
log_fatal ("closing unknown dirmngr ctx %p\n", ctx);
}
/* Clear the set_keyservers_done flag on context CTX. */
static void
clear_context_flags (ctrl_t ctrl, assuan_context_t ctx)
{
dirmngr_local_t dml;
if (!ctx)
return;
for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
{
if (dml->ctx == ctx)
{
if (!dml->is_active)
log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx);
dml->set_keyservers_done = 0;
return;
}
}
log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx);
}
/* Status callback for ks_list, ks_get and ks_search. */
static gpg_error_t
ks_status_cb (void *opaque, const char *line)
{
struct ks_status_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *s, *s2;
const char *warn;
if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE")))
{
if (!parm->source)
{
parm->source = xtrystrdup (s);
if (!parm->source)
err = gpg_error_from_syserror ();
}
}
else if ((s = has_leading_keyword (line, "WARNING")))
{
if ((s2 = has_leading_keyword (s, "tor_not_running")))
warn = _("Tor is not running");
else if ((s2 = has_leading_keyword (s, "tor_config_problem")))
warn = _("Tor is not properly configured");
else
warn = NULL;
if (warn)
{
log_info (_("WARNING: %s\n"), warn);
if (s2)
{
while (*s2 && !spacep (s2))
s2++;
while (*s2 && spacep (s2))
s2++;
if (*s2)
print_further_info ("%s", s2);
}
}
}
return err;
}
/* Run the "KEYSERVER" command to return the name of the used
keyserver at R_KEYSERVER. */
gpg_error_t
gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
memset (&stparm, 0, sizeof stparm);
stparm.keyword = "KEYSERVER";
if (r_keyserver)
*r_keyserver = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
err = assuan_transact (ctx, "KEYSERVER", NULL, NULL,
NULL, NULL, ks_status_cb, &stparm);
if (err)
goto leave;
if (!stparm.source)
{
err = gpg_error (GPG_ERR_NO_KEYSERVER);
goto leave;
}
if (r_keyserver)
*r_keyserver = stparm.source;
else
xfree (stparm.source);
stparm.source = NULL;
leave:
xfree (stparm.source);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the KS_SEARCH command. */
static gpg_error_t
ks_search_data_cb (void *opaque, const void *data, size_t datalen)
{
gpg_error_t err = 0;
struct ks_search_parm_s *parm = opaque;
const char *line, *s;
size_t rawlen, linelen;
char fixedbuf[256];
if (parm->lasterr)
return 0;
if (parm->stparm->source)
{
err = parm->data_cb (parm->data_cb_value, 1, parm->stparm->source);
if (err)
{
parm->lasterr = err;
return err;
}
/* Clear it so that we won't get back here unless the server
accidentally sends a second source status line. Note that
will not see all accidentally sent source lines because it
depends on whether data lines have been send in between. */
xfree (parm->stparm->source);
parm->stparm->source = NULL;
}
if (!data)
return 0; /* Ignore END commands. */
put_membuf (&parm->saveddata, data, datalen);
again:
line = peek_membuf (&parm->saveddata, &rawlen);
if (!line)
{
parm->lasterr = gpg_error_from_syserror ();
return parm->lasterr; /* Tell the server about our problem. */
}
if ((s = memchr (line, '\n', rawlen)))
{
linelen = s - line; /* That is the length excluding the LF. */
if (linelen + 1 < sizeof fixedbuf)
{
/* We can use the static buffer. */
memcpy (fixedbuf, line, linelen);
fixedbuf[linelen] = 0;
if (linelen && fixedbuf[linelen-1] == '\r')
fixedbuf[linelen-1] = 0;
err = parm->data_cb (parm->data_cb_value, 0, fixedbuf);
}
else
{
if (linelen + 1 >= parm->helpbufsize)
{
xfree (parm->helpbuf);
parm->helpbufsize = linelen + 1 + 1024;
parm->helpbuf = xtrymalloc (parm->helpbufsize);
if (!parm->helpbuf)
{
parm->lasterr = gpg_error_from_syserror ();
return parm->lasterr;
}
}
memcpy (parm->helpbuf, line, linelen);
parm->helpbuf[linelen] = 0;
if (linelen && parm->helpbuf[linelen-1] == '\r')
parm->helpbuf[linelen-1] = 0;
err = parm->data_cb (parm->data_cb_value, 0, parm->helpbuf);
}
if (err)
parm->lasterr = err;
else
{
clear_membuf (&parm->saveddata, linelen+1);
goto again; /* There might be another complete line. */
}
}
return err;
}
/* Run the KS_SEARCH command using the search string SEARCHSTR. All
data lines are passed to the CB function. That function is called
with CB_VALUE as its first argument, a 0 as second argument, and
the decoded data line as third argument. The callback function may
modify the data line and it is guaranteed that this data line is a
complete line with a terminating 0 character but without the
linefeed. NULL is passed to the callback to indicate EOF. */
gpg_error_t
gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr,
gpg_error_t (*cb)(void*, int, char *), void *cb_value)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
struct ks_search_parm_s parm;
char line[ASSUAN_LINELENGTH];
err = open_context (ctrl, &ctx);
if (err)
return err;
{
char *escsearchstr = percent_plus_escape (searchstr);
if (!escsearchstr)
{
err = gpg_error_from_syserror ();
close_context (ctrl, ctx);
return err;
}
snprintf (line, sizeof line, "KS_SEARCH -- %s", escsearchstr);
xfree (escsearchstr);
}
memset (&stparm, 0, sizeof stparm);
memset (&parm, 0, sizeof parm);
init_membuf (&parm.saveddata, 1024);
parm.data_cb = cb;
parm.data_cb_value = cb_value;
parm.stparm = &stparm;
err = assuan_transact (ctx, line, ks_search_data_cb, &parm,
NULL, NULL, ks_status_cb, &stparm);
if (!err)
err = cb (cb_value, 0, NULL); /* Send EOF. */
xfree (get_membuf (&parm.saveddata, NULL));
xfree (parm.helpbuf);
xfree (stparm.source);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the KS_GET and KS_FETCH commands. */
static gpg_error_t
ks_get_data_cb (void *opaque, const void *data, size_t datalen)
{
gpg_error_t err = 0;
struct ks_get_parm_s *parm = opaque;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Run the KS_GET command using the patterns in the array PATTERN. On
success an estream object is returned to retrieve the keys. On
error an error code is returned and NULL stored at R_FP.
The pattern may only use search specification which a keyserver can
use to retrieve keys. Because we know the format of the pattern we
don't need to escape the patterns before sending them to the
server.
If QUICK is set the dirmngr is advised to use a shorter timeout.
If R_SOURCE is not NULL the source of the data is stored as a
malloced string there. If a source is not known NULL is stored.
If there are too many patterns the function returns an error. That
could be fixed by issuing several search commands or by
implementing a different interface. However with long keyids we
are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once. */
gpg_error_t
gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern,
keyserver_spec_t override_keyserver, int quick,
estream_t *r_fp, char **r_source)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
struct ks_get_parm_s parm;
char *line = NULL;
size_t linelen;
membuf_t mb;
int idx;
memset (&stparm, 0, sizeof stparm);
memset (&parm, 0, sizeof parm);
*r_fp = NULL;
if (r_source)
*r_source = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
/* If we have an override keyserver we first indicate that the next
user of the context needs to again setup the global keyservers and
them we send the override keyserver. */
if (override_keyserver)
{
clear_context_flags (ctrl, ctx);
line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
if (err)
goto leave;
xfree (line);
line = NULL;
}
/* Lump all patterns into one string. */
init_membuf (&mb, 1024);
put_membuf_str (&mb, quick? "KS_GET --quick --" : "KS_GET --");
for (idx=0; pattern[idx]; idx++)
{
put_membuf (&mb, " ", 1); /* Append Delimiter. */
put_membuf_str (&mb, pattern[idx]);
}
put_membuf (&mb, "", 1); /* Append Nul. */
line = get_membuf (&mb, &linelen);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (linelen + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_MANY);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
NULL, NULL, ks_status_cb, &stparm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_fp = parm.memfp;
parm.memfp = NULL;
if (r_source)
{
*r_source = stparm.source;
stparm.source = NULL;
}
leave:
es_fclose (parm.memfp);
xfree (stparm.source);
xfree (line);
close_context (ctrl, ctx);
return err;
}
/* Run the KS_FETCH and pass URL as argument. On success an estream
object is returned to retrieve the keys. On error an error code is
returned and NULL stored at R_FP.
The url is expected to point to a small set of keys; in many cases
only to one key. However, schemes like finger may return several
keys. Note that the configured keyservers are ignored by the
KS_FETCH command. */
gpg_error_t
gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_get_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
*r_fp = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = strconcat ("KS_FETCH -- ", url, NULL);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
NULL, NULL, NULL, NULL);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_fp = parm.memfp;
parm.memfp = NULL;
leave:
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
static void
record_output (estream_t output,
pkttype_t type,
const char *validity,
/* The public key length or -1. */
int pub_key_length,
/* The public key algo or -1. */
int pub_key_algo,
/* 2 ulongs or NULL. */
const u32 *keyid,
/* The creation / expiration date or 0. */
u32 creation_date,
u32 expiration_date,
const char *userid)
{
const char *type_str = NULL;
char *pub_key_length_str = NULL;
char *pub_key_algo_str = NULL;
char *keyid_str = NULL;
char *creation_date_str = NULL;
char *expiration_date_str = NULL;
char *userid_escaped = NULL;
switch (type)
{
case PKT_PUBLIC_KEY:
type_str = "pub";
break;
case PKT_PUBLIC_SUBKEY:
type_str = "sub";
break;
case PKT_USER_ID:
type_str = "uid";
break;
case PKT_SIGNATURE:
type_str = "sig";
break;
default:
log_assert (! "Unhandled type.");
}
if (pub_key_length > 0)
pub_key_length_str = xasprintf ("%d", pub_key_length);
if (pub_key_algo != -1)
pub_key_algo_str = xasprintf ("%d", pub_key_algo);
if (keyid)
keyid_str = xasprintf ("%08lX%08lX", (ulong) keyid[0], (ulong) keyid[1]);
if (creation_date)
creation_date_str = xstrdup (colon_strtime (creation_date));
if (expiration_date)
expiration_date_str = xstrdup (colon_strtime (expiration_date));
/* Quote ':', '%', and any 8-bit characters. */
if (userid)
{
int r;
int w = 0;
int len = strlen (userid);
/* A 100k character limit on the uid should be way more than
enough. */
if (len > 100 * 1024)
len = 100 * 1024;
/* The minimum amount of space that we need. */
userid_escaped = xmalloc (len * 3 + 1);
for (r = 0; r < len; r++)
{
if (userid[r] == ':' || userid[r]== '%' || (userid[r] & 0x80))
{
sprintf (&userid_escaped[w], "%%%02X", (byte) userid[r]);
w += 3;
}
else
userid_escaped[w ++] = userid[r];
}
userid_escaped[w] = '\0';
}
es_fprintf (output, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s\n",
type_str,
validity ?: "",
pub_key_length_str ?: "",
pub_key_algo_str ?: "",
keyid_str ?: "",
creation_date_str ?: "",
expiration_date_str ?: "",
"" /* Certificate S/N */,
"" /* Ownertrust. */,
userid_escaped ?: "",
"" /* Signature class. */,
"" /* Key capabilities. */,
"" /* Issuer certificate fingerprint. */,
"" /* Flag field. */,
"" /* S/N of a token. */,
"" /* Hash algo. */,
"" /* Curve name. */);
xfree (userid_escaped);
xfree (expiration_date_str);
xfree (creation_date_str);
xfree (keyid_str);
xfree (pub_key_algo_str);
xfree (pub_key_length_str);
}
/* Handle the KS_PUT inquiries. */
static gpg_error_t
ks_put_inq_cb (void *opaque, const char *line)
{
struct ks_put_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "KEYBLOCK"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else if (has_leading_keyword (line, "KEYBLOCK_INFO"))
{
kbnode_t node;
estream_t fp;
/* Parse the keyblock and send info lines back to the server. */
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
err = gpg_error_from_syserror ();
/* Note: the output format for the INFO block follows the colon
format as described in doc/DETAILS. We don't actually reuse
the functionality from g10/keylist.c to produce the output,
because we don't need all of it and some of it is quite
expensive to generate.
The fields are (the starred fields are the ones we need):
* Field 1 - Type of record
* Field 2 - Validity
* Field 3 - Key length
* Field 4 - Public key algorithm
* Field 5 - KeyID
* Field 6 - Creation date
* Field 7 - Expiration date
Field 8 - Certificate S/N, UID hash, trust signature info
Field 9 - Ownertrust
* Field 10 - User-ID
Field 11 - Signature class
Field 12 - Key capabilities
Field 13 - Issuer certificate fingerprint or other info
Field 14 - Flag field
Field 15 - S/N of a token
Field 16 - Hash algorithm
Field 17 - Curve name
*/
for (node = parm->keyblock; !err && node; node=node->next)
{
switch (node->pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
{
PKT_public_key *pk = node->pkt->pkt.public_key;
char validity[3];
int i;
i = 0;
if (pk->flags.revoked)
validity[i ++] = 'r';
if (pk->has_expired)
validity[i ++] = 'e';
validity[i] = '\0';
keyid_from_pk (pk, NULL);
record_output (fp, node->pkt->pkttype, validity,
nbits_from_pk (pk), pk->pubkey_algo,
pk->keyid, pk->timestamp, pk->expiredate,
NULL);
}
break;
case PKT_USER_ID:
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!uid->attrib_data)
{
char validity[3];
int i;
i = 0;
if (uid->is_revoked)
validity[i ++] = 'r';
if (uid->is_expired)
validity[i ++] = 'e';
validity[i] = '\0';
record_output (fp, node->pkt->pkttype, validity,
-1, -1, NULL,
uid->created, uid->expiredate,
uid->name);
}
}
break;
/* This bit is really for the benefit of people who
store their keys in LDAP servers. It makes it easy
to do queries for things like "all keys signed by
Isabella". */
case PKT_SIGNATURE:
{
PKT_signature *sig = node->pkt->pkt.signature;
if (IS_UID_SIG (sig))
record_output (fp, node->pkt->pkttype, NULL,
-1, -1, sig->keyid,
sig->timestamp, sig->expiredate, NULL);
}
break;
default:
continue;
}
/* Given that the last operation was an es_fprintf we should
get the correct ERRNO if ferror indicates an error. */
if (es_ferror (fp))
err = gpg_error_from_syserror ();
}
/* Without an error and if we have an keyblock at all, send the
data back. */
if (!err && parm->keyblock)
{
int rc;
char buffer[512];
size_t nread;
es_rewind (fp);
while (!(rc=es_read (fp, buffer, sizeof buffer, &nread)) && nread)
{
err = assuan_send_data (parm->ctx, buffer, nread);
if (err)
break;
}
if (!err && rc)
err = gpg_error_from_syserror ();
}
es_fclose (fp);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/* Send a key to the configured server. {DATA,DATLEN} contains the
key in OpenPGP binary transport format. If KEYBLOCK is not NULL it
has the internal representaion of that key; this is for example
used to convey meta data to LDAP keyservers. */
gpg_error_t
gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_put_parm_s parm;
memset (&parm, 0, sizeof parm);
/* We are going to parse the keyblock, thus we better make sure the
all information is readily available. */
if (keyblock)
merge_keys_and_selfsig (keyblock);
err = open_context (ctrl, &ctx);
if (err)
return err;
parm.ctx = ctx;
parm.keyblock = keyblock;
parm.data = data;
parm.datalen = datalen;
err = assuan_transact (ctx, "KS_PUT", NULL, NULL,
ks_put_inq_cb, &parm, NULL, NULL);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the DNS_CERT and WKD_GET commands. */
static gpg_error_t
dns_cert_data_cb (void *opaque, const void *data, size_t datalen)
{
struct dns_cert_parm_s *parm = opaque;
gpg_error_t err = 0;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (!parm->memfp)
return 0; /* Data is not required. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Status callback for the DNS_CERT command. */
static gpg_error_t
dns_cert_status_cb (void *opaque, const char *line)
{
struct dns_cert_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *s;
size_t nbytes;
if ((s = has_leading_keyword (line, "FPR")))
{
char *buf;
if (!(buf = xtrystrdup (s)))
err = gpg_error_from_syserror ();
else if (parm->fpr)
err = gpg_error (GPG_ERR_DUP_KEY);
else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes))
err = gpg_error_from_syserror ();
else if (nbytes < 20)
err = gpg_error (GPG_ERR_TOO_SHORT);
else
{
parm->fpr = xtrymalloc (nbytes);
if (!parm->fpr)
err = gpg_error_from_syserror ();
else
memcpy (parm->fpr, buf, (parm->fprlen = nbytes));
}
xfree (buf);
}
else if ((s = has_leading_keyword (line, "URL")) && *s)
{
if (parm->url)
err = gpg_error (GPG_ERR_DUP_KEY);
else if (!(parm->url = xtrystrdup (s)))
err = gpg_error_from_syserror ();
}
return err;
}
/* Ask the dirmngr for a DNS CERT record. Depending on the found
subtypes different return values are set:
- For a PGP subtype a new estream with that key will be returned at
R_KEY and the other return parameters are set to NULL/0.
- For an IPGP subtype the fingerprint is stored as a malloced block
at (R_FPR,R_FPRLEN). If an URL is available it is stored as a
malloced string at R_URL; NULL is stored if there is no URL.
If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first
CERT record found with a supported type; it is expected that only
one CERT record is used. If CERTTYPE is one of the supported
certtypes, only records with this certtype are considered and the
first one found is returned. All R_* args are optional.
If CERTTYPE is NULL the DANE method is used to fetch the key.
*/
gpg_error_t
gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype,
estream_t *r_key,
unsigned char **r_fpr, size_t *r_fprlen,
char **r_url)
{
gpg_error_t err;
assuan_context_t ctx;
struct dns_cert_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
if (r_key)
*r_key = NULL;
if (r_fpr)
*r_fpr = NULL;
if (r_fprlen)
*r_fprlen = 0;
if (r_url)
*r_url = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
NULL, NULL, dns_cert_status_cb, &parm);
if (err)
goto leave;
if (r_key)
{
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
}
if (r_fpr && parm.fpr)
{
*r_fpr = parm.fpr;
parm.fpr = NULL;
}
if (r_fprlen)
*r_fprlen = parm.fprlen;
if (r_url && parm.url)
{
*r_url = parm.url;
parm.url = NULL;
}
leave:
xfree (parm.fpr);
xfree (parm.url);
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
/* Ask the dirmngr for PKA info. On success the retrieved fingerprint
is returned in a malloced buffer at R_FPR and its length is stored
at R_FPRLEN. If an URL is available it is stored as a malloced
string at R_URL. On error all return values are set to NULL/0. */
gpg_error_t
gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
unsigned char **r_fpr, size_t *r_fprlen,
char **r_url)
{
gpg_error_t err;
assuan_context_t ctx;
struct dns_cert_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
if (r_fpr)
*r_fpr = NULL;
if (r_fprlen)
*r_fprlen = 0;
if (r_url)
*r_url = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = es_bsprintf ("DNS_CERT --pka -- %s", userid);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
NULL, NULL, dns_cert_status_cb, &parm);
if (err)
goto leave;
if (r_fpr && parm.fpr)
{
*r_fpr = parm.fpr;
parm.fpr = NULL;
}
if (r_fprlen)
*r_fprlen = parm.fprlen;
if (r_url && parm.url)
{
*r_url = parm.url;
parm.url = NULL;
}
leave:
xfree (parm.fpr);
xfree (parm.url);
xfree (line);
close_context (ctrl, ctx);
return err;
}
/* Ask the dirmngr to retrieve a key via the Web Key Directory
* protocol. If QUICK is set the dirmngr is advised to use a shorter
* timeout. On success a new estream with the key is stored at R_KEY.
*/
gpg_error_t
gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick, estream_t *r_key)
{
gpg_error_t err;
assuan_context_t ctx;
struct dns_cert_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
err = open_context (ctrl, &ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
NULL, NULL, NULL, &parm);
if (err)
goto leave;
if (r_key)
{
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
}
leave:
xfree (parm.fpr);
xfree (parm.url);
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
diff --git a/g10/card-util.c b/g10/card-util.c
index 969f6c919..9bc3e25b3 100644
--- a/g10/card-util.c
+++ b/g10/card-util.c
@@ -1,2134 +1,2134 @@
/* card-util.c - Utility functions for the OpenPGP card.
* Copyright (C) 2003-2005, 2009 Free Software Foundation, Inc.
* Copyright (C) 2003-2005, 2009 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#if GNUPG_MAJOR_VERSION != 1
# include "gpg.h"
#endif /*GNUPG_MAJOR_VERSION != 1*/
-#include "util.h"
-#include "i18n.h"
-#include "ttyio.h"
-#include "status.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
+#include "../common/status.h"
#include "options.h"
#include "main.h"
#include "keyserver-internal.h"
#if GNUPG_MAJOR_VERSION == 1
# include "cardglue.h"
#else /*GNUPG_MAJOR_VERSION!=1*/
# include "call-agent.h"
#endif /*GNUPG_MAJOR_VERSION!=1*/
#define CONTROL_D ('D' - 'A' + 1)
static void
write_sc_op_status (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
write_status (STATUS_SC_OP_SUCCESS);
break;
#if GNUPG_MAJOR_VERSION != 1
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
#endif /* GNUPG_MAJOR_VERSION != 1 */
}
}
/* Change the PIN of an OpenPGP card. This is an interactive
function. */
void
change_pin (int unblock_v2, int allow_admin)
{
struct agent_card_info_s info;
int rc;
rc = agent_scd_learn (&info, 0);
if (rc)
{
log_error (_("OpenPGP card not available: %s\n"),
gpg_strerror (rc));
return;
}
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
agent_clear_pin_cache (info.serialno);
if (opt.batch)
{
agent_release_card_info (&info);
log_error (_("can't do this in batch mode\n"));
return;
}
if (unblock_v2)
{
if (!info.is_v2)
log_error (_("This command is only available for version 2 cards\n"));
else if (!info.chvretry[1])
log_error (_("Reset Code not or not anymore available\n"));
else
{
rc = agent_scd_change_pin (2, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
}
else if (!allow_admin)
{
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else
for (;;)
{
char *answer;
tty_printf ("\n");
tty_printf ("1 - change PIN\n"
"2 - unblock PIN\n"
"3 - change Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
tty_printf ("\n");
answer = cpr_get("cardutil.change_pin.menu",_("Your selection? "));
cpr_kill_prompt();
if (strlen (answer) != 1)
continue;
if (*answer == '1')
{
/* Change PIN. */
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '2')
{
/* Unblock PIN. */
rc = agent_scd_change_pin (101, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN unblocked and new PIN set.\n");
}
else if (*answer == '3')
{
/* Change Admin PIN. */
rc = agent_scd_change_pin (3, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '4')
{
/* Set a new Reset Code. */
rc = agent_scd_change_pin (102, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error setting the Reset Code: %s\n",
gpg_strerror (rc));
else
tty_printf ("Reset Code set.\n");
}
else if (*answer == 'q' || *answer == 'Q')
{
break;
}
}
agent_release_card_info (&info);
}
static const char *
get_manufacturer (unsigned int no)
{
/* Note: Make sure that there is no colon or linefeed in the string. */
switch (no)
{
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x0009: return "Fidesmo";
case 0x000A: return "Dangerous Things";
case 0x002A: return "Magrathea";
case 0x1337: return "Warsaw Hackerspace";
case 0x2342: return "warpzone"; /* hackerspace Muenster. */
case 0xF517: return "FSIJ";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
0xFF00 to 0xFFFE are assigned for use with randomly created
serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
}
}
static void
print_sha1_fpr (estream_t fp, const unsigned char *fpr)
{
int i;
if (fpr)
{
for (i=0; i < 20 ; i+=2, fpr += 2 )
{
if (i == 10 )
tty_fprintf (fp, " ");
tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
}
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
static void
print_sha1_fpr_colon (estream_t fp, const unsigned char *fpr)
{
int i;
if (fpr)
{
for (i=0; i < 20 ; i++, fpr++)
es_fprintf (fp, "%02X", *fpr);
}
es_putc (':', fp);
}
static void
print_name (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
static void
print_isoname (estream_t fp, const char *text,
const char *tag, const char *name)
{
if (opt.with_colons)
es_fprintf (fp, "%s:", tag);
else
tty_fprintf (fp, "%s", text);
if (name && *name)
{
char *p, *given, *buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (opt.with_colons)
es_write_sanitized (fp, given, strlen (given), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (opt.with_colons)
es_putc (':', fp);
else if (*buf)
tty_fprintf (fp, " ");
}
if (opt.with_colons)
es_write_sanitized (fp, buf, strlen (buf), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
if (opt.with_colons)
es_putc (':', fp);
else
tty_fprintf (fp, _("[not set]"));
}
if (opt.with_colons)
es_fputs (":\n", fp);
else
tty_fprintf (fp, "\n");
}
/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
static int
fpr_is_zero (const char *fpr)
{
int i;
for (i=0; i < 20 && !fpr[i]; i++)
;
return (i == 20);
}
/* Return true if the SHA1 fingerprint FPR consists only of 0xFF. */
static int
fpr_is_ff (const char *fpr)
{
int i;
for (i=0; i < 20 && fpr[i] == '\xff'; i++)
;
return (i == 20);
}
/* Print all available information about the current card. */
void
card_status (estream_t fp, char *serialno, size_t serialnobuflen)
{
struct agent_card_info_s info;
PKT_public_key *pk = xcalloc (1, sizeof *pk);
kbnode_t keyblock = NULL;
int rc;
unsigned int uval;
const unsigned char *thefpr;
int i;
if (serialno && serialnobuflen)
*serialno = 0;
rc = agent_scd_learn (&info, 0);
if (rc)
{
if (opt.with_colons)
es_fputs ("AID:::\n", fp);
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc));
xfree (pk);
return;
}
if (opt.with_colons)
es_fprintf (fp, "Reader:%s:", info.reader? info.reader : "");
else
tty_fprintf (fp, "Reader ...........: %s\n",
info.reader? info.reader : "[none]");
if (opt.with_colons)
es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : "");
else
tty_fprintf (fp, "Application ID ...: %s\n",
info.serialno? info.serialno : "[none]");
if (!info.serialno || strncmp (info.serialno, "D27600012401", 12)
|| strlen (info.serialno) != 32 )
{
if (info.apptype && !strcmp (info.apptype, "NKS"))
{
if (opt.with_colons)
es_fputs ("netkey-card:\n", fp);
log_info ("this is a NetKey card\n");
}
else if (info.apptype && !strcmp (info.apptype, "DINSIG"))
{
if (opt.with_colons)
es_fputs ("dinsig-card:\n", fp);
log_info ("this is a DINSIG compliant card\n");
}
else if (info.apptype && !strcmp (info.apptype, "P15"))
{
if (opt.with_colons)
es_fputs ("pkcs15-card:\n", fp);
log_info ("this is a PKCS#15 compliant card\n");
}
else if (info.apptype && !strcmp (info.apptype, "GELDKARTE"))
{
if (opt.with_colons)
es_fputs ("geldkarte-card:\n", fp);
log_info ("this is a Geldkarte compliant card\n");
}
else
{
if (opt.with_colons)
es_fputs ("unknown:\n", fp);
}
log_info ("not an OpenPGP card\n");
agent_release_card_info (&info);
xfree (pk);
return;
}
if (!serialno)
;
else if (strlen (serialno)+1 > serialnobuflen)
log_error ("serial number longer than expected\n");
else
strcpy (serialno, info.serialno);
if (opt.with_colons)
es_fputs ("openpgp-card:\n", fp);
if (opt.with_colons)
{
es_fprintf (fp, "version:%.4s:\n", info.serialno+12);
uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18);
es_fprintf (fp, "vendor:%04x:%s:\n", uval, get_manufacturer (uval));
es_fprintf (fp, "serial:%.8s:\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
es_fputs ("lang:", fp);
if (info.disp_lang)
es_write_sanitized (fp, info.disp_lang, strlen (info.disp_lang),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm':
info.disp_sex == 2? 'f' : 'u'));
es_fputs ("url:", fp);
if (info.pubkey_url)
es_write_sanitized (fp, info.pubkey_url, strlen (info.pubkey_url),
":", NULL);
es_fputs (":\n", fp);
es_fputs ("login:", fp);
if (info.login_data)
es_write_sanitized (fp, info.login_data, strlen (info.login_data),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached);
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
es_fprintf (fp, "keyattr:%d:%d:%u:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
es_fprintf (fp, "keyattr:%d:%d:%s:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].curve);
es_fprintf (fp, "maxpinlen:%d:%d:%d:\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
es_fprintf (fp, "pinretry:%d:%d:%d:\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter);
for (i=0; i < 4; i++)
{
if (info.private_do[i])
{
es_fprintf (fp, "private_do:%d:", i+1);
es_write_sanitized (fp, info.private_do[i],
strlen (info.private_do[i]), ":", NULL);
es_fputs (":\n", fp);
}
}
es_fputs ("cafpr:", fp);
print_sha1_fpr_colon (fp, info.cafpr1valid? info.cafpr1:NULL);
print_sha1_fpr_colon (fp, info.cafpr2valid? info.cafpr2:NULL);
print_sha1_fpr_colon (fp, info.cafpr3valid? info.cafpr3:NULL);
es_putc ('\n', fp);
es_fputs ("fpr:", fp);
print_sha1_fpr_colon (fp, info.fpr1valid? info.fpr1:NULL);
print_sha1_fpr_colon (fp, info.fpr2valid? info.fpr2:NULL);
print_sha1_fpr_colon (fp, info.fpr3valid? info.fpr3:NULL);
es_putc ('\n', fp);
es_fprintf (fp, "fprtime:%lu:%lu:%lu:\n",
(unsigned long)info.fpr1time, (unsigned long)info.fpr2time,
(unsigned long)info.fpr3time);
}
else
{
tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
info.serialno[12] == '0'?"":info.serialno+12,
info.serialno[13],
info.serialno[14] == '0'?"":info.serialno+14,
info.serialno[15]);
tty_fprintf (fp, "Manufacturer .....: %s\n",
get_manufacturer (xtoi_2(info.serialno+16)*256
+ xtoi_2 (info.serialno+18)));
tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
print_name (fp, "Language prefs ...: ", info.disp_lang);
tty_fprintf (fp, "Sex ..............: %s\n",
info.disp_sex == 1? _("male"):
info.disp_sex == 2? _("female") : _("unspecified"));
print_name (fp, "URL of public key : ", info.pubkey_url);
print_name (fp, "Login data .......: ", info.login_data);
if (info.private_do[0])
print_name (fp, "Private DO 1 .....: ", info.private_do[0]);
if (info.private_do[1])
print_name (fp, "Private DO 2 .....: ", info.private_do[1]);
if (info.private_do[2])
print_name (fp, "Private DO 3 .....: ", info.private_do[2]);
if (info.private_do[3])
print_name (fp, "Private DO 4 .....: ", info.private_do[3]);
if (info.cafpr1valid)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_sha1_fpr (fp, info.cafpr1);
}
if (info.cafpr2valid)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_sha1_fpr (fp, info.cafpr2);
}
if (info.cafpr3valid)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_sha1_fpr (fp, info.cafpr3);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info.chv1_cached? _("not forced"): _("forced"));
if (info.key_attr[0].algo)
{
tty_fprintf (fp, "Key attributes ...:");
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
{
const char *curve_for_print = "?";
if (info.key_attr[i].curve)
{
const char *oid;
oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL);
if (oid)
curve_for_print = openpgp_oid_to_curve (oid, 0);
}
tty_fprintf (fp, " %s", curve_for_print);
}
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter);
tty_fprintf (fp, "Signature key ....:");
print_sha1_fpr (fp, info.fpr1valid? info.fpr1:NULL);
if (info.fpr1valid && info.fpr1time)
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr1time));
tty_fprintf (fp, "Encryption key....:");
print_sha1_fpr (fp, info.fpr2valid? info.fpr2:NULL);
if (info.fpr2valid && info.fpr2time)
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr2time));
tty_fprintf (fp, "Authentication key:");
print_sha1_fpr (fp, info.fpr3valid? info.fpr3:NULL);
if (info.fpr3valid && info.fpr3time)
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr3time));
tty_fprintf (fp, "General key info..: ");
thefpr = (info.fpr1valid? info.fpr1 : info.fpr2valid? info.fpr2 :
info.fpr3valid? info.fpr3 : NULL);
/* If the fingerprint is all 0xff, the key has no asssociated
OpenPGP certificate. */
if ( thefpr && !fpr_is_ff (thefpr)
&& !get_pubkey_byfprint (pk, &keyblock, thefpr, 20))
{
print_pubkey_info (fp, pk);
if (keyblock)
print_card_key_info (fp, keyblock);
}
else
tty_fprintf (fp, "[none]\n");
}
release_kbnode (keyblock);
free_public_key (pk);
agent_release_card_info (&info);
}
static char *
get_one_name (const char *prompt1, const char *prompt2)
{
char *name;
int i;
for (;;)
{
name = cpr_get (prompt1, prompt2);
if (!name)
return NULL;
trim_spaces (name);
cpr_kill_prompt ();
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
static int
change_name (void)
{
char *surname = NULL, *givenname = NULL;
char *isoname, *p;
int rc;
surname = get_one_name ("keygen.smartcard.surname",
_("Cardholder's surname: "));
givenname = get_one_name ("keygen.smartcard.givenname",
_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return -1; /*canceled*/
}
isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1);
strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
tty_printf (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
return -1;
}
rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname), NULL );
if (rc)
log_error ("error setting Name: %s\n", gpg_strerror (rc));
xfree (isoname);
return rc;
}
static int
change_url (void)
{
char *url;
int rc;
url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: "));
if (!url)
return -1;
trim_spaces (url);
cpr_kill_prompt ();
if (strlen (url) > 254 )
{
tty_printf (_("Error: URL too long "
"(limit is %d characters).\n"), 254);
xfree (url);
return -1;
}
rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url), NULL );
if (rc)
log_error ("error setting URL: %s\n", gpg_strerror (rc));
xfree (url);
write_sc_op_status (rc);
return rc;
}
/* Fetch the key from the URL given on the card or try to get it from
the default keyserver. */
static int
fetch_url (ctrl_t ctrl)
{
int rc;
struct agent_card_info_s info;
memset(&info,0,sizeof(info));
rc=agent_scd_getattr("PUBKEY-URL",&info);
if(rc)
log_error("error retrieving URL from card: %s\n",gpg_strerror(rc));
else
{
rc=agent_scd_getattr("KEY-FPR",&info);
if(rc)
log_error("error retrieving key fingerprint from card: %s\n",
gpg_strerror(rc));
else if (info.pubkey_url && *info.pubkey_url)
{
strlist_t sl = NULL;
add_to_strlist (&sl, info.pubkey_url);
rc = keyserver_fetch (ctrl, sl);
free_strlist (sl);
}
else if (info.fpr1valid)
{
rc = keyserver_import_fprint (ctrl, info.fpr1, 20, opt.keyserver, 0);
}
}
return rc;
}
/* Read data from file FNAME up to MAXLEN characters. On error return
-1 and store NULL at R_BUFFER; on success return the number of
bytes read and store the address of a newly allocated buffer at
R_BUFFER. */
static int
get_data_from_file (const char *fname, size_t maxlen, char **r_buffer)
{
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
fp = es_fopen (fname, "rb");
#if GNUPG_MAJOR_VERSION == 1
if (fp && is_secured_file (fileno (fp)))
{
fclose (fp);
fp = NULL;
errno = EPERM;
}
#endif
if (!fp)
{
tty_printf (_("can't open '%s': %s\n"), fname, strerror (errno));
return -1;
}
data = xtrymalloc (maxlen? maxlen:1);
if (!data)
{
tty_printf (_("error allocating enough memory: %s\n"), strerror (errno));
es_fclose (fp);
return -1;
}
if (maxlen)
n = es_fread (data, 1, maxlen, fp);
else
n = 0;
es_fclose (fp);
if (n < 0)
{
tty_printf (_("error reading '%s': %s\n"), fname, strerror (errno));
xfree (data);
return -1;
}
*r_buffer = data;
return n;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
success. */
static int
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
estream_t fp;
fp = es_fopen (fname, "wb");
#if GNUPG_MAJOR_VERSION == 1
if (fp && is_secured_file (fileno (fp)))
{
fclose (fp);
fp = NULL;
errno = EPERM;
}
#endif
if (!fp)
{
tty_printf (_("can't create '%s': %s\n"), fname, strerror (errno));
return -1;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
tty_printf (_("error writing '%s': %s\n"), fname, strerror (errno));
es_fclose (fp);
return -1;
}
es_fclose (fp);
return 0;
}
static int
change_login (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, 254, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_login",
_("Login data (account name): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
if (n > 254 )
{
tty_printf (_("Error: Login data too long "
"(limit is %d characters).\n"), 254);
xfree (data);
return -1;
}
rc = agent_scd_setattr ("LOGIN-DATA", data, n, NULL );
if (rc)
log_error ("error setting login data: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_private_do (const char *args, int nr)
{
char do_name[] = "PRIVATE-DO-X";
char *data;
int n;
int rc;
log_assert (nr >= 1 && nr <= 4);
do_name[11] = '0' + nr;
if (args && (args = strchr (args, '<'))) /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, 254, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_private_do",
_("Private DO data: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
if (n > 254 )
{
tty_printf (_("Error: Private DO too long "
"(limit is %d characters).\n"), 254);
xfree (data);
return -1;
}
rc = agent_scd_setattr (do_name, data, n, NULL );
if (rc)
log_error ("error setting private DO: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cert (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, 16384, &data);
if (n < 0)
return -1;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_writecert ("OPENPGP.3", data, n);
if (rc)
log_error ("error writing certificate to card: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
read_cert (const char *args)
{
const char *fname;
void *buffer;
size_t length;
int rc;
if (args && *args == '>') /* Write it to a file */
{
for (args++; spacep (args); args++)
;
fname = args;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_readcert ("OPENPGP.3", &buffer, &length);
if (rc)
log_error ("error reading certificate from card: %s\n", gpg_strerror (rc));
else
rc = put_data_to_file (fname, buffer, length);
xfree (buffer);
write_sc_op_status (rc);
return rc;
}
static int
change_lang (void)
{
char *data, *p;
int rc;
data = cpr_get ("cardedit.change_lang",
_("Language preferences: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (strlen (data) > 8 || (strlen (data) & 1))
{
tty_printf (_("Error: invalid length of preference string.\n"));
xfree (data);
return -1;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
tty_printf (_("Error: invalid characters in preference string.\n"));
xfree (data);
return -1;
}
rc = agent_scd_setattr ("DISP-LANG", data, strlen (data), NULL );
if (rc)
log_error ("error setting lang: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_sex (void)
{
char *data;
const char *str;
int rc;
data = cpr_get ("cardedit.change_sex",
_("Sex ((M)ale, (F)emale or space): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
return -1;
}
rc = agent_scd_setattr ("DISP-SEX", str, 1, NULL );
if (rc)
log_error ("error setting sex: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cafpr (int fprno)
{
char *data;
const char *s;
int i, c, rc;
unsigned char fpr[20];
data = cpr_get ("cardedit.change_cafpr", _("CA fingerprint: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
for (i=0, s=data; i < 20 && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
xfree (data);
if (i != 20 || *s)
{
tty_printf (_("Error: invalid formatted fingerprint.\n"));
return -1;
}
rc = agent_scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, 20, NULL );
if (rc)
log_error ("error setting cafpr: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
return rc;
}
static void
toggle_forcesig (void)
{
struct agent_card_info_s info;
int rc;
int newstate;
memset (&info, 0, sizeof info);
rc = agent_scd_getattr ("CHV-STATUS", &info);
if (rc)
{
log_error ("error getting current status: %s\n", gpg_strerror (rc));
return;
}
newstate = !info.chv1_cached;
agent_release_card_info (&info);
rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1, NULL);
if (rc)
log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
/* Helper for the key generation/edit functions. */
static int
get_info_for_key_operation (struct agent_card_info_s *info)
{
int rc;
memset (info, 0, sizeof *info);
rc = agent_scd_getattr ("SERIALNO", info);
if (rc || !info->serialno || strncmp (info->serialno, "D27600012401", 12)
|| strlen (info->serialno) != 32 )
{
log_error (_("key operation not possible: %s\n"),
rc ? gpg_strerror (rc) : _("not an OpenPGP card"));
return rc? rc: -1;
}
rc = agent_scd_getattr ("KEY-FPR", info);
if (!rc)
rc = agent_scd_getattr ("CHV-STATUS", info);
if (!rc)
rc = agent_scd_getattr ("DISP-NAME", info);
if (!rc)
rc = agent_scd_getattr ("EXTCAP", info);
if (!rc)
rc = agent_scd_getattr ("KEY-ATTR", info);
if (rc)
log_error (_("error getting current key info: %s\n"), gpg_strerror (rc));
return rc;
}
/* Helper for the key generation/edit functions. */
static int
check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1)
{
int rc = 0;
agent_clear_pin_cache (info->serialno);
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
don't get bothered with PIN queries for each
self-signature. */
rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1, info->serialno);
if (rc)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (rc));
*forced_chv1 = 0;
}
}
if (!rc)
{
/* Check the PIN now, so that we won't get asked later for each
binding signature. */
rc = agent_scd_checkpin (info->serialno);
if (rc)
{
log_error ("error checking the PIN: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
}
return rc;
}
/* Helper for the key generation/edit functions. */
static void
restore_forced_chv1 (int *forced_chv1)
{
int rc;
if (*forced_chv1)
{ /* Switch back to forced state. */
rc = agent_scd_setattr ("CHV-STATUS-1", "", 1, NULL);
if (rc)
{
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (rc));
}
}
}
/* Helper for the key generation/edit functions. */
static void
show_card_key_info (struct agent_card_info_s *info)
{
tty_fprintf (NULL, "Signature key ....:");
print_sha1_fpr (NULL, info->fpr1valid? info->fpr1:NULL);
tty_fprintf (NULL, "Encryption key....:");
print_sha1_fpr (NULL, info->fpr2valid? info->fpr2:NULL);
tty_fprintf (NULL, "Authentication key:");
print_sha1_fpr (NULL, info->fpr3valid? info->fpr3:NULL);
tty_printf ("\n");
}
/* Helper for the key generation/edit functions. */
static int
replace_existing_key_p (struct agent_card_info_s *info, int keyno)
{
log_assert (keyno >= 0 && keyno <= 3);
if ((keyno == 1 && info->fpr1valid)
|| (keyno == 2 && info->fpr2valid)
|| (keyno == 3 && info->fpr3valid))
{
tty_printf ("\n");
log_info ("WARNING: such a key has already been stored on the card!\n");
tty_printf ("\n");
if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_key",
_("Replace existing key? (y/N) ")))
return -1;
return 1;
}
return 0;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card "
"supports the requested size.\n"
" If the key generation does not succeed, "
"please check the\n"
" documentation of your card to see what "
"sizes are allowed.\n"));
}
/* Ask for the size of a card key. NBITS is the current size
configured for the card. KEYNO is the number of the key used to
select the prompt. Returns 0 to use the default size (i.e. NBITS)
or the selected size. */
static unsigned int
ask_card_rsa_keysize (int keyno, unsigned int nbits)
{
unsigned int min_nbits = 1024;
unsigned int max_nbits = 4096;
char *prompt, *answer;
unsigned int req_nbits;
for (;;)
{
prompt = xasprintf
(keyno == 0?
_("What keysize do you want for the Signature key? (%u) "):
keyno == 1?
_("What keysize do you want for the Encryption key? (%u) "):
_("What keysize do you want for the Authentication key? (%u) "),
nbits);
answer = cpr_get ("cardedit.genkeys.size", prompt);
cpr_kill_prompt ();
req_nbits = *answer? atoi (answer): nbits;
xfree (prompt);
xfree (answer);
if (req_nbits != nbits && (req_nbits % 32) )
{
req_nbits = ((req_nbits + 31) / 32) * 32;
tty_printf (_("rounded up to %u bits\n"), req_nbits);
}
if (req_nbits == nbits)
return 0; /* Use default. */
if (req_nbits < min_nbits || req_nbits > max_nbits)
{
tty_printf (_("%s keysizes must be in the range %u-%u\n"),
"RSA", min_nbits, max_nbits);
}
else
{
tty_printf (_("The card will now be re-configured "
"to generate a key of %u bits\n"), req_nbits);
show_keysize_warning ();
return req_nbits;
}
}
}
/* Change the size of key KEYNO (0..2) to NBITS and show an error
message if that fails. */
static gpg_error_t
do_change_rsa_keysize (int keyno, unsigned int nbits)
{
gpg_error_t err;
char args[100];
snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, nbits);
err = agent_scd_setattr ("KEY-ATTR", args, strlen (args), NULL);
if (err)
log_error (_("error changing size of key %d to %u bits: %s\n"),
keyno+1, nbits, gpg_strerror (err));
return err;
}
static void
generate_card_keys (ctrl_t ctrl)
{
struct agent_card_info_s info;
int forced_chv1;
int want_backup;
int keyno;
if (get_info_for_key_operation (&info))
return;
if (info.extcap.ki)
{
char *answer;
/* FIXME: Should be something like cpr_get_bool so that a status
GET_BOOL will be emitted. */
answer = cpr_get ("cardedit.genkeys.backup_enc",
_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
cpr_kill_prompt ();
xfree (answer);
}
else
want_backup = 0;
if ( (info.fpr1valid && !fpr_is_zero (info.fpr1))
|| (info.fpr2valid && !fpr_is_zero (info.fpr2))
|| (info.fpr3valid && !fpr_is_zero (info.fpr3)))
{
tty_printf ("\n");
log_info (_("Note: keys are already stored on the card!\n"));
tty_printf ("\n");
if ( !cpr_get_answer_is_yes ("cardedit.genkeys.replace_keys",
_("Replace existing keys? (y/N) ")))
{
agent_release_card_info (&info);
return;
}
}
/* If no displayed name has been set, we assume that this is a fresh
card and print a hint about the default PINs. */
if (!info.disp_name || !*info.disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
"123456", "12345678");
tty_printf ("\n");
}
if (check_pin_for_key_operation (&info, &forced_chv1))
goto leave;
/* If the cards features changeable key attributes, we ask for the
key size. */
if (info.is_v2 && info.extcap.aac)
{
unsigned int nbits;
for (keyno = 0; keyno < DIM (info.key_attr); keyno++)
{
if (info.key_attr[keyno].algo == PUBKEY_ALGO_RSA)
{
nbits = ask_card_rsa_keysize (keyno, info.key_attr[keyno].nbits);
if (nbits && do_change_rsa_keysize (keyno, nbits))
{
/* Error: Better read the default key size again. */
agent_release_card_info (&info);
if (get_info_for_key_operation (&info))
goto leave;
/* Ask again for this key size. */
keyno--;
}
}
}
/* Note that INFO has not be synced. However we will only use
the serialnumber and thus it won't harm. */
}
generate_keypair (ctrl, 1, NULL, info.serialno, want_backup);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
}
/* This function is used by the key edit menu to generate an arbitrary
subkey. */
gpg_error_t
card_generate_subkey (KBNODE pub_keyblock)
{
gpg_error_t err;
struct agent_card_info_s info;
int forced_chv1 = 0;
int keyno;
err = get_info_for_key_operation (&info);
if (err)
return err;
show_card_key_info (&info);
tty_printf (_("Please select the type of key to generate:\n"));
tty_printf (_(" (1) Signature key\n"));
tty_printf (_(" (2) Encryption key\n"));
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.subkeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D)
{
xfree (answer);
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3)
break; /* Okay. */
tty_printf(_("Invalid selection.\n"));
}
if (replace_existing_key_p (&info, keyno) < 0)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
err = check_pin_for_key_operation (&info, &forced_chv1);
if (err)
goto leave;
/* If the cards features changeable key attributes, we ask for the
key size. */
if (info.is_v2 && info.extcap.aac)
{
if (info.key_attr[keyno-1].algo == PUBKEY_ALGO_RSA)
{
unsigned int nbits;
ask_again:
nbits = ask_card_rsa_keysize (keyno-1, info.key_attr[keyno-1].nbits);
if (nbits && do_change_rsa_keysize (keyno-1, nbits))
{
/* Error: Better read the default key size again. */
agent_release_card_info (&info);
err = get_info_for_key_operation (&info);
if (err)
goto leave;
goto ask_again;
}
}
/* Note that INFO has not be synced. However we will only use
the serialnumber and thus it won't harm. */
}
err = generate_card_subkeypair (pub_keyblock, keyno, info.serialno);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
return err;
}
/* Store the key at NODE into the smartcard and modify NODE to
carry the serialno stuff instead of the actual secret key
parameters. USE is the usage for that key; 0 means any
usage. */
int
card_store_subkey (KBNODE node, int use)
{
struct agent_card_info_s info;
int okay = 0;
unsigned int nbits;
int allow_keyno[3];
int keyno;
PKT_public_key *pk;
gpg_error_t err;
char *hexgrip;
int rc;
gnupg_isotime_t timebuf;
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
pk = node->pkt->pkt.public_key;
if (get_info_for_key_operation (&info))
return 0;
if (!info.extcap.ki)
{
tty_printf ("The card does not support the import of keys\n");
tty_printf ("\n");
goto leave;
}
nbits = nbits_from_pk (pk);
if (!info.is_v2 && nbits != 1024)
{
tty_printf ("You may only store a 1024 bit RSA key on the card\n");
tty_printf ("\n");
goto leave;
}
allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)));
allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC)));
allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH)));
tty_printf (_("Please select where to store the key:\n"));
if (allow_keyno[0])
tty_printf (_(" (1) Signature key\n"));
if (allow_keyno[1])
tty_printf (_(" (2) Encryption key\n"));
if (allow_keyno[2])
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.storekeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D || !*answer)
{
xfree (answer);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1])
{
if (info.is_v2 && !info.extcap.aac
&& info.key_attr[keyno-1].nbits != nbits)
{
tty_printf ("Key does not match the card's capability.\n");
}
else
break; /* Okay. */
}
else
tty_printf(_("Invalid selection.\n"));
}
if ((rc = replace_existing_key_p (&info, keyno)) < 0)
goto leave;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
epoch2isotime (timebuf, (time_t)pk->timestamp);
rc = agent_keytocard (hexgrip, keyno, rc, info.serialno, timebuf);
if (rc)
log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc));
else
okay = 1;
xfree (hexgrip);
leave:
agent_release_card_info (&info);
return okay;
}
/* Direct sending of an hex encoded APDU with error printing. */
static gpg_error_t
send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
{
gpg_error_t err;
unsigned int sw;
err = agent_scd_apdu (hexapdu, &sw);
if (err)
tty_printf ("sending card command %s failed: %s\n", desc,
gpg_strerror (err));
else if (!hexapdu || !strcmp (hexapdu, "undefined"))
;
else if (ignore == 0xffff)
; /* Ignore all status words. */
else if (sw != 0x9000)
{
switch (sw)
{
case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
default: err = gpg_error (GPG_ERR_CARD);
}
if (!(ignore && ignore == sw))
tty_printf ("card command %s failed: %s (0x%04x)\n", desc,
gpg_strerror (err), sw);
}
return err;
}
/* Do a factory reset after confirmation. */
static void
factory_reset (void)
{
struct agent_card_info_s info;
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int i;
/* The code below basically does the same what this
gpg-connect-agent script does:
scd reset
scd serialno undefined
scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd reset
scd serialno undefined
scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
scd apdu 00 44 00 00
/echo Card has been reset to factory defaults
but tries to find out something about the card first.
*/
err = agent_scd_learn (&info, 0);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
return;
}
if (!termstate)
{
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
if (!(info.status_indicator == 3 || info.status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it is not
possible to select a card application in termination state. */
log_error (_("This command is not supported by this card\n"));
goto leave;
}
tty_printf ("\n");
log_info (_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed",
_("Continue? (y/N) ")))
goto leave;
answer = cpr_get ("cardedit.factory-reset.really",
_("Really do a factory reset? (enter \"yes\") "));
cpr_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes"))
goto leave;
/* We need to select a card application before we can send APDUs
to the card without scdaemon doing anything on its own. */
err = send_apdu (NULL, "RESET", 0);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
counter to zero. We can't easily use the card version 2.1
feature of presenting the admin PIN to allow the terminate
command because there is no machinery in scdaemon to catch
the verify command and ask for the PIN when the "APDU"
command is used. */
for (i=0; i < 4; i++)
send_apdu ("00200081084040404040404040", "VERIFY", 0xffff);
for (i=0; i < 4; i++)
send_apdu ("00200083084040404040404040", "VERIFY", 0xffff);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
if (err)
goto leave;
}
/* The card is in termination state - reset and select again. */
err = send_apdu (NULL, "RESET", 0);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select", 0);
if (err)
goto leave;
/* Select the OpenPGP application. (no error checking here). */
send_apdu ("00A4040006D27600012401", "SELECT AID", 0xffff);
/* Send activate datafile command. This is used without
confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0);
if (err)
goto leave;
/* Finally we reset the card reader once more. */
err = send_apdu (NULL, "RESET", 0);
if (err)
goto leave;
leave:
xfree (answer);
agent_release_card_info (&info);
}
/* Data used by the command parser. This needs to be outside of the
function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
int admin_only;
const char *desc;
} cmds[] =
{
{ "quit" , cmdQUIT , 0, N_("quit this menu")},
{ "q" , cmdQUIT , 0, NULL },
{ "admin" , cmdADMIN , 0, N_("show admin commands")},
{ "help" , cmdHELP , 0, N_("show this help")},
{ "?" , cmdHELP , 0, NULL },
{ "list" , cmdLIST , 0, N_("list all available data")},
{ "l" , cmdLIST , 0, NULL },
{ "debug" , cmdDEBUG , 0, NULL },
{ "name" , cmdNAME , 1, N_("change card holder's name")},
{ "url" , cmdURL , 1, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN , 1, N_("change the login name")},
{ "lang" , cmdLANG , 1, N_("change the language preferences")},
{ "sex" , cmdSEX , 1, N_("change card holder's sex")},
{ "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, 1, N_("generate new keys")},
{ "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") },
{ "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
/* Note, that we do not announce these command yet. */
{ "privatedo", cmdPRIVATEDO, 0, NULL },
{ "readcert", cmdREADCERT, 0, NULL },
{ "writecert", cmdWRITECERT, 1, NULL },
{ NULL, cmdINVCMD, 0, NULL }
};
#ifdef HAVE_LIBREADLINE
/* These two functions are used by readline for command completion. */
static char *
command_generator(const char *text,int state)
{
static int list_index,len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if(!state)
{
list_index=0;
len=strlen(text);
}
/* Return the next partial match */
while((name=cmds[list_index].name))
{
/* Only complete commands that have help text */
if(cmds[list_index++].desc && strncmp(name,text,len)==0)
return strdup(name);
}
return NULL;
}
static char **
card_edit_completion(const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
if(start==0)
return rl_completion_matches(text,command_generator);
rl_attempted_completion_over=1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
/* Menu to edit all user changeable values on an OpenPGP card. Only
Key creation is not handled here. */
void
card_edit (ctrl_t ctrl, strlist_t commands)
{
enum cmdids cmd = cmdNOP;
int have_commands = !!commands;
int redisplay = 1;
char *answer = NULL;
int allow_admin=0;
char serialnobuf[50];
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error(_("can't do this in batch mode\n"));
goto leave;
}
for (;;)
{
int arg_number;
const char *arg_string = "";
const char *arg_rest = "";
char *p;
int i;
int cmd_admin_only;
tty_printf("\n");
if (redisplay )
{
if (opt.with_colons)
{
card_status (es_stdout, serialnobuf, DIM (serialnobuf));
fflush (stdout);
}
else
{
card_status (NULL, serialnobuf, DIM (serialnobuf));
tty_printf("\n");
}
redisplay = 0;
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
tty_enable_completion (card_edit_completion);
answer = cpr_get_no_help("cardedit.prompt", _("gpg/card> "));
cpr_kill_prompt();
tty_disable_completion ();
}
trim_spaces(answer);
}
while ( *answer == '#' );
arg_number = 0; /* Yes, here is the init which egcc complains about */
cmd_admin_only = 0;
if (!*answer)
cmd = cmdLIST; /* Default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((p=strchr (answer,' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi(p);
arg_string = p;
arg_rest = p;
while (digitp (arg_rest))
arg_rest++;
while (spacep (arg_rest))
arg_rest++;
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
cmd_admin_only = cmds[i].admin_only;
}
if (!allow_admin && cmd_admin_only)
{
tty_printf ("\n");
tty_printf (_("Admin-only command\n"));
continue;
}
switch (cmd)
{
case cmdHELP:
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc
&& (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin)))
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
break;
case cmdADMIN:
if ( !strcmp (arg_string, "on") )
allow_admin = 1;
else if ( !strcmp (arg_string, "off") )
allow_admin = 0;
else if ( !strcmp (arg_string, "verify") )
{
/* Force verification of the Admin Command. However,
this is only done if the retry counter is at initial
state. */
char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1);
strcpy (stpcpy (tmp, serialnobuf), "[CHV3]");
allow_admin = !agent_scd_checkpin (tmp);
xfree (tmp);
}
else /* Toggle. */
allow_admin=!allow_admin;
if(allow_admin)
tty_printf(_("Admin commands are allowed\n"));
else
tty_printf(_("Admin commands are not allowed\n"));
break;
case cmdVERIFY:
agent_scd_checkpin (serialnobuf);
redisplay = 1;
break;
case cmdLIST:
redisplay = 1;
break;
case cmdNAME:
change_name ();
break;
case cmdURL:
change_url ();
break;
case cmdFETCH:
fetch_url (ctrl);
break;
case cmdLOGIN:
change_login (arg_string);
break;
case cmdLANG:
change_lang ();
break;
case cmdSEX:
change_sex ();
break;
case cmdCAFPR:
if ( arg_number < 1 || arg_number > 3 )
tty_printf ("usage: cafpr N\n"
" 1 <= N <= 3\n");
else
change_cafpr (arg_number);
break;
case cmdPRIVATEDO:
if ( arg_number < 1 || arg_number > 4 )
tty_printf ("usage: privatedo N\n"
" 1 <= N <= 4\n");
else
change_private_do (arg_string, arg_number);
break;
case cmdWRITECERT:
if ( arg_number != 3 )
tty_printf ("usage: writecert 3 < FILE\n");
else
change_cert (arg_rest);
break;
case cmdREADCERT:
if ( arg_number != 3 )
tty_printf ("usage: readcert 3 > FILE\n");
else
read_cert (arg_rest);
break;
case cmdFORCESIG:
toggle_forcesig ();
break;
case cmdGENERATE:
generate_card_keys (ctrl);
break;
case cmdPASSWD:
change_pin (0, allow_admin);
break;
case cmdUNBLOCK:
change_pin (1, allow_admin);
break;
case cmdFACTORYRESET:
factory_reset ();
break;
case cmdQUIT:
goto leave;
case cmdNOP:
break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
} /* End of main menu loop. */
leave:
xfree (answer);
}
diff --git a/g10/cipher.c b/g10/cipher.c
index 98f398e3d..655937f07 100644
--- a/g10/cipher.c
+++ b/g10/cipher.c
@@ -1,162 +1,162 @@
/* cipher.c - En-/De-ciphering filter
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2006, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "filter.h"
#include "packet.h"
#include "options.h"
#include "main.h"
-#include "status.h"
+#include "../common/status.h"
#define MIN_PARTIAL_SIZE 512
static void
write_header( cipher_filter_context_t *cfx, IOBUF a )
{
gcry_error_t err;
PACKET pkt;
PKT_encrypted ed;
byte temp[18];
unsigned int blocksize;
unsigned int nprefix;
blocksize = openpgp_cipher_get_algo_blklen (cfx->dek->algo);
if ( blocksize < 8 || blocksize > 16 )
log_fatal("unsupported blocksize %u\n", blocksize );
memset( &ed, 0, sizeof ed );
ed.len = cfx->datalen;
ed.extralen = blocksize+2;
ed.new_ctb = !ed.len;
if( cfx->dek->use_mdc ) {
ed.mdc_method = DIGEST_ALGO_SHA1;
gcry_md_open (&cfx->mdc_hash, DIGEST_ALGO_SHA1, 0);
if ( DBG_HASHING )
gcry_md_debug (cfx->mdc_hash, "creatmdc");
}
{
char buf[20];
sprintf (buf, "%d %d", ed.mdc_method, cfx->dek->algo);
write_status_text (STATUS_BEGIN_ENCRYPTION, buf);
}
init_packet( &pkt );
pkt.pkttype = cfx->dek->use_mdc? PKT_ENCRYPTED_MDC : PKT_ENCRYPTED;
pkt.pkt.encrypted = &ed;
if( build_packet( a, &pkt ))
log_bug("build_packet(ENCR_DATA) failed\n");
nprefix = blocksize;
gcry_randomize (temp, nprefix, GCRY_STRONG_RANDOM );
temp[nprefix] = temp[nprefix-2];
temp[nprefix+1] = temp[nprefix-1];
print_cipher_algo_note( cfx->dek->algo );
err = openpgp_cipher_open (&cfx->cipher_hd,
cfx->dek->algo,
GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| ((cfx->dek->use_mdc || cfx->dek->algo >= 100)?
0 : GCRY_CIPHER_ENABLE_SYNC)));
if (err) {
/* We should never get an error here cause we already checked,
* that the algorithm is available. */
BUG();
}
/* log_hexdump( "thekey", cfx->dek->key, cfx->dek->keylen );*/
gcry_cipher_setkey( cfx->cipher_hd, cfx->dek->key, cfx->dek->keylen );
gcry_cipher_setiv( cfx->cipher_hd, NULL, 0 );
/* log_hexdump( "prefix", temp, nprefix+2 ); */
if (cfx->mdc_hash) /* Hash the "IV". */
gcry_md_write (cfx->mdc_hash, temp, nprefix+2 );
gcry_cipher_encrypt (cfx->cipher_hd, temp, nprefix+2, NULL, 0);
gcry_cipher_sync (cfx->cipher_hd);
iobuf_write(a, temp, nprefix+2);
cfx->header=1;
}
/****************
* This filter is used to en/de-cipher data with a conventional algorithm
*/
int
cipher_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
cipher_filter_context_t *cfx = opaque;
int rc=0;
if( control == IOBUFCTRL_UNDERFLOW ) { /* decrypt */
rc = -1; /* not yet used */
}
else if( control == IOBUFCTRL_FLUSH ) { /* encrypt */
log_assert(a);
if( !cfx->header ) {
write_header( cfx, a );
}
if (cfx->mdc_hash)
gcry_md_write (cfx->mdc_hash, buf, size);
gcry_cipher_encrypt (cfx->cipher_hd, buf, size, NULL, 0);
rc = iobuf_write( a, buf, size );
}
else if( control == IOBUFCTRL_FREE ) {
if( cfx->mdc_hash ) {
byte *hash;
int hashlen = gcry_md_get_algo_dlen (gcry_md_get_algo
(cfx->mdc_hash));
byte temp[22];
log_assert( hashlen == 20 );
/* We must hash the prefix of the MDC packet here. */
temp[0] = 0xd3;
temp[1] = 0x14;
gcry_md_putc (cfx->mdc_hash, temp[0]);
gcry_md_putc (cfx->mdc_hash, temp[1]);
gcry_md_final (cfx->mdc_hash);
hash = gcry_md_read (cfx->mdc_hash, 0);
memcpy(temp+2, hash, 20);
gcry_cipher_encrypt (cfx->cipher_hd, temp, 22, NULL, 0);
gcry_md_close (cfx->mdc_hash); cfx->mdc_hash = NULL;
if( iobuf_write( a, temp, 22 ) )
log_error("writing MDC packet failed\n" );
}
gcry_cipher_close (cfx->cipher_hd);
}
else if( control == IOBUFCTRL_DESC ) {
mem2str (buf, "cipher_filter", *ret_len);
}
return rc;
}
diff --git a/g10/compress-bz2.c b/g10/compress-bz2.c
index 22cefd99c..4adca9b37 100644
--- a/g10/compress-bz2.c
+++ b/g10/compress-bz2.c
@@ -1,253 +1,253 @@
/* compress.c - bzip2 compress filter
* Copyright (C) 2003, 2004 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <string.h>
#include <stdio.h> /* Early versions of bzlib (1.0) require stdio.h */
#include <bzlib.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "filter.h"
#include "main.h"
#include "options.h"
/* Note that the code in compress.c is nearly identical to the code
here, so if you fix a bug here, look there to see if a matching bug
needs to be fixed. I tried to have one set of functions that could
do ZIP, ZLIB, and BZIP2, but it became dangerously unreadable with
#ifdefs and if(algo) -dshaw */
static void
init_compress( compress_filter_context_t *zfx, bz_stream *bzs )
{
int rc;
int level;
if( opt.bz2_compress_level >= 1 && opt.bz2_compress_level <= 9 )
level = opt.bz2_compress_level;
else if( opt.bz2_compress_level == -1 )
level = 6; /* no particular reason, but it seems reasonable */
else
{
log_error("invalid compression level; using default level\n");
level = 6;
}
if((rc=BZ2_bzCompressInit(bzs,level,0,0))!=BZ_OK)
log_fatal("bz2lib problem: %d\n",rc);
zfx->outbufsize = 8192;
zfx->outbuf = xmalloc( zfx->outbufsize );
}
static int
do_compress(compress_filter_context_t *zfx, bz_stream *bzs, int flush, IOBUF a)
{
int rc;
int zrc;
unsigned n;
do
{
bzs->next_out = zfx->outbuf;
bzs->avail_out = zfx->outbufsize;
if( DBG_FILTER )
log_debug("enter bzCompress: avail_in=%u, avail_out=%u, flush=%d\n",
(unsigned)bzs->avail_in, (unsigned)bzs->avail_out, flush );
zrc = BZ2_bzCompress( bzs, flush );
if( zrc == BZ_STREAM_END && flush == BZ_FINISH )
;
else if( zrc != BZ_RUN_OK && zrc != BZ_FINISH_OK )
log_fatal("bz2lib deflate problem: rc=%d\n", zrc );
n = zfx->outbufsize - bzs->avail_out;
if( DBG_FILTER )
log_debug("leave bzCompress:"
" avail_in=%u, avail_out=%u, n=%u, zrc=%d\n",
(unsigned)bzs->avail_in, (unsigned)bzs->avail_out,
(unsigned)n, zrc );
if( (rc=iobuf_write( a, zfx->outbuf, n )) )
{
log_debug("bzCompress: iobuf_write failed\n");
return rc;
}
}
while( bzs->avail_in || (flush == BZ_FINISH && zrc != BZ_STREAM_END) );
return 0;
}
static void
init_uncompress( compress_filter_context_t *zfx, bz_stream *bzs )
{
int rc;
if((rc=BZ2_bzDecompressInit(bzs,0,opt.bz2_decompress_lowmem))!=BZ_OK)
log_fatal("bz2lib problem: %d\n",rc);
zfx->inbufsize = 2048;
zfx->inbuf = xmalloc( zfx->inbufsize );
bzs->avail_in = 0;
}
static int
do_uncompress( compress_filter_context_t *zfx, bz_stream *bzs,
IOBUF a, size_t *ret_len )
{
int zrc;
int rc=0;
size_t n;
int nread, count;
int refill = !bzs->avail_in;
int eofseen = 0;
if( DBG_FILTER )
log_debug("begin bzDecompress: avail_in=%u, avail_out=%u, inbuf=%u\n",
(unsigned)bzs->avail_in, (unsigned)bzs->avail_out,
(unsigned)zfx->inbufsize );
do
{
if( bzs->avail_in < zfx->inbufsize && refill )
{
n = bzs->avail_in;
if( !n )
bzs->next_in = zfx->inbuf;
count = zfx->inbufsize - n;
nread = iobuf_read( a, zfx->inbuf + n, count );
if( nread == -1 )
{
eofseen = 1;
nread = 0;
}
n += nread;
bzs->avail_in = n;
}
if (!eofseen)
refill = 1;
if( DBG_FILTER )
log_debug("enter bzDecompress: avail_in=%u, avail_out=%u\n",
(unsigned)bzs->avail_in, (unsigned)bzs->avail_out);
zrc=BZ2_bzDecompress(bzs);
if( DBG_FILTER )
log_debug("leave bzDecompress: avail_in=%u, avail_out=%u, zrc=%d\n",
(unsigned)bzs->avail_in, (unsigned)bzs->avail_out, zrc);
if( zrc == BZ_STREAM_END )
rc = -1; /* eof */
else if( zrc != BZ_OK && zrc != BZ_PARAM_ERROR )
log_fatal("bz2lib inflate problem: rc=%d\n", zrc );
else if (zrc == BZ_OK && eofseen
&& !bzs->avail_in && bzs->avail_out > 0)
{
log_error ("unexpected EOF in bz2lib\n");
rc = GPG_ERR_BAD_DATA;
break;
}
}
while( bzs->avail_out && zrc != BZ_STREAM_END && zrc != BZ_PARAM_ERROR );
/* I'm not completely happy with the two uses of BZ_PARAM_ERROR
here. The corresponding zlib function is Z_BUF_ERROR, which
covers a narrower scope than BZ_PARAM_ERROR. -dshaw */
*ret_len = zfx->outbufsize - bzs->avail_out;
if( DBG_FILTER )
log_debug("do_uncompress: returning %u bytes\n", (unsigned)*ret_len );
return rc;
}
int
compress_filter_bz2( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
compress_filter_context_t *zfx = opaque;
bz_stream *bzs = zfx->opaque;
int rc=0;
if( control == IOBUFCTRL_UNDERFLOW )
{
if( !zfx->status )
{
bzs = zfx->opaque = xmalloc_clear( sizeof *bzs );
init_uncompress( zfx, bzs );
zfx->status = 1;
}
bzs->next_out = buf;
bzs->avail_out = size;
zfx->outbufsize = size; /* needed only for calculation */
rc = do_uncompress( zfx, bzs, a, ret_len );
}
else if( control == IOBUFCTRL_FLUSH )
{
if( !zfx->status )
{
PACKET pkt;
PKT_compressed cd;
if( zfx->algo != COMPRESS_ALGO_BZIP2 )
BUG();
memset( &cd, 0, sizeof cd );
cd.len = 0;
cd.algorithm = zfx->algo;
init_packet( &pkt );
pkt.pkttype = PKT_COMPRESSED;
pkt.pkt.compressed = &cd;
if( build_packet( a, &pkt ))
log_bug("build_packet(PKT_COMPRESSED) failed\n");
bzs = zfx->opaque = xmalloc_clear( sizeof *bzs );
init_compress( zfx, bzs );
zfx->status = 2;
}
bzs->next_in = buf;
bzs->avail_in = size;
rc = do_compress( zfx, bzs, BZ_RUN, a );
}
else if( control == IOBUFCTRL_FREE )
{
if( zfx->status == 1 )
{
BZ2_bzDecompressEnd(bzs);
xfree(bzs);
zfx->opaque = NULL;
xfree(zfx->outbuf); zfx->outbuf = NULL;
}
else if( zfx->status == 2 )
{
bzs->next_in = buf;
bzs->avail_in = 0;
do_compress( zfx, bzs, BZ_FINISH, a );
BZ2_bzCompressEnd(bzs);
xfree(bzs);
zfx->opaque = NULL;
xfree(zfx->outbuf); zfx->outbuf = NULL;
}
if (zfx->release)
zfx->release (zfx);
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "compress_filter", *ret_len);
return rc;
}
diff --git a/g10/compress.c b/g10/compress.c
index a14d107ed..61bb756f2 100644
--- a/g10/compress.c
+++ b/g10/compress.c
@@ -1,365 +1,365 @@
/* compress.c - compress filter
* Copyright (C) 1998, 1999, 2000, 2001, 2002,
* 2003, 2006, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* Note that the code in compress-bz2.c is nearly identical to the
code here, so if you fix a bug here, look there to see if a
matching bug needs to be fixed. I tried to have one set of
functions that could do ZIP, ZLIB, and BZIP2, but it became
dangerously unreadable with #ifdefs and if(algo) -dshaw */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_ZIP
# include <zlib.h>
# if defined(__riscos__) && defined(USE_ZLIBRISCOS)
# include "zlib-riscos.h"
# endif
#endif
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "filter.h"
#include "main.h"
#include "options.h"
#ifdef __riscos__
#define BYTEF_CAST(a) ((Bytef *)(a))
#else
#define BYTEF_CAST(a) (a)
#endif
int compress_filter_bz2( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len);
#ifdef HAVE_ZIP
static void
init_compress( compress_filter_context_t *zfx, z_stream *zs )
{
int rc;
int level;
#if defined(__riscos__) && defined(USE_ZLIBRISCOS)
static int zlib_initialized = 0;
if (!zlib_initialized)
zlib_initialized = riscos_load_module("ZLib", zlib_path, 1);
#endif
if( opt.compress_level >= 1 && opt.compress_level <= 9 )
level = opt.compress_level;
else if( opt.compress_level == -1 )
level = Z_DEFAULT_COMPRESSION;
else {
log_error("invalid compression level; using default level\n");
level = Z_DEFAULT_COMPRESSION;
}
if( (rc = zfx->algo == 1? deflateInit2( zs, level, Z_DEFLATED,
-13, 8, Z_DEFAULT_STRATEGY)
: deflateInit( zs, level )
) != Z_OK ) {
log_fatal("zlib problem: %s\n", zs->msg? zs->msg :
rc == Z_MEM_ERROR ? "out of core" :
rc == Z_VERSION_ERROR ? "invalid lib version" :
"unknown error" );
}
zfx->outbufsize = 8192;
zfx->outbuf = xmalloc( zfx->outbufsize );
}
static int
do_compress( compress_filter_context_t *zfx, z_stream *zs, int flush, IOBUF a )
{
int rc;
int zrc;
unsigned n;
do {
zs->next_out = BYTEF_CAST (zfx->outbuf);
zs->avail_out = zfx->outbufsize;
if( DBG_FILTER )
log_debug("enter deflate: avail_in=%u, avail_out=%u, flush=%d\n",
(unsigned)zs->avail_in, (unsigned)zs->avail_out, flush );
zrc = deflate( zs, flush );
if( zrc == Z_STREAM_END && flush == Z_FINISH )
;
else if( zrc != Z_OK ) {
if( zs->msg )
log_fatal("zlib deflate problem: %s\n", zs->msg );
else
log_fatal("zlib deflate problem: rc=%d\n", zrc );
}
n = zfx->outbufsize - zs->avail_out;
if( DBG_FILTER )
log_debug("leave deflate: "
"avail_in=%u, avail_out=%u, n=%u, zrc=%d\n",
(unsigned)zs->avail_in, (unsigned)zs->avail_out,
(unsigned)n, zrc );
if( (rc=iobuf_write( a, zfx->outbuf, n )) ) {
log_debug("deflate: iobuf_write failed\n");
return rc;
}
} while( zs->avail_in || (flush == Z_FINISH && zrc != Z_STREAM_END) );
return 0;
}
static void
init_uncompress( compress_filter_context_t *zfx, z_stream *zs )
{
int rc;
/****************
* PGP uses a windowsize of 13 bits. Using a negative value for
* it forces zlib not to expect a zlib header. This is a
* undocumented feature Peter Gutmann told me about.
*
* We must use 15 bits for the inflator because CryptoEx uses 15
* bits thus the output would get scrambled w/o error indication
* if we would use 13 bits. For the uncompressing this does not
* matter at all.
*/
if( (rc = zfx->algo == 1? inflateInit2( zs, -15)
: inflateInit( zs )) != Z_OK ) {
log_fatal("zlib problem: %s\n", zs->msg? zs->msg :
rc == Z_MEM_ERROR ? "out of core" :
rc == Z_VERSION_ERROR ? "invalid lib version" :
"unknown error" );
}
zfx->inbufsize = 2048;
zfx->inbuf = xmalloc( zfx->inbufsize );
zs->avail_in = 0;
}
static int
do_uncompress( compress_filter_context_t *zfx, z_stream *zs,
IOBUF a, size_t *ret_len )
{
int zrc;
int rc = 0;
int leave = 0;
size_t n;
int nread, count;
int refill = !zs->avail_in;
if( DBG_FILTER )
log_debug("begin inflate: avail_in=%u, avail_out=%u, inbuf=%u\n",
(unsigned)zs->avail_in, (unsigned)zs->avail_out,
(unsigned)zfx->inbufsize );
do {
if( zs->avail_in < zfx->inbufsize && refill ) {
n = zs->avail_in;
if( !n )
zs->next_in = BYTEF_CAST (zfx->inbuf);
count = zfx->inbufsize - n;
nread = iobuf_read( a, zfx->inbuf + n, count );
if( nread == -1 ) nread = 0;
n += nread;
/* Algo 1 has no zlib header which requires us to give
* inflate an extra dummy byte to read. To be on the safe
* side we allow for up to 4 ff bytes. */
if( nread < count && zfx->algo == 1 && zfx->algo1hack < 4) {
*(zfx->inbuf + n) = 0xFF;
zfx->algo1hack++;
n++;
leave = 1;
}
zs->avail_in = n;
}
refill = 1;
if( DBG_FILTER )
log_debug("enter inflate: avail_in=%u, avail_out=%u\n",
(unsigned)zs->avail_in, (unsigned)zs->avail_out);
zrc = inflate ( zs, Z_SYNC_FLUSH );
if( DBG_FILTER )
log_debug("leave inflate: avail_in=%u, avail_out=%u, zrc=%d\n",
(unsigned)zs->avail_in, (unsigned)zs->avail_out, zrc);
if( zrc == Z_STREAM_END )
rc = -1; /* eof */
else if( zrc != Z_OK && zrc != Z_BUF_ERROR ) {
if( zs->msg )
log_fatal("zlib inflate problem: %s\n", zs->msg );
else
log_fatal("zlib inflate problem: rc=%d\n", zrc );
}
} while (zs->avail_out && zrc != Z_STREAM_END && zrc != Z_BUF_ERROR
&& !leave);
*ret_len = zfx->outbufsize - zs->avail_out;
if( DBG_FILTER )
log_debug("do_uncompress: returning %u bytes (%u ignored)\n",
(unsigned int)*ret_len, (unsigned int)zs->avail_in );
return rc;
}
static int
compress_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
compress_filter_context_t *zfx = opaque;
z_stream *zs = zfx->opaque;
int rc=0;
if( control == IOBUFCTRL_UNDERFLOW ) {
if( !zfx->status ) {
zs = zfx->opaque = xmalloc_clear( sizeof *zs );
init_uncompress( zfx, zs );
zfx->status = 1;
}
zs->next_out = BYTEF_CAST (buf);
zs->avail_out = size;
zfx->outbufsize = size; /* needed only for calculation */
rc = do_uncompress( zfx, zs, a, ret_len );
}
else if( control == IOBUFCTRL_FLUSH ) {
if( !zfx->status ) {
PACKET pkt;
PKT_compressed cd;
if(zfx->algo != COMPRESS_ALGO_ZIP
&& zfx->algo != COMPRESS_ALGO_ZLIB)
BUG();
memset( &cd, 0, sizeof cd );
cd.len = 0;
cd.algorithm = zfx->algo;
/* Fixme: We should force a new CTB here:
cd.new_ctb = zfx->new_ctb;
*/
init_packet( &pkt );
pkt.pkttype = PKT_COMPRESSED;
pkt.pkt.compressed = &cd;
if( build_packet( a, &pkt ))
log_bug("build_packet(PKT_COMPRESSED) failed\n");
zs = zfx->opaque = xmalloc_clear( sizeof *zs );
init_compress( zfx, zs );
zfx->status = 2;
}
zs->next_in = BYTEF_CAST (buf);
zs->avail_in = size;
rc = do_compress( zfx, zs, Z_NO_FLUSH, a );
}
else if( control == IOBUFCTRL_FREE ) {
if( zfx->status == 1 ) {
inflateEnd(zs);
xfree(zs);
zfx->opaque = NULL;
xfree(zfx->outbuf); zfx->outbuf = NULL;
}
else if( zfx->status == 2 ) {
zs->next_in = BYTEF_CAST (buf);
zs->avail_in = 0;
do_compress( zfx, zs, Z_FINISH, a );
deflateEnd(zs);
xfree(zs);
zfx->opaque = NULL;
xfree(zfx->outbuf); zfx->outbuf = NULL;
}
if (zfx->release)
zfx->release (zfx);
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "compress_filter", *ret_len);
return rc;
}
#endif /*HAVE_ZIP*/
static void
release_context (compress_filter_context_t *ctx)
{
xfree(ctx->inbuf);
ctx->inbuf = NULL;
xfree(ctx->outbuf);
ctx->outbuf = NULL;
xfree (ctx);
}
/****************
* Handle a compressed packet
*/
int
handle_compressed (ctrl_t ctrl, void *procctx, PKT_compressed *cd,
int (*callback)(IOBUF, void *), void *passthru )
{
compress_filter_context_t *cfx;
int rc;
if(check_compress_algo(cd->algorithm))
return GPG_ERR_COMPR_ALGO;
cfx = xmalloc_clear (sizeof *cfx);
cfx->release = release_context;
cfx->algo = cd->algorithm;
push_compress_filter(cd->buf,cfx,cd->algorithm);
if( callback )
rc = callback(cd->buf, passthru );
else
rc = proc_packets (ctrl,procctx, cd->buf);
cd->buf = NULL;
return rc;
}
void
push_compress_filter(IOBUF out,compress_filter_context_t *zfx,int algo)
{
push_compress_filter2(out,zfx,algo,0);
}
void
push_compress_filter2(IOBUF out,compress_filter_context_t *zfx,
int algo,int rel)
{
if(algo>=0)
zfx->algo=algo;
else
zfx->algo=DEFAULT_COMPRESS_ALGO;
switch(zfx->algo)
{
case COMPRESS_ALGO_NONE:
break;
#ifdef HAVE_ZIP
case COMPRESS_ALGO_ZIP:
case COMPRESS_ALGO_ZLIB:
iobuf_push_filter2(out,compress_filter,zfx,rel);
break;
#endif
#ifdef HAVE_BZIP2
case COMPRESS_ALGO_BZIP2:
iobuf_push_filter2(out,compress_filter_bz2,zfx,rel);
break;
#endif
default:
BUG();
}
}
diff --git a/g10/cpr.c b/g10/cpr.c
index 4984e8903..339107184 100644
--- a/g10/cpr.c
+++ b/g10/cpr.c
@@ -1,630 +1,630 @@
/* status.c - Status message and command-fd interface
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004, 2005, 2006, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include "gpg.h"
-#include "util.h"
-#include "status.h"
-#include "ttyio.h"
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/ttyio.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#define CONTROL_D ('D' - 'A' + 1)
/* The stream to output the status information. Output is disabled if
this is NULL. */
static estream_t statusfp;
static void
progress_cb (void *ctx, const char *what, int printchar,
int current, int total)
{
char buf[50];
(void)ctx;
if ( printchar == '\n' && !strcmp (what, "primegen") )
snprintf (buf, sizeof buf, "%.20s X 100 100", what );
else
snprintf (buf, sizeof buf, "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total );
write_status_text (STATUS_PROGRESS, buf);
}
/* Return true if the status message NO may currently be issued. We
need this to avoid syncronisation problem while auto retrieving a
key. There it may happen that a status NODATA is issued for a non
available key and the user may falsely interpret this has a missing
signature. */
static int
status_currently_allowed (int no)
{
if (!glo_ctrl.in_auto_key_retrieve)
return 1; /* Yes. */
/* We allow some statis anyway, so that import statistics are
correct and to avoid problems if the retriebval subsystem will
prompt the user. */
switch (no)
{
case STATUS_GET_BOOL:
case STATUS_GET_LINE:
case STATUS_GET_HIDDEN:
case STATUS_GOT_IT:
case STATUS_IMPORTED:
case STATUS_IMPORT_OK:
case STATUS_IMPORT_CHECK:
case STATUS_IMPORT_RES:
return 1; /* Yes. */
default:
break;
}
return 0; /* No. */
}
void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr )
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (! gnupg_fd_valid (fd))
log_fatal ("status-fd is invalid: %s\n", strerror (errno));
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, strerror (errno));
}
last_fd = fd;
gcry_set_progress_handler (progress_cb, NULL);
}
int
is_status_enabled ()
{
return !!statusfp;
}
void
write_status ( int no )
{
write_status_text( no, NULL );
}
/* Write a status line with code NO followed by the string TEXT and
directly followed by the remaining strings up to a NULL. */
void
write_status_strings (int no, const char *text, ...)
{
va_list arg_ptr;
const char *s;
if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if ( text )
{
es_putc ( ' ', statusfp);
va_start (arg_ptr, text);
s = text;
do
{
for (; *s; s++)
{
if (*s == '\n')
es_fputs ("\\n", statusfp);
else if (*s == '\r')
es_fputs ("\\r", statusfp);
else
es_fputc (*(const byte *)s, statusfp);
}
}
while ((s = va_arg (arg_ptr, const char*)));
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
void
write_status_text (int no, const char *text)
{
write_status_strings (no, text, NULL);
}
/* Write a status line with code NO followed by the outout of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
write_status_printf (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc ( ' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Write an ERROR status line using a full gpg-error error value. */
void
write_status_error (const char *where, gpg_error_t err)
{
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, err);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Same as above but outputs the error code only. */
void
write_status_errcode (const char *where, int errcode)
{
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, gpg_err_code (errcode));
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Write a FAILURE status line. */
void
write_status_failure (const char *where, gpg_error_t err)
{
if (!statusfp || !status_currently_allowed (STATUS_FAILURE))
return; /* Not enabled or allowed. */
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_FAILURE), where, err);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/*
* Write a status line with a buffer using %XX escapes. If WRAP is >
* 0 wrap the line after this length. If STRING is not NULL it will
* be prepended to the buffer, no escaping is done for string.
* A wrap of -1 forces spaces not to be encoded as %20.
*/
void
write_status_text_and_buffer (int no, const char *string,
const char *buffer, size_t len, int wrap)
{
const char *s, *text;
int esc, first;
int lower_limit = ' ';
size_t n, count, dowrap;
if (!statusfp || !status_currently_allowed (no))
return; /* Not enabled or allowed. */
if (wrap == -1)
{
lower_limit--;
wrap = 0;
}
text = get_status_string (no);
count = dowrap = first = 1;
do
{
if (dowrap)
{
es_fprintf (statusfp, "[GNUPG:] %s ", text);
count = dowrap = 0;
if (first && string)
{
es_fputs (string, statusfp);
count += strlen (string);
/* Make sure that there is a space after the string. */
if (*string && string[strlen (string)-1] != ' ')
{
es_putc (' ', statusfp);
count++;
}
}
first = 0;
}
for (esc=0, s=buffer, n=len; n && !esc; s++, n--)
{
if (*s == '%' || *(const byte*)s <= lower_limit
|| *(const byte*)s == 127 )
esc = 1;
if (wrap && ++count > wrap)
{
dowrap=1;
break;
}
}
if (esc)
{
s--; n++;
}
if (s != buffer)
es_fwrite (buffer, s-buffer, 1, statusfp);
if ( esc )
{
es_fprintf (statusfp, "%%%02X", *(const byte*)s );
s++; n--;
}
buffer = s;
len = n;
if (dowrap && len)
es_putc ('\n', statusfp);
}
while (len);
es_putc ('\n',statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
void
write_status_buffer (int no, const char *buffer, size_t len, int wrap)
{
write_status_text_and_buffer (no, NULL, buffer, len, wrap);
}
/* Print the BEGIN_SIGNING status message. If MD is not NULL it is
used to retrieve the hash algorithms used for the message. */
void
write_status_begin_signing (gcry_md_hd_t md)
{
if (md)
{
char buf[100];
size_t buflen;
int i, ga;
buflen = 0;
for (i=1; i <= 110; i++)
{
ga = map_md_openpgp_to_gcry (i);
if (ga && gcry_md_is_enabled (md, ga) && buflen+10 < DIM(buf))
{
snprintf (buf+buflen, DIM(buf) - buflen,
"%sH%d", buflen? " ":"",i);
buflen += strlen (buf+buflen);
}
}
write_status_text (STATUS_BEGIN_SIGNING, buf);
}
else
write_status ( STATUS_BEGIN_SIGNING );
}
static int
myread(int fd, void *buf, size_t count)
{
int rc;
do
{
rc = read( fd, buf, count );
}
while (rc == -1 && errno == EINTR);
if (!rc && count)
{
static int eof_emmited=0;
if ( eof_emmited < 3 )
{
*(char*)buf = CONTROL_D;
rc = 1;
eof_emmited++;
}
else /* Ctrl-D not caught - do something reasonable */
{
#ifdef HAVE_DOSISH_SYSTEM
#ifndef HAVE_W32CE_SYSTEM
raise (SIGINT); /* Nothing to hangup under DOS. */
#endif
#else
raise (SIGHUP); /* No more input data. */
#endif
}
}
return rc;
}
/* Request a string from the client over the command-fd. If GETBOOL
is set the function returns a static string (do not free) if the
entered value was true or NULL if the entered value was false. */
static char *
do_get_from_fd ( const char *keyword, int hidden, int getbool )
{
int i, len;
char *string;
if (statusfp != es_stdout)
es_fflush (es_stdout);
write_status_text (getbool? STATUS_GET_BOOL :
hidden? STATUS_GET_HIDDEN : STATUS_GET_LINE, keyword);
for (string = NULL, i = len = 200; ; i++ )
{
if (i >= len-1 )
{
char *save = string;
len += 100;
string = hidden? xmalloc_secure ( len ) : xmalloc ( len );
if (save)
memcpy (string, save, i );
else
i = 0;
}
/* Fixme: why not use our read_line function here? */
if ( myread( opt.command_fd, string+i, 1) != 1 || string[i] == '\n' )
break;
else if ( string[i] == CONTROL_D )
{
/* Found ETX - Cancel the line and return a sole ETX. */
string[0] = CONTROL_D;
i = 1;
break;
}
}
string[i] = 0;
write_status (STATUS_GOT_IT);
if (getbool) /* Fixme: is this correct??? */
return (string[0] == 'Y' || string[0] == 'y') ? "" : NULL;
return string;
}
int
cpr_enabled()
{
if( opt.command_fd != -1 )
return 1;
return 0;
}
char *
cpr_get_no_help( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 0, 0 );
for(;;) {
p = tty_get( prompt );
return p;
}
}
char *
cpr_get( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 0, 0 );
for(;;) {
p = tty_get( prompt );
if( *p=='?' && !p[1] && !(keyword && !*keyword)) {
xfree(p);
display_online_help( keyword );
}
else
return p;
}
}
char *
cpr_get_utf8( const char *keyword, const char *prompt )
{
char *p;
p = cpr_get( keyword, prompt );
if( p ) {
char *utf8 = native_to_utf8( p );
xfree( p );
p = utf8;
}
return p;
}
char *
cpr_get_hidden( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 1, 0 );
for(;;) {
p = tty_get_hidden( prompt );
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else
return p;
}
}
void
cpr_kill_prompt(void)
{
if( opt.command_fd != -1 )
return;
tty_kill_prompt();
return;
}
int
cpr_get_answer_is_yes_def (const char *keyword, const char *prompt, int def_yes)
{
int yes;
char *p;
if( opt.command_fd != -1 )
return !!do_get_from_fd ( keyword, 0, 1 );
for(;;) {
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else {
tty_kill_prompt();
yes = answer_is_yes_no_default (p, def_yes);
xfree(p);
return yes;
}
}
}
int
cpr_get_answer_is_yes (const char *keyword, const char *prompt)
{
return cpr_get_answer_is_yes_def (keyword, prompt, 0);
}
int
cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt )
{
int yes;
char *p;
if( opt.command_fd != -1 )
return !!do_get_from_fd ( keyword, 0, 1 );
for(;;) {
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else {
tty_kill_prompt();
yes = answer_is_yes_no_quit(p);
xfree(p);
return yes;
}
}
}
int
cpr_get_answer_okay_cancel (const char *keyword,
const char *prompt,
int def_answer)
{
int yes;
char *answer = NULL;
char *p;
if( opt.command_fd != -1 )
answer = do_get_from_fd ( keyword, 0, 0 );
if (answer)
{
yes = answer_is_okay_cancel (answer, def_answer);
xfree (answer);
return yes;
}
for(;;)
{
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if (*p == '?' && !p[1])
{
xfree(p);
display_online_help (keyword);
}
else
{
tty_kill_prompt();
yes = answer_is_okay_cancel (p, def_answer);
xfree(p);
return yes;
}
}
}
diff --git a/g10/dearmor.c b/g10/dearmor.c
index 6217ddab5..92239ccfd 100644
--- a/g10/dearmor.c
+++ b/g10/dearmor.c
@@ -1,131 +1,131 @@
/* dearmor.c - Armor utility
* Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "filter.h"
#include "packet.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/****************
* Take an armor file and write it out without armor
*/
int
dearmor_file( const char *fname )
{
armor_filter_context_t *afx;
IOBUF inp = NULL, out = NULL;
int rc = 0;
int c;
afx = new_armor_context ();
/* prepare iobufs */
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp) {
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), fname? fname: "[stdin]",
strerror(errno) );
goto leave;
}
push_armor_filter ( afx, inp );
if( (rc = open_outfile (-1, fname, 0, 0, &out)) )
goto leave;
while( (c = iobuf_get(inp)) != -1 )
iobuf_put( out, c );
leave:
if( rc )
iobuf_cancel(out);
else
iobuf_close(out);
iobuf_close(inp);
release_armor_context (afx);
return rc;
}
/****************
* Take file and write it out with armor
*/
int
enarmor_file( const char *fname )
{
armor_filter_context_t *afx;
IOBUF inp = NULL, out = NULL;
int rc = 0;
int c;
afx = new_armor_context ();
/* prepare iobufs */
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp) {
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), fname? fname: "[stdin]",
strerror(errno) );
goto leave;
}
if( (rc = open_outfile (-1, fname, 1, 0, &out )) )
goto leave;
afx->what = 4;
afx->hdrlines = "Comment: Use \"gpg --dearmor\" for unpacking\n";
push_armor_filter ( afx, out );
while( (c = iobuf_get(inp)) != -1 )
iobuf_put( out, c );
leave:
if( rc )
iobuf_cancel(out);
else
iobuf_close(out);
iobuf_close(inp);
release_armor_context (afx);
return rc;
}
diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c
index 585b1507f..11a253ff6 100644
--- a/g10/decrypt-data.c
+++ b/g10/decrypt-data.c
@@ -1,501 +1,501 @@
/* decrypt-data.c - Decrypt an encrypted data packet
* Copyright (C) 1998, 1999, 2000, 2001, 2005,
* 2006, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "options.h"
-#include "i18n.h"
-#include "status.h"
+#include "../common/i18n.h"
+#include "../common/status.h"
static int mdc_decode_filter ( void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len);
static int decode_filter ( void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len);
typedef struct decode_filter_context_s
{
gcry_cipher_hd_t cipher_hd;
gcry_md_hd_t mdc_hash;
char defer[22];
int defer_filled;
int eof_seen;
int refcount;
int partial; /* Working on a partial length packet. */
size_t length; /* If !partial: Remaining bytes in the packet. */
} *decode_filter_ctx_t;
/* Helper to release the decode context. */
static void
release_dfx_context (decode_filter_ctx_t dfx)
{
if (!dfx)
return;
log_assert (dfx->refcount);
if ( !--dfx->refcount )
{
gcry_cipher_close (dfx->cipher_hd);
dfx->cipher_hd = NULL;
gcry_md_close (dfx->mdc_hash);
dfx->mdc_hash = NULL;
xfree (dfx);
}
}
/****************
* Decrypt the data, specified by ED with the key DEK.
*/
int
decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
{
decode_filter_ctx_t dfx;
byte *p;
int rc=0, c, i;
byte temp[32];
unsigned blocksize;
unsigned nprefix;
dfx = xtrycalloc (1, sizeof *dfx);
if (!dfx)
return gpg_error_from_syserror ();
dfx->refcount = 1;
if ( opt.verbose && !dek->algo_info_printed )
{
if (!openpgp_cipher_test_algo (dek->algo))
log_info (_("%s encrypted data\n"),
openpgp_cipher_algo_name (dek->algo));
else
log_info (_("encrypted with unknown algorithm %d\n"), dek->algo );
dek->algo_info_printed = 1;
}
{
char buf[20];
snprintf (buf, sizeof buf, "%d %d", ed->mdc_method, dek->algo);
write_status_text (STATUS_DECRYPTION_INFO, buf);
}
if (opt.show_session_key)
{
char numbuf[25];
char *hexbuf;
snprintf (numbuf, sizeof numbuf, "%d:", dek->algo);
hexbuf = bin2hex (dek->key, dek->keylen, NULL);
if (!hexbuf)
{
rc = gpg_error_from_syserror ();
goto leave;
}
log_info ("session key: '%s%s'\n", numbuf, hexbuf);
write_status_strings (STATUS_SESSION_KEY, numbuf, hexbuf, NULL);
xfree (hexbuf);
}
rc = openpgp_cipher_test_algo (dek->algo);
if (rc)
goto leave;
blocksize = openpgp_cipher_get_algo_blklen (dek->algo);
if ( !blocksize || blocksize > 16 )
log_fatal ("unsupported blocksize %u\n", blocksize );
nprefix = blocksize;
if ( ed->len && ed->len < (nprefix+2) )
{
/* An invalid message. We can't check that during parsing
because we may not know the used cipher then. */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if ( ed->mdc_method )
{
if (gcry_md_open (&dfx->mdc_hash, ed->mdc_method, 0 ))
BUG ();
if ( DBG_HASHING )
gcry_md_debug (dfx->mdc_hash, "checkmdc");
}
rc = openpgp_cipher_open (&dfx->cipher_hd, dek->algo,
GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| ((ed->mdc_method || dek->algo >= 100)?
0 : GCRY_CIPHER_ENABLE_SYNC)));
if (rc)
{
/* We should never get an error here cause we already checked
* that the algorithm is available. */
BUG();
}
/* log_hexdump( "thekey", dek->key, dek->keylen );*/
rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen);
if ( gpg_err_code (rc) == GPG_ERR_WEAK_KEY )
{
log_info(_("WARNING: message was encrypted with"
" a weak key in the symmetric cipher.\n"));
rc=0;
}
else if( rc )
{
log_error("key setup failed: %s\n", gpg_strerror (rc) );
goto leave;
}
if (!ed->buf)
{
log_error(_("problem handling encrypted packet\n"));
goto leave;
}
gcry_cipher_setiv (dfx->cipher_hd, NULL, 0);
if ( ed->len )
{
for (i=0; i < (nprefix+2) && ed->len; i++, ed->len-- )
{
if ( (c=iobuf_get(ed->buf)) == -1 )
break;
else
temp[i] = c;
}
}
else
{
for (i=0; i < (nprefix+2); i++ )
if ( (c=iobuf_get(ed->buf)) == -1 )
break;
else
temp[i] = c;
}
gcry_cipher_decrypt (dfx->cipher_hd, temp, nprefix+2, NULL, 0);
gcry_cipher_sync (dfx->cipher_hd);
p = temp;
/* log_hexdump( "prefix", temp, nprefix+2 ); */
if (dek->symmetric
&& (p[nprefix-2] != p[nprefix] || p[nprefix-1] != p[nprefix+1]) )
{
rc = gpg_error (GPG_ERR_BAD_KEY);
goto leave;
}
if ( dfx->mdc_hash )
gcry_md_write (dfx->mdc_hash, temp, nprefix+2);
dfx->refcount++;
dfx->partial = ed->is_partial;
dfx->length = ed->len;
if ( ed->mdc_method )
iobuf_push_filter ( ed->buf, mdc_decode_filter, dfx );
else
iobuf_push_filter ( ed->buf, decode_filter, dfx );
if (opt.unwrap_encryption)
{
char *filename;
estream_t fp;
rc = get_output_file ("", 0, ed->buf, &filename, &fp);
if (! rc)
{
iobuf_t output = iobuf_esopen (fp, "w", 0);
armor_filter_context_t *afx = NULL;
if (opt.armor)
{
afx = new_armor_context ();
push_armor_filter (afx, output);
}
iobuf_copy (output, ed->buf);
if ((rc = iobuf_error (ed->buf)))
log_error (_("error reading '%s': %s\n"),
filename, gpg_strerror (rc));
else if ((rc = iobuf_error (output)))
log_error (_("error writing '%s': %s\n"),
filename, gpg_strerror (rc));
iobuf_close (output);
if (afx)
release_armor_context (afx);
}
}
else
proc_packets (ctrl, procctx, ed->buf );
ed->buf = NULL;
if (dfx->eof_seen > 1 )
rc = gpg_error (GPG_ERR_INV_PACKET);
else if ( ed->mdc_method )
{
/* We used to let parse-packet.c handle the MDC packet but this
turned out to be a problem with compressed packets: With old
style packets there is no length information available and
the decompressor uses an implicit end. However we can't know
this implicit end beforehand (:-) and thus may feed the
decompressor with more bytes than actually needed. It would
be possible to unread the extra bytes but due to our weird
iobuf system any unread is non reliable due to filters
already popped off. The easy and sane solution is to care
about the MDC packet only here and never pass it to the
packet parser. Fortunatley the OpenPGP spec requires a
strict format for the MDC packet so that we know that 22
bytes are appended. */
int datalen = gcry_md_get_algo_dlen (ed->mdc_method);
log_assert (dfx->cipher_hd);
log_assert (dfx->mdc_hash);
gcry_cipher_decrypt (dfx->cipher_hd, dfx->defer, 22, NULL, 0);
gcry_md_write (dfx->mdc_hash, dfx->defer, 2);
gcry_md_final (dfx->mdc_hash);
if ( dfx->defer[0] != '\xd3'
|| dfx->defer[1] != '\x14'
|| datalen != 20
|| memcmp (gcry_md_read (dfx->mdc_hash, 0), dfx->defer+2, datalen))
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
/* log_printhex("MDC message:", dfx->defer, 22); */
/* log_printhex("MDC calc:", gcry_md_read (dfx->mdc_hash,0), datalen); */
}
leave:
release_dfx_context (dfx);
return rc;
}
static int
mdc_decode_filter (void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len)
{
decode_filter_ctx_t dfx = opaque;
size_t n, size = *ret_len;
int rc = 0;
int c;
/* Note: We need to distinguish between a partial and a fixed length
packet. The first is the usual case as created by GPG. However
for short messages the format degrades to a fixed length packet
and other implementations might use fixed length as well. Only
looking for the EOF on fixed data works only if the encrypted
packet is not followed by other data. This used to be a long
standing bug which was fixed on 2009-10-02. */
if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen )
{
*ret_len = 0;
rc = -1;
}
else if( control == IOBUFCTRL_UNDERFLOW )
{
log_assert (a);
log_assert (size > 44); /* Our code requires at least this size. */
/* Get at least 22 bytes and put it ahead in the buffer. */
if (dfx->partial)
{
for (n=22; n < 44; n++)
{
if ( (c = iobuf_get(a)) == -1 )
break;
buf[n] = c;
}
}
else
{
for (n=22; n < 44 && dfx->length; n++, dfx->length--)
{
c = iobuf_get (a);
if (c == -1)
break; /* Premature EOF. */
buf[n] = c;
}
}
if (n == 44)
{
/* We have enough stuff - flush the deferred stuff. */
if ( !dfx->defer_filled ) /* First time. */
{
memcpy (buf, buf+22, 22);
n = 22;
}
else
{
memcpy (buf, dfx->defer, 22);
}
/* Fill up the buffer. */
if (dfx->partial)
{
for (; n < size; n++ )
{
if ( (c = iobuf_get(a)) == -1 )
{
dfx->eof_seen = 1; /* Normal EOF. */
break;
}
buf[n] = c;
}
}
else
{
for (; n < size && dfx->length; n++, dfx->length--)
{
c = iobuf_get(a);
if (c == -1)
{
dfx->eof_seen = 3; /* Premature EOF. */
break;
}
buf[n] = c;
}
if (!dfx->length)
dfx->eof_seen = 1; /* Normal EOF. */
}
/* Move the trailing 22 bytes back to the defer buffer. We
have at least 44 bytes thus a memmove is not needed. */
n -= 22;
memcpy (dfx->defer, buf+n, 22 );
dfx->defer_filled = 1;
}
else if ( !dfx->defer_filled ) /* EOF seen but empty defer buffer. */
{
/* This is bad because it means an incomplete hash. */
n -= 22;
memcpy (buf, buf+22, n );
dfx->eof_seen = 2; /* EOF with incomplete hash. */
}
else /* EOF seen (i.e. read less than 22 bytes). */
{
memcpy (buf, dfx->defer, 22 );
n -= 22;
memcpy (dfx->defer, buf+n, 22 );
dfx->eof_seen = 1; /* Normal EOF. */
}
if ( n )
{
if ( dfx->cipher_hd )
gcry_cipher_decrypt (dfx->cipher_hd, buf, n, NULL, 0);
if ( dfx->mdc_hash )
gcry_md_write (dfx->mdc_hash, buf, n);
}
else
{
log_assert ( dfx->eof_seen );
rc = -1; /* Return EOF. */
}
*ret_len = n;
}
else if ( control == IOBUFCTRL_FREE )
{
release_dfx_context (dfx);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "mdc_decode_filter", *ret_len);
}
return rc;
}
static int
decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len)
{
decode_filter_ctx_t fc = opaque;
size_t size = *ret_len;
size_t n;
int c, rc = 0;
if ( control == IOBUFCTRL_UNDERFLOW && fc->eof_seen )
{
*ret_len = 0;
rc = -1;
}
else if ( control == IOBUFCTRL_UNDERFLOW )
{
log_assert (a);
if (fc->partial)
{
for (n=0; n < size; n++ )
{
c = iobuf_get(a);
if (c == -1)
{
fc->eof_seen = 1; /* Normal EOF. */
break;
}
buf[n] = c;
}
}
else
{
for (n=0; n < size && fc->length; n++, fc->length--)
{
c = iobuf_get(a);
if (c == -1)
{
fc->eof_seen = 3; /* Premature EOF. */
break;
}
buf[n] = c;
}
if (!fc->length)
fc->eof_seen = 1; /* Normal EOF. */
}
if (n)
{
if (fc->cipher_hd)
gcry_cipher_decrypt (fc->cipher_hd, buf, n, NULL, 0);
}
else
{
if (!fc->eof_seen)
fc->eof_seen = 1;
rc = -1; /* Return EOF. */
}
*ret_len = n;
}
else if ( control == IOBUFCTRL_FREE )
{
release_dfx_context (fc);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "decode_filter", *ret_len);
}
return rc;
}
diff --git a/g10/decrypt.c b/g10/decrypt.c
index c99f0640e..4d6734d40 100644
--- a/g10/decrypt.c
+++ b/g10/decrypt.c
@@ -1,281 +1,281 @@
/* decrypt.c - decrypt and verify data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
/* Assume that the input is an encrypted message and decrypt
* (and if signed, verify the signature on) it.
* This command differs from the default operation, as it never
* writes to the filename which is included in the file and it
* rejects files which don't begin with an encrypted message.
*/
int
decrypt_message (ctrl_t ctrl, const char *filename)
{
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx;
int rc;
int no_out = 0;
pfx = new_progress_context ();
/* Open the message file. */
fp = iobuf_open (filename);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if ( !fp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), print_fname_stdin(filename),
gpg_strerror (rc));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, filename);
if ( !opt.no_armor )
{
if ( use_armor_filter( fp ) )
{
afx = new_armor_context ();
push_armor_filter ( afx, fp );
}
}
if (!opt.outfile)
{
no_out = 1;
opt.outfile = "-";
}
rc = proc_encryption_packets (ctrl, NULL, fp );
if (no_out)
opt.outfile = NULL;
iobuf_close (fp);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
/* Same as decrypt_message but takes a file descriptor for input and
output. */
gpg_error_t
decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd)
{
#ifdef HAVE_W32_SYSTEM
/* No server mode yet. */
(void)ctrl;
(void)input_fd;
(void)output_fd;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#else
gpg_error_t err;
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx;
if (opt.outfp)
return gpg_error (GPG_ERR_BUG);
pfx = new_progress_context ();
/* Open the message file. */
fp = iobuf_fdopen_nc (FD2INT(input_fd), "rb");
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
char xname[64];
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", input_fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
release_progress_context (pfx);
return err;
}
#ifdef HAVE_W32CE_SYSTEM
#warning Need to fix this if we want to use g13
opt.outfp = NULL;
#else
opt.outfp = es_fdopen_nc (output_fd, "wb");
#endif
if (!opt.outfp)
{
char xname[64];
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", output_fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
iobuf_close (fp);
release_progress_context (pfx);
return err;
}
if (!opt.no_armor)
{
if (use_armor_filter (fp))
{
afx = new_armor_context ();
push_armor_filter ( afx, fp );
}
}
err = proc_encryption_packets (ctrl, NULL, fp );
iobuf_close (fp);
es_fclose (opt.outfp);
opt.outfp = NULL;
release_armor_context (afx);
release_progress_context (pfx);
return err;
#endif
}
void
decrypt_messages (ctrl_t ctrl, int nfiles, char *files[])
{
IOBUF fp;
progress_filter_context_t *pfx;
char *p, *output = NULL;
int rc=0,use_stdin=0;
unsigned int lno=0;
if (opt.outfile)
{
log_error(_("--output doesn't work for this command\n"));
return;
}
pfx = new_progress_context ();
if(!nfiles)
use_stdin=1;
for(;;)
{
char line[2048];
char *filename=NULL;
if(use_stdin)
{
if(fgets(line, DIM(line), stdin))
{
lno++;
if (!*line || line[strlen(line)-1] != '\n')
log_error("input line %u too long or missing LF\n", lno);
else
{
line[strlen(line)-1] = '\0';
filename=line;
}
}
}
else
{
if(nfiles)
{
filename=*files;
nfiles--;
files++;
}
}
if(filename==NULL)
break;
print_file_status(STATUS_FILE_START, filename, 3);
output = make_outfile_name(filename);
if (!output)
goto next_file;
fp = iobuf_open(filename);
if (fp)
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
log_error(_("can't open '%s'\n"), print_fname_stdin(filename));
goto next_file;
}
handle_progress (pfx, fp, filename);
if (!opt.no_armor)
{
if (use_armor_filter(fp))
{
armor_filter_context_t *afx = new_armor_context ();
rc = push_armor_filter (afx, fp);
if (rc)
log_error("failed to push armor filter");
release_armor_context (afx);
}
}
rc = proc_packets (ctrl,NULL, fp);
iobuf_close(fp);
if (rc)
log_error("%s: decryption failed: %s\n", print_fname_stdin(filename),
gpg_strerror (rc));
p = get_last_passphrase();
set_next_passphrase(p);
xfree (p);
next_file:
/* Note that we emit file_done even after an error. */
write_status( STATUS_FILE_DONE );
xfree(output);
reset_literals_seen();
}
set_next_passphrase(NULL);
release_progress_context (pfx);
}
diff --git a/g10/delkey.c b/g10/delkey.c
index 06aca9e85..547b40dd2 100644
--- a/g10/delkey.c
+++ b/g10/delkey.c
@@ -1,293 +1,292 @@
/* delkey.c - delete keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004,
* 2005, 2006 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
-#include "ttyio.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
#include "call-agent.h"
/****************
* Delete a public or secret key from a keyring.
* r_sec_avail will be set if a secret key is available and the public
* key can't be deleted for that reason.
*/
static gpg_error_t
do_delete_key( const char *username, int secret, int force, int *r_sec_avail )
{
gpg_error_t err;
kbnode_t keyblock = NULL;
kbnode_t node, kbctx;
KEYDB_HANDLE hd;
PKT_public_key *pk = NULL;
u32 keyid[2];
int okay=0;
int yes;
KEYDB_SEARCH_DESC desc;
int exactmatch;
*r_sec_avail = 0;
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
/* Search the userid. */
err = classify_user_id (username, &desc, 1);
exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_FPR16
|| desc.mode == KEYDB_SEARCH_MODE_FPR20);
if (!err)
err = keydb_search (hd, &desc, 1, NULL);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
write_status_text (STATUS_DELETE_PROBLEM, "1");
goto leave;
}
/* Read the keyblock. */
err = keydb_get_keyblock (hd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err) );
goto leave;
}
/* Get the keyid from the keyblock. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node)
{
log_error ("Oops; key not found anymore!\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
if (!secret && !force)
{
if (have_secret_key_with_kid (keyid))
{
*r_sec_avail = 1;
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
else
err = 0;
}
if (secret && !have_secret_key_with_kid (keyid))
{
err = gpg_error (GPG_ERR_NOT_FOUND);
log_error (_("key \"%s\" not found\n"), username);
write_status_text (STATUS_DELETE_PROBLEM, "1");
goto leave;
}
if (opt.batch && exactmatch)
okay++;
else if (opt.batch && secret)
{
log_error(_("can't do this in batch mode\n"));
log_info (_("(unless you specify the key by fingerprint)\n"));
}
else if (opt.batch && opt.answer_yes)
okay++;
else if (opt.batch)
{
log_error(_("can't do this in batch mode without \"--yes\"\n"));
log_info (_("(unless you specify the key by fingerprint)\n"));
}
else
{
if (secret)
print_seckey_info (pk);
else
print_pubkey_info (NULL, pk );
tty_printf( "\n" );
yes = cpr_get_answer_is_yes
(secret? "delete_key.secret.okay": "delete_key.okay",
_("Delete this key from the keyring? (y/N) "));
if (!cpr_enabled() && secret && yes)
{
/* I think it is not required to check a passphrase; if the
* user is so stupid as to let others access his secret
* keyring (and has no backup) - it is up him to read some
* very basic texts about security. */
yes = cpr_get_answer_is_yes
("delete_key.secret.okay",
_("This is a secret key! - really delete? (y/N) "));
}
if (yes)
okay++;
}
if (okay)
{
if (secret)
{
char *prompt;
gpg_error_t firsterr = 0;
char *hexgrip;
setup_main_keyids (keyblock);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
continue; /* No secret key for that public (sub)key. */
prompt = gpg_format_keydesc (node->pkt->pkt.public_key,
FORMAT_KEYDESC_DELKEY, 1);
err = hexkeygrip_from_pk (node->pkt->pkt.public_key, &hexgrip);
/* NB: We require --yes to advise the agent not to
* request a confirmation. The rationale for this extra
* pre-caution is that since 2.1 the secret key may also
* be used for other protocols and thus deleting it from
* the gpg would also delete the key for other tools. */
if (!err)
err = agent_delete_key (NULL, hexgrip, prompt,
opt.answer_yes);
xfree (prompt);
xfree (hexgrip);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_KEY_ON_CARD)
write_status_text (STATUS_DELETE_PROBLEM, "1");
log_error (_("deleting secret %s failed: %s\n"),
(node->pkt->pkttype == PKT_PUBLIC_KEY
? _("key"):_("subkey")),
gpg_strerror (err));
if (!firsterr)
firsterr = err;
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
write_status_error ("delete_key.secret", err);
break;
}
}
}
err = firsterr;
if (firsterr)
goto leave;
}
else
{
err = keydb_delete_keyblock (hd);
if (err)
{
log_error (_("deleting keyblock failed: %s\n"),
gpg_strerror (err));
goto leave;
}
}
/* Note that the ownertrust being cleared will trigger a
revalidation_mark(). This makes sense - only deleting keys
that have ownertrust set should trigger this. */
if (!secret && pk && clear_ownertrusts (pk))
{
if (opt.verbose)
log_info (_("ownertrust information cleared\n"));
}
}
leave:
keydb_release (hd);
release_kbnode (keyblock);
return err;
}
/****************
* Delete a public or secret key from a keyring.
*/
gpg_error_t
delete_keys (strlist_t names, int secret, int allow_both)
{
gpg_error_t err;
int avail;
int force = (!allow_both && !secret && opt.expert);
/* Force allows us to delete a public key even if a secret key
exists. */
for ( ;names ; names=names->next )
{
err = do_delete_key (names->d, secret, force, &avail);
if (err && avail)
{
if (allow_both)
{
err = do_delete_key (names->d, 1, 0, &avail);
if (!err)
err = do_delete_key (names->d, 0, 0, &avail);
}
else
{
log_error (_("there is a secret key for public key \"%s\"!\n"),
names->d);
log_info(_("use option \"--delete-secret-keys\" to delete"
" it first.\n"));
write_status_text (STATUS_DELETE_PROBLEM, "2");
return err;
}
}
if (err)
{
log_error ("%s: delete key failed: %s\n",
names->d, gpg_strerror (err));
return err;
}
}
return 0;
}
diff --git a/g10/ecdh.c b/g10/ecdh.c
index 89e8cf1cb..6c2a56b84 100644
--- a/g10/ecdh.c
+++ b/g10/ecdh.c
@@ -1,495 +1,495 @@
/* ecdh.c - ECDH public key operations used in public key glue code
* Copyright (C) 2010, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "pkglue.h"
#include "main.h"
#include "options.h"
/* A table with the default KEK parameters used by GnuPG. */
static const struct
{
unsigned int qbits;
int openpgp_hash_id; /* KEK digest algorithm. */
int openpgp_cipher_id; /* KEK cipher algorithm. */
} kek_params_table[] =
/* Note: Must be sorted by ascending values for QBITS. */
{
{ 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES },
{ 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES256 },
/* Note: 528 is 521 rounded to the 8 bit boundary */
{ 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 }
};
/* Return KEK parameters as an opaque MPI The caller must free the
returned value. Returns NULL and sets ERRNO on error. */
gcry_mpi_t
pk_ecdh_default_params (unsigned int qbits)
{
byte *kek_params;
int i;
kek_params = xtrymalloc (4);
if (!kek_params)
return NULL;
kek_params[0] = 3; /* Number of bytes to follow. */
kek_params[1] = 1; /* Version for KDF+AESWRAP. */
/* Search for matching KEK parameter. Defaults to the strongest
possible choices. Performance is not an issue here, only
interoperability. */
for (i=0; i < DIM (kek_params_table); i++)
{
if (kek_params_table[i].qbits >= qbits
|| i+1 == DIM (kek_params_table))
{
kek_params[2] = kek_params_table[i].openpgp_hash_id;
kek_params[3] = kek_params_table[i].openpgp_cipher_id;
break;
}
}
log_assert (i < DIM (kek_params_table));
if (DBG_CRYPTO)
log_printhex ("ECDH KEK params are", kek_params, sizeof(kek_params) );
return gcry_mpi_set_opaque (NULL, kek_params, 4 * 8);
}
/* Encrypts/decrypts DATA using a key derived from the ECC shared
point SHARED_MPI using the FIPS SP 800-56A compliant method
key_derivation+key_wrapping. If IS_ENCRYPT is true the function
encrypts; if false, it decrypts. PKEY is the public key and PK_FP
the fingerprint of this public key. On success the result is
stored at R_RESULT; on failure NULL is stored at R_RESULT and an
error code returned. */
gpg_error_t
pk_ecdh_encrypt_with_shared_point (int is_encrypt, gcry_mpi_t shared_mpi,
const byte pk_fp[MAX_FINGERPRINT_LEN],
gcry_mpi_t data, gcry_mpi_t *pkey,
gcry_mpi_t *r_result)
{
gpg_error_t err;
byte *secret_x;
int secret_x_size;
unsigned int nbits;
const unsigned char *kek_params;
size_t kek_params_size;
int kdf_hash_algo;
int kdf_encr_algo;
unsigned char message[256];
size_t message_size;
*r_result = NULL;
nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey);
if (!nbits)
return gpg_error (GPG_ERR_TOO_SHORT);
{
size_t nbytes;
/* Extract x component of the shared point: this is the actual
shared secret. */
nbytes = (mpi_get_nbits (pkey[1] /* public point */)+7)/8;
secret_x = xtrymalloc_secure (nbytes);
if (!secret_x)
return gpg_error_from_syserror ();
err = gcry_mpi_print (GCRYMPI_FMT_USG, secret_x, nbytes,
&nbytes, shared_mpi);
if (err)
{
xfree (secret_x);
log_error ("ECDH ephemeral export of shared point failed: %s\n",
gpg_strerror (err));
return err;
}
/* Expected size of the x component */
secret_x_size = (nbits+7)/8;
/* Extract X from the result. It must be in the format of:
04 || X || Y
40 || X
41 || X
Since it always comes with the prefix, it's larger than X. In
old experimental version of libgcrypt, there is a case where it
returns X with no prefix of 40, so, nbytes == secret_x_size
is allowed. */
if (nbytes < secret_x_size)
{
xfree (secret_x);
return gpg_error (GPG_ERR_BAD_DATA);
}
/* Remove the prefix. */
if ((nbytes & 1))
memmove (secret_x, secret_x+1, secret_x_size);
/* Clear the rest of data. */
if (nbytes - secret_x_size)
memset (secret_x+secret_x_size, 0, nbytes-secret_x_size);
if (DBG_CRYPTO)
log_printhex ("ECDH shared secret X is:", secret_x, secret_x_size );
}
/*** We have now the shared secret bytes in secret_x. ***/
/* At this point we are done with PK encryption and the rest of the
* function uses symmetric key encryption techniques to protect the
* input DATA. The following two sections will simply replace
* current secret_x with a value derived from it. This will become
* a KEK.
*/
if (!gcry_mpi_get_flag (pkey[2], GCRYMPI_FLAG_OPAQUE))
{
xfree (secret_x);
return gpg_error (GPG_ERR_BUG);
}
kek_params = gcry_mpi_get_opaque (pkey[2], &nbits);
kek_params_size = (nbits+7)/8;
if (DBG_CRYPTO)
log_printhex ("ecdh KDF params:", kek_params, kek_params_size);
/* Expect 4 bytes 03 01 hash_alg symm_alg. */
if (kek_params_size != 4 || kek_params[0] != 3 || kek_params[1] != 1)
{
xfree (secret_x);
return gpg_error (GPG_ERR_BAD_PUBKEY);
}
kdf_hash_algo = kek_params[2];
kdf_encr_algo = kek_params[3];
if (DBG_CRYPTO)
log_debug ("ecdh KDF algorithms %s+%s with aeswrap\n",
openpgp_md_algo_name (kdf_hash_algo),
openpgp_cipher_algo_name (kdf_encr_algo));
if (kdf_hash_algo != GCRY_MD_SHA256
&& kdf_hash_algo != GCRY_MD_SHA384
&& kdf_hash_algo != GCRY_MD_SHA512)
{
xfree (secret_x);
return gpg_error (GPG_ERR_BAD_PUBKEY);
}
if (kdf_encr_algo != CIPHER_ALGO_AES
&& kdf_encr_algo != CIPHER_ALGO_AES192
&& kdf_encr_algo != CIPHER_ALGO_AES256)
{
xfree (secret_x);
return gpg_error (GPG_ERR_BAD_PUBKEY);
}
/* Build kdf_params. */
{
IOBUF obuf;
obuf = iobuf_temp();
/* variable-length field 1, curve name OID */
err = gpg_mpi_write_nohdr (obuf, pkey[0]);
/* fixed-length field 2 */
iobuf_put (obuf, PUBKEY_ALGO_ECDH);
/* variable-length field 3, KDF params */
err = (err ? err : gpg_mpi_write_nohdr (obuf, pkey[2]));
/* fixed-length field 4 */
iobuf_write (obuf, "Anonymous Sender ", 20);
/* fixed-length field 5, recipient fp */
iobuf_write (obuf, pk_fp, 20);
message_size = iobuf_temp_to_buffer (obuf, message, sizeof message);
iobuf_close (obuf);
if (err)
{
xfree (secret_x);
return err;
}
if(DBG_CRYPTO)
log_printhex ("ecdh KDF message params are:", message, message_size);
}
/* Derive a KEK (key wrapping key) using MESSAGE and SECRET_X. */
{
gcry_md_hd_t h;
int old_size;
err = gcry_md_open (&h, kdf_hash_algo, 0);
if (err)
{
log_error ("gcry_md_open failed for kdf_hash_algo %d: %s",
kdf_hash_algo, gpg_strerror (err));
xfree (secret_x);
return err;
}
gcry_md_write(h, "\x00\x00\x00\x01", 4); /* counter = 1 */
gcry_md_write(h, secret_x, secret_x_size); /* x of the point X */
gcry_md_write(h, message, message_size); /* KDF parameters */
gcry_md_final (h);
log_assert( gcry_md_get_algo_dlen (kdf_hash_algo) >= 32 );
memcpy (secret_x, gcry_md_read (h, kdf_hash_algo),
gcry_md_get_algo_dlen (kdf_hash_algo));
gcry_md_close (h);
old_size = secret_x_size;
log_assert( old_size >= gcry_cipher_get_algo_keylen( kdf_encr_algo ) );
secret_x_size = gcry_cipher_get_algo_keylen( kdf_encr_algo );
log_assert( secret_x_size <= gcry_md_get_algo_dlen (kdf_hash_algo) );
/* We could have allocated more, so clean the tail before returning. */
memset (secret_x+secret_x_size, 0, old_size - secret_x_size);
if (DBG_CRYPTO)
log_printhex ("ecdh KEK is:", secret_x, secret_x_size );
}
/* And, finally, aeswrap with key secret_x. */
{
gcry_cipher_hd_t hd;
size_t nbytes;
byte *data_buf;
int data_buf_size;
gcry_mpi_t result;
err = gcry_cipher_open (&hd, kdf_encr_algo, GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
{
log_error ("ecdh failed to initialize AESWRAP: %s\n",
gpg_strerror (err));
xfree (secret_x);
return err;
}
err = gcry_cipher_setkey (hd, secret_x, secret_x_size);
xfree (secret_x);
secret_x = NULL;
if (err)
{
gcry_cipher_close (hd);
log_error ("ecdh failed in gcry_cipher_setkey: %s\n",
gpg_strerror (err));
return err;
}
data_buf_size = (gcry_mpi_get_nbits(data)+7)/8;
if ((data_buf_size & 7) != (is_encrypt ? 0 : 1))
{
log_error ("can't use a shared secret of %d bytes for ecdh\n",
data_buf_size);
return gpg_error (GPG_ERR_BAD_DATA);
}
data_buf = xtrymalloc_secure( 1 + 2*data_buf_size + 8);
if (!data_buf)
{
err = gpg_error_from_syserror ();
gcry_cipher_close (hd);
return err;
}
if (is_encrypt)
{
byte *in = data_buf+1+data_buf_size+8;
/* Write data MPI into the end of data_buf. data_buf is size
aeswrap data. */
err = gcry_mpi_print (GCRYMPI_FMT_USG, in,
data_buf_size, &nbytes, data/*in*/);
if (err)
{
log_error ("ecdh failed to export DEK: %s\n", gpg_strerror (err));
gcry_cipher_close (hd);
xfree (data_buf);
return err;
}
if (DBG_CRYPTO)
log_printhex ("ecdh encrypting :", in, data_buf_size );
err = gcry_cipher_encrypt (hd, data_buf+1, data_buf_size+8,
in, data_buf_size);
memset (in, 0, data_buf_size);
gcry_cipher_close (hd);
if (err)
{
log_error ("ecdh failed in gcry_cipher_encrypt: %s\n",
gpg_strerror (err));
xfree (data_buf);
return err;
}
data_buf[0] = data_buf_size+8;
if (DBG_CRYPTO)
log_printhex ("ecdh encrypted to:", data_buf+1, data_buf[0] );
result = gcry_mpi_set_opaque (NULL, data_buf, 8 * (1+data_buf[0]));
if (!result)
{
err = gpg_error_from_syserror ();
xfree (data_buf);
log_error ("ecdh failed to create an MPI: %s\n",
gpg_strerror (err));
return err;
}
*r_result = result;
}
else
{
byte *in;
const void *p;
p = gcry_mpi_get_opaque (data, &nbits);
nbytes = (nbits+7)/8;
if (!p || nbytes > data_buf_size || !nbytes)
{
xfree (data_buf);
return gpg_error (GPG_ERR_BAD_MPI);
}
memcpy (data_buf, p, nbytes);
if (data_buf[0] != nbytes-1)
{
log_error ("ecdh inconsistent size\n");
xfree (data_buf);
return gpg_error (GPG_ERR_BAD_MPI);
}
in = data_buf+data_buf_size;
data_buf_size = data_buf[0];
if (DBG_CRYPTO)
log_printhex ("ecdh decrypting :", data_buf+1, data_buf_size);
err = gcry_cipher_decrypt (hd, in, data_buf_size, data_buf+1,
data_buf_size);
gcry_cipher_close (hd);
if (err)
{
log_error ("ecdh failed in gcry_cipher_decrypt: %s\n",
gpg_strerror (err));
xfree (data_buf);
return err;
}
data_buf_size -= 8;
if (DBG_CRYPTO)
log_printhex ("ecdh decrypted to :", in, data_buf_size);
/* Padding is removed later. */
/* if (in[data_buf_size-1] > 8 ) */
/* { */
/* log_error ("ecdh failed at decryption: invalid padding." */
/* " 0x%02x > 8\n", in[data_buf_size-1] ); */
/* return gpg_error (GPG_ERR_BAD_KEY); */
/* } */
err = gcry_mpi_scan (&result, GCRYMPI_FMT_USG, in, data_buf_size, NULL);
xfree (data_buf);
if (err)
{
log_error ("ecdh failed to create a plain text MPI: %s\n",
gpg_strerror (err));
return err;
}
*r_result = result;
}
}
return err;
}
static gcry_mpi_t
gen_k (unsigned nbits)
{
gcry_mpi_t k;
k = gcry_mpi_snew (nbits);
if (DBG_CRYPTO)
log_debug ("choosing a random k of %u bits\n", nbits);
gcry_mpi_randomize (k, nbits-1, GCRY_STRONG_RANDOM);
if (DBG_CRYPTO)
{
unsigned char *buffer;
if (gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buffer, NULL, k))
BUG ();
log_debug ("ephemeral scalar MPI #0: %s\n", buffer);
gcry_free (buffer);
}
return k;
}
/* Generate an ephemeral key for the public ECDH key in PKEY. On
success the generated key is stored at R_K; on failure NULL is
stored at R_K and an error code returned. */
gpg_error_t
pk_ecdh_generate_ephemeral_key (gcry_mpi_t *pkey, gcry_mpi_t *r_k)
{
unsigned int nbits;
gcry_mpi_t k;
*r_k = NULL;
nbits = pubkey_nbits (PUBKEY_ALGO_ECDH, pkey);
if (!nbits)
return gpg_error (GPG_ERR_TOO_SHORT);
k = gen_k (nbits);
if (!k)
BUG ();
*r_k = k;
return 0;
}
/* Perform ECDH decryption. */
int
pk_ecdh_decrypt (gcry_mpi_t * result, const byte sk_fp[MAX_FINGERPRINT_LEN],
gcry_mpi_t data, gcry_mpi_t shared, gcry_mpi_t * skey)
{
if (!data)
return gpg_error (GPG_ERR_BAD_MPI);
return pk_ecdh_encrypt_with_shared_point (0 /*=decryption*/, shared,
sk_fp, data/*encr data as an MPI*/,
skey, result);
}
diff --git a/g10/encrypt.c b/g10/encrypt.c
index 6130ba013..0a892c2e7 100644
--- a/g10/encrypt.c
+++ b/g10/encrypt.c
@@ -1,992 +1,992 @@
/* encrypt.c - Main encryption driver
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2009 Free Software Foundation, Inc.
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "filter.h"
#include "trustdb.h"
-#include "i18n.h"
-#include "status.h"
+#include "../common/i18n.h"
+#include "../common/status.h"
#include "pkglue.h"
static int encrypt_simple( const char *filename, int mode, int use_seskey );
static int write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, iobuf_t out );
/****************
* Encrypt FILENAME with only the symmetric cipher. Take input from
* stdin if FILENAME is NULL.
*/
int
encrypt_symmetric (const char *filename)
{
return encrypt_simple( filename, 1, 0 );
}
/****************
* Encrypt FILENAME as a literal data packet only. Take input from
* stdin if FILENAME is NULL.
*/
int
encrypt_store (const char *filename)
{
return encrypt_simple( filename, 0, 0 );
}
/* *SESKEY contains the unencrypted session key ((*SESKEY)->KEY) and
the algorithm that will be used to encrypt the contents of the SED
packet ((*SESKEY)->ALGO). If *SESKEY is NULL, then a random
session key that is appropriate for DEK->ALGO is generated and
stored there.
Encrypt that session key using DEK and store the result in ENCKEY,
which must be large enough to hold (*SESKEY)->KEYLEN + 1 bytes. */
void
encrypt_seskey (DEK *dek, DEK **seskey, byte *enckey)
{
gcry_cipher_hd_t hd;
byte buf[33];
log_assert ( dek->keylen <= 32 );
if (!*seskey)
{
*seskey=xmalloc_clear(sizeof(DEK));
(*seskey)->algo=dek->algo;
make_session_key(*seskey);
/*log_hexdump( "thekey", c->key, c->keylen );*/
}
/* The encrypted session key is prefixed with a one-octet algorithm id. */
buf[0] = (*seskey)->algo;
memcpy( buf + 1, (*seskey)->key, (*seskey)->keylen );
/* We only pass already checked values to the following function,
thus we consider any failure as fatal. */
if (openpgp_cipher_open (&hd, dek->algo, GCRY_CIPHER_MODE_CFB, 1))
BUG ();
if (gcry_cipher_setkey (hd, dek->key, dek->keylen))
BUG ();
gcry_cipher_setiv (hd, NULL, 0);
gcry_cipher_encrypt (hd, buf, (*seskey)->keylen + 1, NULL, 0);
gcry_cipher_close (hd);
memcpy( enckey, buf, (*seskey)->keylen + 1 );
wipememory( buf, sizeof buf ); /* burn key */
}
/* We try very hard to use a MDC */
int
use_mdc (pk_list_t pk_list,int algo)
{
/* RFC-2440 don't has MDC */
if (RFC2440)
return 0;
/* --force-mdc overrides --disable-mdc */
if(opt.force_mdc)
return 1;
if(opt.disable_mdc)
return 0;
/* Do the keys really support MDC? */
if(select_mdc_from_pklist(pk_list))
return 1;
/* The keys don't support MDC, so now we do a bit of a hack - if any
of the AESes or TWOFISH are in the prefs, we assume that the user
can handle a MDC. This is valid for PGP 7, which can handle MDCs
though it will not generate them. 2440bis allows this, by the
way. */
if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
CIPHER_ALGO_AES,NULL)==CIPHER_ALGO_AES)
return 1;
if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
CIPHER_ALGO_AES192,NULL)==CIPHER_ALGO_AES192)
return 1;
if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
CIPHER_ALGO_AES256,NULL)==CIPHER_ALGO_AES256)
return 1;
if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
CIPHER_ALGO_TWOFISH,NULL)==CIPHER_ALGO_TWOFISH)
return 1;
/* Last try. Use MDC for the modern ciphers. */
if (openpgp_cipher_get_algo_blklen (algo) != 8)
return 1;
if (opt.verbose)
warn_missing_mdc_from_pklist (pk_list);
return 0; /* No MDC */
}
/* We don't want to use use_seskey yet because older gnupg versions
can't handle it, and there isn't really any point unless we're
making a message that can be decrypted by a public key or
passphrase. */
static int
encrypt_simple (const char *filename, int mode, int use_seskey)
{
iobuf_t inp, out;
PACKET pkt;
PKT_plaintext *pt = NULL;
STRING2KEY *s2k = NULL;
byte enckey[33];
int rc = 0;
int seskeylen = 0;
u32 filesize;
cipher_filter_context_t cfx;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
int do_compress = !!default_compress_algo();
pfx = new_progress_context ();
memset( &cfx, 0, sizeof cfx);
memset( &zfx, 0, sizeof zfx);
memset( &tfx, 0, sizeof tfx);
init_packet(&pkt);
/* Prepare iobufs. */
inp = iobuf_open(filename);
if (inp)
iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), filename? filename: "[stdin]",
strerror(errno) );
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, inp, filename);
if (opt.textmode)
iobuf_push_filter( inp, text_filter, &tfx );
cfx.dek = NULL;
if ( mode )
{
int canceled;
s2k = xmalloc_clear( sizeof *s2k );
s2k->mode = opt.s2k_mode;
s2k->hash_algo = S2K_DIGEST_ALGO;
cfx.dek = passphrase_to_dek (default_cipher_algo (), s2k, 1, 0,
NULL, &canceled);
if ( !cfx.dek || !cfx.dek->keylen )
{
rc = gpg_error (canceled? GPG_ERR_CANCELED:GPG_ERR_INV_PASSPHRASE);
xfree (cfx.dek);
xfree (s2k);
iobuf_close (inp);
log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc));
release_progress_context (pfx);
return rc;
}
if (use_seskey && s2k->mode != 1 && s2k->mode != 3)
{
use_seskey = 0;
log_info (_("can't use a symmetric ESK packet "
"due to the S2K mode\n"));
}
if ( use_seskey )
{
DEK *dek = NULL;
seskeylen = openpgp_cipher_get_algo_keylen (default_cipher_algo ());
encrypt_seskey( cfx.dek, &dek, enckey );
xfree( cfx.dek ); cfx.dek = dek;
}
if (opt.verbose)
log_info(_("using cipher %s\n"),
openpgp_cipher_algo_name (cfx.dek->algo));
cfx.dek->use_mdc=use_mdc(NULL,cfx.dek->algo);
}
if (do_compress && cfx.dek && cfx.dek->use_mdc
&& is_file_compressed(filename, &rc))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename);
do_compress = 0;
}
if ( rc || (rc = open_outfile (-1, filename, opt.armor? 1:0, 0, &out )))
{
iobuf_cancel (inp);
xfree (cfx.dek);
xfree (s2k);
release_progress_context (pfx);
return rc;
}
if ( opt.armor )
{
afx = new_armor_context ();
push_armor_filter (afx, out);
}
if ( s2k )
{
PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc + seskeylen + 1 );
enc->version = 4;
enc->cipher_algo = cfx.dek->algo;
enc->s2k = *s2k;
if ( use_seskey && seskeylen )
{
enc->seskeylen = seskeylen + 1; /* algo id */
memcpy (enc->seskey, enckey, seskeylen + 1 );
}
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc = build_packet( out, &pkt )))
log_error("build symkey packet failed: %s\n", gpg_strerror (rc) );
xfree (enc);
}
if (!opt.no_literal)
pt = setup_plaintext_name (filename, inp);
/* Note that PGP 5 has problems decrypting symmetrically encrypted
data if the file length is in the inner packet. It works when
only partial length headers are use. In the past, we always used
partial body length here, but since PGP 2, PGP 6, and PGP 7 need
the file length, and nobody should be using PGP 5 nowadays
anyway, this is now set to the file length. Note also that this
only applies to the RFC-1991 style symmetric messages, and not
the RFC-2440 style. PGP 6 and 7 work with either partial length
or fixed length with the new style messages. */
if ( !iobuf_is_pipe_filename (filename) && *filename && !opt.textmode )
{
off_t tmpsize;
int overflow;
if ( !(tmpsize = iobuf_get_filelength(inp, &overflow))
&& !overflow && opt.verbose)
log_info(_("WARNING: '%s' is an empty file\n"), filename );
/* We can't encode the length of very large files because
OpenPGP uses only 32 bit for file sizes. So if the
size of a file is larger than 2^32 minus some bytes for
packet headers, we switch to partial length encoding. */
if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
filesize = tmpsize;
else
filesize = 0;
}
else
filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
if (!opt.no_literal)
{
/* Note that PT has been initialized above in !no_literal mode. */
pt->timestamp = make_timestamp();
pt->mode = opt.mimemode? 'm' : opt.textmode? 't' : 'b';
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
cfx.datalen = filesize && !do_compress ? calc_packet_length( &pkt ) : 0;
}
else
{
cfx.datalen = filesize && !do_compress ? filesize : 0;
pkt.pkttype = 0;
pkt.pkt.generic = NULL;
}
/* Register the cipher filter. */
if (mode)
iobuf_push_filter ( out, cipher_filter, &cfx );
/* Register the compress filter. */
if ( do_compress )
{
if (cfx.dek && cfx.dek->use_mdc)
zfx.new_ctb = 1;
push_compress_filter (out, &zfx, default_compress_algo());
}
/* Do the work. */
if (!opt.no_literal)
{
if ( (rc = build_packet( out, &pkt )) )
log_error("build_packet failed: %s\n", gpg_strerror (rc) );
}
else
{
/* User requested not to create a literal packet, so we copy the
plain data. */
byte copy_buffer[4096];
int bytes_copied;
while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1)
if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) {
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc) );
break;
}
wipememory (copy_buffer, 4096); /* burn buffer */
}
/* Finish the stuff. */
iobuf_close (inp);
if (rc)
iobuf_cancel(out);
else
{
iobuf_close (out); /* fixme: check returncode */
if (mode)
write_status ( STATUS_END_ENCRYPTION );
}
if (pt)
pt->buf = NULL;
free_packet (&pkt);
xfree (cfx.dek);
xfree (s2k);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
int
setup_symkey (STRING2KEY **symkey_s2k,DEK **symkey_dek)
{
int canceled;
*symkey_s2k=xmalloc_clear(sizeof(STRING2KEY));
(*symkey_s2k)->mode = opt.s2k_mode;
(*symkey_s2k)->hash_algo = S2K_DIGEST_ALGO;
*symkey_dek = passphrase_to_dek (opt.s2k_cipher_algo,
*symkey_s2k, 1, 0, NULL, &canceled);
if(!*symkey_dek || !(*symkey_dek)->keylen)
{
xfree(*symkey_dek);
xfree(*symkey_s2k);
return gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE);
}
return 0;
}
static int
write_symkey_enc (STRING2KEY *symkey_s2k, DEK *symkey_dek, DEK *dek,
iobuf_t out)
{
int rc, seskeylen = openpgp_cipher_get_algo_keylen (dek->algo);
PKT_symkey_enc *enc;
byte enckey[33];
PACKET pkt;
enc=xmalloc_clear(sizeof(PKT_symkey_enc)+seskeylen+1);
encrypt_seskey(symkey_dek,&dek,enckey);
enc->version = 4;
enc->cipher_algo = opt.s2k_cipher_algo;
enc->s2k = *symkey_s2k;
enc->seskeylen = seskeylen + 1; /* algo id */
memcpy( enc->seskey, enckey, seskeylen + 1 );
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc=build_packet(out,&pkt)))
log_error("build symkey_enc packet failed: %s\n",gpg_strerror (rc));
xfree(enc);
return rc;
}
/*
* Encrypt the file with the given userids (or ask if none is
* supplied). Either FILENAME or FILEFD must be given, but not both.
* The caller may provide a checked list of public keys in
* PROVIDED_PKS; if not the function builds a list of keys on its own.
*
* Note that FILEFD is currently only used by cmd_encrypt in the
* not yet finished server.c.
*/
int
encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
strlist_t remusr, int use_symkey, pk_list_t provided_keys,
int outputfd)
{
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
PKT_plaintext *pt = NULL;
DEK *symkey_dek = NULL;
STRING2KEY *symkey_s2k = NULL;
int rc = 0, rc2 = 0;
u32 filesize;
cipher_filter_context_t cfx;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
PK_LIST pk_list;
int do_compress;
if (filefd != -1 && filename)
return gpg_error (GPG_ERR_INV_ARG); /* Both given. */
do_compress = !!opt.compress_algo;
pfx = new_progress_context ();
memset( &cfx, 0, sizeof cfx);
memset( &zfx, 0, sizeof zfx);
memset( &tfx, 0, sizeof tfx);
init_packet(&pkt);
if (use_symkey
&& (rc=setup_symkey(&symkey_s2k,&symkey_dek)))
{
release_progress_context (pfx);
return rc;
}
if (provided_keys)
pk_list = provided_keys;
else
{
if ((rc = build_pk_list (ctrl, remusr, &pk_list)))
{
release_progress_context (pfx);
return rc;
}
}
/* Prepare iobufs. */
#ifdef HAVE_W32_SYSTEM
if (filefd == -1)
inp = iobuf_open (filename);
else
{
inp = NULL;
gpg_err_set_errno (ENOSYS);
}
#else
if (filefd == GNUPG_INVALID_FD)
inp = iobuf_open (filename);
else
inp = iobuf_fdopen_nc (FD2INT(filefd), "rb");
#endif
if (inp)
iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
char xname[64];
rc = gpg_error_from_syserror ();
if (filefd != -1)
snprintf (xname, sizeof xname, "[fd %d]", filefd);
else if (!filename)
strcpy (xname, "[stdin]");
else
*xname = 0;
log_error (_("can't open '%s': %s\n"),
*xname? xname : filename, gpg_strerror (rc) );
goto leave;
}
if (opt.verbose)
log_info (_("reading from '%s'\n"), iobuf_get_fname_nonnull (inp));
handle_progress (pfx, inp, filename);
if (opt.textmode)
iobuf_push_filter (inp, text_filter, &tfx);
rc = open_outfile (outputfd, filename, opt.armor? 1:0, 0, &out);
if (rc)
goto leave;
if (opt.armor)
{
afx = new_armor_context ();
push_armor_filter (afx, out);
}
/* Create a session key. */
cfx.dek = xmalloc_secure_clear (sizeof *cfx.dek);
if (!opt.def_cipher_algo)
{
/* Try to get it from the prefs. */
cfx.dek->algo = select_algo_from_prefs (pk_list, PREFTYPE_SYM, -1, NULL);
/* The only way select_algo_from_prefs can fail here is when
mixing v3 and v4 keys, as v4 keys have an implicit preference
entry for 3DES, and the pk_list cannot be empty. In this
case, use 3DES anyway as it's the safest choice - perhaps the
v3 key is being used in an OpenPGP implementation and we know
that the implementation behind any v4 key can handle 3DES. */
if (cfx.dek->algo == -1)
{
cfx.dek->algo = CIPHER_ALGO_3DES;
}
/* In case 3DES has been selected, print a warning if any key
does not have a preference for AES. This should help to
indentify why encrypting to several recipients falls back to
3DES. */
if (opt.verbose && cfx.dek->algo == CIPHER_ALGO_3DES)
warn_missing_aes_from_pklist (pk_list);
}
else
{
if (!opt.expert
&& (select_algo_from_prefs (pk_list, PREFTYPE_SYM,
opt.def_cipher_algo, NULL)
!= opt.def_cipher_algo))
{
log_info(_("WARNING: forcing symmetric cipher %s (%d)"
" violates recipient preferences\n"),
openpgp_cipher_algo_name (opt.def_cipher_algo),
opt.def_cipher_algo);
}
cfx.dek->algo = opt.def_cipher_algo;
}
cfx.dek->use_mdc = use_mdc (pk_list,cfx.dek->algo);
/* Only do the is-file-already-compressed check if we are using a
MDC. This forces compressed files to be re-compressed if we do
not have a MDC to give some protection against chosen ciphertext
attacks. */
if (do_compress && cfx.dek->use_mdc && is_file_compressed(filename, &rc2))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename);
do_compress = 0;
}
if (rc2)
{
rc = rc2;
goto leave;
}
make_session_key (cfx.dek);
if (DBG_CRYPTO)
log_printhex ("DEK is: ", cfx.dek->key, cfx.dek->keylen );
rc = write_pubkey_enc_from_list (pk_list, cfx.dek, out);
if (rc)
goto leave;
/* We put the passphrase (if any) after any public keys as this
seems to be the most useful on the recipient side - there is no
point in prompting a user for a passphrase if they have the
secret key needed to decrypt. */
if(use_symkey && (rc = write_symkey_enc(symkey_s2k,symkey_dek,cfx.dek,out)))
goto leave;
if (!opt.no_literal)
pt = setup_plaintext_name (filename, inp);
/* Get the size of the file if possible, i.e., if it is a real file. */
if (filename && *filename
&& !iobuf_is_pipe_filename (filename) && !opt.textmode )
{
off_t tmpsize;
int overflow;
if ( !(tmpsize = iobuf_get_filelength(inp, &overflow))
&& !overflow && opt.verbose)
log_info(_("WARNING: '%s' is an empty file\n"), filename );
/* We can't encode the length of very large files because
OpenPGP uses only 32 bit for file sizes. So if the size
of a file is larger than 2^32 minus some bytes for packet
headers, we switch to partial length encoding. */
if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
filesize = tmpsize;
else
filesize = 0;
}
else
filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
if (!opt.no_literal)
{
pt->timestamp = make_timestamp();
pt->mode = opt.mimemode? 'm' : opt.textmode ? 't' : 'b';
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
cfx.datalen = filesize && !do_compress? calc_packet_length( &pkt ) : 0;
}
else
cfx.datalen = filesize && !do_compress ? filesize : 0;
/* Register the cipher filter. */
iobuf_push_filter (out, cipher_filter, &cfx);
/* Register the compress filter. */
if (do_compress)
{
int compr_algo = opt.compress_algo;
if (compr_algo == -1)
{
compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, -1, NULL);
if (compr_algo == -1)
compr_algo = DEFAULT_COMPRESS_ALGO;
/* Theoretically impossible to get here since uncompressed
is implicit. */
}
else if (!opt.expert
&& select_algo_from_prefs(pk_list, PREFTYPE_ZIP,
compr_algo, NULL) != compr_algo)
{
log_info (_("WARNING: forcing compression algorithm %s (%d)"
" violates recipient preferences\n"),
compress_algo_to_string(compr_algo), compr_algo);
}
/* Algo 0 means no compression. */
if (compr_algo)
{
if (cfx.dek && cfx.dek->use_mdc)
zfx.new_ctb = 1;
push_compress_filter (out,&zfx,compr_algo);
}
}
/* Do the work. */
if (!opt.no_literal)
{
if ((rc = build_packet( out, &pkt )))
log_error ("build_packet failed: %s\n", gpg_strerror (rc));
}
else
{
/* User requested not to create a literal packet, so we copy the
plain data. */
byte copy_buffer[4096];
int bytes_copied;
while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1)
{
rc = iobuf_write (out, copy_buffer, bytes_copied);
if (rc)
{
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc));
break;
}
}
wipememory (copy_buffer, 4096); /* Burn the buffer. */
}
/* Finish the stuff. */
leave:
iobuf_close (inp);
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out); /* fixme: check returncode */
write_status (STATUS_END_ENCRYPTION);
}
if (pt)
pt->buf = NULL;
free_packet (&pkt);
xfree (cfx.dek);
xfree (symkey_dek);
xfree (symkey_s2k);
if (!provided_keys)
release_pk_list (pk_list);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
/*
* Filter to do a complete public key encryption.
*/
int
encrypt_filter (void *opaque, int control,
iobuf_t a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
encrypt_filter_context_t *efx = opaque;
int rc = 0;
if (control == IOBUFCTRL_UNDERFLOW) /* decrypt */
{
BUG(); /* not used */
}
else if ( control == IOBUFCTRL_FLUSH ) /* encrypt */
{
if ( !efx->header_okay )
{
efx->cfx.dek = xmalloc_secure_clear ( sizeof *efx->cfx.dek );
if ( !opt.def_cipher_algo )
{
/* Try to get it from the prefs. */
efx->cfx.dek->algo =
select_algo_from_prefs (efx->pk_list, PREFTYPE_SYM, -1, NULL);
if (efx->cfx.dek->algo == -1 )
{
/* Because 3DES is implicitly in the prefs, this can
only happen if we do not have any public keys in
the list. */
efx->cfx.dek->algo = DEFAULT_CIPHER_ALGO;
}
/* In case 3DES has been selected, print a warning if
any key does not have a preference for AES. This
should help to indentify why encrypting to several
recipients falls back to 3DES. */
if (opt.verbose
&& efx->cfx.dek->algo == CIPHER_ALGO_3DES)
warn_missing_aes_from_pklist (efx->pk_list);
}
else
{
if (!opt.expert
&& select_algo_from_prefs (efx->pk_list,PREFTYPE_SYM,
opt.def_cipher_algo,
NULL) != opt.def_cipher_algo)
log_info(_("forcing symmetric cipher %s (%d) "
"violates recipient preferences\n"),
openpgp_cipher_algo_name (opt.def_cipher_algo),
opt.def_cipher_algo);
efx->cfx.dek->algo = opt.def_cipher_algo;
}
efx->cfx.dek->use_mdc = use_mdc (efx->pk_list,efx->cfx.dek->algo);
make_session_key ( efx->cfx.dek );
if (DBG_CRYPTO)
log_printhex ("DEK is: ", efx->cfx.dek->key, efx->cfx.dek->keylen);
rc = write_pubkey_enc_from_list (efx->pk_list, efx->cfx.dek, a);
if (rc)
return rc;
if(efx->symkey_s2k && efx->symkey_dek)
{
rc=write_symkey_enc(efx->symkey_s2k,efx->symkey_dek,
efx->cfx.dek,a);
if(rc)
return rc;
}
iobuf_push_filter (a, cipher_filter, &efx->cfx);
efx->header_okay = 1;
}
rc = iobuf_write (a, buf, size);
}
else if (control == IOBUFCTRL_FREE)
{
xfree (efx->symkey_dek);
xfree (efx->symkey_s2k);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "encrypt_filter", *ret_len);
}
return rc;
}
/*
* Write a pubkey-enc packet for the public key PK to OUT.
*/
int
write_pubkey_enc (PKT_public_key *pk, int throw_keyid, DEK *dek, iobuf_t out)
{
PACKET pkt;
PKT_pubkey_enc *enc;
int rc;
gcry_mpi_t frame;
print_pubkey_algo_note ( pk->pubkey_algo );
enc = xmalloc_clear ( sizeof *enc );
enc->pubkey_algo = pk->pubkey_algo;
keyid_from_pk( pk, enc->keyid );
enc->throw_keyid = throw_keyid;
/* Okay, what's going on: We have the session key somewhere in
* the structure DEK and want to encode this session key in an
* integer value of n bits. pubkey_nbits gives us the number of
* bits we have to use. We then encode the session key in some
* way and we get it back in the big intger value FRAME. Then
* we use FRAME, the public key PK->PKEY and the algorithm
* number PK->PUBKEY_ALGO and pass it to pubkey_encrypt which
* returns the encrypted value in the array ENC->DATA. This
* array has a size which depends on the used algorithm (e.g. 2
* for Elgamal). We don't need frame anymore because we have
* everything now in enc->data which is the passed to
* build_packet(). */
frame = encode_session_key (pk->pubkey_algo, dek,
pubkey_nbits (pk->pubkey_algo, pk->pkey));
rc = pk_encrypt (pk->pubkey_algo, enc->data, frame, pk, pk->pkey);
gcry_mpi_release (frame);
if (rc)
log_error ("pubkey_encrypt failed: %s\n", gpg_strerror (rc) );
else
{
if ( opt.verbose )
{
char *ustr = get_user_id_string_native (enc->keyid);
log_info (_("%s/%s encrypted for: \"%s\"\n"),
openpgp_pk_algo_name (enc->pubkey_algo),
openpgp_cipher_algo_name (dek->algo),
ustr );
xfree (ustr);
}
/* And write it. */
init_packet (&pkt);
pkt.pkttype = PKT_PUBKEY_ENC;
pkt.pkt.pubkey_enc = enc;
rc = build_packet (out, &pkt);
if (rc)
log_error ("build_packet(pubkey_enc) failed: %s\n",
gpg_strerror (rc));
}
free_pubkey_enc(enc);
return rc;
}
/*
* Write pubkey-enc packets from the list of PKs to OUT.
*/
static int
write_pubkey_enc_from_list (PK_LIST pk_list, DEK *dek, iobuf_t out)
{
if (opt.throw_keyids && (PGP6 || PGP7 || PGP8))
{
log_info(_("you may not use %s while in %s mode\n"),
"--throw-keyids",compliance_option_string());
compliance_failure();
}
for ( ; pk_list; pk_list = pk_list->next )
{
PKT_public_key *pk = pk_list->pk;
int throw_keyid = (opt.throw_keyids || (pk_list->flags&1));
int rc = write_pubkey_enc (pk, throw_keyid, dek, out);
if (rc)
return rc;
}
return 0;
}
void
encrypt_crypt_files (ctrl_t ctrl, int nfiles, char **files, strlist_t remusr)
{
int rc = 0;
if (opt.outfile)
{
log_error(_("--output doesn't work for this command\n"));
return;
}
if (!nfiles)
{
char line[2048];
unsigned int lno = 0;
while ( fgets(line, DIM(line), stdin) )
{
lno++;
if (!*line || line[strlen(line)-1] != '\n')
{
log_error("input line %u too long or missing LF\n", lno);
return;
}
line[strlen(line)-1] = '\0';
print_file_status(STATUS_FILE_START, line, 2);
rc = encrypt_crypt (ctrl, -1, line, remusr, 0, NULL, -1);
if (rc)
log_error ("encryption of '%s' failed: %s\n",
print_fname_stdin(line), gpg_strerror (rc) );
write_status( STATUS_FILE_DONE );
}
}
else
{
while (nfiles--)
{
print_file_status(STATUS_FILE_START, *files, 2);
if ( (rc = encrypt_crypt (ctrl, -1, *files, remusr, 0, NULL, -1)) )
log_error("encryption of '%s' failed: %s\n",
print_fname_stdin(*files), gpg_strerror (rc) );
write_status( STATUS_FILE_DONE );
files++;
}
}
}
diff --git a/g10/exec.c b/g10/exec.c
index b868a1ff1..74a83970e 100644
--- a/g10/exec.c
+++ b/g10/exec.c
@@ -1,635 +1,635 @@
/* exec.c - generic call-a-program code
* Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
FIXME: We should replace most code in this module by our
spawn implementation from common/exechelp.c.
*/
#include <config.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef EXEC_TEMPFILE_ONLY
#include <sys/wait.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
-#include "i18n.h"
-#include "iobuf.h"
-#include "util.h"
-#include "membuf.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/sysutils.h"
#include "exec.h"
#ifdef NO_EXEC
int
exec_write(struct exec_info **info,const char *program,
const char *args_in,const char *name,int writeonly,int binary)
{
log_error(_("no remote program execution supported\n"));
return GPG_ERR_GENERAL;
}
int
exec_read(struct exec_info *info) { return GPG_ERR_GENERAL; }
int
exec_finish(struct exec_info *info) { return GPG_ERR_GENERAL; }
int
set_exec_path(const char *path) { return GPG_ERR_GENERAL; }
#else /* ! NO_EXEC */
#if defined (_WIN32)
/* This is a nicer system() for windows that waits for programs to
return before returning control to the caller. I hate helpful
computers. */
static int
w32_system(const char *command)
{
#ifdef HAVE_W32CE_SYSTEM
#warning Change this code to use common/exechelp.c
#else
PROCESS_INFORMATION pi;
STARTUPINFO si;
char *string;
/* We must use a copy of the command as CreateProcess modifies this
argument. */
string=xstrdup(command);
memset(&pi,0,sizeof(pi));
memset(&si,0,sizeof(si));
si.cb=sizeof(si);
if(!CreateProcess(NULL,string,NULL,NULL,FALSE,
DETACHED_PROCESS,
NULL,NULL,&si,&pi))
return -1;
/* Wait for the child to exit */
WaitForSingleObject(pi.hProcess,INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
xfree(string);
return 0;
#endif
}
#endif
/* Replaces current $PATH */
int
set_exec_path(const char *path)
{
#ifdef HAVE_W32CE_SYSTEM
#warning Change this code to use common/exechelp.c
#else
char *p;
p=xmalloc(5+strlen(path)+1);
strcpy(p,"PATH=");
strcat(p,path);
if(DBG_EXTPROG)
log_debug("set_exec_path: %s\n",p);
/* Notice that path is never freed. That is intentional due to the
way putenv() works. This leaks a few bytes if we call
set_exec_path multiple times. */
if(putenv(p)!=0)
return GPG_ERR_GENERAL;
else
return 0;
#endif
}
/* Makes a temp directory and filenames */
static int
make_tempdir(struct exec_info *info)
{
char *tmp=opt.temp_dir,*namein=info->name,*nameout;
if(!namein)
namein=info->flags.binary?"tempin" EXTSEP_S "bin":"tempin" EXTSEP_S "txt";
nameout=info->flags.binary?"tempout" EXTSEP_S "bin":"tempout" EXTSEP_S "txt";
/* Make up the temp dir and files in case we need them */
if(tmp==NULL)
{
#if defined (_WIN32)
int err;
tmp=xmalloc(MAX_PATH+2);
err=GetTempPath(MAX_PATH+1,tmp);
if(err==0 || err>MAX_PATH+1)
strcpy(tmp,"c:\\windows\\temp");
else
{
int len=strlen(tmp);
/* GetTempPath may return with \ on the end */
while(len>0 && tmp[len-1]=='\\')
{
tmp[len-1]='\0';
len--;
}
}
#else /* More unixish systems */
tmp=getenv("TMPDIR");
if(tmp==NULL)
{
tmp=getenv("TMP");
if(tmp==NULL)
{
#ifdef __riscos__
tmp="<Wimp$ScrapDir>.GnuPG";
mkdir(tmp,0700); /* Error checks occur later on */
#else
tmp="/tmp";
#endif
}
}
#endif
}
info->tempdir=xmalloc(strlen(tmp)+strlen(DIRSEP_S)+10+1);
sprintf(info->tempdir,"%s" DIRSEP_S "gpg-XXXXXX",tmp);
#if defined (_WIN32)
xfree(tmp);
#endif
if (!gnupg_mkdtemp(info->tempdir))
log_error(_("can't create directory '%s': %s\n"),
info->tempdir,strerror(errno));
else
{
info->flags.madedir=1;
info->tempfile_in=xmalloc(strlen(info->tempdir)+
strlen(DIRSEP_S)+strlen(namein)+1);
sprintf(info->tempfile_in,"%s" DIRSEP_S "%s",info->tempdir,namein);
if(!info->flags.writeonly)
{
info->tempfile_out=xmalloc(strlen(info->tempdir)+
strlen(DIRSEP_S)+strlen(nameout)+1);
sprintf(info->tempfile_out,"%s" DIRSEP_S "%s",info->tempdir,nameout);
}
}
return info->flags.madedir? 0 : GPG_ERR_GENERAL;
}
/* Expands %i and %o in the args to the full temp files within the
temp directory. */
static int
expand_args(struct exec_info *info,const char *args_in)
{
const char *ch = args_in;
membuf_t command;
info->flags.use_temp_files=0;
info->flags.keep_temp_files=0;
if(DBG_EXTPROG)
log_debug("expanding string \"%s\"\n",args_in);
init_membuf (&command, 100);
while(*ch!='\0')
{
if(*ch=='%')
{
char *append=NULL;
ch++;
switch(*ch)
{
case 'O':
info->flags.keep_temp_files=1;
/* fall through */
case 'o': /* out */
if(!info->flags.madedir)
{
if(make_tempdir(info))
goto fail;
}
append=info->tempfile_out;
info->flags.use_temp_files=1;
break;
case 'I':
info->flags.keep_temp_files=1;
/* fall through */
case 'i': /* in */
if(!info->flags.madedir)
{
if(make_tempdir(info))
goto fail;
}
append=info->tempfile_in;
info->flags.use_temp_files=1;
break;
case '%':
append="%";
break;
}
if(append)
put_membuf_str (&command, append);
}
else
put_membuf (&command, ch, 1);
ch++;
}
put_membuf (&command, "", 1); /* Terminate string. */
info->command = get_membuf (&command, NULL);
if (!info->command)
return gpg_error_from_syserror ();
if(DBG_EXTPROG)
log_debug("args expanded to \"%s\", use %u, keep %u\n",info->command,
info->flags.use_temp_files,info->flags.keep_temp_files);
return 0;
fail:
xfree (get_membuf (&command, NULL));
return GPG_ERR_GENERAL;
}
/* Either handles the tempfile creation, or the fork/exec. If it
returns ok, then info->tochild is a FILE * that can be written to.
The rules are: if there are no args, then it's a fork/exec/pipe.
If there are args, but no tempfiles, then it's a fork/exec/pipe via
shell -c. If there are tempfiles, then it's a system. */
int
exec_write(struct exec_info **info,const char *program,
const char *args_in,const char *name,int writeonly,int binary)
{
int ret = GPG_ERR_GENERAL;
if(opt.exec_disable && !opt.no_perm_warn)
{
log_info(_("external program calls are disabled due to unsafe "
"options file permissions\n"));
return ret;
}
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
/* There should be no way to get to this spot while still carrying
setuid privs. Just in case, bomb out if we are. */
if ( getuid () != geteuid ())
BUG ();
#endif
if(program==NULL && args_in==NULL)
BUG();
*info=xmalloc_clear(sizeof(struct exec_info));
if(name)
(*info)->name=xstrdup(name);
(*info)->flags.binary=binary;
(*info)->flags.writeonly=writeonly;
/* Expand the args, if any */
if(args_in && expand_args(*info,args_in))
goto fail;
#ifdef EXEC_TEMPFILE_ONLY
if(!(*info)->flags.use_temp_files)
{
log_error(_("this platform requires temporary files when calling"
" external programs\n"));
goto fail;
}
#else /* !EXEC_TEMPFILE_ONLY */
/* If there are no args, or there are args, but no temp files, we
can use fork/exec/pipe */
if(args_in==NULL || (*info)->flags.use_temp_files==0)
{
int to[2],from[2];
if(pipe(to)==-1)
goto fail;
if(pipe(from)==-1)
{
close(to[0]);
close(to[1]);
goto fail;
}
if(((*info)->child=fork())==-1)
{
close(to[0]);
close(to[1]);
close(from[0]);
close(from[1]);
goto fail;
}
if((*info)->child==0)
{
char *shell=getenv("SHELL");
if(shell==NULL)
shell="/bin/sh";
/* I'm the child */
/* If the program isn't going to respond back, they get to
keep their stdout/stderr */
if(!(*info)->flags.writeonly)
{
/* implied close of STDERR */
if(dup2(STDOUT_FILENO,STDERR_FILENO)==-1)
_exit(1);
/* implied close of STDOUT */
close(from[0]);
if(dup2(from[1],STDOUT_FILENO)==-1)
_exit(1);
}
/* implied close of STDIN */
close(to[1]);
if(dup2(to[0],STDIN_FILENO)==-1)
_exit(1);
if(args_in==NULL)
{
if(DBG_EXTPROG)
log_debug("execlp: %s\n",program);
execlp(program,program,(void *)NULL);
}
else
{
if(DBG_EXTPROG)
log_debug("execlp: %s -c %s\n",shell,(*info)->command);
execlp(shell,shell,"-c",(*info)->command,(void *)NULL);
}
/* If we get this far the exec failed. Clean up and return. */
if(args_in==NULL)
log_error(_("unable to execute program '%s': %s\n"),
program,strerror(errno));
else
log_error(_("unable to execute shell '%s': %s\n"),
shell,strerror(errno));
/* This mimics the POSIX sh behavior - 127 means "not found"
from the shell. */
if(errno==ENOENT)
_exit(127);
_exit(1);
}
/* I'm the parent */
close(to[0]);
(*info)->tochild=fdopen(to[1],binary?"wb":"w");
if((*info)->tochild==NULL)
{
ret = gpg_error_from_syserror ();
close(to[1]);
goto fail;
}
close(from[1]);
(*info)->fromchild=iobuf_fdopen(from[0],"r");
if((*info)->fromchild==NULL)
{
ret = gpg_error_from_syserror ();
close(from[0]);
goto fail;
}
/* fd iobufs are cached! */
iobuf_ioctl((*info)->fromchild, IOBUF_IOCTL_NO_CACHE, 1, NULL);
return 0;
}
#endif /* !EXEC_TEMPFILE_ONLY */
if(DBG_EXTPROG)
log_debug("using temp file '%s'\n",(*info)->tempfile_in);
/* It's not fork/exec/pipe, so create a temp file */
if( is_secured_filename ((*info)->tempfile_in) )
{
(*info)->tochild = NULL;
gpg_err_set_errno (EPERM);
}
else
(*info)->tochild=fopen((*info)->tempfile_in,binary?"wb":"w");
if((*info)->tochild==NULL)
{
ret = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"),
(*info)->tempfile_in,strerror(errno));
goto fail;
}
ret=0;
fail:
if (ret)
{
xfree (*info);
*info = NULL;
}
return ret;
}
int
exec_read(struct exec_info *info)
{
int ret = GPG_ERR_GENERAL;
fclose(info->tochild);
info->tochild=NULL;
if(info->flags.use_temp_files)
{
if(DBG_EXTPROG)
log_debug("system() command is %s\n",info->command);
#if defined (_WIN32)
info->progreturn=w32_system(info->command);
#else
info->progreturn=system(info->command);
#endif
if(info->progreturn==-1)
{
log_error(_("system error while calling external program: %s\n"),
strerror(errno));
info->progreturn=127;
goto fail;
}
#if defined(WIFEXITED) && defined(WEXITSTATUS)
if(WIFEXITED(info->progreturn))
info->progreturn=WEXITSTATUS(info->progreturn);
else
{
log_error(_("unnatural exit of external program\n"));
info->progreturn=127;
goto fail;
}
#else
/* If we don't have the macros, do the best we can. */
info->progreturn = (info->progreturn & 0xff00) >> 8;
#endif
/* 127 is the magic value returned from system() to indicate
that the shell could not be executed, or from /bin/sh to
indicate that the program could not be executed. */
if(info->progreturn==127)
{
log_error(_("unable to execute external program\n"));
goto fail;
}
if(!info->flags.writeonly)
{
info->fromchild=iobuf_open(info->tempfile_out);
if (info->fromchild
&& is_secured_file (iobuf_get_fd (info->fromchild)))
{
iobuf_close (info->fromchild);
info->fromchild = NULL;
gpg_err_set_errno (EPERM);
}
if(info->fromchild==NULL)
{
ret = gpg_error_from_syserror ();
log_error(_("unable to read external program response: %s\n"),
strerror(errno));
goto fail;
}
/* Do not cache this iobuf on close */
iobuf_ioctl(info->fromchild, IOBUF_IOCTL_NO_CACHE, 1, NULL);
}
}
ret=0;
fail:
return ret;
}
int
exec_finish(struct exec_info *info)
{
int ret=info->progreturn;
if(info->fromchild)
iobuf_close(info->fromchild);
if(info->tochild)
fclose(info->tochild);
#ifndef EXEC_TEMPFILE_ONLY
if(info->child>0)
{
if(waitpid(info->child,&info->progreturn,0)!=0 &&
WIFEXITED(info->progreturn))
ret=WEXITSTATUS(info->progreturn);
else
{
log_error(_("unnatural exit of external program\n"));
ret=127;
}
}
#endif
if(info->flags.madedir && !info->flags.keep_temp_files)
{
if(info->tempfile_in)
{
if(unlink(info->tempfile_in)==-1)
log_info(_("WARNING: unable to remove tempfile (%s) '%s': %s\n"),
"in",info->tempfile_in,strerror(errno));
}
if(info->tempfile_out)
{
if(unlink(info->tempfile_out)==-1)
log_info(_("WARNING: unable to remove tempfile (%s) '%s': %s\n"),
"out",info->tempfile_out,strerror(errno));
}
if(rmdir(info->tempdir)==-1)
log_info(_("WARNING: unable to remove temp directory '%s': %s\n"),
info->tempdir,strerror(errno));
}
xfree(info->command);
xfree(info->name);
xfree(info->tempdir);
xfree(info->tempfile_in);
xfree(info->tempfile_out);
xfree(info);
return ret;
}
#endif /* ! NO_EXEC */
diff --git a/g10/export.c b/g10/export.c
index 323bf1742..2da5309bd 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -1,2385 +1,2385 @@
/* export.c - Export keys in the OpenPGP defined format.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
-#include "i18n.h"
-#include "membuf.h"
-#include "host2net.h"
-#include "zb32.h"
-#include "recsel.h"
-#include "mbox-util.h"
-#include "init.h"
+#include "../common/i18n.h"
+#include "../common/membuf.h"
+#include "../common/host2net.h"
+#include "../common/zb32.h"
+#include "../common/recsel.h"
+#include "../common/mbox-util.h"
+#include "../common/init.h"
#include "trustdb.h"
#include "call-agent.h"
/* An object to keep track of subkeys. */
struct subkey_list_s
{
struct subkey_list_s *next;
u32 kid[2];
};
typedef struct subkey_list_s *subkey_list_t;
/* An object to track statistics for export operations. */
struct export_stats_s
{
ulong count; /* Number of processed keys. */
ulong secret_count; /* Number of secret keys seen. */
ulong exported; /* Number of actual exported keys. */
};
/* A global variable to store the selector created from
* --export-filter keep-uid=EXPR.
* --export-filter drop-subkey=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now.
*/
static recsel_expr_t export_keep_uid;
static recsel_expr_t export_drop_subkey;
/* Local prototypes. */
static int do_export (ctrl_t ctrl, strlist_t users, int secret,
unsigned int options, export_stats_t stats);
static int do_export_stream (ctrl_t ctrl, iobuf_t out,
strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any);
static gpg_error_t print_pka_or_dane_records
/**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane);
static void
cleanup_export_globals (void)
{
recsel_release (export_keep_uid);
export_keep_uid = NULL;
recsel_release (export_drop_subkey);
export_drop_subkey = NULL;
}
/* Option parser for export options. See parse_options fro
details. */
int
parse_export_options(char *str,unsigned int *options,int noisy)
{
struct parse_options export_opts[]=
{
{"export-local-sigs",EXPORT_LOCAL_SIGS,NULL,
N_("export signatures that are marked as local-only")},
{"export-attributes",EXPORT_ATTRIBUTES,NULL,
N_("export attribute user IDs (generally photo IDs)")},
{"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,
N_("export revocation keys marked as \"sensitive\"")},
{"export-clean",EXPORT_CLEAN,NULL,
N_("remove unusable parts from key during export")},
{"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL,
N_("remove as much as possible from key during export")},
{"export-pka", EXPORT_PKA_FORMAT, NULL, NULL },
{"export-dane", EXPORT_DANE_FORMAT, NULL, NULL },
{"backup", EXPORT_BACKUP, NULL,
N_("use the GnuPG key backup format")},
{"export-backup", EXPORT_BACKUP, NULL, NULL },
/* Aliases for backward compatibility */
{"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL},
{"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL},
{"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL},
/* dummy */
{"export-unusable-sigs",0,NULL,NULL},
{"export-clean-sigs",0,NULL,NULL},
{"export-clean-uids",0,NULL,NULL},
{NULL,0,NULL,NULL}
/* add tags for include revoked and disabled? */
};
int rc;
rc = parse_options (str, options, export_opts, noisy);
if (rc && (*options & EXPORT_BACKUP))
{
/* Alter other options we want or don't want for restore. */
*options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES
| EXPORT_SENSITIVE_REVKEYS);
*options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL
| EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT);
}
return rc;
}
/* Parse and set an export filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is called several
* times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be exported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*
* - drop-subkey :: If the expression evaluates to true for a subkey
* packet that subkey and all it dependencies will be
* remove from the keyblock. The expression may use these
* variables:
*
* - secret :: 1 for a secret subkey, else 0.
* - key_algo :: Public key algorithm id
*/
gpg_error_t
parse_and_set_export_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_export_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&export_keep_uid, string+9);
else if (!strncmp (string, "drop-subkey=", 12))
err = recsel_parse_expr (&export_drop_subkey, string+12);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Create a new export stats object initialized to zero. On error
returns NULL and sets ERRNO. */
export_stats_t
export_new_stats (void)
{
export_stats_t stats;
return xtrycalloc (1, sizeof *stats);
}
/* Release an export stats object. */
void
export_release_stats (export_stats_t stats)
{
xfree (stats);
}
/* Print export statistics using the status interface. */
void
export_print_stats (export_stats_t stats)
{
if (!stats)
return;
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf, "%lu %lu %lu",
stats->count,
stats->secret_count,
stats->exported );
write_status_text (STATUS_EXPORT_RES, buf);
}
}
/*
* Export public keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all keys will be exported. STATS is
* either an export stats object for update or NULL.
*
* This function is the core of "gpg --export".
*/
int
export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 0, options, stats);
}
/*
* Export secret keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all secret keys will be exported.
* STATS is either an export stats object for update or NULL.
*
* This function is the core of "gpg --export-secret-keys".
*/
int
export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 1, options, stats);
}
/*
* Export secret sub keys (to stdout or to --output FILE).
*
* This is the same as export_seckeys but replaces the primary key by
* a stub key. Depending on opt.armor the output is armored. OPTIONS
* are defined in main.h. If USERS is NULL, all secret subkeys will
* be exported. STATS is either an export stats object for update or
* NULL.
*
* This function is the core of "gpg --export-secret-subkeys".
*/
int
export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 2, options, stats);
}
/*
* Export a single key into a memory buffer. STATS is either an
* export stats object for update or NULL.
*/
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
gpg_error_t err;
iobuf_t iobuf;
int any;
strlist_t helplist;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
helplist = NULL;
if (!add_to_strlist_try (&helplist, keyspec))
return gpg_error_from_syserror ();
iobuf = iobuf_temp ();
err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options,
stats, &any);
if (!err && !any)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err)
{
const void *src;
size_t datalen;
iobuf_flush_temp (iobuf);
src = iobuf_get_temp_buffer (iobuf);
datalen = iobuf_get_temp_length (iobuf);
if (!datalen)
err = gpg_error (GPG_ERR_NO_PUBKEY);
else if (!(*r_data = xtrymalloc (datalen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*r_data, src, datalen);
*r_datalen = datalen;
}
}
iobuf_close (iobuf);
free_strlist (helplist);
if (err && *r_keyblock)
{
release_kbnode (*r_keyblock);
*r_keyblock = NULL;
}
return err;
}
/* Export the keys identified by the list of strings in USERS. If
Secret is false public keys will be exported. With secret true
secret keys will be exported; in this case 1 means the entire
secret keyblock and 2 only the subkeys. OPTIONS are the export
options to apply. */
static int
do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options,
export_stats_t stats)
{
IOBUF out = NULL;
int any, rc;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
memset( &zfx, 0, sizeof zfx);
rc = open_outfile (-1, NULL, 0, !!secret, &out );
if (rc)
return rc;
if ( opt.armor && !(options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)) )
{
afx = new_armor_context ();
afx->what = secret? 5 : 1;
push_armor_filter (afx, out);
}
rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any);
if ( rc || !any )
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* Release an entire subkey list. */
static void
release_subkey_list (subkey_list_t list)
{
while (list)
{
subkey_list_t tmp = list->next;;
xfree (list);
list = tmp;
}
}
/* Returns true if NODE is a subkey and contained in LIST. */
static int
subkey_in_list_p (subkey_list_t list, KBNODE node)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
u32 kid[2];
keyid_from_pk (node->pkt->pkt.public_key, kid);
for (; list; list = list->next)
if (list->kid[0] == kid[0] && list->kid[1] == kid[1])
return 1;
}
return 0;
}
/* Allocate a new subkey list item from NODE. */
static subkey_list_t
new_subkey_list_item (KBNODE node)
{
subkey_list_t list = xcalloc (1, sizeof *list);
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
keyid_from_pk (node->pkt->pkt.public_key, list->kid);
return list;
}
/* Helper function to check whether the subkey at NODE actually
matches the description at DESC. The function returns true if the
key under question has been specified by an exact specification
(keyID or fingerprint) and does match the one at NODE. It is
assumed that the packet at NODE is either a public or secret
subkey. */
static int
exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
{
u32 kid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
int result = 0;
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
keyid_from_pk (node->pkt->pkt.public_key, kid);
break;
case KEYDB_SEARCH_MODE_FPR16:
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
fingerprint_from_pk (node->pkt->pkt.public_key, fpr,&fprlen);
break;
default:
break;
}
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
if (desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_FPR16:
if (!memcmp (desc->u.fpr, fpr, 16))
result = 1;
break;
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
if (!memcmp (desc->u.fpr, fpr, 20))
result = 1;
break;
default:
break;
}
return result;
}
/* Return an error if the key represented by the S-expression S_KEY
* and the OpenPGP key represented by PK do not use the same curve. */
static gpg_error_t
match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
{
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char *curve_str = NULL;
char *flag;
const char *oidstr = NULL;
gcry_mpi_t curve_as_mpi = NULL;
gpg_error_t err;
int is_eddsa = 0;
int idx = 0;
if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH
|| pk->pubkey_algo==PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
return gpg_error (GPG_ERR_PUBKEY_ALGO);
curve = gcry_sexp_find_token (s_key, "curve", 0);
if (!curve)
{
log_error ("no reported curve\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
curve_str = gcry_sexp_nth_string (curve, 1);
gcry_sexp_release (curve); curve = NULL;
if (!curve_str)
{
log_error ("no curve name\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
oidstr = openpgp_curve_to_oid (curve_str, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve_str);
xfree (curve_str);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
xfree (curve_str);
err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
if (err)
return err;
if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi))
{
log_error ("curves do not match\n");
gcry_mpi_release (curve_as_mpi);
return gpg_error (GPG_ERR_INV_CURVE);
}
gcry_mpi_release (curve_as_mpi);
flags = gcry_sexp_find_token (s_key, "flags", 0);
if (flags)
{
for (idx = 1; idx < gcry_sexp_length (flags); idx++)
{
flag = gcry_sexp_nth_string (flags, idx);
if (flag && (strcmp ("eddsa", flag) == 0))
is_eddsa = 1;
gcry_free (flag);
}
}
if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
{
log_error ("disagreement about EdDSA\n");
err = gpg_error (GPG_ERR_INV_CURVE);
}
return err;
}
/* Return a canonicalized public key algoithms. This is used to
compare different flavors of algorithms (e.g. ELG and ELG_E are
considered the same). */
static enum gcry_pk_algos
canon_pk_algo (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S: return GCRY_PK_RSA;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E: return GCRY_PK_ELG;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH: return GCRY_PK_ECC;
default: return algo;
}
}
/* Take a cleartext dump of a secret key in PK and change the
* parameter array in PK to include the secret parameters. */
static gpg_error_t
cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
{
gpg_error_t err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
gcry_sexp_t top_list;
gcry_sexp_t key = NULL;
char *key_type = NULL;
enum gcry_pk_algos pk_algo;
struct seckey_info *ski;
int idx, sec_start;
gcry_mpi_t pub_params[10] = { NULL };
/* we look for a private-key, then the first element in it tells us
the type */
top_list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!top_list)
goto bad_seckey;
if (gcry_sexp_length(top_list) != 2)
goto bad_seckey;
key = gcry_sexp_nth (top_list, 1);
if (!key)
goto bad_seckey;
key_type = gcry_sexp_nth_string(key, 0);
pk_algo = gcry_pk_map_name (key_type);
log_assert (!pk->seckey_info);
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "ne",
&pub_params[0],
&pub_params[1],
NULL);
for (idx=0; idx < 2 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
for (idx = 2; idx < 6 && !err; idx++)
{
gcry_mpi_release (pk->pkey[idx]);
pk->pkey[idx] = NULL;
}
err = gcry_sexp_extract_param (key, NULL, "dpqu",
&pk->pkey[2],
&pk->pkey[3],
&pk->pkey[4],
&pk->pkey[5],
NULL);
}
if (!err)
{
for (idx = 2; idx < 6; idx++)
ski->csum += checksum_mpi (pk->pkey[idx]);
}
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pqgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
&pub_params[3],
NULL);
for (idx=0; idx < 4 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[4]);
pk->pkey[4] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[4],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[4]);
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
NULL);
for (idx=0; idx < 3 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[3]);
pk->pkey[3] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[3],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[3]);
break;
case GCRY_PK_ECC:
err = match_curve_skey_pk (key, pk);
if (err)
goto leave;
if (!err)
err = gcry_sexp_extract_param (key, NULL, "q",
&pub_params[0],
NULL);
if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
sec_start = 2;
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
sec_start += 1;
if (!err)
{
gcry_mpi_release (pk->pkey[sec_start]);
pk->pkey[sec_start] = NULL;
err = gcry_sexp_extract_param (key, NULL, "d",
&pk->pkey[sec_start],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[sec_start]);
break;
default:
pk->seckey_info = NULL;
xfree (ski);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
leave:
gcry_sexp_release (top_list);
gcry_sexp_release (key);
gcry_free (key_type);
for (idx=0; idx < DIM(pub_params); idx++)
gcry_mpi_release (pub_params[idx]);
return err;
bad_pubkey_algo:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Use the key transfer format given in S_PGP to create the secinfo
structure in PK and change the parameter array in PK to include the
secret parameters. */
static gpg_error_t
transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
char *curve = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
enum gcry_pk_algos pk_algo;
int protect_algo = 0;
char iv[16];
int ivlen = 0;
int s2k_mode = 0;
int s2k_algo = 0;
byte s2k_salt[8];
u32 s2k_count = 0;
int is_ecdh = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
int skeyidx = 0;
struct seckey_info *ski;
/* gcry_log_debugsxp ("transferkey", s_pgp); */
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
goto bad_seckey;
list = gcry_sexp_find_token (top_list, "version", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
goto bad_seckey;
is_v4 = (value[0] == '4');
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "protection", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value)
goto bad_seckey;
if (valuelen == 4 && !memcmp (value, "sha1", 4))
is_protected = 2;
else if (valuelen == 3 && !memcmp (value, "sum", 3))
is_protected = 1;
else if (valuelen == 4 && !memcmp (value, "none", 4))
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
if (!string)
goto bad_seckey;
protect_algo = gcry_cipher_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 3, &valuelen);
if (!value || !valuelen || valuelen > sizeof iv)
goto bad_seckey;
memcpy (iv, value, valuelen);
ivlen = valuelen;
string = gcry_sexp_nth_string (list, 4);
if (!string)
goto bad_seckey;
s2k_mode = strtol (string, NULL, 10);
xfree (string);
string = gcry_sexp_nth_string (list, 5);
if (!string)
goto bad_seckey;
s2k_algo = gcry_md_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 6, &valuelen);
if (!value || !valuelen || valuelen > sizeof s2k_salt)
goto bad_seckey;
memcpy (s2k_salt, value, valuelen);
string = gcry_sexp_nth_string (list, 7);
if (!string)
goto bad_seckey;
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
/* Parse the gcrypt PK algo and check that it is okay. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "algo", 0);
if (!list)
goto bad_seckey;
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
pk_algo = gcry_pk_map_name (string);
xfree (string); string = NULL;
if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
|| gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
|| !npkey || npkey >= nskey)
goto bad_seckey;
/* Check that the pubkey algo matches the one from the public key. */
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ECC:
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
;
else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
is_ecdh = 1;
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
;
else
pk_algo = 0; /* Does not match. */
/* For ECC we do not have the domain parameters thus fix our info. */
npkey = 1;
nskey = 2;
break;
default:
pk_algo = 0; /* Oops. */
break;
}
if (!pk_algo)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
/* This check has to go after the ecc adjustments. */
if (nskey > PUBKEY_MAX_NSKEY)
goto bad_seckey;
/* Parse the key parameters. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "skey", 0);
if (!list)
goto bad_seckey;
for (idx=0;;)
{
int is_enc;
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value && skeyidx >= npkey)
break; /* Ready. */
/* Check for too many parameters. Note that depending on the
protection mode and version number we may see less than NSKEY
(but at least NPKEY+1) parameters. */
if (idx >= 2*nskey)
goto bad_seckey;
if (skeyidx >= DIM (skey)-1)
goto bad_seckey;
if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
goto bad_seckey;
is_enc = (value[0] == 'e');
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value || !valuelen)
goto bad_seckey;
if (is_enc)
{
void *p = xtrymalloc (valuelen);
if (!p)
goto outofmem;
memcpy (p, value, valuelen);
skey[skeyidx] = gcry_mpi_set_opaque (NULL, p, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
}
else
{
if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
value, valuelen, NULL))
goto bad_seckey;
}
skeyidx++;
}
skey[skeyidx++] = NULL;
gcry_sexp_release (list); list = NULL;
/* We have no need for the CSUM value thus we don't parse it. */
/* list = gcry_sexp_find_token (top_list, "csum", 0); */
/* if (list) */
/* { */
/* string = gcry_sexp_nth_string (list, 1); */
/* if (!string) */
/* goto bad_seckey; */
/* desired_csum = strtoul (string, NULL, 10); */
/* xfree (string); */
/* } */
/* else */
/* desired_csum = 0; */
/* gcry_sexp_release (list); list = NULL; */
/* Get the curve name if any, */
list = gcry_sexp_find_token (top_list, "curve", 0);
if (list)
{
curve = gcry_sexp_nth_string (list, 1);
gcry_sexp_release (list); list = NULL;
}
gcry_sexp_release (top_list); top_list = NULL;
/* log_debug ("XXX is_v4=%d\n", is_v4); */
/* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */
/* log_debug ("XXX is_protected=%d\n", is_protected); */
/* log_debug ("XXX protect_algo=%d\n", protect_algo); */
/* log_printhex ("XXX iv", iv, ivlen); */
/* log_debug ("XXX ivlen=%d\n", ivlen); */
/* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */
/* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */
/* log_printhex ("XXX s2k_salt", s2k_salt, sizeof s2k_salt); */
/* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */
/* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */
/* if (is_enc) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
if (!is_v4 || is_protected != 2 )
{
/* We only support the v4 format and a SHA-1 checksum. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
/* We need to change the received parameters for ECC algorithms.
The transfer format has the curve name and the parameters
separate. We put them all into the SKEY array. */
if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
{
const char *oidstr;
/* Assert that all required parameters are available. We also
check that the array does not contain more parameters than
needed (this was used by some beta versions of 2.1. */
if (!curve || !skey[0] || !skey[1] || skey[2])
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = openpgp_curve_to_oid (curve, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve);
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
/* Put the curve's OID into the MPI array. This requires
that we shift Q and D. For ECDH also insert the KDF parms. */
if (is_ecdh)
{
skey[4] = NULL;
skey[3] = skey[1];
skey[2] = gcry_mpi_copy (pk->pkey[2]);
}
else
{
skey[3] = NULL;
skey[2] = skey[1];
}
skey[1] = skey[0];
skey[0] = NULL;
err = openpgp_oid_from_str (oidstr, skey + 0);
if (err)
goto leave;
/* Fixup the NPKEY and NSKEY to match OpenPGP reality. */
npkey = 2 + is_ecdh;
nskey = 3 + is_ecdh;
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* log_info ("YYY skey[%d]:", idx); */
/* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
}
/* Do some sanity checks. */
if (s2k_count > 255)
{
/* We expect an already encoded S2K count. */
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_cipher_test_algo (protect_algo);
if (err)
goto leave;
err = openpgp_md_test_algo (s2k_algo);
if (err)
goto leave;
/* Check that the public key parameters match. Note that since
Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */
for (idx=0; idx < npkey; idx++)
if (gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* Check that the first secret key parameter in SKEY is encrypted
and that there are no more secret key parameters. The latter is
guaranteed by the v4 packet format. */
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
goto bad_seckey;
if (npkey+1 < DIM (skey) && skey[npkey+1])
goto bad_seckey;
/* Check that the secret key parameters in PK are all set to NULL. */
for (idx=npkey; idx < nskey; idx++)
if (pk->pkey[idx])
goto bad_seckey;
/* Now build the protection info. */
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
ski->sha1chk = 1;
ski->algo = protect_algo;
ski->s2k.mode = s2k_mode;
ski->s2k.hash_algo = s2k_algo;
log_assert (sizeof ski->s2k.salt == sizeof s2k_salt);
memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
ski->s2k.count = s2k_count;
log_assert (ivlen <= sizeof ski->iv);
memcpy (ski->iv, iv, ivlen);
ski->ivlen = ivlen;
/* Store the protected secret key parameter. */
pk->pkey[npkey] = skey[npkey];
skey[npkey] = NULL;
/* That's it. */
leave:
gcry_free (curve);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Print an "EXPORTED" status line. PK is the primary public key. */
static void
print_status_exported (PKT_public_key *pk)
{
char *hexfpr;
if (!is_status_enabled ())
return;
hexfpr = hexfingerprint (pk, NULL, 0);
write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]");
xfree (hexfpr);
}
/*
* Receive a secret key from agent specified by HEXGRIP.
*
* Since the key data from the agent is encrypted, decrypt it using
* CIPHERHD context. Then, parse the decrypted key data into transfer
* format, and put secret parameters into PK.
*
* If CLEARTEXT is 0, store the secret key material
* passphrase-protected. Otherwise, store secret key material in the
* clear.
*
* CACHE_NONCE_ADDR is used to share nonce for multple key retrievals.
*/
gpg_error_t
receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext,
char **cache_nonce_addr, const char *hexgrip,
PKT_public_key *pk)
{
gpg_error_t err = 0;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
unsigned char *key = NULL;
size_t keylen, realkeylen;
gcry_sexp_t s_skey;
char *prompt;
if (opt.verbose)
log_info ("key %s: asking agent for the secret parts\n", hexgrip);
prompt = gpg_format_keydesc (pk, FORMAT_KEYDESC_EXPORT,1);
err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr,
&wrappedkey, &wrappedkeylen);
xfree (prompt);
if (err)
goto unwraperror;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto unwraperror;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto unwraperror;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto unwraperror;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto unwraperror; /* Invalid csexp. */
err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
if (!err)
{
if (cleartext)
err = cleartext_secret_key_to_openpgp (s_skey, pk);
else
err = transfer_format_to_openpgp (s_skey, pk);
gcry_sexp_release (s_skey);
}
unwraperror:
xfree (key);
xfree (wrappedkey);
if (err)
{
log_error ("key %s: error receiving key from agent:"
" %s%s\n", hexgrip, gpg_strerror (err),
gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
"":_(" - skipped"));
}
return err;
}
/* Write KEYBLOCK either to stdout or to the file set with the
* --output option. This is a simplified version of do_export_stream
* which supports only a few export options. */
gpg_error_t
write_keyblock_to_output (kbnode_t keyblock, int with_armor,
unsigned int options)
{
gpg_error_t err;
const char *fname;
iobuf_t out;
kbnode_t node;
armor_filter_context_t *afx = NULL;
iobuf_t out_help = NULL;
PKT_public_key *pk = NULL;
fname = opt.outfile? opt.outfile : "-";
if (is_secured_filename (fname) )
return gpg_error (GPG_ERR_EPERM);
out = iobuf_create (fname, 0);
if (!out)
{
err = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out));
if ((options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)))
{
with_armor = 0;
out_help = iobuf_temp ();
}
if (with_armor)
{
afx = new_armor_context ();
afx->what = 1;
push_armor_filter (afx, out);
}
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node) || node->pkt->pkttype == PKT_RING_TRUST)
continue;
if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY))
pk = node->pkt->pkt.public_key;
err = build_packet (out_help? out_help : out, node->pkt);
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err) );
goto leave;
}
}
err = 0;
if (out_help && pk)
{
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
}
leave:
if (err)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_cancel (out_help);
release_armor_context (afx);
return err;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (!recsel_select (selector, impex_filter_getval, node))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-subkey filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_subkey_filter (kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (recsel_select (selector, impex_filter_getval, node))
{
/*log_debug ("drop-subkey: deleting a key\n");*/
/* The subkey packet and all following packets up to the
* next subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
}
}
}
/* Print DANE or PKA records for all user IDs in KEYBLOCK to OUT. The
* data for the record is taken from (DATA,DATELEN). PK is the public
* key packet with the primary key. */
static gpg_error_t
print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane)
{
gpg_error_t err = 0;
kbnode_t kbctx, node;
PKT_user_id *uid;
char *mbox = NULL;
char hashbuf[32];
char *hash = NULL;
char *domain;
const char *s;
unsigned int len;
estream_t fp = NULL;
char *hexdata = NULL;
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
hexdata = bin2hex (data, datalen, NULL);
if (!hexdata)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hexdata);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype != PKT_USER_ID)
continue;
uid = node->pkt->pkt.user_id;
if (uid->is_expired || uid->is_revoked)
continue;
xfree (mbox);
mbox = mailbox_from_userid (uid->name);
if (!mbox)
continue;
domain = strchr (mbox, '@');
*domain++ = 0;
if (print_pka)
{
es_fprintf (fp, "$ORIGIN _pka.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = zb32_encode (hashbuf, 8*20);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = strlen (hexfpr)/2;
es_fprintf (fp, "%s TYPE37 \\# %u 0006 0000 00 %02X %s\n\n",
hash, 6 + len, len, hexfpr);
}
if (print_dane && hexdata)
{
es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = bin2hex (hashbuf, 28, NULL);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hash);
len = strlen (hexdata)/2;
es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len);
for (s = hexdata; ;)
{
es_fprintf (fp, "\t%.64s\n", s);
if (strlen (s) < 64)
break;
s += 64;
}
es_fputs ("\t)\n\n", fp);
}
}
/* Make sure it is a string and write it. */
es_fputc (0, fp);
{
void *vp;
if (es_fclose_snatch (fp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = NULL;
iobuf_writestr (out, vp);
es_free (vp);
}
err = 0;
leave:
xfree (hash);
xfree (mbox);
es_fclose (fp);
xfree (hexdata);
xfree (hexfpr);
return err;
}
/* Helper for do_export_stream which writes one keyblock to OUT. */
static gpg_error_t
do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
iobuf_t out, int secret, unsigned int options,
export_stats_t stats, int *any,
KEYDB_SEARCH_DESC *desc, size_t ndesc,
size_t descindex, gcry_cipher_hd_t cipherhd)
{
gpg_error_t err;
char *cache_nonce = NULL;
subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */
int skip_until_subkey = 0;
int cleartext = 0;
char *hexgrip = NULL;
char *serialno = NULL;
PKT_public_key *pk;
u32 subkidbuf[2], *subkid;
kbnode_t kbctx, node;
/* NB: walk_kbnode skips packets marked as deleted. */
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (skip_until_subkey)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
skip_until_subkey = 0;
else
continue;
}
/* We used to use comment packets, but not any longer. In
* case we still have comments on a key, strip them here
* before we call build_packet(). */
if (node->pkt->pkttype == PKT_COMMENT)
continue;
/* Make sure that ring_trust packets are only exported in backup
* mode. */
if (node->pkt->pkttype == PKT_RING_TRUST && !(options & EXPORT_BACKUP))
continue;
/* If exact is set, then we only export what was requested
* (plus the primary key, if the user didn't specifically
* request it). */
if (desc[descindex].exact && node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (!exact_subkey_match_p (desc+descindex, node))
{
/* Before skipping this subkey, check whether any
* other description wants an exact match on a
* subkey and include that subkey into the output
* too. Need to add this subkey to a list so that
* it won't get processed a second time.
*
* So the first step here is to check that list and
* skip in any case if the key is in that list.
*
* We need this whole mess because the import
* function of GnuPG < 2.1 is not able to merge
* secret keys and thus it is useless to output them
* as two separate keys and have import merge them.
*/
if (subkey_in_list_p (subkey_list, node))
skip_until_subkey = 1; /* Already processed this one. */
else
{
size_t j;
for (j=0; j < ndesc; j++)
if (j != descindex && desc[j].exact
&& exact_subkey_match_p (desc+j, node))
break;
if (!(j < ndesc))
skip_until_subkey = 1; /* No other one matching. */
}
}
if (skip_until_subkey)
continue;
/* Mark this one as processed. */
{
subkey_list_t tmp = new_subkey_list_item (node);
tmp->next = subkey_list;
subkey_list = tmp;
}
}
if (node->pkt->pkttype == PKT_SIGNATURE)
{
/* Do not export packets which are marked as not
* exportable. */
if (!(options & EXPORT_LOCAL_SIGS)
&& !node->pkt->pkt.signature->flags.exportable)
continue; /* not exportable */
/* Do not export packets with a "sensitive" revocation key
* unless the user wants us to. Note that we do export
* these when issuing the actual revocation (see revoke.c). */
if (!(options & EXPORT_SENSITIVE_REVKEYS)
&& node->pkt->pkt.signature->revkey)
{
int i;
for (i = 0; i < node->pkt->pkt.signature->numrevkeys; i++)
if ((node->pkt->pkt.signature->revkey[i].class & 0x40))
break;
if (i < node->pkt->pkt.signature->numrevkeys)
continue;
}
}
/* Don't export attribs? */
if (!(options & EXPORT_ATTRIBUTES)
&& node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data)
{
/* Skip until we get to something that is not an attrib or a
* signature on an attrib. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
if (secret && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
pk = node->pkt->pkt.public_key;
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
subkid = NULL;
else
{
keyid_from_pk (pk, subkidbuf);
subkid = subkidbuf;
}
if (pk->seckey_info)
{
log_error ("key %s: oops: seckey_info already set"
" - skipped\n", keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
continue;
}
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("key %s: error computing keygrip: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
continue;
}
xfree (serialno);
serialno = NULL;
if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* We are asked not to export the secret parts of the
* primary key. Make up an error code to create the
* stub. */
err = GPG_ERR_NOT_FOUND;
}
else
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext);
if ((!err && serialno)
&& secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* It does not make sense to export a key with its
* primary key on card using a non-key stub. Thus we
* skip those keys when used with --export-secret-subkeys. */
log_info (_("key %s: key material on-card - skipped\n"),
keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND
|| (!err && serialno))
{
/* Create a key stub. */
struct seckey_info *ski;
const char *s;
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
if (err)
ski->s2k.mode = 1001; /* GNU dummy (no secret key). */
else
{
ski->s2k.mode = 1002; /* GNU-divert-to-card. */
for (s=serialno; sizeof (ski->ivlen) && *s && s[1];
ski->ivlen++, s += 2)
ski->iv[ski->ivlen] = xtoi_2 (s);
}
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
else if (!err)
{
err = receive_seckey_from_agent (ctrl, cipherhd,
cleartext, &cache_nonce,
hexgrip, pk);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
goto leave;
skip_until_subkey = 1;
err = 0;
}
else
{
err = build_packet (out, node->pkt);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
}
else
{
log_error ("key %s: error getting keyinfo from agent: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
}
xfree (pk->seckey_info);
pk->seckey_info = NULL;
{
int i;
for (i = pubkey_get_npkey (pk->pubkey_algo);
i < pubkey_get_nskey (pk->pubkey_algo); i++)
{
gcry_mpi_release (pk->pkey[i]);
pk->pkey[i] = NULL;
}
}
}
else /* Not secret or common packets. */
{
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err));
goto leave;
}
if (!skip_until_subkey)
*any = 1;
}
leave:
release_subkey_list (subkey_list);
xfree (serialno);
xfree (hexgrip);
xfree (cache_nonce);
return err;
}
/* Export the keys identified by the list of strings in USERS to the
stream OUT. If SECRET is false public keys will be exported. With
secret true secret keys will be exported; in this case 1 means the
entire secret keyblock and 2 only the subkeys. OPTIONS are the
export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit
code is zero, a pointer to the first keyblock found and exported
will be stored at this address; no other keyblocks are exported in
this case. The caller must free the returned keyblock. If any
key has been exported true is stored at ANY. */
static int
do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any)
{
gpg_error_t err = 0;
PACKET pkt;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t ndesc, descindex;
KEYDB_SEARCH_DESC *desc = NULL;
KEYDB_HANDLE kdbhd;
strlist_t sl;
gcry_cipher_hd_t cipherhd = NULL;
struct export_stats_s dummystats;
iobuf_t out_help = NULL;
if (!stats)
stats = &dummystats;
*any = 0;
init_packet (&pkt);
kdbhd = keydb_new ();
if (!kdbhd)
return gpg_error_from_syserror ();
/* For the PKA and DANE format open a helper iobuf and for DANE
* enforce some options. */
if ((options & (EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT)))
{
out_help = iobuf_temp ();
if ((options & EXPORT_DANE_FORMAT))
options |= EXPORT_MINIMAL | EXPORT_CLEAN;
}
if (!users)
{
ndesc = 1;
desc = xcalloc (ndesc, sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
if (!(err=classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have
a found flag for each entry in desc. To set this flag we
must check all those entries after a match to mark all
matched one - currently we stop at the first match. To do
this we need an extra flag to enable this feature. */
}
#ifdef ENABLE_SELINUX_HACKS
if (secret)
{
log_error (_("exporting secret keys not allowed\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
#endif
/* For secret key export we need to setup a decryption context. */
if (secret)
{
void *kek = NULL;
size_t keklen;
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
xfree (kek);
kek = NULL;
}
for (;;)
{
u32 keyid[2];
PKT_public_key *pk;
err = keydb_search (kdbhd, desc, ndesc, &descindex);
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
if (err)
break;
/* Read the keyblock. */
release_kbnode (keyblock);
keyblock = NULL;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("public key packet not found in keyblock - skipped\n");
continue;
}
stats->count++;
setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* If a secret key export is required we need to check whether
we have a secret key at all and if so create the seckey_info
structure. */
if (secret)
{
if (agent_probe_any_secret_key (ctrl, keyblock))
continue; /* No secret key (neither primary nor subkey). */
/* No v3 keys with GNU mode 1001. */
if (secret == 2 && pk->version == 3)
{
log_info (_("key %s: PGP 2.x style key - skipped\n"),
keystr (keyid));
continue;
}
/* The agent does not yet allow export of v3 packets. It is
actually questionable whether we should allow them at
all. */
if (pk->version == 3)
{
log_info ("key %s: PGP 2.x style key (v3) export "
"not yet supported - skipped\n", keystr (keyid));
continue;
}
stats->secret_count++;
}
/* Always do the cleaning on the public key part if requested.
* Note that both export-clean and export-minimal only apply to
* UID sigs (0x10, 0x11, 0x12, and 0x13). A designated
* revocation is never stripped, even with export-minimal set. */
if ((options & EXPORT_CLEAN))
clean_key (keyblock, opt.verbose, (options&EXPORT_MINIMAL), NULL, NULL);
if (export_keep_uid)
{
commit_kbnode (&keyblock);
apply_keep_uid_filter (keyblock, export_keep_uid);
commit_kbnode (&keyblock);
}
if (export_drop_subkey)
{
commit_kbnode (&keyblock);
apply_drop_subkey_filter (keyblock, export_drop_subkey);
commit_kbnode (&keyblock);
}
/* And write it. */
err = do_export_one_keyblock (ctrl, keyblock, keyid,
out_help? out_help : out,
secret, options, stats, any,
desc, ndesc, descindex, cipherhd);
if (err)
break;
if (keyblock_out)
{
*keyblock_out = keyblock;
break;
}
if (out_help)
{
/* We want to write PKA or DANE records. OUT_HELP has the
* keyblock and we print a record for each uid to OUT. */
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
if (err)
goto leave;
iobuf_close (out_help);
out_help = iobuf_temp ();
}
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
leave:
iobuf_cancel (out_help);
gcry_cipher_close (cipherhd);
xfree(desc);
keydb_release (kdbhd);
if (err || !keyblock_out)
release_kbnode( keyblock );
if( !*any )
log_info(_("WARNING: nothing exported\n"));
return err;
}
static gpg_error_t
key_to_sshblob (membuf_t *mb, const char *identifier, ...)
{
va_list arg_ptr;
gpg_error_t err = 0;
unsigned char nbuf[4];
unsigned char *buf;
size_t buflen;
gcry_mpi_t a;
ulongtobuf (nbuf, (ulong)strlen (identifier));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier);
if (!strncmp (identifier, "ecdsa-sha2-", 11))
{
ulongtobuf (nbuf, (ulong)strlen (identifier+11));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier+11);
}
va_start (arg_ptr, identifier);
while ((a = va_arg (arg_ptr, gcry_mpi_t)))
{
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
if (err)
break;
if (!strcmp (identifier, "ssh-ed25519")
&& buflen > 5 && buf[4] == 0x40)
{
/* We need to strip our 0x40 prefix. */
put_membuf (mb, "\x00\x00\x00\x20", 4);
put_membuf (mb, buf+5, buflen-5);
}
else
put_membuf (mb, buf, buflen);
gcry_free (buf);
}
va_end (arg_ptr);
return err;
}
/* Export the key identified by USERID in the SSH public key format.
The function exports the latest subkey with Authentication
capability unless the '!' suffix is used to export a specific
key. */
gpg_error_t
export_ssh_key (ctrl_t ctrl, const char *userid)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
u32 latest_date;
u32 curtime = make_timestamp ();
kbnode_t latest_key, node;
PKT_public_key *pk;
const char *identifier;
membuf_t mb;
estream_t fp = NULL;
struct b64state b64_state;
const char *fname = "-";
init_membuf (&mb, 4096);
/* We need to know whether the key has been specified using the
exact syntax ('!' suffix). Thus we need to run a
classify_user_id on our own. */
err = classify_user_id (userid, &desc, 1);
/* Get the public key. */
if (!err)
{
getkey_ctx_t getkeyctx;
err = get_pubkey_byname (ctrl, &getkeyctx, NULL, userid, &keyblock,
NULL,
0 /* Only usable keys or given exact. */,
1 /* No AKL lookup. */);
if (!err)
{
err = getkey_next (getkeyctx, NULL, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
err = 0;
}
getkey_end (getkeyctx);
}
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
return err;
}
/* The finish_lookup code in getkey.c does not handle auth keys,
thus we have to duplicate the code here to find the latest
subkey. However, if the key has been found using an exact match
('!' notation) we use that key without any further checks and
even allow the use of the primary key. */
latest_date = 0;
latest_key = NULL;
for (node = keyblock; node; node = node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_PUBLIC_KEY)
&& node->pkt->pkt.public_key->flags.exact)
{
latest_key = node;
break;
}
}
if (!latest_key)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not usable for authentication\n");
continue;
}
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure that it
is used. A better change would be to compare ">=" but that
might also change the selected keys and is as such a more
intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = node;
}
}
/* If no subkey was suitable check the primary key. */
if (!latest_key
&& (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking primary key %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not usable for authentication\n");
}
else if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not yet valid\n");
}
else
{
if (DBG_LOOKUP)
log_debug ("\tprimary key is fine\n");
latest_date = pk->timestamp;
latest_key = node;
}
}
}
if (!latest_key)
{
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
goto leave;
}
pk = latest_key->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_DSA:
identifier = "ssh-dss";
err = key_to_sshblob (&mb, identifier,
pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
NULL);
break;
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_S:
identifier = "ssh-rsa";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
break;
case PUBKEY_ALGO_ECDSA:
{
char *curveoid;
const char *curve;
curveoid = openpgp_oid_to_str (pk->pkey[0]);
if (!curveoid)
err = gpg_error_from_syserror ();
else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
if (!strcmp (curve, "nistp256"))
identifier = "ecdsa-sha2-nistp256";
else if (!strcmp (curve, "nistp384"))
identifier = "ecdsa-sha2-nistp384";
else if (!strcmp (curve, "nistp521"))
identifier = "ecdsa-sha2-nistp521";
else
identifier = NULL;
if (!identifier)
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
xfree (curveoid);
}
break;
case PUBKEY_ALGO_EDDSA:
if (!openpgp_oid_is_ed25519 (pk->pkey[0]))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
identifier = "ssh-ed25519";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
break;
default:
err = GPG_ERR_PUBKEY_ALGO;
break;
}
if (err)
goto leave;
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
fp = es_fopen ((fname = opt.outfile), "w");
else
fp = es_stdout;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
es_fprintf (fp, "%s ", identifier);
err = b64enc_start_es (&b64_state, fp, "");
if (err)
goto leave;
{
void *blob;
size_t bloblen;
blob = get_membuf (&mb, &bloblen);
if (!blob)
err = gpg_error_from_syserror ();
else
err = b64enc_write (&b64_state, blob, bloblen);
xfree (blob);
if (err)
goto leave;
}
err = b64enc_finish (&b64_state);
if (err)
goto leave;
es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
{
if (es_fclose (fp))
err = gpg_error_from_syserror ();
fp = NULL;
}
if (err)
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
leave:
es_fclose (fp);
xfree (get_membuf (&mb, NULL));
release_kbnode (keyblock);
return err;
}
diff --git a/g10/filter.h b/g10/filter.h
index 7accd7d4d..275608d4a 100644
--- a/g10/filter.h
+++ b/g10/filter.h
@@ -1,162 +1,162 @@
/* filter.h
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2005 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_FILTER_H
#define G10_FILTER_H
-#include "types.h"
+#include "../common/types.h"
#include "dek.h"
typedef struct {
gcry_md_hd_t md; /* catch all */
gcry_md_hd_t md2; /* if we want to calculate an alternate hash */
size_t maxbuf_size;
} md_filter_context_t;
typedef struct {
int refcount; /* Initialized to 1. */
/* these fields may be initialized */
int what; /* what kind of armor headers to write */
int only_keyblocks; /* skip all headers but ".... key block" */
const char *hdrlines; /* write these headerlines */
/* these fields must be initialized to zero */
int no_openpgp_data; /* output flag: "No valid OpenPGP data found" */
/* the following fields must be initialized to zero */
int inp_checked; /* set if the input has been checked */
int inp_bypass; /* set if the input is not armored */
int in_cleartext; /* clear text message */
int not_dash_escaped; /* clear text is not dash escaped */
int hashes; /* detected hash algorithms */
int faked; /* we are faking a literal data packet */
int truncated; /* number of truncated lines */
int qp_detected;
byte eol[3]; /* The end of line characters as a
zero-terminated string. Defaults
(eol[0]=='\0') to whatever the local
platform uses. */
byte *buffer; /* malloced buffer */
unsigned buffer_size; /* and size of this buffer */
unsigned buffer_len; /* used length of the buffer */
unsigned buffer_pos; /* read position */
byte radbuf[4];
int idx, idx2;
u32 crc;
int status; /* an internal state flag */
int cancel;
int any_data; /* any valid armored data seen */
int pending_lf; /* used together with faked */
} armor_filter_context_t;
struct unarmor_pump_s;
typedef struct unarmor_pump_s *UnarmorPump;
struct compress_filter_context_s {
int status;
void *opaque; /* (used for z_stream) */
byte *inbuf;
unsigned inbufsize;
byte *outbuf;
unsigned outbufsize;
int algo; /* compress algo */
int algo1hack;
int new_ctb;
void (*release)(struct compress_filter_context_s*);
};
typedef struct compress_filter_context_s compress_filter_context_t;
typedef struct {
DEK *dek;
u32 datalen;
gcry_cipher_hd_t cipher_hd;
int header;
gcry_md_hd_t mdc_hash;
byte enchash[20];
int create_mdc; /* flag will be set by the cipher filter */
} cipher_filter_context_t;
typedef struct {
byte *buffer; /* malloced buffer */
unsigned buffer_size; /* and size of this buffer */
unsigned buffer_len; /* used length of the buffer */
unsigned buffer_pos; /* read position */
int truncated; /* number of truncated lines */
int not_dash_escaped;
int escape_from;
gcry_md_hd_t md;
int pending_lf;
int pending_esc;
} text_filter_context_t;
typedef struct {
char *what; /* description */
u32 last_time; /* last time reported */
unsigned long last; /* last amount reported */
unsigned long offset; /* current amount */
unsigned long total; /* total amount */
int refcount;
} progress_filter_context_t;
/* encrypt_filter_context_t defined in main.h */
/*-- mdfilter.c --*/
int md_filter( void *opaque, int control, iobuf_t a, byte *buf, size_t *ret_len);
void free_md_filter_context( md_filter_context_t *mfx );
/*-- armor.c --*/
armor_filter_context_t *new_armor_context (void);
void release_armor_context (armor_filter_context_t *afx);
int push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf);
int use_armor_filter( iobuf_t a );
UnarmorPump unarmor_pump_new (void);
void unarmor_pump_release (UnarmorPump x);
int unarmor_pump (UnarmorPump x, int c);
/*-- compress.c --*/
void push_compress_filter(iobuf_t out,compress_filter_context_t *zfx,int algo);
void push_compress_filter2(iobuf_t out,compress_filter_context_t *zfx,
int algo,int rel);
/*-- cipher.c --*/
int cipher_filter( void *opaque, int control,
iobuf_t chain, byte *buf, size_t *ret_len);
/*-- textfilter.c --*/
int text_filter( void *opaque, int control,
iobuf_t chain, byte *buf, size_t *ret_len);
int copy_clearsig_text (iobuf_t out, iobuf_t inp, gcry_md_hd_t md,
int escape_dash, int escape_from);
/*-- progress.c --*/
progress_filter_context_t *new_progress_context (void);
void release_progress_context (progress_filter_context_t *pfx);
void handle_progress (progress_filter_context_t *pfx,
iobuf_t inp, const char *name);
#endif /*G10_FILTER_H*/
diff --git a/g10/free-packet.c b/g10/free-packet.c
index 6038d262e..01db2a028 100644
--- a/g10/free-packet.c
+++ b/g10/free-packet.c
@@ -1,527 +1,527 @@
/* free-packet.c - cleanup stuff for packets
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2005, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "options.h"
/* This is mpi_copy with a fix for opaque MPIs which store a NULL
pointer. This will also be fixed in Libggcrypt 1.7.0. */
static gcry_mpi_t
my_mpi_copy (gcry_mpi_t a)
{
if (a
&& gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)
&& !gcry_mpi_get_opaque (a, NULL))
return NULL;
return gcry_mpi_copy (a);
}
void
free_symkey_enc( PKT_symkey_enc *enc )
{
xfree(enc);
}
void
free_pubkey_enc( PKT_pubkey_enc *enc )
{
int n, i;
n = pubkey_get_nenc( enc->pubkey_algo );
if( !n )
mpi_release(enc->data[0]);
for(i=0; i < n; i++ )
mpi_release( enc->data[i] );
xfree(enc);
}
void
free_seckey_enc( PKT_signature *sig )
{
int n, i;
n = pubkey_get_nsig( sig->pubkey_algo );
if( !n )
mpi_release(sig->data[0]);
for(i=0; i < n; i++ )
mpi_release( sig->data[i] );
xfree(sig->revkey);
xfree(sig->hashed);
xfree(sig->unhashed);
if (sig->pka_info)
{
xfree (sig->pka_info->uri);
xfree (sig->pka_info);
}
xfree (sig->signers_uid);
xfree(sig);
}
void
release_public_key_parts (PKT_public_key *pk)
{
int n, i;
if (pk->seckey_info)
n = pubkey_get_nskey (pk->pubkey_algo);
else
n = pubkey_get_npkey (pk->pubkey_algo);
if (!n)
mpi_release (pk->pkey[0]);
for (i=0; i < n; i++ )
{
mpi_release (pk->pkey[i]);
pk->pkey[i] = NULL;
}
if (pk->seckey_info)
{
xfree (pk->seckey_info);
pk->seckey_info = NULL;
}
if (pk->prefs)
{
xfree (pk->prefs);
pk->prefs = NULL;
}
free_user_id (pk->user_id);
pk->user_id = NULL;
if (pk->revkey)
{
xfree(pk->revkey);
pk->revkey=NULL;
pk->numrevkeys=0;
}
if (pk->serialno)
{
xfree (pk->serialno);
pk->serialno = NULL;
}
}
/* Free an allocated public key structure including all parts.
Passing NULL is allowed. */
void
free_public_key (PKT_public_key *pk)
{
if (pk)
{
release_public_key_parts (pk);
xfree(pk);
}
}
static subpktarea_t *
cp_subpktarea (subpktarea_t *s )
{
subpktarea_t *d;
if( !s )
return NULL;
d = xmalloc (sizeof (*d) + s->size - 1 );
d->size = s->size;
d->len = s->len;
memcpy (d->data, s->data, s->len);
return d;
}
/*
* Return a copy of the preferences
*/
prefitem_t *
copy_prefs (const prefitem_t *prefs)
{
size_t n;
prefitem_t *new;
if (!prefs)
return NULL;
for (n=0; prefs[n].type; n++)
;
new = xmalloc ( sizeof (*new) * (n+1));
for (n=0; prefs[n].type; n++) {
new[n].type = prefs[n].type;
new[n].value = prefs[n].value;
}
new[n].type = PREFTYPE_NONE;
new[n].value = 0;
return new;
}
/* Copy the public key S to D. If D is NULL allocate a new public key
structure. If S has seckret key infos, only the public stuff is
copied. */
PKT_public_key *
copy_public_key (PKT_public_key *d, PKT_public_key *s)
{
int n, i;
if (!d)
d = xmalloc (sizeof *d);
memcpy (d, s, sizeof *d);
d->seckey_info = NULL;
d->user_id = scopy_user_id (s->user_id);
d->prefs = copy_prefs (s->prefs);
n = pubkey_get_npkey (s->pubkey_algo);
i = 0;
if (!n)
d->pkey[i++] = my_mpi_copy (s->pkey[0]);
else
{
for (; i < n; i++ )
d->pkey[i] = my_mpi_copy (s->pkey[i]);
}
for (; i < PUBKEY_MAX_NSKEY; i++)
d->pkey[i] = NULL;
if (!s->revkey && s->numrevkeys)
BUG();
if (s->numrevkeys)
{
d->revkey = xmalloc(sizeof(struct revocation_key)*s->numrevkeys);
memcpy(d->revkey,s->revkey,sizeof(struct revocation_key)*s->numrevkeys);
}
else
d->revkey = NULL;
return d;
}
static pka_info_t *
cp_pka_info (const pka_info_t *s)
{
pka_info_t *d = xmalloc (sizeof *s + strlen (s->email));
d->valid = s->valid;
d->checked = s->checked;
d->uri = s->uri? xstrdup (s->uri):NULL;
memcpy (d->fpr, s->fpr, sizeof s->fpr);
strcpy (d->email, s->email);
return d;
}
PKT_signature *
copy_signature( PKT_signature *d, PKT_signature *s )
{
int n, i;
if( !d )
d = xmalloc(sizeof *d);
memcpy( d, s, sizeof *d );
n = pubkey_get_nsig( s->pubkey_algo );
if( !n )
d->data[0] = my_mpi_copy(s->data[0]);
else {
for(i=0; i < n; i++ )
d->data[i] = my_mpi_copy( s->data[i] );
}
d->pka_info = s->pka_info? cp_pka_info (s->pka_info) : NULL;
d->hashed = cp_subpktarea (s->hashed);
d->unhashed = cp_subpktarea (s->unhashed);
if (s->signers_uid)
d->signers_uid = xstrdup (s->signers_uid);
if(s->numrevkeys)
{
d->revkey=NULL;
d->numrevkeys=0;
parse_revkeys(d);
}
return d;
}
/*
* shallow copy of the user ID
*/
PKT_user_id *
scopy_user_id (PKT_user_id *s)
{
if (s)
s->ref++;
return s;
}
void
free_comment( PKT_comment *rem )
{
xfree(rem);
}
void
free_attributes(PKT_user_id *uid)
{
if (!uid)
return;
xfree(uid->attribs);
xfree(uid->attrib_data);
uid->attribs=NULL;
uid->attrib_data=NULL;
uid->attrib_len=0;
}
void
free_user_id (PKT_user_id *uid)
{
if (!uid)
return;
log_assert (uid->ref > 0);
if (--uid->ref)
return;
free_attributes(uid);
xfree (uid->prefs);
xfree (uid->namehash);
xfree (uid->mbox);
xfree (uid);
}
void
free_compressed( PKT_compressed *zd )
{
if (!zd)
return;
if (zd->buf)
{
/* We need to skip some bytes. Because don't have any
* information about the length, so we assume this is the last
* packet */
while (iobuf_read( zd->buf, NULL, 1<<30 ) != -1)
;
}
xfree(zd);
}
void
free_encrypted( PKT_encrypted *ed )
{
if (!ed)
return;
if (ed->buf)
{
/* We need to skip some bytes. */
if (ed->is_partial)
{
while (iobuf_read( ed->buf, NULL, 1<<30 ) != -1)
;
}
else
{
while (ed->len)
{
/* Skip the packet. */
int n = iobuf_read( ed->buf, NULL, ed->len );
if (n == -1)
ed->len = 0;
else
ed->len -= n;
}
}
}
xfree (ed);
}
void
free_plaintext( PKT_plaintext *pt )
{
if (!pt)
return;
if (pt->buf)
{ /* We need to skip some bytes. */
if (pt->is_partial)
{
while (iobuf_read( pt->buf, NULL, 1<<30 ) != -1)
;
}
else
{
while( pt->len )
{ /* Skip the packet. */
int n = iobuf_read( pt->buf, NULL, pt->len );
if (n == -1)
pt->len = 0;
else
pt->len -= n;
}
}
}
xfree (pt);
}
/****************
* Free the packet in pkt.
*/
void
free_packet( PACKET *pkt )
{
if( !pkt || !pkt->pkt.generic )
return;
if( DBG_MEMORY )
log_debug("free_packet() type=%d\n", pkt->pkttype );
switch( pkt->pkttype ) {
case PKT_SIGNATURE:
free_seckey_enc( pkt->pkt.signature );
break;
case PKT_PUBKEY_ENC:
free_pubkey_enc( pkt->pkt.pubkey_enc );
break;
case PKT_SYMKEY_ENC:
free_symkey_enc( pkt->pkt.symkey_enc );
break;
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
free_public_key (pkt->pkt.public_key);
break;
case PKT_COMMENT:
free_comment( pkt->pkt.comment );
break;
case PKT_USER_ID:
free_user_id( pkt->pkt.user_id );
break;
case PKT_COMPRESSED:
free_compressed( pkt->pkt.compressed);
break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
free_encrypted( pkt->pkt.encrypted );
break;
case PKT_PLAINTEXT:
free_plaintext( pkt->pkt.plaintext );
break;
default:
xfree( pkt->pkt.generic );
break;
}
pkt->pkt.generic = NULL;
}
/****************
* returns 0 if they match.
*/
int
cmp_public_keys( PKT_public_key *a, PKT_public_key *b )
{
int n, i;
if( a->timestamp != b->timestamp )
return -1;
if( a->version < 4 && a->expiredate != b->expiredate )
return -1;
if( a->pubkey_algo != b->pubkey_algo )
return -1;
n = pubkey_get_npkey( b->pubkey_algo );
if( !n ) { /* unknown algorithm, rest is in opaque MPI */
if( mpi_cmp( a->pkey[0], b->pkey[0] ) )
return -1; /* can't compare due to unknown algorithm */
} else {
for(i=0; i < n; i++ ) {
if( mpi_cmp( a->pkey[i], b->pkey[i] ) )
return -1;
}
}
return 0;
}
int
cmp_signatures( PKT_signature *a, PKT_signature *b )
{
int n, i;
if( a->keyid[0] != b->keyid[0] )
return -1;
if( a->keyid[1] != b->keyid[1] )
return -1;
if( a->pubkey_algo != b->pubkey_algo )
return -1;
n = pubkey_get_nsig( a->pubkey_algo );
if( !n )
return -1; /* can't compare due to unknown algorithm */
for(i=0; i < n; i++ ) {
if( mpi_cmp( a->data[i] , b->data[i] ) )
return -1;
}
return 0;
}
/****************
* Returns: true if the user ids do not match
*/
int
cmp_user_ids( PKT_user_id *a, PKT_user_id *b )
{
int res=1;
if( a == b )
return 0;
if( a->attrib_data && b->attrib_data )
{
res = a->attrib_len - b->attrib_len;
if( !res )
res = memcmp( a->attrib_data, b->attrib_data, a->attrib_len );
}
else if( !a->attrib_data && !b->attrib_data )
{
res = a->len - b->len;
if( !res )
res = memcmp( a->name, b->name, a->len );
}
return res;
}
diff --git a/g10/getkey.c b/g10/getkey.c
index be7367faf..548f8bf37 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -1,4264 +1,4264 @@
/* getkey.c - Get a key from the database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "keydb.h"
#include "options.h"
#include "main.h"
#include "trustdb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
-#include "host2net.h"
-#include "mbox-util.h"
-#include "status.h"
+#include "../common/host2net.h"
+#include "../common/mbox-util.h"
+#include "../common/status.h"
#define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE
#define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE
#if MAX_PK_CACHE_ENTRIES < 2
#error We need the cache for key creation
#endif
/* Flags values returned by the lookup code. Note that the values are
* directly used by the KEY_CONSIDERED status line. */
#define LOOKUP_NOT_SELECTED (1<<0)
#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */
/* A context object used by the lookup functions. */
struct getkey_ctx_s
{
/* Part of the search criteria: whether the search is an exact
search or not. A search that is exact requires that a key or
subkey meet all of the specified criteria. A search that is not
exact allows selecting a different key or subkey from the
keyblock that matched the critera. Further, an exact search
returns the key or subkey that matched whereas a non-exact search
typically returns the primary key. See finish_lookup for
details. */
int exact;
/* Part of the search criteria: Whether the caller only wants keys
with an available secret key. This is used by getkey_next to get
the next result with the same initial criteria. */
int want_secret;
/* Part of the search criteria: The type of the requested key. A
mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT.
If non-zero, then for a key to match, it must implement one of
the required uses. */
int req_usage;
/* The database handle. */
KEYDB_HANDLE kr_handle;
/* Whether we should call xfree() on the context when the context is
released using getkey_end()). */
int not_allocated;
/* This variable is used as backing store for strings which have
their address used in ITEMS. */
strlist_t extra_list;
/* Part of the search criteria: The low-level search specification
as passed to keydb_search. */
int nitems;
/* This must be the last element in the structure. When we allocate
the structure, we allocate it so that ITEMS can hold NITEMS. */
KEYDB_SEARCH_DESC items[1];
};
#if 0
static struct
{
int any;
int okay_count;
int nokey_count;
int error_count;
} lkup_stats[21];
#endif
typedef struct keyid_list
{
struct keyid_list *next;
char fpr[MAX_FINGERPRINT_LEN];
u32 keyid[2];
} *keyid_list_t;
#if MAX_PK_CACHE_ENTRIES
typedef struct pk_cache_entry
{
struct pk_cache_entry *next;
u32 keyid[2];
PKT_public_key *pk;
} *pk_cache_entry_t;
static pk_cache_entry_t pk_cache;
static int pk_cache_entries; /* Number of entries in pk cache. */
static int pk_cache_disabled;
#endif
#if MAX_UID_CACHE_ENTRIES < 5
#error we really need the userid cache
#endif
typedef struct user_id_db
{
struct user_id_db *next;
keyid_list_t keyids;
int len;
char name[1];
} *user_id_db_t;
static user_id_db_t user_id_db;
static int uid_cache_entries; /* Number of entries in uid cache. */
static void merge_selfsigs (kbnode_t keyblock);
static int lookup (getkey_ctx_t ctx,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key,
int want_secret);
static kbnode_t finish_lookup (kbnode_t keyblock,
unsigned int req_usage, int want_exact,
unsigned int *r_flags);
static void print_status_key_considered (kbnode_t keyblock, unsigned int flags);
#if 0
static void
print_stats ()
{
int i;
for (i = 0; i < DIM (lkup_stats); i++)
{
if (lkup_stats[i].any)
es_fprintf (es_stderr,
"lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n",
i,
lkup_stats[i].okay_count,
lkup_stats[i].nokey_count, lkup_stats[i].error_count);
}
}
#endif
/* Cache a copy of a public key in the public key cache. PK is not
* cached if caching is disabled (via getkey_disable_caches), if
* PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id
* from the public key (e.g., unsupported algorithm), or a key with
* the key id is already in the cache.
*
* The public key packet is copied into the cache using
* copy_public_key. Thus, any secret parts are not copied, for
* instance.
*
* This cache is filled by get_pubkey and is read by get_pubkey and
* get_pubkey_fast. */
void
cache_public_key (PKT_public_key * pk)
{
#if MAX_PK_CACHE_ENTRIES
pk_cache_entry_t ce, ce2;
u32 keyid[2];
if (pk_cache_disabled)
return;
if (pk->flags.dont_cache)
return;
if (is_ELGAMAL (pk->pubkey_algo)
|| pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH
|| is_RSA (pk->pubkey_algo))
{
keyid_from_pk (pk, keyid);
}
else
return; /* Don't know how to get the keyid. */
for (ce = pk_cache; ce; ce = ce->next)
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
{
if (DBG_CACHE)
log_debug ("cache_public_key: already in cache\n");
return;
}
if (pk_cache_entries >= MAX_PK_CACHE_ENTRIES)
{
int n;
/* Remove the last 50% of the entries. */
for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++)
ce = ce->next;
if (ce && ce != pk_cache && ce->next)
{
ce2 = ce->next;
ce->next = NULL;
ce = ce2;
for (; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
pk_cache_entries--;
}
}
log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES);
}
pk_cache_entries++;
ce = xmalloc (sizeof *ce);
ce->next = pk_cache;
pk_cache = ce;
ce->pk = copy_public_key (NULL, pk);
ce->keyid[0] = keyid[0];
ce->keyid[1] = keyid[1];
#endif
}
/* Return a const utf-8 string with the text "[User ID not found]".
This function is required so that we don't need to switch gettext's
encoding temporary. */
static const char *
user_id_not_found_utf8 (void)
{
static char *text;
if (!text)
text = native_to_utf8 (_("[User ID not found]"));
return text;
}
/* Return the user ID from the given keyblock.
* We use the primary uid flag which has been set by the merge_selfsigs
* function. The returned value is only valid as long as the given
* keyblock is not changed. */
static const char *
get_primary_uid (KBNODE keyblock, size_t * uidlen)
{
KBNODE k;
const char *s;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->is_primary)
{
*uidlen = k->pkt->pkt.user_id->len;
return k->pkt->pkt.user_id->name;
}
}
s = user_id_not_found_utf8 ();
*uidlen = strlen (s);
return s;
}
static void
release_keyid_list (keyid_list_t k)
{
while (k)
{
keyid_list_t k2 = k->next;
xfree (k);
k = k2;
}
}
/****************
* Store the association of keyid and userid
* Feed only public keys to this function.
*/
static void
cache_user_id (KBNODE keyblock)
{
user_id_db_t r;
const char *uid;
size_t uidlen;
keyid_list_t keyids = NULL;
KBNODE k;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keyid_list_t a = xmalloc_clear (sizeof *a);
/* Hmmm: For a long list of keyids it might be an advantage
* to append the keys. */
fingerprint_from_pk (k->pkt->pkt.public_key, a->fpr, NULL);
keyid_from_pk (k->pkt->pkt.public_key, a->keyid);
/* First check for duplicates. */
for (r = user_id_db; r; r = r->next)
{
keyid_list_t b;
for (b = r->keyids; b; b = b->next)
{
if (!memcmp (b->fpr, a->fpr, MAX_FINGERPRINT_LEN))
{
if (DBG_CACHE)
log_debug ("cache_user_id: already in cache\n");
release_keyid_list (keyids);
xfree (a);
return;
}
}
}
/* Now put it into the cache. */
a->next = keyids;
keyids = a;
}
}
if (!keyids)
BUG (); /* No key no fun. */
uid = get_primary_uid (keyblock, &uidlen);
if (uid_cache_entries >= MAX_UID_CACHE_ENTRIES)
{
/* fixme: use another algorithm to free some cache slots */
r = user_id_db;
user_id_db = r->next;
release_keyid_list (r->keyids);
xfree (r);
uid_cache_entries--;
}
r = xmalloc (sizeof *r + uidlen - 1);
r->keyids = keyids;
r->len = uidlen;
memcpy (r->name, uid, r->len);
r->next = user_id_db;
user_id_db = r;
uid_cache_entries++;
}
/* Disable and drop the public key cache (which is filled by
cache_public_key and get_pubkey). Note: there is currently no way
to reenable this cache. */
void
getkey_disable_caches ()
{
#if MAX_PK_CACHE_ENTRIES
{
pk_cache_entry_t ce, ce2;
for (ce = pk_cache; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
}
pk_cache_disabled = 1;
pk_cache_entries = 0;
pk_cache = NULL;
}
#endif
/* fixme: disable user id cache ? */
}
void
pubkey_free (pubkey_t key)
{
if (key)
{
xfree (key->pk);
release_kbnode (key->keyblock);
xfree (key);
}
}
void
pubkeys_free (pubkey_t keys)
{
while (keys)
{
pubkey_t next = keys->next;
pubkey_free (keys);
keys = next;
}
}
/* Returns all keys that match the search specfication SEARCH_TERMS.
This function also checks for and warns about duplicate entries in
the keydb, which can occur if the user has configured multiple
keyrings or keyboxes or if a keyring or keybox was corrupted.
Note: SEARCH_TERMS will not be expanded (i.e., it may not be a
group).
USE is the operation for which the key is required. It must be
either PUBKEY_USAGE_ENC, PUBKEY_USAGE_SIG, PUBKEY_USAGE_CERT or
PUBKEY_USAGE_AUTH.
XXX: Currently, only PUBKEY_USAGE_ENC and PUBKEY_USAGE_SIG are
implemented.
INCLUDE_UNUSABLE indicates whether disabled keys are allowed.
(Recipients specified with --encrypt-to and --hidden-encrypt-to may
be disabled. It is possible to edit disabled keys.)
SOURCE is the context in which SEARCH_TERMS was specified, e.g.,
"--encrypt-to", etc. If this function is called interactively,
then this should be NULL.
If WARN_POSSIBLY_AMBIGUOUS is set, then emits a warning if the user
does not specify a long key id or a fingerprint.
The results are placed in *KEYS. *KEYS must be NULL! */
gpg_error_t
get_pubkeys (ctrl_t ctrl,
char *search_terms, int use, int include_unusable, char *source,
int warn_possibly_ambiguous,
pubkey_t *r_keys)
{
/* We show a warning when a key appears multiple times in the DB.
This can happen for two reasons:
- The user has configured multiple keyrings or keyboxes.
- The keyring or keybox has been corrupted in some way, e.g., a
bug or a random process changing them.
For each duplicate, we only want to show the key once. Hence,
this list. */
static strlist_t key_dups;
/* USE transformed to a string. */
char *use_str;
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
GETKEY_CTX ctx;
pubkey_t results = NULL;
pubkey_t r;
int count;
char fingerprint[2 * MAX_FINGERPRINT_LEN + 1];
if (DBG_LOOKUP)
{
log_debug ("\n");
log_debug ("%s: Checking %s=%s\n",
__func__, source ? source : "user input", search_terms);
}
if (*r_keys)
log_bug ("%s: KEYS should be NULL!\n", __func__);
switch (use)
{
case PUBKEY_USAGE_ENC: use_str = "encrypt"; break;
case PUBKEY_USAGE_SIG: use_str = "sign"; break;
case PUBKEY_USAGE_CERT: use_str = "cetify"; break;
case PUBKEY_USAGE_AUTH: use_str = "authentication"; break;
default: log_bug ("%s: Bad value for USE (%d)\n", __func__, use);
}
if (use == PUBKEY_USAGE_CERT || use == PUBKEY_USAGE_AUTH)
log_bug ("%s: use=%s is unimplemented.\n", __func__, use_str);
err = classify_user_id (search_terms, &desc, 1);
if (err)
{
log_info (_("key \"%s\" not found: %s\n"),
search_terms, gpg_strerror (err));
if (!opt.quiet && source)
log_info (_("(check argument of option '%s')\n"), source);
goto out;
}
if (warn_possibly_ambiguous
&& ! (desc.mode == KEYDB_SEARCH_MODE_LONG_KID
|| desc.mode == KEYDB_SEARCH_MODE_FPR16
|| desc.mode == KEYDB_SEARCH_MODE_FPR20
|| desc.mode == KEYDB_SEARCH_MODE_FPR))
{
log_info (_("Warning: '%s' should be a long key ID or a fingerprint\n"),
search_terms);
if (!opt.quiet && source)
log_info (_("(check argument of option '%s')\n"), source);
}
/* Gather all of the results. */
ctx = NULL;
count = 0;
do
{
PKT_public_key *pk = xmalloc_clear (sizeof *pk);
KBNODE kb;
pk->req_usage = use;
if (! ctx)
err = get_pubkey_byname (ctrl, &ctx, pk, search_terms, &kb, NULL,
include_unusable, 1);
else
err = getkey_next (ctx, pk, &kb);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
/* No more results. */
{
xfree (pk);
break;
}
else if (err)
/* An error (other than "not found"). */
{
log_error (_("error looking up: %s\n"),
gpg_strerror (err));
xfree (pk);
break;
}
/* Another result! */
count ++;
r = xmalloc_clear (sizeof (*r));
r->pk = pk;
r->keyblock = kb;
r->next = results;
results = r;
}
while (ctx);
getkey_end (ctx);
if (DBG_LOOKUP)
{
log_debug ("%s resulted in %d matches.\n", search_terms, count);
for (r = results; r; r = r->next)
log_debug (" %s\n",
hexfingerprint (r->keyblock->pkt->pkt.public_key,
fingerprint, sizeof (fingerprint)));
}
if (! results && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
/* No match. */
{
if (DBG_LOOKUP)
log_debug ("%s: '%s' not found.\n", __func__, search_terms);
log_info (_("key \"%s\" not found\n"), search_terms);
if (!opt.quiet && source)
log_info (_("(check argument of option '%s')\n"), source);
goto out;
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
/* No more matches. */
;
else if (err)
/* Some other error. An error message was already printed
out. Free RESULTS and continue. */
goto out;
/* Check for duplicates. */
if (DBG_LOOKUP)
log_debug ("%s: Checking results of %s='%s' for dups\n",
__func__, source ? source : "user input", search_terms);
count = 0;
for (r = results; r; r = r->next)
{
pubkey_t *prevp;
pubkey_t next;
pubkey_t r2;
int dups = 0;
prevp = &r->next;
next = r->next;
while ((r2 = next))
{
if (cmp_public_keys (r->keyblock->pkt->pkt.public_key,
r2->keyblock->pkt->pkt.public_key) != 0)
/* Not a dup. */
{
prevp = &r2->next;
next = r2->next;
continue;
}
dups ++;
count ++;
/* Remove R2 from the list. */
*prevp = r2->next;
release_kbnode (r2->keyblock);
next = r2->next;
xfree (r2);
}
if (dups)
{
hexfingerprint (r->keyblock->pkt->pkt.public_key,
fingerprint, sizeof fingerprint);
if (! strlist_find (key_dups, fingerprint))
{
char fingerprint_formatted[MAX_FORMATTED_FINGERPRINT_LEN + 1];
log_info (_("Warning: %s appears in the keyring %d times\n"),
format_hexfingerprint (fingerprint,
fingerprint_formatted,
sizeof fingerprint_formatted),
1 + dups);
add_to_strlist (&key_dups, fingerprint);
}
}
}
if (DBG_LOOKUP && count)
{
log_debug ("After removing %d dups:\n", count);
for (r = results, count = 0; r; r = r->next)
log_debug (" %d: %s\n",
count,
hexfingerprint (r->keyblock->pkt->pkt.public_key,
fingerprint, sizeof fingerprint));
}
out:
if (err)
pubkeys_free (results);
else
*r_keys = results;
return err;
}
static void
pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key)
{
kbnode_t a = found_key ? found_key : keyblock;
log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY
|| a->pkt->pkttype == PKT_PUBLIC_SUBKEY);
copy_public_key (pk, a->pkt->pkt.public_key);
}
/* Return the public key with the key id KEYID and store it at PK.
* The resources in *PK should be released using
* release_public_key_parts(). This function also stores a copy of
* the public key in the user id cache (see cache_public_key).
*
* If PK is NULL, this function just stores the public key in the
* cache and returns the usual return code.
*
* PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the
* lookup function. If this is non-zero, only keys with the specified
* usage will be returned. As such, it is essential that
* PK->REQ_USAGE be correctly initialized!
*
* Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key
* with the specified key id, or another error code if an error
* occurs.
*
* If the data was not read from the cache, then the self-signed data
* has definitely been merged into the public key using
* merge_selfsigs. */
int
get_pubkey (PKT_public_key * pk, u32 * keyid)
{
int internal = 0;
int rc = 0;
#if MAX_PK_CACHE_ENTRIES
if (pk)
{
/* Try to get it from the cache. We don't do this when pk is
NULL as it does not guarantee that the user IDs are
cached. */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
/* XXX: We don't check PK->REQ_USAGE here, but if we don't
read from the cache, we do check it! */
{
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
/* More init stuff. */
if (!pk)
{
pk = xmalloc_clear (sizeof *pk);
internal++;
}
/* Do a lookup. */
{
struct getkey_ctx_s ctx;
KBNODE kb = NULL;
KBNODE found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
{
rc = gpg_error_from_syserror ();
goto leave;
}
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
rc = lookup (&ctx, &kb, &found_key, 0);
if (!rc)
{
pk_from_block (pk, kb, found_key);
}
getkey_end (&ctx);
release_kbnode (kb);
}
if (!rc)
goto leave;
rc = GPG_ERR_NO_PUBKEY;
leave:
if (!rc)
cache_public_key (pk);
if (internal)
free_public_key (pk);
return rc;
}
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
* account nor does it merge in the self-signed data. This function
* also only considers primary keys. It is intended to be used as a
* quick check of the key to avoid recursion. It should only be used
* in very certain cases. Like get_pubkey and unlike any of the other
* lookup functions, this function also consults the user id cache
* (see cache_public_key).
*
* Return the public key in *PK. The resources in *PK should be
* released using release_public_key_parts(). */
int
get_pubkey_fast (PKT_public_key * pk, u32 * keyid)
{
int rc = 0;
KEYDB_HANDLE hd;
KBNODE keyblock;
u32 pkid[2];
log_assert (pk);
#if MAX_PK_CACHE_ENTRIES
{
/* Try to get it from the cache */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]
/* Only consider primary keys. */
&& ce->pk->keyid[0] == ce->pk->main_keyid[0]
&& ce->pk->keyid[1] == ce->pk->main_keyid[1])
{
if (pk)
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_kid (hd, keyid);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keydb_release (hd);
return GPG_ERR_NO_PUBKEY;
}
rc = keydb_get_keyblock (hd, &keyblock);
keydb_release (hd);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
return GPG_ERR_NO_PUBKEY;
}
log_assert (keyblock && keyblock->pkt
&& keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* We return the primary key. If KEYID matched a subkey, then we
return an error. */
keyid_from_pk (keyblock->pkt->pkt.public_key, pkid);
if (keyid[0] == pkid[0] && keyid[1] == pkid[1])
copy_public_key (pk, keyblock->pkt->pkt.public_key);
else
rc = GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
/* Not caching key here since it won't have all of the fields
properly set. */
return rc;
}
/* Return the key block for the key with key id KEYID or NULL, if an
* error occurs. Use release_kbnode() to release the key block.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
kbnode_t
get_pubkeyblock (u32 * keyid)
{
struct getkey_ctx_s ctx;
int rc = 0;
KBNODE keyblock = NULL;
memset (&ctx, 0, sizeof ctx);
/* No need to set exact here because we want the entire block. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return NULL;
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
rc = lookup (&ctx, &keyblock, NULL, 0);
getkey_end (&ctx);
return rc ? NULL : keyblock;
}
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. The resources should be released
* using release_public_key_parts().
*
* Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE
* is passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it
* must be valid! If this is non-zero, only keys with the specified
* usage will be returned.
*
* Returns 0 on success. If a public key with the specified key id is
* not found or a secret key is not available for that public key, an
* error code is returned. Note: this function ignores legacy keys.
* An error code is also return if an error occurs.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
get_seckey (PKT_public_key *pk, u32 *keyid)
{
gpg_error_t err;
struct getkey_ctx_s ctx;
kbnode_t keyblock = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
err = lookup (&ctx, &keyblock, &found_key, 1);
if (!err)
{
pk_from_block (pk, keyblock, found_key);
}
getkey_end (&ctx);
release_kbnode (keyblock);
if (!err)
{
err = agent_probe_secret_key (/*ctrl*/NULL, pk);
if (err)
release_public_key_parts (pk);
}
return err;
}
/* Skip unusable keys. A key is unusable if it is revoked, expired or
disabled or if the selected user id is revoked or expired. */
static int
skip_unusable (void *dummy, u32 * keyid, int uid_no)
{
int unusable = 0;
KBNODE keyblock;
PKT_public_key *pk;
(void) dummy;
keyblock = get_pubkeyblock (keyid);
if (!keyblock)
{
log_error ("error checking usability status of %s\n", keystr (keyid));
goto leave;
}
pk = keyblock->pkt->pkt.public_key;
/* Is the key revoked or expired? */
if (pk->flags.revoked || pk->has_expired)
unusable = 1;
/* Is the user ID in question revoked or expired? */
if (!unusable && uid_no)
{
KBNODE node;
int uids_seen = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *user_id = node->pkt->pkt.user_id;
uids_seen ++;
if (uids_seen != uid_no)
continue;
if (user_id->is_revoked || user_id->is_expired)
unusable = 1;
break;
}
}
/* If UID_NO is non-zero, then the keyblock better have at least
that many UIDs. */
log_assert (uids_seen == uid_no);
}
if (!unusable)
unusable = pk_is_disabled (pk);
leave:
release_kbnode (keyblock);
return unusable;
}
/* Search for keys matching some criteria.
If RETCTX is not NULL, then the constructed context is returned in
*RETCTX so that getpubkey_next can be used to get subsequent
results. In this case, getkey_end() must be used to free the
search context. If RETCTX is not NULL, then RET_KDBHD must be
NULL.
If NAMELIST is not NULL, then a search query is constructed using
classify_user_id on each of the strings in the list. (Recall: the
database does an OR of the terms, not an AND.) If NAMELIST is
NULL, then all results are returned.
If PK is not NULL, the public key of the first result is returned
in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
set, it is used to filter the search results. See the
documentation for finish_lookup to understand exactly how this is
used. Note: The self-signed data has already been merged into the
public key using merge_selfsigs. Free *PK by calling
release_public_key_parts (or, if PK was allocated using xfree, you
can use free_public_key, which calls release_public_key_parts(PK)
and then xfree(PK)).
If WANT_SECRET is set, then only keys with an available secret key
(either locally or via key registered on a smartcard) are returned.
If INCLUDE_UNUSABLE is set, then unusable keys (see the
documentation for skip_unusable for an exact definition) are
skipped unless they are looked up by key id or by fingerprint.
If RET_KB is not NULL, the keyblock is returned in *RET_KB. This
should be freed using release_kbnode().
If RET_KDBHD is not NULL, then the new database handle used to
conduct the search is returned in *RET_KDBHD. This can be used to
get subsequent results using keydb_search_next. Note: in this
case, no advanced filtering is done for subsequent results (e.g.,
WANT_SECRET and PK->REQ_USAGE are not respected).
This function returns 0 on success. Otherwise, an error code is
returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
(if want_secret is set) is returned if the key is not found. */
static int
key_byname (GETKEY_CTX *retctx, strlist_t namelist,
PKT_public_key *pk,
int want_secret, int include_unusable,
KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd)
{
int rc = 0;
int n;
strlist_t r;
GETKEY_CTX ctx;
KBNODE help_kb = NULL;
KBNODE found_key = NULL;
if (retctx)
{
/* Reset the returned context in case of error. */
log_assert (!ret_kdbhd); /* Not allowed because the handle is stored
in the context. */
*retctx = NULL;
}
if (ret_kdbhd)
*ret_kdbhd = NULL;
if (!namelist)
/* No search terms: iterate over the whole DB. */
{
ctx = xmalloc_clear (sizeof *ctx);
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_FIRST;
if (!include_unusable)
ctx->items[0].skipfnc = skip_unusable;
}
else
{
/* Build the search context. */
for (n = 0, r = namelist; r; r = r->next)
n++;
/* CTX has space for a single search term at the end. Thus, we
need to allocate sizeof *CTX plus (n - 1) sizeof
CTX->ITEMS. */
ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items);
ctx->nitems = n;
for (n = 0, r = namelist; r; r = r->next, n++)
{
gpg_error_t err;
err = classify_user_id (r->d, &ctx->items[n], 1);
if (ctx->items[n].exact)
ctx->exact = 1;
if (err)
{
xfree (ctx);
return gpg_err_code (err); /* FIXME: remove gpg_err_code. */
}
if (!include_unusable
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR16
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR20
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR)
ctx->items[n].skipfnc = skip_unusable;
}
}
ctx->want_secret = want_secret;
ctx->kr_handle = keydb_new ();
if (!ctx->kr_handle)
{
rc = gpg_error_from_syserror ();
getkey_end (ctx);
return rc;
}
if (!ret_kb)
ret_kb = &help_kb;
if (pk)
{
ctx->req_usage = pk->req_usage;
}
rc = lookup (ctx, ret_kb, &found_key, want_secret);
if (!rc && pk)
{
pk_from_block (pk, *ret_kb, found_key);
}
release_kbnode (help_kb);
if (retctx) /* Caller wants the context. */
*retctx = ctx;
else
{
if (ret_kdbhd)
{
*ret_kdbhd = ctx->kr_handle;
ctx->kr_handle = NULL;
}
getkey_end (ctx);
}
return rc;
}
/* Find a public key identified by NAME.
*
* If name appears to be a valid RFC822 mailbox (i.e., email
* address) and auto key lookup is enabled (no_akl == 0), then the
* specified auto key lookup methods (--auto-key-lookup) are used to
* import the key into the local keyring. Otherwise, just the local
* keyring is consulted.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is
* passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this
* is non-zero, only keys with the specified usage will be returned.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* NAME is a string, which is turned into a search query using
* classify_user_id.
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* If RET_KDBHD is not NULL, then the new database handle used to
* conduct the search is returned in *RET_KDBHD. This can be used to
* get subsequent results using keydb_search_next or to modify the
* returned record. Note: in this case, no advanced filtering is done
* for subsequent results (e.g., PK->REQ_USAGE is not respected).
* Unlike RETCTX, this is always returned.
*
* If INCLUDE_UNUSABLE is set, then unusable keys (see the
* documentation for skip_unusable for an exact definition) are
* skipped unless they are looked up by key id or by fingerprint.
*
* If NO_AKL is set, then the auto key locate functionality is
* disabled and only the local key ring is considered. Note: the
* local key ring is consulted even if local is not in the
* --auto-key-locate option list!
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
int
get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
const char *name, KBNODE * ret_keyblock,
KEYDB_HANDLE * ret_kdbhd, int include_unusable, int no_akl)
{
int rc;
strlist_t namelist = NULL;
struct akl *akl;
int is_mbox;
int nodefault = 0;
int anylocalfirst = 0;
/* If RETCTX is not NULL, then RET_KDBHD must be NULL. */
log_assert (retctx == NULL || ret_kdbhd == NULL);
if (retctx)
*retctx = NULL;
/* Does NAME appear to be a mailbox (mail address)? */
is_mbox = is_valid_mailbox (name);
/* The auto-key-locate feature works as follows: there are a number
* of methods to look up keys. By default, the local keyring is
* tried first. Then, each method listed in the --auto-key-locate is
* tried in the order it appears.
*
* This can be changed as follows:
*
* - if nodefault appears anywhere in the list of options, then
* the local keyring is not tried first, or,
*
* - if local appears anywhere in the list of options, then the
* local keyring is not tried first, but in the order in which
* it was listed in the --auto-key-locate option.
*
* Note: we only save the search context in RETCTX if the local
* method is the first method tried (either explicitly or
* implicitly). */
if (!no_akl)
{
/* auto-key-locate is enabled. */
/* nodefault is true if "nodefault" or "local" appear. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL)
{
nodefault = 1;
break;
}
/* anylocalfirst is true if "local" appears before any other
search methods (except "nodefault"). */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT)
{
if (akl->type == AKL_LOCAL)
anylocalfirst = 1;
break;
}
}
if (!nodefault)
{
/* "nodefault" didn't occur. Thus, "local" is implicitly the
* first method to try. */
anylocalfirst = 1;
}
if (nodefault && is_mbox)
{
/* Either "nodefault" or "local" (explicitly) appeared in the
* auto key locate list and NAME appears to be an email address.
* Don't try the local keyring. */
rc = GPG_ERR_NO_PUBKEY;
}
else
{
/* Either "nodefault" and "local" don't appear in the auto key
* locate list (in which case we try the local keyring first) or
* NAME does not appear to be an email address (in which case we
* only try the local keyring). In this case, lookup NAME in
* the local keyring. */
add_to_strlist (&namelist, name);
rc = key_byname (retctx, namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
/* If the requested name resembles a valid mailbox and automatic
retrieval has been enabled, we try to import the key. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && !no_akl && is_mbox)
{
/* NAME wasn't present in the local keyring (or we didn't try
* the local keyring). Since the auto key locate feature is
* enabled and NAME appears to be an email address, try the auto
* locate feature. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
{
unsigned char *fpr = NULL;
size_t fpr_len;
int did_akl_local = 0;
int no_fingerprint = 0;
const char *mechanism = "?";
switch (akl->type)
{
case AKL_NODEFAULT:
/* This is a dummy mechanism. */
mechanism = "None";
rc = GPG_ERR_NO_PUBKEY;
break;
case AKL_LOCAL:
mechanism = "Local";
did_akl_local = 1;
if (retctx)
{
getkey_end (*retctx);
*retctx = NULL;
}
add_to_strlist (&namelist, name);
rc = key_byname (anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
break;
case AKL_CERT:
mechanism = "DNS CERT";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_PKA:
mechanism = "PKA";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_DANE:
mechanism = "DANE";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_WKD:
mechanism = "WKD";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_LDAP:
mechanism = "LDAP";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_KEYSERVER:
/* Strictly speaking, we don't need to only use a valid
* mailbox for the getname search, but it helps cut down
* on the problem of searching for something like "john"
* and getting a whole lot of keys back. */
if (keyserver_any_configured (ctrl))
{
mechanism = "keyserver";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len,
opt.keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
else
{
mechanism = "Unconfigured keyserver";
rc = GPG_ERR_NO_PUBKEY;
}
break;
case AKL_SPEC:
{
struct keyserver_spec *keyserver;
mechanism = akl->spec->uri;
keyserver = keyserver_match (akl->spec);
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl,
name, &fpr, &fpr_len, keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
break;
}
/* Use the fingerprint of the key that we actually fetched.
* This helps prevent problems where the key that we fetched
* doesn't have the same name that we used to fetch it. In
* the case of CERT and PKA, this is an actual security
* requirement as the URL might point to a key put in by an
* attacker. By forcing the use of the fingerprint, we
* won't use the attacker's key here. */
if (!rc && fpr)
{
char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (fpr_len <= MAX_FINGERPRINT_LEN);
free_strlist (namelist);
namelist = NULL;
bin2hex (fpr, fpr_len, fpr_string);
if (opt.verbose)
log_info ("auto-key-locate found fingerprint %s\n",
fpr_string);
add_to_strlist (&namelist, fpr_string);
}
else if (!rc && !fpr && !did_akl_local)
{ /* The acquisition method said no failure occurred, but
* it didn't return a fingerprint. That's a failure. */
no_fingerprint = 1;
rc = GPG_ERR_NO_PUBKEY;
}
xfree (fpr);
fpr = NULL;
if (!rc && !did_akl_local)
{ /* There was no error and we didn't do a local lookup.
* This means that we imported a key into the local
* keyring. Try to read the imported key from the
* keyring. */
if (retctx)
{
getkey_end (*retctx);
*retctx = NULL;
}
rc = key_byname (anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
if (!rc)
{
/* Key found. */
log_info (_("automatically retrieved '%s' via %s\n"),
name, mechanism);
break;
}
if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
|| opt.verbose || no_fingerprint)
log_info (_("error retrieving '%s' via %s: %s\n"),
name, mechanism,
no_fingerprint ? _("No fingerprint") : gpg_strerror (rc));
}
}
if (rc && retctx)
{
getkey_end (*retctx);
*retctx = NULL;
}
if (retctx && *retctx)
{
log_assert (!(*retctx)->extra_list);
(*retctx)->extra_list = namelist;
}
else
free_strlist (namelist);
return rc;
}
/* Comparison machinery for get_best_pubkey_byname. */
/* First we have a struct to cache computed information about the key
* in question. */
struct pubkey_cmp_cookie
{
int valid; /* Is this cookie valid? */
PKT_public_key key; /* The key. */
PKT_user_id *uid; /* The matching UID packet. */
unsigned int validity; /* Computed validity of (KEY, UID). */
u32 creation_time; /* Creation time of the newest subkey
capable of encryption. */
};
/* Then we have a series of helper functions. */
static int
key_is_ok (const PKT_public_key *key)
{
return (! key->has_expired && ! key->flags.revoked
&& key->flags.valid && ! key->flags.disabled);
}
static int
uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid)
{
return key_is_ok (key) && ! uid->is_revoked;
}
static int
subkey_is_ok (const PKT_public_key *sub)
{
return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
}
/* Finally this function compares a NEW key to the former candidate
* OLD. Returns < 0 if the old key is worse, > 0 if the old key is
* better, == 0 if it is a tie. */
static int
pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
struct pubkey_cmp_cookie *new, KBNODE new_keyblock)
{
kbnode_t n;
new->creation_time = 0;
for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY);
n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))
{
PKT_public_key *sub = n->pkt->pkt.public_key;
if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0)
continue;
if (! subkey_is_ok (sub))
continue;
if (sub->timestamp > new->creation_time)
new->creation_time = sub->timestamp;
}
for (n = find_next_kbnode (new_keyblock, PKT_USER_ID);
n; n = find_next_kbnode (n, PKT_USER_ID))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
char *mbox = mailbox_from_userid (uid->name);
int match = mbox ? strcasecmp (name, mbox) == 0 : 0;
xfree (mbox);
if (! match)
continue;
new->uid = scopy_user_id (uid);
new->validity =
get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK;
new->valid = 1;
if (! old->valid)
return -1; /* No OLD key. */
if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid))
return -1; /* Validity of the NEW key is better. */
if (old->validity < new->validity)
return -1; /* Validity of the NEW key is better. */
if (old->validity == new->validity && uid_is_ok (&new->key, uid)
&& old->creation_time < new->creation_time)
return -1; /* Both keys are of the same validity, but the
NEW key is newer. */
}
/* Stick with the OLD key. */
return 1;
}
/* This function works like get_pubkey_byname, but if the name
* resembles a mail address, the results are ranked and only the best
* result is returned. */
int
get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable, int no_akl)
{
int rc;
struct getkey_ctx_s *ctx = NULL;
if (retctx)
*retctx = NULL;
rc = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock,
NULL, include_unusable, no_akl);
if (rc)
{
if (ctx)
getkey_end (ctx);
return rc;
}
if (is_valid_mailbox (name) && ctx)
{
/* Rank results and return only the most relevant key. */
struct pubkey_cmp_cookie best = { 0 };
struct pubkey_cmp_cookie new;
kbnode_t new_keyblock;
while (getkey_next (ctx, &new.key, &new_keyblock) == 0)
{
int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock);
release_kbnode (new_keyblock);
if (diff < 0)
{
/* New key is better. */
release_public_key_parts (&best.key);
free_user_id (best.uid);
best = new;
}
else if (diff > 0)
{
/* Old key is better. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
new.uid = NULL;
}
else
{
/* A tie. Keep the old key. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
new.uid = NULL;
}
}
getkey_end (ctx);
ctx = NULL;
free_user_id (best.uid);
best.uid = NULL;
if (best.valid)
{
if (retctx || ret_keyblock)
{
ctx = xtrycalloc (1, sizeof **retctx);
if (! ctx)
rc = gpg_error_from_syserror ();
else
{
ctx->kr_handle = keydb_new ();
if (! ctx->kr_handle)
{
xfree (ctx);
*retctx = NULL;
rc = gpg_error_from_syserror ();
}
else
{
u32 *keyid = pk_keyid (&best.key);
ctx->exact = 1;
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx->items[0].u.kid[0] = keyid[0];
ctx->items[0].u.kid[1] = keyid[1];
if (ret_keyblock)
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
rc = getkey_next (ctx, NULL, ret_keyblock);
}
}
}
}
if (pk)
*pk = best.key;
else
release_public_key_parts (&best.key);
}
}
if (rc && ctx)
{
getkey_end (ctx);
ctx = NULL;
}
if (retctx && ctx)
*retctx = ctx;
else
getkey_end (ctx);
return rc;
}
/* Get a public key from a file.
*
* PK is the buffer to store the key. The caller needs to make sure
* that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to
* the lookup function and is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only
* keys with the specified usage will be returned.
*
* FNAME is the file name. That file should contain exactly one
* keyblock.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key
* is not found.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. The caller must release the content of PK by
* calling release_public_key_parts (or, if PK was malloced, using
* free_public_key).
*/
gpg_error_t
get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname)
{
gpg_error_t err;
kbnode_t keyblock;
kbnode_t found_key;
unsigned int infoflags;
err = read_key_from_file (ctrl, fname, &keyblock);
if (!err)
{
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. FIXME: Check whether this still holds. */
merge_selfsigs (keyblock);
found_key = finish_lookup (keyblock, pk->req_usage, 0, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
pk_from_block (pk, keyblock, found_key);
else
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
}
release_kbnode (keyblock);
return err;
}
/* Lookup a key with the specified fingerprint.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: this function does an exact search and thus the
* returned public key may be a subkey rather than the primary key.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If PK->REQ_USAGE is set, it is used to filter the search results.
* (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the
* documentation for finish_lookup to understand exactly how this is
* used.
*
* If R_KEYBLOCK is not NULL, then the first result's keyblock is
* returned in *R_KEYBLOCK. This should be freed using
* release_kbnode().
*
* FPRINT is a byte array whose contents is the fingerprint to use as
* the search term. FPRINT_LEN specifies the length of the
* fingerprint (in bytes). Currently, only 16 and 20-byte
* fingerprints are supported.
*
* FIXME: We should replace this with the _byname function. This can
* be done by creating a userID conforming to the unified fingerprint
* style. */
int
get_pubkey_byfprint (PKT_public_key *pk, kbnode_t *r_keyblock,
const byte * fprint, size_t fprint_len)
{
int rc;
if (r_keyblock)
*r_keyblock = NULL;
if (fprint_len == 20 || fprint_len == 16)
{
struct getkey_ctx_s ctx;
KBNODE kb = NULL;
KBNODE found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1;
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = fprint_len == 16 ? KEYDB_SEARCH_MODE_FPR16
: KEYDB_SEARCH_MODE_FPR20;
memcpy (ctx.items[0].u.fpr, fprint, fprint_len);
rc = lookup (&ctx, &kb, &found_key, 0);
if (!rc && pk)
pk_from_block (pk, kb, found_key);
if (!rc && r_keyblock)
{
*r_keyblock = kb;
kb = NULL;
}
release_kbnode (kb);
getkey_end (&ctx);
}
else
rc = GPG_ERR_GENERAL; /* Oops */
return rc;
}
/* This function is similar to get_pubkey_byfprint, but it doesn't
* merge the self-signed data into the public key and subkeys or into
* the user ids. It also doesn't add the key to the user id cache.
* Further, this function ignores PK->REQ_USAGE.
*
* This function is intended to avoid recursion and, as such, should
* only be used in very specific situations.
*
* Like get_pubkey_byfprint, PK may be NULL. In that case, this
* function effectively just checks for the existence of the key. */
int
get_pubkey_byfprint_fast (PKT_public_key * pk,
const byte * fprint, size_t fprint_len)
{
int rc = 0;
KEYDB_HANDLE hd;
KBNODE keyblock;
byte fprbuf[MAX_FINGERPRINT_LEN];
int i;
for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++)
fprbuf[i] = fprint[i];
while (i < MAX_FINGERPRINT_LEN)
fprbuf[i++] = 0;
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_fpr (hd, fprbuf);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keydb_release (hd);
return GPG_ERR_NO_PUBKEY;
}
rc = keydb_get_keyblock (hd, &keyblock);
keydb_release (hd);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
return GPG_ERR_NO_PUBKEY;
}
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (pk)
copy_public_key (pk, keyblock->pkt->pkt.public_key);
release_kbnode (keyblock);
/* Not caching key here since it won't have all of the fields
properly set. */
return 0;
}
const char *
parse_def_secret_key (ctrl_t ctrl)
{
KEYDB_HANDLE hd = NULL;
strlist_t t;
static int warned;
for (t = opt.def_secret_key; t; t = t->next)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
KBNODE kb;
KBNODE node;
err = classify_user_id (t->d, &desc, 1);
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
t->d, gpg_strerror (err));
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"), "--default-key");
continue;
}
if (! hd)
{
hd = keydb_new ();
if (!hd)
return NULL;
}
else
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
continue;
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err));
t = NULL;
break;
}
err = keydb_get_keyblock (hd, &kb);
if (err)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (err));
continue;
}
merge_selfsigs (kb);
err = gpg_error (GPG_ERR_NO_SECKEY);
node = kb;
do
{
PKT_public_key *pk = node->pkt->pkt.public_key;
/* Check that the key has the signing capability. */
if (! (pk->pubkey_usage & PUBKEY_USAGE_SIG))
continue;
/* Check if the key is valid. */
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "revoked");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "expired");
continue;
}
if (pk_is_disabled (pk))
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "disabled");
continue;
}
err = agent_probe_secret_key (ctrl, pk);
if (! err)
/* This is a valid key. */
break;
}
while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY)));
release_kbnode (kb);
if (err)
{
if (! warned && ! opt.quiet)
{
log_info (_("Warning: not using '%s' as default key: %s\n"),
t->d, gpg_strerror (GPG_ERR_NO_SECKEY));
print_reported_error (err, GPG_ERR_NO_SECKEY);
}
}
else
{
if (! warned && ! opt.quiet)
log_info (_("using \"%s\" as default secret key for signing\n"),
t->d);
break;
}
}
if (! warned && opt.def_secret_key && ! t)
log_info (_("all values passed to '%s' ignored\n"),
"--default-key");
warned = 1;
if (hd)
keydb_release (hd);
if (t)
return t->d;
return NULL;
}
/* Look up a secret key.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If --default-key was set, then the specified key is looked up. (In
* this case, the default key is returned even if it is considered
* unusable. See the documentation for skip_unusable for exactly what
* this means.)
*
* Otherwise, this initiates a DB scan that returns all keys that are
* usable (see previous paragraph for exactly what usable means) and
* for which a secret key is available.
*
* This function returns the first match. Additional results can be
* returned using getkey_next. */
gpg_error_t
get_seckey_default (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
strlist_t namelist = NULL;
int include_unusable = 1;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else
include_unusable = 0;
err = key_byname (NULL, namelist, pk, 1, include_unusable, NULL, NULL);
free_strlist (namelist);
return err;
}
/* Search for keys matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAMES is not NULL, then a search query is constructed using
* classify_user_id on each of the strings in the list. (Recall: the
* database does an OR of the terms, not an AND.) If NAMES is
* NULL, then all results are returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
gpg_error_t
getkey_bynames (getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret, kbnode_t *ret_keyblock)
{
return key_byname (retctx, names, pk, want_secret, 1,
ret_keyblock, NULL);
}
/* Search for one key matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAME is not NULL, then a search query is constructed using
* classify_user_id on the string. In this case, even unusable keys
* (see the documentation for skip_unusable for an exact definition of
* unusable) are returned. Otherwise, if --default-key was set, then
* that key is returned (even if it is unusable). If neither of these
* conditions holds, then the first usable key is returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found.
*
* FIXME: We also have the get_pubkey_byname function which has a
* different semantic. Should be merged with this one. */
gpg_error_t
getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret, kbnode_t *ret_keyblock)
{
gpg_error_t err;
strlist_t namelist = NULL;
int with_unusable = 1;
const char *def_secret_key = NULL;
if (want_secret && !name)
def_secret_key = parse_def_secret_key (ctrl);
if (want_secret && !name && def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (name)
add_to_strlist (&namelist, name);
else
with_unusable = 0;
err = key_byname (retctx, namelist, pk, want_secret, with_unusable,
ret_keyblock, NULL);
/* FIXME: Check that we really return GPG_ERR_NO_SECKEY if
WANT_SECRET has been used. */
free_strlist (namelist);
return err;
}
/* Return the next search result.
*
* If PK is not NULL, the public key of the next result is returned in
* *PK. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xmalloc, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire
* found keyblock is returned which must be released with
* release_kbnode. If the function returns an error NULL is stored at
* RET_KEYBLOCK.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
getkey_next (getkey_ctx_t ctx, PKT_public_key *pk, kbnode_t *ret_keyblock)
{
int rc; /* Fixme: Make sure this is proper gpg_error */
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
/* We need to disable the caching so that for an exact key search we
won't get the result back from the cache and thus end up in an
endless loop. The endless loop can occur, because the cache is
used without respecting the current file pointer! */
keydb_disable_caching (ctx->kr_handle);
/* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the
* caller wants PK, but not RET_KEYBLOCK, we need hand in our own
* keyblock. */
if (pk && ret_keyblock == NULL)
ret_keyblock = &keyblock;
rc = lookup (ctx, ret_keyblock, pk ? &found_key : NULL, ctx->want_secret);
if (!rc && pk)
{
log_assert (found_key);
pk_from_block (pk, NULL, found_key);
release_kbnode (keyblock);
}
return rc;
}
/* Release any resources used by a key listing context. This must be
* called on the context returned by, e.g., getkey_byname. */
void
getkey_end (getkey_ctx_t ctx)
{
if (ctx)
{
keydb_release (ctx->kr_handle);
free_strlist (ctx->extra_list);
if (!ctx->not_allocated)
xfree (ctx);
}
}
/************************************************
************* Merging stuff ********************
************************************************/
/* Set the mainkey_id fields for all keys in KEYBLOCK. This is
* usually done by merge_selfsigs but at some places we only need the
* main_kid not a full merge. The function also guarantees that all
* pk->keyids are computed. */
void
setup_main_keyids (kbnode_t keyblock)
{
u32 kid[2], mainkid[2];
kbnode_t kbctx, node;
PKT_public_key *pk;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keyid_from_pk (pk, mainkid);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */
if (!pk->main_keyid[0] && !pk->main_keyid[1])
{
pk->main_keyid[0] = mainkid[0];
pk->main_keyid[1] = mainkid[1];
}
}
}
/* KEYBLOCK corresponds to a public key block. This function merges
* much of the information from the self-signed data into the public
* key, public subkey and user id data structures. If you use the
* high-level search API (e.g., get_pubkey) for looking up key blocks,
* then you don't need to call this function. This function is
* useful, however, if you change the keyblock, e.g., by adding or
* removing a self-signed data packet. */
void
merge_keys_and_selfsig (KBNODE keyblock)
{
if (!keyblock)
;
else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
merge_selfsigs (keyblock);
else
log_debug ("FIXME: merging secret key blocks is not anymore available\n");
}
static int
parse_key_usage (PKT_signature * sig)
{
int key_usage = 0;
const byte *p;
size_t n;
byte flags;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_FLAGS, &n);
if (p && n)
{
/* First octet of the keyflags. */
flags = *p;
if (flags & 1)
{
key_usage |= PUBKEY_USAGE_CERT;
flags &= ~1;
}
if (flags & 2)
{
key_usage |= PUBKEY_USAGE_SIG;
flags &= ~2;
}
/* We do not distinguish between encrypting communications and
encrypting storage. */
if (flags & (0x04 | 0x08))
{
key_usage |= PUBKEY_USAGE_ENC;
flags &= ~(0x04 | 0x08);
}
if (flags & 0x20)
{
key_usage |= PUBKEY_USAGE_AUTH;
flags &= ~0x20;
}
if (flags)
key_usage |= PUBKEY_USAGE_UNKNOWN;
if (!key_usage)
key_usage |= PUBKEY_USAGE_NONE;
}
else if (p) /* Key flags of length zero. */
key_usage |= PUBKEY_USAGE_NONE;
/* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a
capability that we do not handle. This serves to distinguish
between a zero key usage which we handle as the default
capabilities for that algorithm, and a usage that we do not
handle. Likewise we use PUBKEY_USAGE_NONE to indicate that
key_flags have been given but they do not specify any usage. */
return key_usage;
}
/* Apply information from SIGNODE (which is the valid self-signature
* associated with that UID) to the UIDNODE:
* - weather the UID has been revoked
* - assumed creation date of the UID
* - temporary store the keyflags here
* - temporary store the key expiration time here
* - mark whether the primary user ID flag hat been set.
* - store the preferences
*/
static void
fixup_uidnode (KBNODE uidnode, KBNODE signode, u32 keycreated)
{
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p, *sym, *hash, *zip;
size_t n, nsym, nhash, nzip;
sig->flags.chosen_selfsig = 1;/* We chose this one. */
uid->created = 0; /* Not created == invalid. */
if (IS_UID_REV (sig))
{
uid->is_revoked = 1;
return; /* Has been revoked. */
}
else
uid->is_revoked = 0;
uid->expiredate = sig->expiredate;
if (sig->flags.expired)
{
uid->is_expired = 1;
return; /* Has expired. */
}
else
uid->is_expired = 0;
uid->created = sig->timestamp; /* This one is okay. */
uid->selfsigversion = sig->version;
/* If we got this far, it's not expired :) */
uid->is_expired = 0;
/* Store the key flags in the helper variable for later processing. */
uid->help_key_usage = parse_key_usage (sig);
/* Ditto for the key expiration. */
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
uid->help_key_expire = keycreated + buf32_to_u32 (p);
else
uid->help_key_expire = 0;
/* Set the primary user ID flag - we will later wipe out some
* of them to only have one in our keyblock. */
uid->is_primary = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p)
uid->is_primary = 2;
/* We could also query this from the unhashed area if it is not in
* the hased area and then later try to decide which is the better
* there should be no security problem with this.
* For now we only look at the hashed one. */
/* Now build the preferences list. These must come from the
hashed section so nobody can modify the ciphers a key is
willing to accept. */
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM, &n);
sym = p;
nsym = p ? n : 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH, &n);
hash = p;
nhash = p ? n : 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR, &n);
zip = p;
nzip = p ? n : 0;
if (uid->prefs)
xfree (uid->prefs);
n = nsym + nhash + nzip;
if (!n)
uid->prefs = NULL;
else
{
uid->prefs = xmalloc (sizeof (*uid->prefs) * (n + 1));
n = 0;
for (; nsym; nsym--, n++)
{
uid->prefs[n].type = PREFTYPE_SYM;
uid->prefs[n].value = *sym++;
}
for (; nhash; nhash--, n++)
{
uid->prefs[n].type = PREFTYPE_HASH;
uid->prefs[n].value = *hash++;
}
for (; nzip; nzip--, n++)
{
uid->prefs[n].type = PREFTYPE_ZIP;
uid->prefs[n].value = *zip++;
}
uid->prefs[n].type = PREFTYPE_NONE; /* End of list marker */
uid->prefs[n].value = 0;
}
/* See whether we have the MDC feature. */
uid->flags.mdc = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x01))
uid->flags.mdc = 1;
/* And the keyserver modify flag. */
uid->flags.ks_modify = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n);
if (p && n && (p[0] & 0x80))
uid->flags.ks_modify = 0;
}
static void
sig_to_revoke_info (PKT_signature * sig, struct revoke_info *rinfo)
{
rinfo->date = sig->timestamp;
rinfo->algo = sig->pubkey_algo;
rinfo->keyid[0] = sig->keyid[0];
rinfo->keyid[1] = sig->keyid[1];
}
/* Given a keyblock, parse the key block and extract various pieces of
information and save them with the primary key packet and the user
id packets. For instance, some information is stored in signature
packets. We find the latest such valid packet (since the user can
change that information) and copy its contents into the
PKT_public_key.
Note that R_REVOKED may be set to 0, 1 or 2.
This function fills in the following fields in the primary key's
keyblock:
main_keyid (computed)
revkey / numrevkeys (derived from self signed key data)
flags.valid (whether we have at least 1 self-sig)
flags.maybe_revoked (whether a designed revoked the key, but
we are missing the key to check the sig)
selfsigversion (highest version of any valid self-sig)
pubkey_usage (derived from most recent self-sig or most
recent user id)
has_expired (various sources)
expiredate (various sources)
See the documentation for fixup_uidnode for how the user id packets
are modified. In addition to that the primary user id's is_primary
field is set to 1 and the other user id's is_primary are set to
0. */
static void
merge_selfsigs_main (KBNODE keyblock, int *r_revoked,
struct revoke_info *rinfo)
{
PKT_public_key *pk = NULL;
KBNODE k;
u32 kid[2];
u32 sigdate, uiddate, uiddate2;
KBNODE signode, uidnode, uidnode2;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
int key_expire_seen = 0;
byte sigversion = 0;
*r_revoked = 0;
memset (rinfo, 0, sizeof (*rinfo));
/* Section 11.1 of RFC 4880 determines the order of packets within a
message. There are three sections, which must occur in the
following order: the public key, the user ids and user attributes
and the subkeys. Within each section, each primary packet (e.g.,
a user id packet) is followed by one or more signature packets,
which modify that packet. */
/* According to Section 11.1 of RFC 4880, the public key must be the
first packet. */
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
/* parse_keyblock_image ensures that the first packet is the
public key. */
BUG ();
pk = keyblock->pkt->pkt.public_key;
keytimestamp = pk->timestamp;
keyid_from_pk (pk, kid);
pk->main_keyid[0] = kid[0];
pk->main_keyid[1] = kid[1];
if (pk->version < 4)
{
/* Before v4 the key packet itself contains the expiration date
* and there was no way to change it, so we start with the one
* from the key packet. */
key_expire = pk->max_expiredate;
key_expire_seen = 1;
}
/* First pass:
- Find the latest direct key self-signature. We assume that the
newest one overrides all others.
- Determine whether the key has been revoked.
- Gather all revocation keys (unlike other data, we don't just
take them from the latest self-signed packet).
- Determine max (sig[...]->version).
*/
/* Reset this in case this key was already merged. */
xfree (pk->revkey);
pk->revkey = NULL;
pk->numrevkeys = 0;
signode = NULL;
sigdate = 0; /* Helper variable to find the latest signature. */
/* According to Section 11.1 of RFC 4880, the public key comes first
and is immediately followed by any signature packets that modify
it. */
for (k = keyblock;
k && k->pkt->pkttype != PKT_USER_ID
&& k->pkt->pkttype != PKT_ATTRIBUTE
&& k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
/* Self sig. */
{
if (check_key_signature (keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_KEY_REV (sig))
{
/* Key has been revoked - there is no way to
* override such a revocation, so we theoretically
* can stop now. We should not cope with expiration
* times for revocations here because we have to
* assume that an attacker can generate all kinds of
* signatures. However due to the fact that the key
* has been revoked it does not harm either and by
* continuing we gather some more info on that
* key. */
*r_revoked = 1;
sig_to_revoke_info (sig, rinfo);
}
else if (IS_KEY_SIG (sig))
{
/* Add the indicated revocations keys from all
signatures not just the latest. We do this
because you need multiple 1F sigs to properly
handle revocation keys (PGP does it this way, and
a revocation key could be sensitive and hence in
a different signature). */
if (sig->revkey)
{
int i;
pk->revkey =
xrealloc (pk->revkey, sizeof (struct revocation_key) *
(pk->numrevkeys + sig->numrevkeys));
for (i = 0; i < sig->numrevkeys; i++)
memcpy (&pk->revkey[pk->numrevkeys++],
&sig->revkey[i],
sizeof (struct revocation_key));
}
if (sig->timestamp >= sigdate)
/* This is the latest signature so far. */
{
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
}
}
/* Remove dupes from the revocation keys. */
if (pk->revkey)
{
int i, j, x, changed = 0;
for (i = 0; i < pk->numrevkeys; i++)
{
for (j = i + 1; j < pk->numrevkeys; j++)
{
if (memcmp (&pk->revkey[i], &pk->revkey[j],
sizeof (struct revocation_key)) == 0)
{
/* remove j */
for (x = j; x < pk->numrevkeys - 1; x++)
pk->revkey[x] = pk->revkey[x + 1];
pk->numrevkeys--;
j--;
changed = 1;
}
}
}
if (changed)
pk->revkey = xrealloc (pk->revkey,
pk->numrevkeys *
sizeof (struct revocation_key));
}
if (signode)
/* SIGNODE is the 1F signature packet with the latest creation
time. Extract some information from it. */
{
/* Some information from a direct key signature take precedence
* over the same information given in UID sigs. */
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p;
key_usage = parse_key_usage (sig);
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
{
key_expire = keytimestamp + buf32_to_u32 (p);
key_expire_seen = 1;
}
/* Mark that key as valid: One direct key signature should
* render a key as valid. */
pk->flags.valid = 1;
}
/* Pass 1.5: Look for key revocation signatures that were not made
by the key (i.e. did a revocation key issue a revocation for
us?). Only bother to do this if there is a revocation key in the
first place and we're not revoked already. */
if (!*r_revoked && pk->revkey)
for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (IS_KEY_REV (sig) &&
(sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1]))
{
int rc = check_revocation_keys (pk, sig);
if (rc == 0)
{
*r_revoked = 2;
sig_to_revoke_info (sig, rinfo);
/* Don't continue checking since we can't be any
more revoked than this. */
break;
}
else if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
pk->flags.maybe_revoked = 1;
/* A failure here means the sig did not verify, was
not issued by a revocation key, or a revocation
key loop was broken. If a revocation key isn't
findable, however, the key might be revoked and
we don't know it. */
/* TODO: In the future handle subkey and cert
revocations? PGP doesn't, but it's in 2440. */
}
}
}
/* Second pass: Look at the self-signature of all user IDs. */
/* According to RFC 4880 section 11.1, user id and attribute packets
are in the second section, after the public key packet and before
the subkey packets. */
signode = uidnode = NULL;
sigdate = 0; /* Helper variable to find the latest signature in one UID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE)
/* New user id packet. */
{
if (uidnode && signode)
/* Apply the data from the most recent self-signed packet
to the preceding user id packet. */
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* Clear SIGNODE. The only relevant self-signed data for
UIDNODE follows it. */
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else
uidnode = NULL;
signode = NULL;
sigdate = 0;
}
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{
if (check_key_signature (keyblock, k, NULL))
; /* signature did not verify */
else if ((IS_UID_SIG (sig) || IS_UID_REV (sig))
&& sig->timestamp >= sigdate)
{
/* Note: we allow invalidation of cert revocations
* by a newer signature. An attacker can't use this
* because a key should be revoked with a key revocation.
* The reason why we have to allow for that is that at
* one time an email address may become invalid but later
* the same email address may become valid again (hired,
* fired, hired again). */
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* If the key isn't valid yet, and we have
--allow-non-selfsigned-uid set, then force it valid. */
if (!pk->flags.valid && opt.allow_non_selfsigned_uid)
{
if (opt.verbose)
log_info (_("Invalid key %s made valid by"
" --allow-non-selfsigned-uid\n"), keystr_from_pk (pk));
pk->flags.valid = 1;
}
/* The key STILL isn't valid, so try and find an ultimately
trusted signature. */
if (!pk->flags.valid)
{
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1])
{
PKT_public_key *ultimate_pk;
ultimate_pk = xmalloc_clear (sizeof (*ultimate_pk));
/* We don't want to use the full get_pubkey to
avoid infinite recursion in certain cases.
There is no reason to check that an ultimately
trusted key is still valid - if it has been
revoked the user should also remove the
ultimate trust flag. */
if (get_pubkey_fast (ultimate_pk, sig->keyid) == 0
&& check_key_signature2 (keyblock, k, ultimate_pk,
NULL, NULL, NULL, NULL) == 0
&& get_ownertrust (ultimate_pk) == TRUST_ULTIMATE)
{
free_public_key (ultimate_pk);
pk->flags.valid = 1;
break;
}
free_public_key (ultimate_pk);
}
}
}
}
/* Record the highest selfsig version so we know if this is a v3
key through and through, or a v3 key with a v4 selfsig
somewhere. This is useful in a few places to know if the key
must be treated as PGP2-style or OpenPGP-style. Note that a
selfsig revocation with a higher version number will also raise
this value. This is okay since such a revocation must be
issued by the user (i.e. it cannot be issued by someone else to
modify the key behavior.) */
pk->selfsigversion = sigversion;
/* Now that we had a look at all user IDs we can now get some information
* from those user IDs.
*/
if (!key_usage)
{
/* Find the latest user ID with key flags set. */
uiddate = 0; /* Helper to find the latest user ID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_usage && uid->created > uiddate)
{
key_usage = uid->help_key_usage;
uiddate = uid->created;
}
}
}
}
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (pk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (pk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
/* Whatever happens, it's a primary key, so it can certify. */
pk->pubkey_usage = key_usage | PUBKEY_USAGE_CERT;
if (!key_expire_seen)
{
/* Find the latest valid user ID with a key expiration set
* Note, that this may be a different one from the above because
* some user IDs may have no expiration date set. */
uiddate = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_expire && uid->created > uiddate)
{
key_expire = uid->help_key_expire;
uiddate = uid->created;
}
}
}
}
/* Currently only v3 keys have a maximum expiration date, but I'll
bet v5 keys get this feature again. */
if (key_expire == 0
|| (pk->max_expiredate && key_expire > pk->max_expiredate))
key_expire = pk->max_expiredate;
pk->has_expired = key_expire >= curtime ? 0 : key_expire;
pk->expiredate = key_expire;
/* Fixme: we should see how to get rid of the expiretime fields but
* this needs changes at other places too. */
/* And now find the real primary user ID and delete all others. */
uiddate = uiddate2 = 0;
uidnode = uidnode2 = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->is_primary)
{
if (uid->created > uiddate)
{
uiddate = uid->created;
uidnode = k;
}
else if (uid->created == uiddate && uidnode)
{
/* The dates are equal, so we need to do a
different (and arbitrary) comparison. This
should rarely, if ever, happen. It's good to
try and guarantee that two different GnuPG
users with two different keyrings at least pick
the same primary. */
if (cmp_user_ids (uid, uidnode->pkt->pkt.user_id) > 0)
uidnode = k;
}
}
else
{
if (uid->created > uiddate2)
{
uiddate2 = uid->created;
uidnode2 = k;
}
else if (uid->created == uiddate2 && uidnode2)
{
if (cmp_user_ids (uid, uidnode2->pkt->pkt.user_id) > 0)
uidnode2 = k;
}
}
}
}
if (uidnode)
{
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID &&
!k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (k != uidnode)
uid->is_primary = 0;
}
}
}
else if (uidnode2)
{
/* None is flagged primary - use the latest user ID we have,
and disambiguate with the arbitrary packet comparison. */
uidnode2->pkt->pkt.user_id->is_primary = 1;
}
else
{
/* None of our uids were self-signed, so pick the one that
sorts first to be the primary. This is the best we can do
here since there are no self sigs to date the uids. */
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data)
{
if (!uidnode)
{
uidnode = k;
uidnode->pkt->pkt.user_id->is_primary = 1;
continue;
}
else
{
if (cmp_user_ids (k->pkt->pkt.user_id,
uidnode->pkt->pkt.user_id) > 0)
{
uidnode->pkt->pkt.user_id->is_primary = 0;
uidnode = k;
uidnode->pkt->pkt.user_id->is_primary = 1;
}
else
k->pkt->pkt.user_id->is_primary = 0; /* just to be
safe */
}
}
}
}
}
/* Convert a buffer to a signature. Useful for 0x19 embedded sigs.
Caller must free the signature when they are done. */
static PKT_signature *
buf_to_sig (const byte * buf, size_t len)
{
PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature));
IOBUF iobuf = iobuf_temp_with_content (buf, len);
int save_mode = set_packet_list_mode (0);
if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0)
{
xfree (sig);
sig = NULL;
}
set_packet_list_mode (save_mode);
iobuf_close (iobuf);
return sig;
}
/* Use the self-signed data to fill in various fields in subkeys.
KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in.
Sets the following fields on the subkey:
main_keyid
flags.valid if the subkey has a valid self-sig binding
flags.revoked
flags.backsig
pubkey_usage
has_expired
expired_date
On this subkey's most revent valid self-signed packet, the
following field is set:
flags.chosen_selfsig
*/
static void
merge_selfsigs_subkey (KBNODE keyblock, KBNODE subnode)
{
PKT_public_key *mainpk = NULL, *subpk = NULL;
PKT_signature *sig;
KBNODE k;
u32 mainkid[2];
u32 sigdate = 0;
KBNODE signode;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
const byte *p;
if (subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY)
BUG ();
mainpk = keyblock->pkt->pkt.public_key;
if (mainpk->version < 4)
return;/* (actually this should never happen) */
keyid_from_pk (mainpk, mainkid);
subpk = subnode->pkt->pkt.public_key;
keytimestamp = subpk->timestamp;
subpk->flags.valid = 0;
subpk->flags.exact = 0;
subpk->main_keyid[0] = mainpk->main_keyid[0];
subpk->main_keyid[1] = mainpk->main_keyid[1];
/* Find the latest key binding self-signature. */
signode = NULL;
sigdate = 0; /* Helper to find the latest signature. */
for (k = subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
sig = k->pkt->pkt.signature;
if (sig->keyid[0] == mainkid[0] && sig->keyid[1] == mainkid[1])
{
if (check_key_signature (keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_SUBKEY_REV (sig))
{
/* Note that this means that the date on a
revocation sig does not matter - even if the
binding sig is dated after the revocation sig,
the subkey is still marked as revoked. This
seems ok, as it is just as easy to make new
subkeys rather than re-sign old ones as the
problem is in the distribution. Plus, PGP (7)
does this the same way. */
subpk->flags.revoked = 1;
sig_to_revoke_info (sig, &subpk->revoked);
/* Although we could stop now, we continue to
* figure out other information like the old expiration
* time. */
}
else if (IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate)
{
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
}
}
}
}
}
/* No valid key binding. */
if (!signode)
return;
sig = signode->pkt->pkt.signature;
sig->flags.chosen_selfsig = 1; /* So we know which selfsig we chose later. */
key_usage = parse_key_usage (sig);
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (subpk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (subpk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
subpk->pubkey_usage = key_usage;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
key_expire = keytimestamp + buf32_to_u32 (p);
else
key_expire = 0;
subpk->has_expired = key_expire >= curtime ? 0 : key_expire;
subpk->expiredate = key_expire;
/* Algo doesn't exist. */
if (openpgp_pk_test_algo (subpk->pubkey_algo))
return;
subpk->flags.valid = 1;
/* Find the most recent 0x19 embedded signature on our self-sig. */
if (!subpk->flags.backsig)
{
int seq = 0;
size_t n;
PKT_signature *backsig = NULL;
sigdate = 0;
/* We do this while() since there may be other embedded
signatures in the future. We only want 0x19 here. */
while ((p = enum_sig_subpkt (sig->hashed,
SIGSUBPKT_SIGNATURE, &n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
seq = 0;
/* It is safe to have this in the unhashed area since the 0x19
is located on the selfsig for convenience, not security. */
while ((p = enum_sig_subpkt (sig->unhashed, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
if (backsig)
{
/* At this point, backsig contains the most recent 0x19 sig.
Let's see if it is good. */
/* 2==valid, 1==invalid, 0==didn't check */
if (check_backsig (mainpk, subpk, backsig) == 0)
subpk->flags.backsig = 2;
else
subpk->flags.backsig = 1;
free_seckey_enc (backsig);
}
}
}
/* Merge information from the self-signatures with the public key,
subkeys and user ids to make using them more easy.
See documentation for merge_selfsigs_main, merge_selfsigs_subkey
and fixup_uidnode for exactly which fields are updated. */
static void
merge_selfsigs (KBNODE keyblock)
{
KBNODE k;
int revoked;
struct revoke_info rinfo;
PKT_public_key *main_pk;
prefitem_t *prefs;
unsigned int mdc_feature;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
log_error ("expected public key but found secret key "
"- must stop\n");
/* We better exit here because a public key is expected at
other places too. FIXME: Figure this out earlier and
don't get to here at all */
g10_exit (1);
}
BUG ();
}
merge_selfsigs_main (keyblock, &revoked, &rinfo);
/* Now merge in the data from each of the subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_selfsigs_subkey (keyblock, k);
}
}
main_pk = keyblock->pkt->pkt.public_key;
if (revoked || main_pk->has_expired || !main_pk->flags.valid)
{
/* If the primary key is revoked, expired, or invalid we
* better set the appropriate flags on that key and all
* subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (!main_pk->flags.valid)
pk->flags.valid = 0;
if (revoked && !pk->flags.revoked)
{
pk->flags.revoked = revoked;
memcpy (&pk->revoked, &rinfo, sizeof (rinfo));
}
if (main_pk->has_expired)
pk->has_expired = main_pk->has_expired;
}
}
return;
}
/* Set the preference list of all keys to those of the primary real
* user ID. Note: we use these preferences when we don't know by
* which user ID the key has been selected.
* fixme: we should keep atoms of commonly used preferences or
* use reference counting to optimize the preference lists storage.
* FIXME: it might be better to use the intersection of
* all preferences.
* Do a similar thing for the MDC feature flag. */
prefs = NULL;
mdc_feature = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->is_primary)
{
prefs = k->pkt->pkt.user_id->prefs;
mdc_feature = k->pkt->pkt.user_id->flags.mdc;
break;
}
}
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (pk->prefs)
xfree (pk->prefs);
pk->prefs = copy_prefs (prefs);
pk->flags.mdc = mdc_feature;
}
}
}
/* See whether the key satisfies any additional requirements specified
* in CTX. If so, return the node of an appropriate key or subkey.
* Otherwise, return NULL if there was no appropriate key.
*
* Note that we do not return a reference, i.e. the result must not be
* freed using 'release_kbnode'.
*
* In case the primary key is not required, select a suitable subkey.
* We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or
* we are in PGP6 or PGP7 mode and PUBKEY_USAGE_SIG is set in
* REQ_USAGE.
*
* If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
* are set in REQ_USAGE, we filter by the key's function. Concretely,
* if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only
* return a key if it is (at least) either a signing or a
* certification key.
*
* If REQ_USAGE is set, then we reject any keys that are not good
* (i.e., valid, not revoked, not expired, etc.). This allows the
* getkey functions to be used for plain key listings.
*
* Sets the matched key's user id field (pk->user_id) to the user id
* that matched the low-level search criteria or NULL.
*
* If R_FLAGS is not NULL set certain flags for more detailed error
* reporting. Used flags are:
*
* - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have
* been revoked.
* - LOOKUP_NOT_SELECTED :: No suitable key found
*
* This function needs to handle several different cases:
*
* 1. No requested usage and no primary key requested
* Examples for this case are that we have a keyID to be used
* for decrytion or verification.
* 2. No usage but primary key requested
* This is the case for all functions which work on an
* entire keyblock, e.g. for editing or listing
* 3. Usage and primary key requested
* FIXME
* 4. Usage but no primary key requested
* FIXME
*
*/
static kbnode_t
finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact,
unsigned int *r_flags)
{
kbnode_t k;
/* If WANT_EXACT is set, the key or subkey that actually matched the
low-level search criteria. */
kbnode_t foundk = NULL;
/* The user id (if any) that matched the low-level search criteria. */
PKT_user_id *foundu = NULL;
u32 latest_date;
kbnode_t latest_key;
PKT_public_key *pk;
int req_prim;
u32 curtime = make_timestamp ();
if (r_flags)
*r_flags = 0;
#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT)
req_usage &= USAGE_MASK;
/* Request the primary if we're certifying another key, and also if
* signing data while --pgp6 or --pgp7 is on since pgp 6 and 7 do
* not understand signatures made by a signing subkey. PGP 8 does. */
req_prim = ((req_usage & PUBKEY_USAGE_CERT)
|| ((PGP6 || PGP7) && (req_usage & PUBKEY_USAGE_SIG)));
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* For an exact match mark the primary or subkey that matched the
low-level search criteria. */
if (want_exact)
{
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 1))
{
log_assert (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY);
foundk = k;
pk = k->pkt->pkt.public_key;
pk->flags.exact = 1;
break;
}
}
}
/* Get the user id that matched that low-level search criteria. */
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 2))
{
log_assert (k->pkt->pkttype == PKT_USER_ID);
foundu = k->pkt->pkt.user_id;
break;
}
}
if (DBG_LOOKUP)
log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n",
(ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL),
foundk ? "one" : "all", req_usage);
if (!req_usage)
{
latest_key = foundk ? foundk : keyblock;
goto found;
}
latest_date = 0;
latest_key = NULL;
/* Set LATEST_KEY to the latest (the one with the most recent
* timestamp) good (valid, not revoked, not expired, etc.) subkey.
*
* Don't bother if we are only looking for a primary key or we need
* an exact match and the exact match is not a subkey. */
if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY))
;
else
{
kbnode_t nextk;
int n_subkeys = 0;
int n_revoked_or_expired = 0;
/* Either start a loop or check just this one subkey. */
for (k = foundk ? foundk : keyblock; k; k = nextk)
{
if (foundk)
{
/* If FOUNDK is not NULL, then only consider that exact
key, i.e., don't iterate. */
nextk = NULL;
}
else
nextk = k->next;
if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = k->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tusage does not match: want=%x have=%x\n",
req_usage, pk->pubkey_usage);
continue;
}
n_subkeys++;
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
n_revoked_or_expired++;
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
n_revoked_or_expired++;
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure
that it is used. A better change would be to compare
">=" but that might also change the selected keys and
is as such a more intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = k;
}
}
if (n_subkeys == n_revoked_or_expired && r_flags)
*r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED;
}
/* Check if the primary key is ok (valid, not revoke, not expire,
* matches requested usage) if:
*
* - we didn't find an appropriate subkey and we're not doing an
* exact search,
*
* - we're doing an exact match and the exact match was the
* primary key, or,
*
* - we're just considering the primary key. */
if ((!latest_key && !want_exact) || foundk == keyblock || req_prim)
{
if (DBG_LOOKUP && !foundk && !req_prim)
log_debug ("\tno suitable subkeys found - trying primary\n");
pk = keyblock->pkt->pkt.public_key;
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key usage does not match: "
"want=%x have=%x\n", req_usage, pk->pubkey_usage);
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else /* Okay. */
{
if (DBG_LOOKUP)
log_debug ("\tprimary key may be used\n");
latest_key = keyblock;
}
}
if (!latest_key)
{
if (DBG_LOOKUP)
log_debug ("\tno suitable key found - giving up\n");
if (r_flags)
*r_flags |= LOOKUP_NOT_SELECTED;
return NULL; /* Not found. */
}
found:
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n",
(ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL));
if (latest_key)
{
pk = latest_key->pkt->pkt.public_key;
free_user_id (pk->user_id);
pk->user_id = scopy_user_id (foundu);
}
if (latest_key != keyblock && opt.verbose)
{
char *tempkeystr =
xstrdup (keystr_from_pk (latest_key->pkt->pkt.public_key));
log_info (_("using subkey %s instead of primary key %s\n"),
tempkeystr, keystr_from_pk (keyblock->pkt->pkt.public_key));
xfree (tempkeystr);
}
cache_user_id (keyblock);
return latest_key ? latest_key : keyblock; /* Found. */
}
/* Print a KEY_CONSIDERED status line. */
static void
print_status_key_considered (kbnode_t keyblock, unsigned int flags)
{
char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
kbnode_t node;
char flagbuf[20];
if (!is_status_enabled ())
return;
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
break;
if (!node)
{
log_error ("%s: keyblock w/o primary key\n", __func__);
return;
}
hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
snprintf (flagbuf, sizeof flagbuf, " %u", flags);
write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL);
}
/* A high-level function to lookup keys.
This function builds on top of the low-level keydb API. It first
searches the database using the description stored in CTX->ITEMS,
then it filters the results using CTX and, finally, if WANT_SECRET
is set, it ignores any keys for which no secret key is available.
Unlike the low-level search functions, this function also merges
all of the self-signed data into the keys, subkeys and user id
packets (see the merge_selfsigs for details).
On success the key's keyblock is stored at *RET_KEYBLOCK, and the
specific subkey is stored at *RET_FOUND_KEY. Note that we do not
return a reference in *RET_FOUND_KEY, i.e. the result must not be
freed using 'release_kbnode', and it is only valid until
*RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not
NULL, then RET_KEYBLOCK must not be NULL. */
static int
lookup (getkey_ctx_t ctx, kbnode_t *ret_keyblock, kbnode_t *ret_found_key,
int want_secret)
{
int rc;
int no_suitable_key = 0;
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
unsigned int infoflags;
log_assert (ret_found_key == NULL || ret_keyblock != NULL);
if (ret_keyblock)
*ret_keyblock = NULL;
for (;;)
{
rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL);
if (rc)
break;
/* If we are iterating over the entire database, then we need to
change from KEYDB_SEARCH_MODE_FIRST, which does an implicit
reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next
record. */
if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST)
ctx->items->mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_keyblock (ctx->kr_handle, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto skip;
}
if (want_secret && agent_probe_any_secret_key (NULL, keyblock))
goto skip; /* No secret key available. */
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. */
merge_selfsigs (keyblock);
found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact,
&infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
{
no_suitable_key = 0;
goto found;
}
else
{
no_suitable_key = 1;
}
skip:
/* Release resources and continue search. */
release_kbnode (keyblock);
keyblock = NULL;
/* The keyblock cache ignores the current "file position".
Thus, if we request the next result and the cache matches
(and it will since it is what we just looked for), we'll get
the same entry back! We can avoid this infinite loop by
disabling the cache. */
keydb_disable_caching (ctx->kr_handle);
}
found:
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
if (!rc)
{
if (ret_keyblock)
{
*ret_keyblock = keyblock; /* Return the keyblock. */
keyblock = NULL;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key)
rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY;
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
if (ret_found_key)
{
if (! rc)
*ret_found_key = found_key;
else
*ret_found_key = NULL;
}
return rc;
}
/* Enumerate some secret keys (specifically, those specified with
* --default-key and --try-secret-key). Use the following procedure:
*
* 1) Initialize a void pointer to NULL
* 2) Pass a reference to this pointer to this function (content)
* and provide space for the secret key (sk)
* 3) Call this function as long as it does not return an error (or
* until you are done). The error code GPG_ERR_EOF indicates the
* end of the listing.
* 4) Call this function a last time with SK set to NULL,
* so that can free it's context.
*
* In pseudo-code:
*
* void *ctx = NULL;
* PKT_public_key *sk = xmalloc_clear (sizeof (*sk));
*
* while ((err = enum_secret_keys (&ctx, sk)))
* { // Process SK.
* if (done)
* break;
* free_public_key (sk);
* sk = xmalloc_clear (sizeof (*sk));
* }
*
* // Release any resources used by CTX.
* enum_secret_keys (&ctx, NULL);
* free_public_key (sk);
*
* if (gpg_err_code (err) != GPG_ERR_EOF)
* ; // An error occurred.
*/
gpg_error_t
enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk)
{
gpg_error_t err = 0;
const char *name;
kbnode_t keyblock;
struct
{
int eof;
int state;
strlist_t sl;
kbnode_t keyblock;
kbnode_t node;
getkey_ctx_t ctx;
} *c = *context;
if (!c)
{
/* Make a new context. */
c = xtrycalloc (1, sizeof *c);
if (!c)
return gpg_error_from_syserror ();
*context = c;
}
if (!sk)
{
/* Free the context. */
release_kbnode (c->keyblock);
getkey_end (c->ctx);
xfree (c);
*context = NULL;
return 0;
}
if (c->eof)
return gpg_error (GPG_ERR_EOF);
for (;;)
{
/* Loop until we have a keyblock. */
while (!c->keyblock)
{
/* Loop over the list of secret keys. */
do
{
name = NULL;
keyblock = NULL;
switch (c->state)
{
case 0: /* First try to use the --default-key. */
name = parse_def_secret_key (ctrl);
c->state = 1;
break;
case 1: /* Init list of keys to try. */
c->sl = opt.secret_keys_to_try;
c->state++;
break;
case 2: /* Get next item from list. */
if (c->sl)
{
name = c->sl->d;
c->sl = c->sl->next;
}
else
c->state++;
break;
case 3: /* Init search context to enum all secret keys. */
err = getkey_bynames (&c->ctx, NULL, NULL, 1, &keyblock);
if (err)
{
release_kbnode (keyblock);
keyblock = NULL;
getkey_end (c->ctx);
c->ctx = NULL;
}
c->state++;
break;
case 4: /* Get next item from the context. */
if (c->ctx)
{
err = getkey_next (c->ctx, NULL, &keyblock);
if (err)
{
release_kbnode (keyblock);
keyblock = NULL;
getkey_end (c->ctx);
c->ctx = NULL;
}
}
else
c->state++;
break;
default: /* No more names to check - stop. */
c->eof = 1;
return gpg_error (GPG_ERR_EOF);
}
}
while ((!name || !*name) && !keyblock);
if (keyblock)
c->node = c->keyblock = keyblock;
else
{
err = getkey_byname (ctrl, NULL, NULL, name, 1, &c->keyblock);
if (err)
{
/* getkey_byname might return a keyblock even in the
error case - I have not checked. Thus better release
it. */
release_kbnode (c->keyblock);
c->keyblock = NULL;
}
else
c->node = c->keyblock;
}
}
/* Get the next key from the current keyblock. */
for (; c->node; c->node = c->node->next)
{
if (c->node->pkt->pkttype == PKT_PUBLIC_KEY
|| c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
copy_public_key (sk, c->node->pkt->pkt.public_key);
c->node = c->node->next;
return 0; /* Found. */
}
}
/* Dispose the keyblock and continue. */
release_kbnode (c->keyblock);
c->keyblock = NULL;
}
}
/*********************************************
*********** User ID printing helpers *******
*********************************************/
/* Return a string with a printable representation of the user_id.
* this string must be freed by xfree. */
static char *
get_user_id_string (u32 * keyid, int mode, size_t *r_len)
{
user_id_db_t r;
keyid_list_t a;
int pass = 0;
char *p;
/* Try it two times; second pass reads from the database. */
do
{
for (r = user_id_db; r; r = r->next)
{
for (a = r->keyids; a; a = a->next)
{
if (a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1])
{
if (mode == 2)
{
/* An empty string as user id is possible. Make
sure that the malloc allocates one byte and
does not bail out. */
p = xmalloc (r->len? r->len : 1);
memcpy (p, r->name, r->len);
if (r_len)
*r_len = r->len;
}
else
{
if (mode)
p = xasprintf ("%08lX%08lX %.*s",
(ulong) keyid[0], (ulong) keyid[1],
r->len, r->name);
else
p = xasprintf ("%s %.*s", keystr (keyid),
r->len, r->name);
if (r_len)
*r_len = strlen (p);
}
return p;
}
}
}
}
while (++pass < 2 && !get_pubkey (NULL, keyid));
if (mode == 2)
p = xstrdup (user_id_not_found_utf8 ());
else if (mode)
p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]);
else
p = xasprintf ("%s [?]", keystr (keyid));
if (r_len)
*r_len = strlen (p);
return p;
}
char *
get_user_id_string_native (u32 * keyid)
{
char *p = get_user_id_string (keyid, 0, NULL);
char *p2 = utf8_to_native (p, strlen (p), 0);
xfree (p);
return p2;
}
char *
get_long_user_id_string (u32 * keyid)
{
return get_user_id_string (keyid, 1, NULL);
}
/* Please try to use get_user_byfpr instead of this one. */
char *
get_user_id (u32 * keyid, size_t * rn)
{
return get_user_id_string (keyid, 2, rn);
}
/* Please try to use get_user_id_byfpr_native instead of this one. */
char *
get_user_id_native (u32 * keyid)
{
size_t rn;
char *p = get_user_id (keyid, &rn);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the user id for a key designated by its fingerprint, FPR,
which must be MAX_FINGERPRINT_LEN bytes in size. Note: the
returned string, which must be freed using xfree, may not be NUL
terminated. To determine the length of the string, you must use
*RN. */
char *
get_user_id_byfpr (const byte *fpr, size_t *rn)
{
user_id_db_t r;
char *p;
int pass = 0;
/* Try it two times; second pass reads from the database. */
do
{
for (r = user_id_db; r; r = r->next)
{
keyid_list_t a;
for (a = r->keyids; a; a = a->next)
{
if (!memcmp (a->fpr, fpr, MAX_FINGERPRINT_LEN))
{
/* An empty string as user id is possible. Make
sure that the malloc allocates one byte and does
not bail out. */
p = xmalloc (r->len? r->len : 1);
memcpy (p, r->name, r->len);
*rn = r->len;
return p;
}
}
}
}
while (++pass < 2
&& !get_pubkey_byfprint (NULL, NULL, fpr, MAX_FINGERPRINT_LEN));
p = xstrdup (user_id_not_found_utf8 ());
*rn = strlen (p);
return p;
}
/* Like get_user_id_byfpr, but convert the string to the native
encoding. The returned string needs to be freed. Unlike
get_user_id_byfpr, the returned string is NUL terminated. */
char *
get_user_id_byfpr_native (const byte *fpr)
{
size_t rn;
char *p = get_user_id_byfpr (fpr, &rn);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE
get_ctx_handle (GETKEY_CTX ctx)
{
return ctx->kr_handle;
}
static void
free_akl (struct akl *akl)
{
if (! akl)
return;
if (akl->spec)
free_keyserver_spec (akl->spec);
xfree (akl);
}
void
release_akl (void)
{
while (opt.auto_key_locate)
{
struct akl *akl2 = opt.auto_key_locate;
opt.auto_key_locate = opt.auto_key_locate->next;
free_akl (akl2);
}
}
/* Returns false on error. */
int
parse_auto_key_locate (char *options)
{
char *tok;
while ((tok = optsep (&options)))
{
struct akl *akl, *check, *last = NULL;
int dupe = 0;
if (tok[0] == '\0')
continue;
akl = xmalloc_clear (sizeof (*akl));
if (ascii_strcasecmp (tok, "clear") == 0)
{
xfree (akl);
free_akl (opt.auto_key_locate);
opt.auto_key_locate = NULL;
continue;
}
else if (ascii_strcasecmp (tok, "nodefault") == 0)
akl->type = AKL_NODEFAULT;
else if (ascii_strcasecmp (tok, "local") == 0)
akl->type = AKL_LOCAL;
else if (ascii_strcasecmp (tok, "ldap") == 0)
akl->type = AKL_LDAP;
else if (ascii_strcasecmp (tok, "keyserver") == 0)
akl->type = AKL_KEYSERVER;
else if (ascii_strcasecmp (tok, "cert") == 0)
akl->type = AKL_CERT;
else if (ascii_strcasecmp (tok, "pka") == 0)
akl->type = AKL_PKA;
else if (ascii_strcasecmp (tok, "dane") == 0)
akl->type = AKL_DANE;
else if (ascii_strcasecmp (tok, "wkd") == 0)
akl->type = AKL_WKD;
else if ((akl->spec = parse_keyserver_uri (tok, 1)))
akl->type = AKL_SPEC;
else
{
free_akl (akl);
return 0;
}
/* We must maintain the order the user gave us */
for (check = opt.auto_key_locate; check;
last = check, check = check->next)
{
/* Check for duplicates */
if (check->type == akl->type
&& (akl->type != AKL_SPEC
|| (akl->type == AKL_SPEC
&& strcmp (check->spec->uri, akl->spec->uri) == 0)))
{
dupe = 1;
free_akl (akl);
break;
}
}
if (!dupe)
{
if (last)
last->next = akl;
else
opt.auto_key_locate = akl;
}
}
return 1;
}
/* Returns true if a secret key is available for the public key with
key id KEYID; returns false if not. This function ignores legacy
keys. Note: this is just a fast check and does not tell us whether
the secret key is valid; this check merely indicates whether there
is some secret key with the specified key id. */
int
have_secret_key_with_kid (u32 *keyid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock;
kbnode_t node;
int result = 0;
kdbhd = keydb_new ();
if (!kdbhd)
return 0;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = keyid[0];
desc.u.kid[1] = keyid[1];
while (!result)
{
err = keydb_search (kdbhd, &desc, 1, NULL);
if (err)
break;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
break;
}
for (node = keyblock; node; node = node->next)
{
/* Bit 0 of the flags is set if the search found the key
using that key or subkey. Note: a search will only ever
match a single key or subkey. */
if ((node->flag & 1))
{
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (!agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
result = 1; /* Secret key available. */
else
result = 0;
break;
}
}
release_kbnode (keyblock);
}
keydb_release (kdbhd);
return result;
}
diff --git a/g10/gpg.c b/g10/gpg.c
index 654dcb99b..eeda60f83 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -1,5334 +1,5334 @@
/* gpg.c - The GnuPG utility (main for gpg)
* Copyright (C) 1998-2011 Free Software Foundation, Inc.
* Copyright (C) 1997-2016 Werner Koch
* Copyright (C) 2015-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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_STAT
#include <sys/stat.h> /* for stat() */
#endif
#include <fcntl.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
#include <assuan.h>
#include "../common/iobuf.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "membuf.h"
+#include "../common/membuf.h"
#include "main.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "filter.h"
-#include "ttyio.h"
-#include "i18n.h"
-#include "sysutils.h"
-#include "status.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/status.h"
#include "keyserver-internal.h"
#include "exec.h"
-#include "gc-opt-flags.h"
-#include "asshelp.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/asshelp.h"
#include "call-dirmngr.h"
#include "tofu.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "../common/shareddefs.h"
#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY O_BINARY
#ifndef S_IRGRP
# define S_IRGRP 0
# define S_IWGRP 0
#endif
#else
#define MY_O_BINARY 0
#endif
enum cmd_and_opt_values
{
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
oRecipientFile = 'f',
oHiddenRecipientFile = 'F',
oInteractive = 'i',
aListKeys = 'k',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
oHiddenRecipient = 'R',
aSign = 's',
oTextmodeShort= 't',
oLocalUser = 'u',
oVerbose = 'v',
oCompress = 'z',
oSetNotation = 'N',
aListSecretKeys = 'K',
oBatch = 500,
oMaxOutput,
oInputSizeHint,
oSigNotation,
oCertNotation,
oShowNotation,
oNoShowNotation,
aEncrFiles,
aEncrSym,
aDecryptFiles,
aClearsign,
aStore,
aQuickKeygen,
aFullKeygen,
aKeygen,
aSignEncr,
aSignEncrSym,
aSignSym,
aSignKey,
aLSignKey,
aQuickSignKey,
aQuickLSignKey,
aQuickAddUid,
aQuickAddKey,
aQuickRevUid,
aQuickSetExpire,
aListConfig,
aListGcryptConfig,
aGPGConfList,
aGPGConfTest,
aListPackets,
aEditKey,
aDeleteKeys,
aDeleteSecretKeys,
aDeleteSecretAndPublicKeys,
aImport,
aFastImport,
aVerify,
aVerifyFiles,
aListSigs,
aSendKeys,
aRecvKeys,
aLocateKeys,
aSearchKeys,
aRefreshKeys,
aFetchKeys,
aExport,
aExportSecret,
aExportSecretSub,
aExportSshKey,
aCheckKeys,
aGenRevoke,
aDesigRevoke,
aPrimegen,
aPrintMD,
aPrintMDs,
aCheckTrustDB,
aUpdateTrustDB,
aFixTrustDB,
aListTrustDB,
aListTrustPath,
aExportOwnerTrust,
aImportOwnerTrust,
aDeArmor,
aEnArmor,
aGenRandom,
aRebuildKeydbCaches,
aCardStatus,
aCardEdit,
aChangePIN,
aPasswd,
aServer,
aTOFUPolicy,
oMimemode,
oTextmode,
oNoTextmode,
oExpert,
oNoExpert,
oDefSigExpire,
oAskSigExpire,
oNoAskSigExpire,
oDefCertExpire,
oAskCertExpire,
oNoAskCertExpire,
oDefCertLevel,
oMinCertLevel,
oAskCertLevel,
oNoAskCertLevel,
oFingerprint,
oWithFingerprint,
oWithSubkeyFingerprint,
oWithICAOSpelling,
oWithKeygrip,
oWithSecret,
oWithWKDHash,
oWithColons,
oWithKeyData,
oWithTofuInfo,
oWithSigList,
oWithSigCheck,
oAnswerYes,
oAnswerNo,
oKeyring,
oPrimaryKeyring,
oSecretKeyring,
oShowKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oTrySecretKey,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugIOLBF,
oStatusFD,
oStatusFile,
oAttributeFD,
oAttributeFile,
oEmitVersion,
oNoEmitVersion,
oCompletesNeeded,
oMarginalsNeeded,
oMaxCertDepth,
oLoadExtension,
oCompliance,
oGnuPG,
oRFC2440,
oRFC4880,
oRFC4880bis,
oOpenPGP,
oPGP6,
oPGP7,
oPGP8,
oDE_VS,
oRFC2440Text,
oNoRFC2440Text,
oCipherAlgo,
oDigestAlgo,
oCertDigestAlgo,
oCompressAlgo,
oCompressLevel,
oBZ2CompressLevel,
oBZ2DecompressLowmem,
oPassphrase,
oPassphraseFD,
oPassphraseFile,
oPassphraseRepeat,
oPinentryMode,
oCommandFD,
oCommandFile,
oQuickRandom,
oNoVerbose,
oTrustDBName,
oNoSecmemWarn,
oRequireSecmem,
oNoRequireSecmem,
oNoPermissionWarn,
oNoMDCWarn,
oNoArmor,
oNoDefKeyring,
oNoKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oSkipVerify,
oSkipHiddenRecipients,
oNoSkipHiddenRecipients,
oAlwaysTrust,
oTrustModel,
oForceOwnertrust,
oSetFilename,
oForYourEyesOnly,
oNoForYourEyesOnly,
oSetPolicyURL,
oSigPolicyURL,
oCertPolicyURL,
oShowPolicyURL,
oNoShowPolicyURL,
oSigKeyserverURL,
oUseEmbeddedFilename,
oNoUseEmbeddedFilename,
oComment,
oDefaultComment,
oNoComments,
oThrowKeyids,
oNoThrowKeyids,
oShowPhotos,
oNoShowPhotos,
oPhotoViewer,
oForceMDC,
oNoForceMDC,
oDisableMDC,
oNoDisableMDC,
oS2KMode,
oS2KDigest,
oS2KCipher,
oS2KCount,
oDisplayCharset,
oNotDashEscaped,
oEscapeFrom,
oNoEscapeFrom,
oLockOnce,
oLockMultiple,
oLockNever,
oKeyServer,
oKeyServerOptions,
oImportOptions,
oImportFilter,
oExportOptions,
oExportFilter,
oListOptions,
oVerifyOptions,
oTempDir,
oExecPath,
oEncryptTo,
oHiddenEncryptTo,
oNoEncryptTo,
oEncryptToDefaultKey,
oLoggerFD,
oLoggerFile,
oUtf8Strings,
oNoUtf8Strings,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oAllowNonSelfsignedUID,
oNoAllowNonSelfsignedUID,
oAllowFreeformUID,
oNoAllowFreeformUID,
oAllowSecretKeyImport,
oEnableSpecialFilenames,
oNoLiteral,
oSetFilesize,
oHonorHttpProxy,
oFastListMode,
oListOnly,
oIgnoreTimeConflict,
oIgnoreValidFrom,
oIgnoreCrcError,
oIgnoreMDCError,
oShowSessionKey,
oOverrideSessionKey,
oOverrideSessionKeyFD,
oNoRandomSeedFile,
oAutoKeyRetrieve,
oNoAutoKeyRetrieve,
oUseAgent,
oNoUseAgent,
oGpgAgentInfo,
oMergeOnly,
oTryAllSecrets,
oTrustedKey,
oNoExpensiveTrustChecks,
oFixedListMode,
oLegacyListMode,
oNoSigCache,
oAutoCheckTrustDB,
oNoAutoCheckTrustDB,
oPreservePermissions,
oDefaultPreferenceList,
oDefaultKeyserverURL,
oPersonalCipherPreferences,
oPersonalDigestPreferences,
oPersonalCompressPreferences,
oAgentProgram,
oDirmngrProgram,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oGroup,
oUnGroup,
oNoGroups,
oStrict,
oNoStrict,
oMangleDosFilenames,
oNoMangleDosFilenames,
oEnableProgressFilter,
oMultifile,
oKeyidFormat,
oExitOnStatusWriteError,
oLimitCardInsertTries,
oReaderPort,
octapiDriver,
opcscDriver,
oDisableCCID,
oRequireCrossCert,
oNoRequireCrossCert,
oAutoKeyLocate,
oNoAutoKeyLocate,
oAllowMultisigVerification,
oEnableLargeRSA,
oDisableLargeRSA,
oEnableDSA2,
oDisableDSA2,
oAllowMultipleMessages,
oNoAllowMultipleMessages,
oAllowWeakDigestAlgos,
oFakedSystemTime,
oNoAutostart,
oPrintPKARecords,
oPrintDANERecords,
oTOFUDefaultPolicy,
oTOFUDBFormat,
oDefaultNewKeyAlgo,
oWeakDigest,
oUnwrap,
oOnlySignTextIDs,
oDisableSignerUID,
oSender,
oNoop
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")),
ARGPARSE_c (aClearsign, "clearsign", "@"),
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
ARGPARSE_c (aEncrFiles, "encrypt-files", "@"),
ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),
ARGPARSE_c (aStore, "store", "@"),
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"),
ARGPARSE_c (aVerify, "verify" , N_("verify a signature")),
ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListKeys, "list-public-keys", "@" ),
ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")),
ARGPARSE_c (aListSigs, "list-sigs", "@"),
ARGPARSE_c (aCheckKeys, "check-signatures",
N_("list and check key signatures")),
ARGPARSE_c (aCheckKeys, "check-sigs", "@"),
ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aKeygen, "generate-key",
N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aQuickKeygen, "quick-generate-key" ,
N_("quickly generate a new key pair")),
ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"),
ARGPARSE_c (aQuickAddUid, "quick-add-uid",
N_("quickly add a new user-id")),
ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"),
ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"),
ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"),
ARGPARSE_c (aQuickRevUid, "quick-revoke-uid",
N_("quickly revoke a user-id")),
ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"),
ARGPARSE_c (aQuickSetExpire, "quick-set-expire",
N_("quickly set a new expiration date")),
ARGPARSE_c (aFullKeygen, "full-generate-key" ,
N_("full featured key pair generation")),
ARGPARSE_c (aFullKeygen, "full-gen-key", "@"),
ARGPARSE_c (aGenRevoke, "generate-revocation",
N_("generate a revocation certificate")),
ARGPARSE_c (aGenRevoke, "gen-revoke", "@"),
ARGPARSE_c (aDeleteKeys,"delete-keys",
N_("remove keys from the public keyring")),
ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys",
N_("remove keys from the secret keyring")),
ARGPARSE_c (aQuickSignKey, "quick-sign-key" ,
N_("quickly sign a key")),
ARGPARSE_c (aQuickLSignKey, "quick-lsign-key",
N_("quickly sign a key locally")),
ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")),
ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")),
ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")),
ARGPARSE_c (aEditKey, "key-edit" ,"@"),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"),
ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ),
ARGPARSE_c (aExport, "export" , N_("export keys") ),
ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ),
ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ),
ARGPARSE_c (aRecvKeys, "recv-keys" , "@"),
ARGPARSE_c (aSearchKeys, "search-keys" ,
N_("search for keys on a keyserver") ),
ARGPARSE_c (aRefreshKeys, "refresh-keys",
N_("update all keys from a keyserver")),
ARGPARSE_c (aLocateKeys, "locate-keys", "@"),
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ),
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
ARGPARSE_c (aFastImport, "fast-import", "@"),
#ifdef ENABLE_CARD_SUPPORT
ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")),
ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")),
ARGPARSE_c (aCardEdit, "card-edit", "@"),
ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")),
#endif
ARGPARSE_c (aListConfig, "list-config", "@"),
ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ),
ARGPARSE_c (aListPackets, "list-packets","@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"),
ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"),
ARGPARSE_c (aUpdateTrustDB,"update-trustdb",
N_("update the trust database")),
ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"),
ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"),
#endif
ARGPARSE_c (aDeArmor, "dearmor", "@"),
ARGPARSE_c (aDeArmor, "dearmour", "@"),
ARGPARSE_c (aEnArmor, "enarmor", "@"),
ARGPARSE_c (aEnArmor, "enarmour", "@"),
ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")),
ARGPARSE_c (aPrimegen, "gen-prime", "@" ),
ARGPARSE_c (aGenRandom,"gen-random", "@" ),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aTOFUPolicy, "tofu-policy",
N_("|VALUE|set the TOFU policy for a key")),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"),
ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"),
ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"),
ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */
ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"),
ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"),
ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"),
ARGPARSE_s_s (oTempDir, "temp-directory", "@"),
ARGPARSE_s_s (oExecPath, "exec-path", "@"),
ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"),
ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"),
ARGPARSE_s_s (oLocalUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oSender, "sender", "@"),
ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"),
ARGPARSE_s_i (oCompress, NULL,
N_("|N|set compress level to N (0 disables)")),
ARGPARSE_s_i (oCompressLevel, "compress-level", "@"),
ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"),
ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"),
ARGPARSE_s_n (oMimemode, "mimemode", "@"),
ARGPARSE_s_n (oTextmodeShort, NULL, "@"),
ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")),
ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"),
ARGPARSE_s_n (oExpert, "expert", "@"),
ARGPARSE_s_n (oNoExpert, "no-expert", "@"),
ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"),
ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"),
ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"),
ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"),
ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"),
ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"),
ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"),
ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"),
ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_p_u (oMaxOutput, "max-output", "@"),
ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", "@"),
ARGPARSE_s_n (oNoTTY, "no-tty", "@"),
ARGPARSE_s_n (oForceMDC, "force-mdc", "@"),
ARGPARSE_s_n (oNoForceMDC, "no-force-mdc", "@"),
ARGPARSE_s_n (oDisableMDC, "disable-mdc", "@"),
ARGPARSE_s_n (oNoDisableMDC, "no-disable-mdc", "@"),
ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_n (oAnswerYes, "yes", "@"),
ARGPARSE_s_n (oAnswerNo, "no", "@"),
ARGPARSE_s_s (oKeyring, "keyring", "@"),
ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"),
ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"),
ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"),
ARGPARSE_s_s (oDefaultKey, "default-key", "@"),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"),
ARGPARSE_s_s (oImportOptions, "import-options", "@"),
ARGPARSE_s_s (oImportFilter, "import-filter", "@"),
ARGPARSE_s_s (oExportOptions, "export-options", "@"),
ARGPARSE_s_s (oExportFilter, "export-filter", "@"),
ARGPARSE_s_s (oListOptions, "list-options", "@"),
ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"),
ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"),
ARGPARSE_s_s (oDisplayCharset, "charset", "@"),
ARGPARSE_s_s (oOptions, "options", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", "@"),
ARGPARSE_s_s (oStatusFile, "status-file", "@"),
ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"),
ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"),
ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"),
ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"),
ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ),
ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"),
ARGPARSE_s_s (oLoadExtension, "load-extension", "@"), /* Dummy. */
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_s_n (oGnuPG, "gnupg", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"),
ARGPARSE_s_n (oRFC2440, "rfc2440", "@"),
ARGPARSE_s_n (oRFC4880, "rfc4880", "@"),
ARGPARSE_s_n (oRFC4880bis, "rfc4880bis", "@"),
ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")),
ARGPARSE_s_n (oPGP6, "pgp6", "@"),
ARGPARSE_s_n (oPGP7, "pgp7", "@"),
ARGPARSE_s_n (oPGP8, "pgp8", "@"),
ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"),
ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"),
ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"),
ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"),
ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"),
ARGPARSE_s_i (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"),
ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"),
ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"),
ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"),
ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */
ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"),
ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"),
ARGPARSE_s_n (oShowPhotos, "show-photos", "@"),
ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"),
ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"),
ARGPARSE_s_s (oSetNotation, "set-notation", "@"),
ARGPARSE_s_s (oSigNotation, "sig-notation", "@"),
ARGPARSE_s_s (oCertNotation, "cert-notation", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_group (303, N_("@\nExamples:\n\n"
" -se -r Bob [file] sign and encrypt for user Bob\n"
" --clear-sign [file] make a clear text signature\n"
" --detach-sign [file] make a detached signature\n"
" --list-keys [names] show keys\n"
" --fingerprint [names] show fingerprints\n")),
/* More hidden commands and options. */
ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */
#ifndef NO_TRUST_MODELS
ARGPARSE_c (aListTrustDB, "list-trustdb", "@"),
#endif
/* Not yet used:
ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */
ARGPARSE_c (aDeleteSecretAndPublicKeys,
"delete-secret-and-public-keys", "@"),
ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"),
ARGPARSE_s_s (oPassphrase, "passphrase", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"),
ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_s_i (oCommandFD, "command-fd", "@"),
ARGPARSE_s_s (oCommandFile, "command-file", "@"),
ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"),
ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"),
ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"),
ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"),
#endif
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"),
ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"),
ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"),
ARGPARSE_s_n (oNoMDCWarn, "no-mdc-warning", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_n (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"),
ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"),
ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */
ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */
ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"),
ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
#ifndef NO_TRUST_MODELS
ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
#endif
ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"),
ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"),
ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"),
ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"),
ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"),
ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"),
ARGPARSE_s_n (oShowNotation, "show-notation", "@"),
ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"),
ARGPARSE_s_s (oComment, "comment", "@"),
ARGPARSE_s_n (oDefaultComment, "default-comment", "@"),
ARGPARSE_s_n (oNoComments, "no-comments", "@"),
ARGPARSE_s_n (oEmitVersion, "emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */
ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"),
ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"),
ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"),
ARGPARSE_s_n (oLockOnce, "lock-once", "@"),
ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"),
ARGPARSE_s_n (oLockNever, "lock-never", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oLoggerFile, "log-file", "@"),
ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */
ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"),
ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"),
ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"),
ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"),
ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"),
ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"),
ARGPARSE_s_n (oNoLiteral, "no-literal", "@"),
ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"),
ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"),
ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"),
ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"),
ARGPARSE_s_n (oListOnly, "list-only", "@"),
ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"),
ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"),
ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"),
ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"),
ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"),
ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"),
ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"),
ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"),
ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"),
ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ),
ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"),
ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"),
ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"),
ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"),
ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"),
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"),
ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"),
ARGPARSE_s_s (oPersonalCompressPreferences,
"personal-compress-preferences", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_s (oWeakDigest, "weak-digest","@"),
ARGPARSE_s_n (oUnwrap, "unwrap", "@"),
ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"),
/* Aliases. I constantly mistype these, and assume other people do
as well. */
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"),
ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-prefs", "@"),
ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oGroup, "group", "@"),
ARGPARSE_s_s (oUnGroup, "ungroup", "@"),
ARGPARSE_s_n (oNoGroups, "no-groups", "@"),
ARGPARSE_s_n (oStrict, "strict", "@"),
ARGPARSE_s_n (oNoStrict, "no-strict", "@"),
ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"),
ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"),
ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"),
ARGPARSE_s_n (oMultifile, "multifile", "@"),
ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"),
ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"),
ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"),
ARGPARSE_s_n (oAllowMultisigVerification,
"allow-multisig-verification", "@"),
ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"),
ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"),
ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"),
ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"),
ARGPARSE_s_n (oAllowMultipleMessages, "allow-multiple-messages", "@"),
ARGPARSE_s_n (oNoAllowMultipleMessages, "no-allow-multiple-messages", "@"),
ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"),
ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"),
/* These two are aliases to help users of the PGP command line
product use gpg with minimal pain. Many commands are common
already as they seem to have borrowed commands from us. Now I'm
returning the favor. */
ARGPARSE_s_s (oLocalUser, "sign-with", "@"),
ARGPARSE_s_s (oRecipient, "user", "@"),
ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"),
ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"),
/* New options. Fixme: Should go more to the top. */
ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", "@"),
ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
/* Dummy options with warnings. */
ARGPARSE_s_n (oUseAgent, "use-agent", "@"),
ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"),
ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"),
ARGPARSE_s_s (oReaderPort, "reader-port", "@"),
ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"),
ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"),
ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"),
ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"),
/* Dummy options. */
ARGPARSE_s_n (oNoop, "sk-comments", "@"),
ARGPARSE_s_n (oNoop, "no-sk-comments", "@"),
ARGPARSE_s_n (oNoop, "compress-keys", "@"),
ARGPARSE_s_n (oNoop, "compress-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v4-certs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_PACKET_VALUE , "packet" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_FILTER_VALUE , "filter" },
{ DBG_IOBUF_VALUE , "iobuf" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_TRUST_VALUE , "trust" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
#ifdef ENABLE_SELINUX_HACKS
#define ALWAYS_ADD_KEYRINGS 1
#else
#define ALWAYS_ADD_KEYRINGS 0
#endif
int g10_errors_seen = 0;
static int utf8_strings = 0;
static int maybe_setuid = 1;
static char *build_list( const char *text, char letter,
const char *(*mapf)(int), int (*chkf)(int) );
static void set_cmd( enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void print_mds( const char *fname, int algo );
static void add_notation_data( const char *string, int which );
static void add_policy_url( const char *string, int which );
static void add_keyserver_url( const char *string, int which );
static void emergency_cleanup (void);
static void read_sessionkey_from_fd (int fd);
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static int
build_list_pk_test_algo (int algo)
{
/* Show only one "RSA" string. If RSA_E or RSA_S is available RSA
is also available. */
if (algo == PUBKEY_ALGO_RSA_E
|| algo == PUBKEY_ALGO_RSA_S)
return GPG_ERR_DIGEST_ALGO;
return openpgp_pk_test_algo (algo);
}
static const char *
build_list_pk_algo_name (int algo)
{
return openpgp_pk_algo_name (algo);
}
static int
build_list_cipher_test_algo (int algo)
{
return openpgp_cipher_test_algo (algo);
}
static const char *
build_list_cipher_algo_name (int algo)
{
return openpgp_cipher_algo_name (algo);
}
static int
build_list_md_test_algo (int algo)
{
/* By default we do not accept MD5 based signatures. To avoid
confusion we do not announce support for it either. */
if (algo == DIGEST_ALGO_MD5)
return GPG_ERR_DIGEST_ALGO;
return openpgp_md_test_algo (algo);
}
static const char *
build_list_md_algo_name (int algo)
{
return openpgp_md_algo_name (algo);
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers, *zips, *ver_gcry;
const char *p;
switch( level ) {
case 11: p = "@GPG@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
#ifdef IS_DEVELOPMENT_VERSION
case 25:
p="NOTE: THIS IS A DEVELOPMENT VERSION!";
break;
case 26:
p="It is only intended for test purposes and should NOT be";
break;
case 27:
p="used in a production environment or with production keys!";
break;
#endif
case 1:
case 40: p =
_("Usage: @GPG@ [options] [files] (-h for help)");
break;
case 41: p =
_("Syntax: @GPG@ [options] [files]\n"
"Sign, check, encrypt or decrypt\n"
"Default operation depends on the input data\n");
break;
case 31: p = "\nHome: "; break;
#ifndef __riscos__
case 32: p = gnupg_homedir (); break;
#else /* __riscos__ */
case 32: p = make_filename(gnupg_homedir (), NULL); break;
#endif /* __riscos__ */
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!pubkeys)
pubkeys = build_list (_("Pubkey: "), 1,
build_list_pk_algo_name,
build_list_pk_test_algo );
p = pubkeys;
break;
case 35:
if( !ciphers )
ciphers = build_list(_("Cipher: "), 'S',
build_list_cipher_algo_name,
build_list_cipher_test_algo );
p = ciphers;
break;
case 36:
if( !digests )
digests = build_list(_("Hash: "), 'H',
build_list_md_algo_name,
build_list_md_test_algo );
p = digests;
break;
case 37:
if( !zips )
zips = build_list(_("Compression: "),'Z',
compress_algo_to_string,
check_compress_algo);
p = zips;
break;
default: p = NULL;
}
return p;
}
static char *
build_list (const char *text, char letter,
const char * (*mapf)(int), int (*chkf)(int))
{
membuf_t mb;
int indent;
int i, j, len;
const char *s;
char *string;
if (maybe_setuid)
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
indent = utf8_charcount (text, -1);
len = 0;
init_membuf (&mb, 512);
for (i=0; i <= 110; i++ )
{
if (!chkf (i) && (s = mapf (i)))
{
if (mb.len - len > 60)
{
put_membuf_str (&mb, ",\n");
len = mb.len;
for (j=0; j < indent; j++)
put_membuf_str (&mb, " ");
}
else if (mb.len)
put_membuf_str (&mb, ", ");
else
put_membuf_str (&mb, text);
put_membuf_str (&mb, s);
if (opt.verbose && letter)
{
char num[20];
if (letter == 1)
snprintf (num, sizeof num, " (%d)", i);
else
snprintf (num, sizeof num, " (%c%d)", letter, i);
put_membuf_str (&mb, num);
}
}
}
if (mb.len)
put_membuf_str (&mb, "\n");
put_membuf (&mb, "", 1);
string = get_membuf (&mb, NULL);
return xrealloc (string, strlen (string)+1);
}
static void
wrong_args( const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text);
g10_exit(2);
}
static char *
make_username( const char *string )
{
char *p;
if( utf8_strings )
p = xstrdup(string);
else
p = native_to_utf8( string );
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_MEMSTAT_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE
|DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
g10_exit (2);
}
if ((opt.debug & DBG_MEMORY_VALUE))
memory_debug_mode = 1;
if ((opt.debug & DBG_MEMSTAT_VALUE))
memory_stat_debug_mode = 1;
if (DBG_MPI)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (DBG_CRYPTO)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
if ((opt.debug & DBG_IOBUF_VALUE))
iobuf_debug_mode = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* We set the screen dimensions for UI purposes. Do not allow screens
smaller than 80x24 for the sake of simplicity. */
static void
set_screen_dimensions(void)
{
#ifndef HAVE_W32_SYSTEM
char *str;
str=getenv("COLUMNS");
if(str)
opt.screen_columns=atoi(str);
str=getenv("LINES");
if(str)
opt.screen_lines=atoi(str);
#endif
if(opt.screen_columns<80 || opt.screen_columns>255)
opt.screen_columns=80;
if(opt.screen_lines<24 || opt.screen_lines>255)
opt.screen_lines=24;
}
/* Helper to open a file FNAME either for reading or writing to be
used with --status-file etc functions. Not generally useful but it
avoids the riscos specific functions and well some Windows people
might like it too. Prints an error message and returns -1 on
error. On success the file descriptor is returned. */
static int
open_info_file (const char *fname, int for_write, int binary)
{
#ifdef __riscos__
return riscos_fdopenfile (fname, for_write);
#elif defined (ENABLE_SELINUX_HACKS)
/* We can't allow these even when testing for a secured filename
because files to be secured might not yet been secured. This is
similar to the option file but in that case it is unlikely that
sensitive information may be retrieved by means of error
messages. */
(void)fname;
(void)for_write;
(void)binary;
return -1;
#else
int fd;
if (binary)
binary = MY_O_BINARY;
/* if (is_secured_filename (fname)) */
/* { */
/* fd = -1; */
/* gpg_err_set_errno (EPERM); */
/* } */
/* else */
/* { */
do
{
if (for_write)
fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
else
fd = open (fname, O_RDONLY | binary);
}
while (fd == -1 && errno == EINTR);
/* } */
if ( fd == -1)
log_error ( for_write? _("can't create '%s': %s\n")
: _("can't open '%s': %s\n"), fname, strerror(errno));
return fd;
#endif
}
static void
set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd )
{
enum cmd_and_opt_values cmd = *ret_cmd;
if( !cmd || cmd == new_cmd )
cmd = new_cmd;
else if( cmd == aSign && new_cmd == aEncr )
cmd = aSignEncr;
else if( cmd == aEncr && new_cmd == aSign )
cmd = aSignEncr;
else if( cmd == aSign && new_cmd == aSym )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aSign )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aEncr )
cmd = aEncrSym;
else if( cmd == aEncr && new_cmd == aSym )
cmd = aEncrSym;
else if (cmd == aSignEncr && new_cmd == aSym)
cmd = aSignEncrSym;
else if (cmd == aSignSym && new_cmd == aEncr)
cmd = aSignEncrSym;
else if (cmd == aEncrSym && new_cmd == aSign)
cmd = aSignEncrSym;
else if( ( cmd == aSign && new_cmd == aClearsign )
|| ( cmd == aClearsign && new_cmd == aSign ) )
cmd = aClearsign;
else {
log_error(_("conflicting commands\n"));
g10_exit(2);
}
*ret_cmd = cmd;
}
static void
add_group(char *string)
{
char *name,*value;
struct groupitem *item;
/* Break off the group name */
name=strsep(&string,"=");
if(string==NULL)
{
log_error(_("no = sign found in group definition '%s'\n"),name);
return;
}
trim_trailing_ws(name,strlen(name));
/* Does this group already exist? */
for(item=opt.grouplist;item;item=item->next)
if(strcasecmp(item->name,name)==0)
break;
if(!item)
{
item=xmalloc(sizeof(struct groupitem));
item->name=name;
item->next=opt.grouplist;
item->values=NULL;
opt.grouplist=item;
}
/* Break apart the values */
while ((value= strsep(&string," \t")))
{
if (*value)
add_to_strlist2(&item->values,value,utf8_strings);
}
}
static void
rm_group(char *name)
{
struct groupitem *item,*last=NULL;
trim_trailing_ws(name,strlen(name));
for(item=opt.grouplist;item;last=item,item=item->next)
{
if(strcasecmp(item->name,name)==0)
{
if(last)
last->next=item->next;
else
opt.grouplist=item->next;
free_strlist(item->values);
xfree(item);
break;
}
}
}
/* We need to check three things.
0) The homedir. It must be x00, a directory, and owned by the
user.
1) The options/gpg.conf file. Okay unless it or its containing
directory is group or other writable or not owned by us. Disable
exec in this case.
2) Extensions. Same as #1.
Returns true if the item is unsafe. */
static int
check_permissions (const char *path, int item)
{
#if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM)
static int homedir_cache=-1;
char *tmppath,*dir;
struct stat statbuf,dirbuf;
int homedir=0,ret=0,checkonly=0;
int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0;
if(opt.no_perm_warn)
return 0;
log_assert(item==0 || item==1 || item==2);
/* extensions may attach a path */
if(item==2 && path[0]!=DIRSEP_C)
{
if(strchr(path,DIRSEP_C))
tmppath=make_filename(path,NULL);
else
tmppath=make_filename(gnupg_libdir (),path,NULL);
}
else
tmppath=xstrdup(path);
/* If the item is located in the homedir, but isn't the homedir,
don't continue if we already checked the homedir itself. This is
to avoid user confusion with an extra options file warning which
could be rectified if the homedir itself had proper
permissions. */
if(item!=0 && homedir_cache>-1
&& !ascii_strncasecmp (gnupg_homedir (), tmppath,
strlen (gnupg_homedir ())))
{
ret=homedir_cache;
goto end;
}
/* It's okay if the file or directory doesn't exist */
if(stat(tmppath,&statbuf)!=0)
{
ret=0;
goto end;
}
/* Now check the enclosing directory. Theoretically, we could walk
this test up to the root directory /, but for the sake of sanity,
I'm stopping at one level down. */
dir=make_dirname(tmppath);
if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode))
{
/* Weird error */
ret=1;
goto end;
}
xfree(dir);
/* Assume failure */
ret=1;
if(item==0)
{
/* The homedir must be x00, a directory, and owned by the user. */
if(S_ISDIR(statbuf.st_mode))
{
if(statbuf.st_uid==getuid())
{
if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=1;
}
else
own=1;
homedir_cache=ret;
}
}
else if(item==1 || item==2)
{
/* The options or extension file. Okay unless it or its
containing directory is group or other writable or not owned
by us or root. */
if(S_ISREG(statbuf.st_mode))
{
if(statbuf.st_uid==getuid() || statbuf.st_uid==0)
{
if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
{
/* it's not writable, so make sure the enclosing
directory is also not writable */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
ret=0;
else
enc_dir_perm=1;
}
else
enc_dir_own=1;
}
else
{
/* it's writable, so the enclosing directory had
better not let people get to it. */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=enc_dir_perm=1; /* unclear which one to fix! */
}
else
enc_dir_own=1;
}
}
else
own=1;
}
}
else
BUG();
if(!checkonly)
{
if(own)
{
if(item==0)
log_info(_("WARNING: unsafe ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe ownership on"
" extension '%s'\n"),tmppath);
}
if(perm)
{
if(item==0)
log_info(_("WARNING: unsafe permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe permissions on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_own)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory ownership on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_perm)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory permissions on"
" extension '%s'\n"),tmppath);
}
}
end:
xfree(tmppath);
if(homedir)
homedir_cache=ret;
return ret;
#else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
(void)path;
(void)item;
return 0;
#endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
}
/* Print the OpenPGP defined algo numbers. */
static void
print_algo_numbers(int (*checker)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%d",i);
}
}
}
static void
print_algo_names(int (*checker)(int),const char *(*mapper)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%s",mapper(i));
}
}
}
/* In the future, we can do all sorts of interesting configuration
output here. For now, just give "group" as the Enigmail folks need
it, and pubkey, cipher, hash, and compress as they may be useful
for frontends. */
static void
list_config(char *items)
{
int show_all = !items;
char *name = NULL;
const char *s;
struct groupitem *giter;
int first, iter;
if(!opt.with_colons)
return;
while(show_all || (name=strsep(&items," ")))
{
int any=0;
if(show_all || ascii_strcasecmp(name,"group")==0)
{
for (giter = opt.grouplist; giter; giter = giter->next)
{
strlist_t sl;
es_fprintf (es_stdout, "cfg:group:");
es_write_sanitized (es_stdout, giter->name, strlen(giter->name),
":", NULL);
es_putc (':', es_stdout);
for(sl=giter->values; sl; sl=sl->next)
{
es_write_sanitized (es_stdout, sl->d, strlen (sl->d),
":;", NULL);
if(sl->next)
es_printf(";");
}
es_printf("\n");
}
any=1;
}
if(show_all || ascii_strcasecmp(name,"version")==0)
{
es_printf("cfg:version:");
es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkey")==0)
{
es_printf ("cfg:pubkey:");
print_algo_numbers (build_list_pk_test_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkeyname")==0)
{
es_printf ("cfg:pubkeyname:");
print_algo_names (build_list_pk_test_algo,
build_list_pk_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"cipher")==0)
{
es_printf ("cfg:cipher:");
print_algo_numbers (build_list_cipher_test_algo);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp (name,"ciphername"))
{
es_printf ("cfg:ciphername:");
print_algo_names (build_list_cipher_test_algo,
build_list_cipher_algo_name);
es_printf ("\n");
any = 1;
}
if(show_all
|| ascii_strcasecmp(name,"digest")==0
|| ascii_strcasecmp(name,"hash")==0)
{
es_printf ("cfg:digest:");
print_algo_numbers (build_list_md_test_algo);
es_printf ("\n");
any=1;
}
if (show_all
|| !ascii_strcasecmp(name,"digestname")
|| !ascii_strcasecmp(name,"hashname"))
{
es_printf ("cfg:digestname:");
print_algo_names (build_list_md_test_algo,
build_list_md_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"compress")==0)
{
es_printf ("cfg:compress:");
print_algo_numbers(check_compress_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp (name, "compressname") == 0)
{
es_printf ("cfg:compressname:");
print_algo_names (check_compress_algo,
compress_algo_to_string);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp(name,"ccid-reader-id"))
{
/* We ignore this for GnuPG 1.4 backward compatibility. */
any=1;
}
if (show_all || !ascii_strcasecmp (name,"curve"))
{
es_printf ("cfg:curve:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0)
es_printf ("%s%s", first?"":";", s);
es_printf ("\n");
any=1;
}
/* Curve OIDs are rarely useful and thus only printed if requested. */
if (name && !ascii_strcasecmp (name,"curveoid"))
{
es_printf ("cfg:curveoid:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0)
{
s = openpgp_curve_to_oid (s, NULL);
es_printf ("%s%s", first?"":";", s? s:"[?]");
}
es_printf ("\n");
any=1;
}
if(show_all)
break;
if(!any)
log_error(_("unknown configuration item '%s'\n"),name);
}
}
/* List options and default values in the GPG Conf format. This is a
new tool distributed with gnupg 1.9.x but we also want some limited
support in older gpg versions. The output is the name of the
configuration file and a list of options available for editing by
gpgconf. */
static void
gpgconf_list (const char *configfile)
{
char *configfile_esc = percent_escape (configfile, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPG_NAME,
GC_OPT_FLAG_DEFAULT,
configfile_esc ? configfile_esc : "/dev/null");
es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("default-key:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("try-secret-key:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-key-locate:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("group:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg");
es_printf ("default-new-key-algo:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("trust-model:%lu:\n", GC_OPT_FLAG_NONE);
/* The next one is an info only item and should match the macros at
the top of keygen.c */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
get_default_pubkey_algo ());
xfree (configfile_esc);
}
static int
parse_subpacket_list(char *list)
{
char *tok;
byte subpackets[128],i;
int count=0;
if(!list)
{
/* No arguments means all subpackets */
memset(subpackets+1,1,sizeof(subpackets)-1);
count=127;
}
else
{
memset(subpackets,0,sizeof(subpackets));
/* Merge with earlier copy */
if(opt.show_subpackets)
{
byte *in;
for(in=opt.show_subpackets;*in;in++)
{
if(*in>127 || *in<1)
BUG();
if(!subpackets[*in])
count++;
subpackets[*in]=1;
}
}
while((tok=strsep(&list," ,")))
{
if(!*tok)
continue;
i=atoi(tok);
if(i>127 || i<1)
return 0;
if(!subpackets[i])
count++;
subpackets[i]=1;
}
}
xfree(opt.show_subpackets);
opt.show_subpackets=xmalloc(count+1);
opt.show_subpackets[count--]=0;
for(i=1;i<128 && count>=0;i++)
if(subpackets[i])
opt.show_subpackets[count--]=i;
return 1;
}
static int
parse_list_options(char *str)
{
char *subpackets=""; /* something that isn't NULL */
struct parse_options lopts[]=
{
{"show-photos",LIST_SHOW_PHOTOS,NULL,
N_("display photo IDs during key listings")},
{"show-usage",LIST_SHOW_USAGE,NULL,
N_("show key usage information during key listings")},
{"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature listings")},
{"show-notations",LIST_SHOW_NOTATIONS,NULL,
N_("show all notations during signature listings")},
{"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature listings")},
{"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature listings")},
{"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature listings")},
{"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during key listings")},
{"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in key listings")},
{"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL,
N_("show revoked and expired subkeys in key listings")},
{"show-keyring",LIST_SHOW_KEYRING,NULL,
N_("show the keyring name in key listings")},
{"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL,
N_("show expiration dates during signature listings")},
{"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL,
NULL},
{NULL,0,NULL,NULL}
};
/* C99 allows for non-constant initializers, but we'd like to
compile everywhere, so fill in the show-sig-subpackets argument
here. Note that if the parse_options array changes, we'll have
to change the subscript here. */
lopts[13].value=&subpackets;
if(parse_options(str,&opt.list_options,lopts,1))
{
if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS)
{
/* Unset so users can pass multiple lists in. */
opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS;
if(!parse_subpacket_list(subpackets))
return 0;
}
else if(subpackets==NULL && opt.show_subpackets)
{
/* User did 'no-show-subpackets' */
xfree(opt.show_subpackets);
opt.show_subpackets=NULL;
}
return 1;
}
else
return 0;
}
/* Collapses argc/argv into a single string that must be freed */
static char *
collapse_args(int argc,char *argv[])
{
char *str=NULL;
int i,first=1,len=0;
for(i=0;i<argc;i++)
{
len+=strlen(argv[i])+2;
str=xrealloc(str,len);
if(first)
{
str[0]='\0';
first=0;
}
else
strcat(str," ");
strcat(str,argv[i]);
}
return str;
}
#ifndef NO_TRUST_MODELS
static void
parse_trust_model(const char *model)
{
if(ascii_strcasecmp(model,"pgp")==0)
opt.trust_model=TM_PGP;
else if(ascii_strcasecmp(model,"classic")==0)
opt.trust_model=TM_CLASSIC;
else if(ascii_strcasecmp(model,"always")==0)
opt.trust_model=TM_ALWAYS;
else if(ascii_strcasecmp(model,"direct")==0)
opt.trust_model=TM_DIRECT;
#ifdef USE_TOFU
else if(ascii_strcasecmp(model,"tofu")==0)
opt.trust_model=TM_TOFU;
else if(ascii_strcasecmp(model,"tofu+pgp")==0)
opt.trust_model=TM_TOFU_PGP;
#endif /*USE_TOFU*/
else if(ascii_strcasecmp(model,"auto")==0)
opt.trust_model=TM_AUTO;
else
log_error("unknown trust model '%s'\n",model);
}
#endif /*NO_TRUST_MODELS*/
static int
parse_tofu_policy (const char *policystr)
{
#ifdef USE_TOFU
struct { const char *keyword; int policy; } list[] = {
{ "auto", TOFU_POLICY_AUTO },
{ "good", TOFU_POLICY_GOOD },
{ "unknown", TOFU_POLICY_UNKNOWN },
{ "bad", TOFU_POLICY_BAD },
{ "ask", TOFU_POLICY_ASK }
};
int i;
if (!ascii_strcasecmp (policystr, "help"))
{
log_info (_("valid values for option '%s':\n"), "--tofu-policy");
for (i=0; i < DIM (list); i++)
log_info (" %s\n", list[i].keyword);
g10_exit (1);
}
for (i=0; i < DIM (list); i++)
if (!ascii_strcasecmp (policystr, list[i].keyword))
return list[i].policy;
#endif /*USE_TOFU*/
log_error (_("unknown TOFU policy '%s'\n"), policystr);
if (!opt.quiet)
log_info (_("(use \"help\" to list choices)\n"));
g10_exit (1);
}
/* Parse the value of --compliance. */
static int
parse_compliance_option (const char *string)
{
struct { const char *keyword; enum cmd_and_opt_values option; } list[] = {
{ "gnupg", oGnuPG },
{ "openpgp", oOpenPGP },
{ "rfc4880bis", oRFC4880bis },
{ "rfc4880", oRFC4880 },
{ "rfc2440", oRFC2440 },
{ "pgp6", oPGP6 },
{ "pgp7", oPGP7 },
{ "pgp8", oPGP8 },
{ "de-vs", oDE_VS }
};
int i;
if (!ascii_strcasecmp (string, "help"))
{
log_info (_("valid values for option '%s':\n"), "--compliance");
for (i=0; i < DIM (list); i++)
log_info (" %s\n", list[i].keyword);
g10_exit (1);
}
for (i=0; i < DIM (list); i++)
if (!ascii_strcasecmp (string, list[i].keyword))
return list[i].option;
log_error (_("invalid value for option '%s'\n"), "--compliance");
if (!opt.quiet)
log_info (_("(use \"help\" to list choices)\n"));
g10_exit (1);
}
/* Helper to set compliance related options. This is a separte
* function so that it can also be used by the --compliance option
* parser. */
static void
set_compliance_option (enum cmd_and_opt_values option)
{
switch (option)
{
case oRFC4880bis:
opt.flags.rfc4880bis = 1;
/* fall through. */
case oOpenPGP:
case oRFC4880:
/* This is effectively the same as RFC2440, but with
"--enable-dsa2 --no-rfc2440-text --escape-from-lines
--require-cross-certification". */
opt.compliance = CO_RFC4880;
opt.flags.dsa2 = 1;
opt.flags.require_cross_cert = 1;
opt.rfc2440_text = 0;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 1;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
break;
case oRFC2440:
opt.compliance = CO_RFC2440;
opt.flags.dsa2 = 0;
opt.rfc2440_text = 1;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 0;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
break;
case oPGP6: opt.compliance = CO_PGP6; break;
case oPGP7: opt.compliance = CO_PGP7; break;
case oPGP8: opt.compliance = CO_PGP8; break;
case oGnuPG: opt.compliance = CO_GNUPG; break;
case oDE_VS:
set_compliance_option (oOpenPGP);
opt.compliance = CO_DE_VS;
/* Fixme: Change other options. */
break;
default:
BUG ();
}
}
/* This function called to initialized a new control object. It is
assumed that this object has been zeroed out before calling this
function. */
static void
gpg_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
/* This function is called to deinitialize a control object. It is
not deallocated. */
static void
gpg_deinit_default_ctrl (ctrl_t ctrl)
{
#ifdef USE_TOFU
tofu_closedbs (ctrl);
#endif
gpg_dirmngr_deinit_session_data (ctrl);
}
char *
get_default_configname (void)
{
char *configname = NULL;
char *name = xstrdup (GPG_NAME EXTSEP_S "conf-" SAFE_VERSION);
char *ver = &name[strlen (GPG_NAME EXTSEP_S "conf-")];
do
{
if (configname)
{
char *tok;
xfree (configname);
configname = NULL;
if ((tok = strrchr (ver, SAFE_VERSION_DASH)))
*tok='\0';
else if ((tok = strrchr (ver, SAFE_VERSION_DOT)))
*tok='\0';
else
break;
}
configname = make_filename (gnupg_homedir (), name, NULL);
}
while (access (configname, R_OK));
xfree(name);
if (! configname)
configname = make_filename (gnupg_homedir (),
GPG_NAME EXTSEP_S "conf", NULL);
if (! access (configname, R_OK))
{
/* Print a warning when both config files are present. */
char *p = make_filename (gnupg_homedir (), "options", NULL);
if (! access (p, R_OK))
log_info (_("Note: old default options file '%s' ignored\n"), p);
xfree (p);
}
else
{
/* Use the old default only if it exists. */
char *p = make_filename (gnupg_homedir (), "options", NULL);
if (!access (p, R_OK))
{
xfree (configname);
configname = p;
}
else
xfree (p);
}
return configname;
}
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
IOBUF a;
int rc=0;
int orig_argc;
char **orig_argv;
const char *fname;
char *username;
int may_coredump;
strlist_t sl;
strlist_t remusr = NULL;
strlist_t locusr = NULL;
strlist_t nrings = NULL;
armor_filter_context_t *afx = NULL;
int detached_sig = 0;
FILE *configfp = NULL;
char *configname = NULL;
char *save_configname = NULL;
char *default_configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int default_config = 1;
int default_keyring = 1;
int greeting = 0;
int nogreeting = 0;
char *logfile = NULL;
int use_random_seed = 1;
enum cmd_and_opt_values cmd = 0;
const char *debug_level = NULL;
#ifndef NO_TRUST_MODELS
const char *trustdb_name = NULL;
#endif /*!NO_TRUST_MODELS*/
char *def_cipher_string = NULL;
char *def_digest_string = NULL;
char *compress_algo_string = NULL;
char *cert_digest_string = NULL;
char *s2k_cipher_string = NULL;
char *s2k_digest_string = NULL;
char *pers_cipher_list = NULL;
char *pers_digest_list = NULL;
char *pers_compress_list = NULL;
int eyes_only=0;
int multifile=0;
int pwfd = -1;
int ovrseskeyfd = -1;
int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */
int any_explicit_recipient = 0;
int require_secmem = 0;
int got_secmem = 0;
struct assuan_malloc_hooks malloc_hooks;
ctrl_t ctrl;
static int print_dane_records;
static int print_pka_records;
#ifdef __riscos__
opt.lock_once = 1;
#endif /* __riscos__ */
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to
secmem_init() somewhere after the option parsing. */
early_system_init ();
gnupg_reopen_std (GPG_NAME);
trap_unaligned ();
gnupg_rl_initialize ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Use our own logging handler for Libcgrypt. */
setup_libgcrypt_logging ();
/* Put random number into secure memory */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lock file cleanup. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
opt.command_fd = -1; /* no command fd */
opt.compress_level = -1; /* defaults to standard compress level */
opt.bz2_compress_level = -1; /* defaults to standard compress level */
/* note: if you change these lines, look at oOpenPGP */
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_count = 0; /* Auto-calibrate when needed. */
opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO;
opt.completes_needed = 1;
opt.marginals_needed = 3;
opt.max_cert_depth = 5;
opt.escape_from = 1;
opt.flags.require_cross_cert = 1;
opt.import_options = 0;
opt.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.import_options = IMPORT_REPAIR_PKS_SUBKEY_BUG;
opt.keyserver_options.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD;
opt.verify_options = (LIST_SHOW_UID_VALIDITY
| VERIFY_SHOW_POLICY_URLS
| VERIFY_SHOW_STD_NOTATIONS
| VERIFY_SHOW_KEYSERVER_URLS);
opt.list_options = (LIST_SHOW_UID_VALIDITY
| LIST_SHOW_USAGE);
#ifdef NO_TRUST_MODELS
opt.trust_model = TM_ALWAYS;
#else
opt.trust_model = TM_AUTO;
#endif
opt.tofu_default_policy = TOFU_POLICY_AUTO;
opt.mangle_dos_filenames = 0;
opt.min_cert_level = 2;
set_screen_dimensions ();
opt.keyid_format = KF_NONE;
opt.def_sig_expire = "0";
opt.def_cert_expire = "0";
gnupg_set_homedir (NULL);
opt.passphrase_repeat = 1;
opt.emit_version = 0;
opt.weak_digests = NULL;
additional_weak_digest("MD5");
/* Check whether we have a config file on the command line. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while( arg_parse( &pargs, opts) ) {
if( pargs.r_opt == oDebug || pargs.r_opt == oDebugAll )
parse_debug++;
else if (pargs.r_opt == oDebugIOLBF)
es_setvbuf (es_stdout, NULL, _IOLBF, 0);
else if( pargs.r_opt == oOptions ) {
/* yes there is one, so we do not try the default one, but
* read the option file when it is encountered at the commandline
*/
default_config = 0;
}
else if( pargs.r_opt == oNoOptions )
{
default_config = 0; /* --no-options */
opt.no_homedir_creation = 1;
}
else if( pargs.r_opt == oHomedir )
gnupg_set_homedir (pargs.r.ret_str);
else if( pargs.r_opt == oNoPermissionWarn )
opt.no_perm_warn=1;
else if (pargs.r_opt == oStrict )
{
/* Not used */
}
else if (pargs.r_opt == oNoStrict )
{
/* Not used */
}
}
#ifdef HAVE_DOSISH_SYSTEM
if ( strchr (gnupg_homedir (), '\\') ) {
char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1);
const char *s;
for (d=buf, s = gnupg_homedir (); *s; s++)
{
*d++ = *s == '\\'? '/': *s;
#ifdef HAVE_W32_SYSTEM
if (s[1] && IsDBCSLeadByte (*s))
*d++ = *++s;
#endif
}
*d = 0;
gnupg_set_homedir (buf);
}
#endif
/* Initialize the secure memory. */
if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0))
got_secmem = 1;
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
/* There should be no way to get to this spot while still carrying
setuid privs. Just in case, bomb out if we are. */
if ( getuid () != geteuid () )
BUG ();
#endif
maybe_setuid = 0;
/* Okay, we are now working under our real uid */
/* malloc hooks go here ... */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Try for a version specific config file first */
default_configname = get_default_configname ();
if (default_config)
configname = xstrdup (default_configname);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= ARGPARSE_FLAG_KEEP;
/* By this point we have a homedir, and cannot change it. */
check_permissions (gnupg_homedir (), 0);
next_pass:
if( configname ) {
if(check_permissions(configname,1))
{
/* If any options file is unsafe, then disable any external
programs for keyserver calls or photo IDs. Since the
external program to call is set in the options file, a
unsafe options file can lead to an arbitrary program
being run. */
opt.exec_disable=1;
}
configlineno = 0;
configfp = fopen( configname, "r" );
if (configfp && is_secured_file (fileno (configfp)))
{
fclose (configfp);
configfp = NULL;
gpg_err_set_errno (EPERM);
}
if( !configfp ) {
if( default_config ) {
if( parse_debug )
log_info(_("Note: no default option file '%s'\n"),
configname );
}
else {
log_error(_("option file '%s': %s\n"),
configname, strerror(errno) );
g10_exit(2);
}
xfree(configname); configname = NULL;
}
if( parse_debug && configname )
log_info(_("reading options from '%s'\n"), configname );
default_config = 0;
}
while( optfile_parse( configfp, configname, &configlineno,
&pargs, opts) )
{
switch( pargs.r_opt )
{
case aListConfig:
case aListGcryptConfig:
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
/* Do not register a keyring for these commands. */
default_keyring = -1;
break;
case aCheckKeys:
case aListPackets:
case aImport:
case aFastImport:
case aSendKeys:
case aRecvKeys:
case aSearchKeys:
case aRefreshKeys:
case aFetchKeys:
case aExport:
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
case aCardEdit:
case aChangePIN:
#endif /* ENABLE_CARD_SUPPORT*/
case aListKeys:
case aLocateKeys:
case aListSigs:
case aExportSecret:
case aExportSecretSub:
case aExportSshKey:
case aSym:
case aClearsign:
case aGenRevoke:
case aDesigRevoke:
case aPrimegen:
case aGenRandom:
case aPrintMD:
case aPrintMDs:
case aListTrustDB:
case aCheckTrustDB:
case aUpdateTrustDB:
case aFixTrustDB:
case aListTrustPath:
case aDeArmor:
case aEnArmor:
case aSign:
case aQuickSignKey:
case aQuickLSignKey:
case aSignKey:
case aLSignKey:
case aStore:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickRevUid:
case aQuickSetExpire:
case aExportOwnerTrust:
case aImportOwnerTrust:
case aRebuildKeydbCaches:
set_cmd (&cmd, pargs.r_opt);
break;
case aKeygen:
case aFullKeygen:
case aEditKey:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aDeleteKeys:
case aPasswd:
set_cmd (&cmd, pargs.r_opt);
greeting=1;
break;
case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break;
case aDecryptFiles: multifile=1; /* fall through */
case aDecrypt: set_cmd( &cmd, aDecrypt); break;
case aEncrFiles: multifile=1; /* fall through */
case aEncr: set_cmd( &cmd, aEncr); break;
case aVerifyFiles: multifile=1; /* fall through */
case aVerify: set_cmd( &cmd, aVerify); break;
case aServer:
set_cmd (&cmd, pargs.r_opt);
opt.batch = 1;
break;
case aTOFUPolicy:
set_cmd (&cmd, pargs.r_opt);
break;
case oArmor: opt.armor = 1; opt.no_armor=0; break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break;
case oInputSizeHint:
opt.input_size_hint = string_to_u64 (pargs.r.ret_str);
break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: tty_no_terminal(1); break;
case oDryRun: opt.dry_run = 1; break;
case oInteractive: opt.interactive = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_options|=LIST_SHOW_UNUSABLE_UIDS;
opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS;
break;
case oBatch:
opt.batch = 1;
nogreeting = 1;
break;
case oUseAgent: /* Dummy. */
break;
case oNoUseAgent:
obsolete_option (configname, configlineno, "no-use-agent");
break;
case oGpgAgentInfo:
obsolete_option (configname, configlineno, "gpg-agent-info");
break;
case oReaderPort:
obsolete_scdaemon_option (configname, configlineno, "reader-port");
break;
case octapiDriver:
obsolete_scdaemon_option (configname, configlineno, "ctapi-driver");
break;
case opcscDriver:
obsolete_scdaemon_option (configname, configlineno, "pcsc-driver");
break;
case oDisableCCID:
obsolete_scdaemon_option (configname, configlineno, "disable-ccid");
break;
case oHonorHttpProxy:
obsolete_option (configname, configlineno, "honor-http-proxy");
break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break;
case oPrimaryKeyring:
sl = append_to_strlist (&nrings, pargs.r.ret_str);
sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY;
break;
case oShowKeyring:
deprecated_warning(configname,configlineno,"--show-keyring",
"--list-options ","show-keyring");
opt.list_options|=LIST_SHOW_KEYRING;
break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugIOLBF: break; /* Already set in pre-parse step. */
case oStatusFD:
set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oStatusFile:
set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) );
break;
case oAttributeFD:
set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oAttributeFile:
set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) );
break;
case oLoggerFD:
log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oLoggerFile:
logfile = pargs.r.ret_str;
break;
case oWithFingerprint:
opt.with_fingerprint = 1;
opt.fingerprint++;
break;
case oWithSubkeyFingerprint:
opt.with_subkey_fingerprint = 1;
break;
case oWithICAOSpelling:
opt.with_icao_spelling = 1;
break;
case oFingerprint:
opt.fingerprint++;
fpr_maybe_cmd = 1;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oWithSecret:
opt.with_secret = 1;
break;
case oWithWKDHash:
opt.with_wkd_hash = 1;
break;
case oSecretKeyring:
/* Ignore this old option. */
break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if( !configfp ) {
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoArmor: opt.no_armor=1; opt.armor=0; break;
case oNoDefKeyring:
if (default_keyring > 0)
default_keyring = 0;
break;
case oNoKeyring:
default_keyring = -1;
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_sigs=0;
break;
case oQuickRandom:
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
break;
case oEmitVersion: opt.emit_version++; break;
case oNoEmitVersion: opt.emit_version=0; break;
case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break;
case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break;
case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break;
#ifndef NO_TRUST_MODELS
case oTrustDBName: trustdb_name = pargs.r.ret_str; break;
#endif /*!NO_TRUST_MODELS*/
case oDefaultKey:
sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str);
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
break;
case oDefRecipient:
if( *pargs.r.ret_str )
{
xfree (opt.def_recipient);
opt.def_recipient = make_username(pargs.r.ret_str);
}
break;
case oDefRecipientSelf:
xfree(opt.def_recipient); opt.def_recipient = NULL;
opt.def_recipient_self = 1;
break;
case oNoDefRecipient:
xfree(opt.def_recipient); opt.def_recipient = NULL;
opt.def_recipient_self = 0;
break;
case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */
case oHomedir: break;
case oNoBatch: opt.batch = 0; break;
case oWithTofuInfo: opt.with_tofu_info = 1; break;
case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/
case oWithColons: opt.with_colons=':'; break;
case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/
case oWithSigList: opt.list_sigs = 1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break;
case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break;
case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break;
#ifndef NO_TRUST_MODELS
/* There are many programs (like mutt) that call gpg with
--always-trust so keep this option around for a long
time. */
case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break;
case oTrustModel:
parse_trust_model(pargs.r.ret_str);
break;
#endif /*!NO_TRUST_MODELS*/
case oTOFUDefaultPolicy:
opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str);
break;
case oTOFUDBFormat:
obsolete_option (configname, configlineno, "tofu-db-format");
break;
case oForceOwnertrust:
log_info(_("Note: %s is not for normal use!\n"),
"--force-ownertrust");
opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str);
if(opt.force_ownertrust==-1)
{
log_error("invalid ownertrust '%s'\n",pargs.r.ret_str);
opt.force_ownertrust=0;
}
break;
case oLoadExtension:
/* Dummy so that gpg 1.4 conf files can work. Should
eventually be removed. */
break;
case oCompliance:
set_compliance_option (parse_compliance_option (pargs.r.ret_str));
break;
case oOpenPGP:
case oRFC2440:
case oRFC4880:
case oRFC4880bis:
case oPGP6:
case oPGP7:
case oPGP8:
case oGnuPG:
set_compliance_option (pargs.r_opt);
break;
case oRFC2440Text: opt.rfc2440_text=1; break;
case oNoRFC2440Text: opt.rfc2440_text=0; break;
case oSetFilename:
if(utf8_strings)
opt.set_filename = pargs.r.ret_str;
else
opt.set_filename = native_to_utf8(pargs.r.ret_str);
break;
case oForYourEyesOnly: eyes_only = 1; break;
case oNoForYourEyesOnly: eyes_only = 0; break;
case oSetPolicyURL:
add_policy_url(pargs.r.ret_str,0);
add_policy_url(pargs.r.ret_str,1);
break;
case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break;
case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break;
case oShowPolicyURL:
deprecated_warning(configname,configlineno,"--show-policy-url",
"--list-options ","show-policy-urls");
deprecated_warning(configname,configlineno,"--show-policy-url",
"--verify-options ","show-policy-urls");
opt.list_options|=LIST_SHOW_POLICY_URLS;
opt.verify_options|=VERIFY_SHOW_POLICY_URLS;
break;
case oNoShowPolicyURL:
deprecated_warning(configname,configlineno,"--no-show-policy-url",
"--list-options ","no-show-policy-urls");
deprecated_warning(configname,configlineno,"--no-show-policy-url",
"--verify-options ","no-show-policy-urls");
opt.list_options&=~LIST_SHOW_POLICY_URLS;
opt.verify_options&=~VERIFY_SHOW_POLICY_URLS;
break;
case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break;
case oUseEmbeddedFilename:
opt.flags.use_embedded_filename=1;
break;
case oNoUseEmbeddedFilename:
opt.flags.use_embedded_filename=0;
break;
case oComment:
if(pargs.r.ret_str[0])
append_to_strlist(&opt.comments,pargs.r.ret_str);
break;
case oDefaultComment:
deprecated_warning(configname,configlineno,
"--default-comment","--no-comments","");
/* fall through */
case oNoComments:
free_strlist(opt.comments);
opt.comments=NULL;
break;
case oThrowKeyids: opt.throw_keyids = 1; break;
case oNoThrowKeyids: opt.throw_keyids = 0; break;
case oShowPhotos:
deprecated_warning(configname,configlineno,"--show-photos",
"--list-options ","show-photos");
deprecated_warning(configname,configlineno,"--show-photos",
"--verify-options ","show-photos");
opt.list_options|=LIST_SHOW_PHOTOS;
opt.verify_options|=VERIFY_SHOW_PHOTOS;
break;
case oNoShowPhotos:
deprecated_warning(configname,configlineno,"--no-show-photos",
"--list-options ","no-show-photos");
deprecated_warning(configname,configlineno,"--no-show-photos",
"--verify-options ","no-show-photos");
opt.list_options&=~LIST_SHOW_PHOTOS;
opt.verify_options&=~VERIFY_SHOW_PHOTOS;
break;
case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break;
case oForceMDC: opt.force_mdc = 1; break;
case oNoForceMDC: opt.force_mdc = 0; break;
case oDisableMDC: opt.disable_mdc = 1; break;
case oNoDisableMDC: opt.disable_mdc = 0; break;
case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break;
case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break;
case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break;
case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break;
case oS2KCount:
if (pargs.r.ret_int)
opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int);
else
opt.s2k_count = 0; /* Auto-calibrate when needed. */
break;
case oRecipient:
case oHiddenRecipient:
case oRecipientFile:
case oHiddenRecipientFile:
/* Store the recipient. Note that we also store the
* option as private data in the flags. This is achieved
* by shifting the option value to the left so to keep
* enough space for the flags. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenRecipient
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_HIDDEN;
if (pargs.r_opt == oRecipientFile
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_FROM_FILE;
any_explicit_recipient = 1;
break;
case oEncryptTo:
case oHiddenEncryptTo:
/* Store an additional recipient. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenEncryptTo)
sl->flags |= PK_LIST_HIDDEN;
break;
case oNoEncryptTo:
opt.no_encrypt_to = 1;
break;
case oEncryptToDefaultKey:
opt.encrypt_to_default_key = configfp ? 2 : 1;
break;
case oTrySecretKey:
add_to_strlist2 (&opt.secret_keys_to_try,
pargs.r.ret_str, utf8_strings);
break;
case oMimemode: opt.mimemode = opt.textmode = 1; break;
case oTextmodeShort: opt.textmode = 2; break;
case oTextmode: opt.textmode=1; break;
case oNoTextmode: opt.textmode=opt.mimemode=0; break;
case oExpert: opt.expert = 1; break;
case oNoExpert: opt.expert = 0; break;
case oDefSigExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_sig_expire=pargs.r.ret_str;
}
break;
case oAskSigExpire: opt.ask_sig_expire = 1; break;
case oNoAskSigExpire: opt.ask_sig_expire = 0; break;
case oDefCertExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_cert_expire=pargs.r.ret_str;
}
break;
case oAskCertExpire: opt.ask_cert_expire = 1; break;
case oNoAskCertExpire: opt.ask_cert_expire = 0; break;
case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break;
case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break;
case oAskCertLevel: opt.ask_cert_level = 1; break;
case oNoAskCertLevel: opt.ask_cert_level = 0; break;
case oLocalUser: /* store the local users */
sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
break;
case oSender:
{
char *mbox = mailbox_from_userid (pargs.r.ret_str);
if (!mbox)
log_error (_("\"%s\" is not a proper mail address\n"),
pargs.r.ret_str);
else
{
add_to_strlist (&opt.sender_list, mbox);
xfree (mbox);
}
}
break;
case oCompress:
/* this is the -z command line option */
opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int;
break;
case oCompressLevel: opt.compress_level = pargs.r.ret_int; break;
case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break;
case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break;
case oPassphrase:
set_passphrase_from_string(pargs.r.ret_str);
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPassphraseFile:
pwfd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oPassphraseRepeat:
opt.passphrase_repeat = pargs.r.ret_int;
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
case oCommandFD:
opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
if (! gnupg_fd_valid (opt.command_fd))
log_fatal ("command-fd is invalid: %s\n", strerror (errno));
break;
case oCommandFile:
opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oCipherAlgo:
def_cipher_string = xstrdup(pargs.r.ret_str);
break;
case oDigestAlgo:
def_digest_string = xstrdup(pargs.r.ret_str);
break;
case oCompressAlgo:
/* If it is all digits, stick a Z in front of it for
later. This is for backwards compatibility with
versions that took the compress algorithm number. */
{
char *pt=pargs.r.ret_str;
while(*pt)
{
if (!isascii (*pt) || !isdigit (*pt))
break;
pt++;
}
if(*pt=='\0')
{
compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2);
strcpy(compress_algo_string,"Z");
strcat(compress_algo_string,pargs.r.ret_str);
}
else
compress_algo_string = xstrdup(pargs.r.ret_str);
}
break;
case oCertDigestAlgo:
cert_digest_string = xstrdup(pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oRequireSecmem: require_secmem=1; break;
case oNoRequireSecmem: require_secmem=0; break;
case oNoPermissionWarn: opt.no_perm_warn=1; break;
case oNoMDCWarn: opt.no_mdc_warn=1; break;
case oDisplayCharset:
if( set_native_charset( pargs.r.ret_str ) )
log_error(_("'%s' is not a valid character set\n"),
pargs.r.ret_str);
break;
case oNotDashEscaped: opt.not_dash_escaped = 1; break;
case oEscapeFrom: opt.escape_from = 1; break;
case oNoEscapeFrom: opt.escape_from = 0; break;
case oLockOnce: opt.lock_once = 1; break;
case oLockNever:
dotlock_disable ();
break;
case oLockMultiple:
#ifndef __riscos__
opt.lock_once = 0;
#else /* __riscos__ */
riscos_not_implemented("lock-multiple");
#endif /* __riscos__ */
break;
case oKeyServer:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str, 0);
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
{
/* We only support a single keyserver. Later ones
override earlier ones. (Since we parse the
config file first and then the command line
arguments, the command line takes
precedence.) */
if (opt.keyserver)
free_keyserver_spec (opt.keyserver);
opt.keyserver = keyserver;
}
}
break;
case oKeyServerOptions:
if(!parse_keyserver_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid keyserver options\n"),
configname,configlineno);
else
log_error(_("invalid keyserver options\n"));
}
break;
case oImportOptions:
if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1))
{
if(configname)
log_error(_("%s:%d: invalid import options\n"),
configname,configlineno);
else
log_error(_("invalid import options\n"));
}
break;
case oImportFilter:
rc = parse_and_set_import_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oExportOptions:
if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1))
{
if(configname)
log_error(_("%s:%d: invalid export options\n"),
configname,configlineno);
else
log_error(_("invalid export options\n"));
}
break;
case oExportFilter:
rc = parse_and_set_export_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oListOptions:
if(!parse_list_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid list options\n"),
configname,configlineno);
else
log_error(_("invalid list options\n"));
}
break;
case oVerifyOptions:
{
struct parse_options vopts[]=
{
{"show-photos",VERIFY_SHOW_PHOTOS,NULL,
N_("display photo IDs during signature verification")},
{"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature verification")},
{"show-notations",VERIFY_SHOW_NOTATIONS,NULL,
N_("show all notations during signature verification")},
{"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature verification")},
{"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature verification")},
{"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature verification")},
{"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during signature verification")},
{"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in signature verification")},
{"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL,
N_("show only the primary user ID in signature verification")},
{"pka-lookups",VERIFY_PKA_LOOKUPS,NULL,
N_("validate signatures with PKA data")},
{"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL,
N_("elevate the trust of signatures with valid PKA data")},
{NULL,0,NULL,NULL}
};
if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1))
{
if(configname)
log_error(_("%s:%d: invalid verify options\n"),
configname,configlineno);
else
log_error(_("invalid verify options\n"));
}
}
break;
case oTempDir: opt.temp_dir=pargs.r.ret_str; break;
case oExecPath:
if(set_exec_path(pargs.r.ret_str))
log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str);
else
opt.exec_path_set=1;
break;
case oSetNotation:
add_notation_data( pargs.r.ret_str, 0 );
add_notation_data( pargs.r.ret_str, 1 );
break;
case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break;
case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break;
case oShowNotation:
deprecated_warning(configname,configlineno,"--show-notation",
"--list-options ","show-notations");
deprecated_warning(configname,configlineno,"--show-notation",
"--verify-options ","show-notations");
opt.list_options|=LIST_SHOW_NOTATIONS;
opt.verify_options|=VERIFY_SHOW_NOTATIONS;
break;
case oNoShowNotation:
deprecated_warning(configname,configlineno,"--no-show-notation",
"--list-options ","no-show-notations");
deprecated_warning(configname,configlineno,"--no-show-notation",
"--verify-options ","no-show-notations");
opt.list_options&=~LIST_SHOW_NOTATIONS;
opt.verify_options&=~VERIFY_SHOW_NOTATIONS;
break;
case oUtf8Strings: utf8_strings = 1; break;
case oNoUtf8Strings: utf8_strings = 0; break;
case oDisableCipherAlgo:
{
int algo = string_to_cipher_algo (pargs.r.ret_str);
gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oDisablePubkeyAlgo:
{
int algo = gcry_pk_map_name (pargs.r.ret_str);
gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oNoSigCache: opt.no_sig_cache = 1; break;
case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break;
case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break;
case oAllowFreeformUID: opt.allow_freeform_uid = 1; break;
case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break;
case oNoLiteral: opt.no_literal = 1; break;
case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break;
case oFastListMode: opt.fast_list_mode = 1; break;
case oFixedListMode: /* Dummy */ break;
case oLegacyListMode: opt.legacy_list_mode = 1; break;
case oPrintPKARecords: print_pka_records = 1; break;
case oPrintDANERecords: print_dane_records = 1; break;
case oListOnly: opt.list_only=1; break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oIgnoreValidFrom: opt.ignore_valid_from = 1; break;
case oIgnoreCrcError: opt.ignore_crc_error = 1; break;
case oIgnoreMDCError: opt.ignore_mdc_error = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oAutoKeyRetrieve:
case oNoAutoKeyRetrieve:
if(pargs.r_opt==oAutoKeyRetrieve)
opt.keyserver_options.options|=KEYSERVER_AUTO_KEY_RETRIEVE;
else
opt.keyserver_options.options&=~KEYSERVER_AUTO_KEY_RETRIEVE;
break;
case oShowSessionKey: opt.show_session_key = 1; break;
case oOverrideSessionKey:
opt.override_session_key = pargs.r.ret_str;
break;
case oOverrideSessionKeyFD:
ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oMergeOnly:
deprecated_warning(configname,configlineno,"--merge-only",
"--import-options ","merge-only");
opt.import_options|=IMPORT_MERGE_ONLY;
break;
case oAllowSecretKeyImport: /* obsolete */ break;
case oTryAllSecrets: opt.try_all_secrets = 1; break;
case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break;
case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break;
case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break;
case oPreservePermissions: opt.preserve_permissions=1; break;
case oDefaultPreferenceList:
opt.def_preference_list = pargs.r.ret_str;
break;
case oDefaultKeyserverURL:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str,1 );
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
free_keyserver_spec (keyserver);
opt.def_keyserver_url = pargs.r.ret_str;
}
break;
case oPersonalCipherPreferences:
pers_cipher_list=pargs.r.ret_str;
break;
case oPersonalDigestPreferences:
pers_digest_list=pargs.r.ret_str;
break;
case oPersonalCompressPreferences:
pers_compress_list=pargs.r.ret_str;
break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oWeakDigest:
additional_weak_digest(pargs.r.ret_str);
break;
case oUnwrap:
opt.unwrap_encryption = 1;
break;
case oOnlySignTextIDs:
opt.only_sign_text_ids = 1;
break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = pargs.r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs.r.ret_str; break;
case oGroup: add_group(pargs.r.ret_str); break;
case oUnGroup: rm_group(pargs.r.ret_str); break;
case oNoGroups:
while(opt.grouplist)
{
struct groupitem *iter=opt.grouplist;
free_strlist(iter->values);
opt.grouplist=opt.grouplist->next;
xfree(iter);
}
break;
case oStrict:
case oNoStrict:
/* Not used */
break;
case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break;
case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break;
case oEnableProgressFilter: opt.enable_progress_filter = 1; break;
case oMultifile: multifile=1; break;
case oKeyidFormat:
if(ascii_strcasecmp(pargs.r.ret_str,"short")==0)
opt.keyid_format=KF_SHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0)
opt.keyid_format=KF_LONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0)
opt.keyid_format=KF_0xSHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0)
opt.keyid_format=KF_0xLONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0)
opt.keyid_format = KF_NONE;
else
log_error("unknown keyid-format '%s'\n",pargs.r.ret_str);
break;
case oExitOnStatusWriteError:
opt.exit_on_status_write_error = 1;
break;
case oLimitCardInsertTries:
opt.limit_card_insert_tries = pargs.r.ret_int;
break;
case oRequireCrossCert: opt.flags.require_cross_cert=1; break;
case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break;
case oAutoKeyLocate:
if(!parse_auto_key_locate(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid auto-key-locate list\n"),
configname,configlineno);
else
log_error(_("invalid auto-key-locate list\n"));
}
break;
case oNoAutoKeyLocate:
release_akl();
break;
case oEnableLargeRSA:
#if SECMEM_BUFFER_SIZE >= 65536
opt.flags.large_rsa=1;
#else
if (configname)
log_info("%s:%d: WARNING: gpg not built with large secure "
"memory buffer. Ignoring enable-large-rsa\n",
configname,configlineno);
else
log_info("WARNING: gpg not built with large secure "
"memory buffer. Ignoring --enable-large-rsa\n");
#endif /* SECMEM_BUFFER_SIZE >= 65536 */
break;
case oDisableLargeRSA: opt.flags.large_rsa=0;
break;
case oEnableDSA2: opt.flags.dsa2=1; break;
case oDisableDSA2: opt.flags.dsa2=0; break;
case oAllowMultisigVerification:
case oAllowMultipleMessages:
opt.flags.allow_multiple_messages=1;
break;
case oNoAllowMultipleMessages:
opt.flags.allow_multiple_messages=0;
break;
case oAllowWeakDigestAlgos:
opt.flags.allow_weak_digest_algos = 1;
break;
case oFakedSystemTime:
{
size_t len = strlen (pargs.r.ret_str);
int freeze = 0;
time_t faked_time;
if (len > 0 && pargs.r.ret_str[len-1] == '!')
{
freeze = 1;
pargs.r.ret_str[len-1] = '\0';
}
faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, freeze);
}
break;
case oNoAutostart: opt.autostart = 0; break;
case oDefaultNewKeyAlgo:
opt.def_new_key_algo = pargs.r.ret_str;
break;
case oNoop: break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Remember the first config file name. */
if (!save_configname)
save_configname = configname;
else
xfree(configname);
configname = NULL;
goto next_pass;
}
xfree(configname); configname = NULL;
if (log_get_errorcount (0))
g10_exit(2);
/* The command --gpgconf-list is pretty simple and may be called
directly after the option parsing. */
if (cmd == aGPGConfList)
{
gpgconf_list (save_configname ? save_configname : default_configname);
g10_exit (0);
}
xfree (save_configname);
xfree (default_configname);
if (print_dane_records)
log_error ("invalid option \"%s\"; use \"%s\" instead\n",
"--print-dane-records",
"--export-options export-dane");
if (print_pka_records)
log_error ("invalid option \"%s\"; use \"%s\" instead\n",
"--print-pks-records",
"--export-options export-pka");
if (log_get_errorcount (0))
g10_exit(2);
if( nogreeting )
greeting = 0;
if( greeting )
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
if (!opt.batch)
{
const char *s;
if((s=strusage(25)))
log_info("%s\n",s);
if((s=strusage(26)))
log_info("%s\n",s);
if((s=strusage(27)))
log_info("%s\n",s);
}
#endif
/* FIXME: We should use logging to a file only in server mode;
however we have not yet implemetyed that. Thus we try to get
away with --batch as indication for logging to file
required. */
if (logfile && opt.batch)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (opt.verbose > 2)
log_info ("using character set '%s'\n", get_native_charset ());
if( may_coredump && !opt.quiet )
log_info(_("WARNING: program may create a core file!\n"));
if (opt.flags.rfc4880bis)
log_info ("WARNING: using experimental features from RFC4880bis!\n");
else
{
opt.mimemode = 0; /* This will use text mode instead. */
}
if (eyes_only) {
if (opt.set_filename)
log_info(_("WARNING: %s overrides %s\n"),
"--for-your-eyes-only","--set-filename");
opt.set_filename="_CONSOLE";
}
if (opt.no_literal) {
log_info(_("Note: %s is not for normal use!\n"), "--no-literal");
if (opt.textmode)
log_error(_("%s not allowed with %s!\n"),
"--textmode", "--no-literal" );
if (opt.set_filename)
log_error(_("%s makes no sense with %s!\n"),
eyes_only?"--for-your-eyes-only":"--set-filename",
"--no-literal" );
}
if (opt.set_filesize)
log_info(_("Note: %s is not for normal use!\n"), "--set-filesize");
if( opt.batch )
tty_batchmode( 1 );
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
if(require_secmem && !got_secmem)
{
log_info(_("will not run with insecure memory due to %s\n"),
"--require-secmem");
g10_exit(2);
}
set_debug (debug_level);
if (DBG_CLOCK)
log_clock ("start");
/* Do these after the switch(), so they can override settings. */
if(PGP6)
{
/* That does not anymore work because we have no more support
for v3 signatures. */
opt.disable_mdc=1;
opt.escape_from=1;
opt.ask_sig_expire=0;
}
else if(PGP7)
{
/* That does not anymore work because we have no more support
for v3 signatures. */
opt.escape_from=1;
opt.ask_sig_expire=0;
}
else if(PGP8)
{
opt.escape_from=1;
}
if( def_cipher_string ) {
opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string);
xfree(def_cipher_string); def_cipher_string = NULL;
if ( openpgp_cipher_test_algo (opt.def_cipher_algo) )
log_error(_("selected cipher algorithm is invalid\n"));
}
if( def_digest_string ) {
opt.def_digest_algo = string_to_digest_algo (def_digest_string);
xfree(def_digest_string); def_digest_string = NULL;
if ( openpgp_md_test_algo (opt.def_digest_algo) )
log_error(_("selected digest algorithm is invalid\n"));
}
if( compress_algo_string ) {
opt.compress_algo = string_to_compress_algo(compress_algo_string);
xfree(compress_algo_string); compress_algo_string = NULL;
if( check_compress_algo(opt.compress_algo) )
log_error(_("selected compression algorithm is invalid\n"));
}
if( cert_digest_string ) {
opt.cert_digest_algo = string_to_digest_algo (cert_digest_string);
xfree(cert_digest_string); cert_digest_string = NULL;
if (openpgp_md_test_algo(opt.cert_digest_algo))
log_error(_("selected certification digest algorithm is invalid\n"));
}
if( s2k_cipher_string ) {
opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string);
xfree(s2k_cipher_string); s2k_cipher_string = NULL;
if (openpgp_cipher_test_algo (opt.s2k_cipher_algo))
log_error(_("selected cipher algorithm is invalid\n"));
}
if( s2k_digest_string ) {
opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string);
xfree(s2k_digest_string); s2k_digest_string = NULL;
if (openpgp_md_test_algo(opt.s2k_digest_algo))
log_error(_("selected digest algorithm is invalid\n"));
}
if( opt.completes_needed < 1 )
log_error(_("completes-needed must be greater than 0\n"));
if( opt.marginals_needed < 2 )
log_error(_("marginals-needed must be greater than 1\n"));
if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 )
log_error(_("max-cert-depth must be in the range from 1 to 255\n"));
if(opt.def_cert_level<0 || opt.def_cert_level>3)
log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n"));
if( opt.min_cert_level < 1 || opt.min_cert_level > 3 )
log_error(_("invalid min-cert-level; must be 1, 2, or 3\n"));
switch( opt.s2k_mode ) {
case 0:
log_info(_("Note: simple S2K mode (0) is strongly discouraged\n"));
break;
case 1: case 3: break;
default:
log_error(_("invalid S2K mode; must be 0, 1 or 3\n"));
}
/* This isn't actually needed, but does serve to error out if the
string is invalid. */
if(opt.def_preference_list &&
keygen_set_std_prefs(opt.def_preference_list,0))
log_error(_("invalid default preferences\n"));
if(pers_cipher_list &&
keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM))
log_error(_("invalid personal cipher preferences\n"));
if(pers_digest_list &&
keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH))
log_error(_("invalid personal digest preferences\n"));
if(pers_compress_list &&
keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP))
log_error(_("invalid personal compress preferences\n"));
/* We don't support all possible commands with multifile yet */
if(multifile)
{
char *cmdname;
switch(cmd)
{
case aSign:
cmdname="--sign";
break;
case aSignEncr:
cmdname="--sign --encrypt";
break;
case aClearsign:
cmdname="--clear-sign";
break;
case aDetachedSign:
cmdname="--detach-sign";
break;
case aSym:
cmdname="--symmetric";
break;
case aEncrSym:
cmdname="--symmetric --encrypt";
break;
case aStore:
cmdname="--store";
break;
default:
cmdname=NULL;
break;
}
if(cmdname)
log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile");
}
if( log_get_errorcount(0) )
g10_exit(2);
if(opt.compress_level==0)
opt.compress_algo=COMPRESS_ALGO_NONE;
/* Check our chosen algorithms against the list of legal
algorithms. */
if(!GNUPG)
{
const char *badalg=NULL;
preftype_t badtype=PREFTYPE_NONE;
if(opt.def_cipher_algo
&& !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL))
{
badalg = openpgp_cipher_algo_name (opt.def_cipher_algo);
badtype = PREFTYPE_SYM;
}
else if(opt.def_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.def_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.cert_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.cert_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.compress_algo!=-1
&& !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL))
{
badalg = compress_algo_to_string(opt.compress_algo);
badtype = PREFTYPE_ZIP;
}
if(badalg)
{
switch(badtype)
{
case PREFTYPE_SYM:
log_info(_("you may not use cipher algorithm '%s'"
" while in %s mode\n"),
badalg,compliance_option_string());
break;
case PREFTYPE_HASH:
log_info(_("you may not use digest algorithm '%s'"
" while in %s mode\n"),
badalg,compliance_option_string());
break;
case PREFTYPE_ZIP:
log_info(_("you may not use compression algorithm '%s'"
" while in %s mode\n"),
badalg,compliance_option_string());
break;
default:
BUG();
}
compliance_failure();
}
}
/* Set the random seed file. */
if( use_random_seed ) {
char *p = make_filename (gnupg_homedir (), "random_seed", NULL );
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
if (!access (p, F_OK))
register_secured_file (p);
xfree(p);
}
/* If there is no command but the --fingerprint is given, default
to the --list-keys command. */
if (!cmd && fpr_maybe_cmd)
{
set_cmd (&cmd, aListKeys);
}
if( opt.verbose > 1 )
set_packet_list_mode(1);
/* Add the keyrings, but not for some special commands. We always
* need to add the keyrings if we are running under SELinux, this
* is so that the rings are added to the list of secured files.
* We do not add any keyring if --no-keyring has been used. */
if (default_keyring >= 0
&& (ALWAYS_ADD_KEYRINGS
|| (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest)))
{
if (!nrings || default_keyring > 0) /* Add default ring. */
keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG,
KEYDB_RESOURCE_FLAG_DEFAULT);
for (sl = nrings; sl; sl = sl->next )
keydb_add_resource (sl->d, sl->flags);
}
FREE_STRLIST(nrings);
if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
/* In loopback mode, never ask for the password multiple
times. */
{
opt.passphrase_repeat = 0;
}
if (cmd == aGPGConfTest)
g10_exit(0);
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */
read_sessionkey_from_fd (ovrseskeyfd);
fname = argc? *argv : NULL;
if(fname && utf8_strings)
opt.flags.utf8_filename=1;
ctrl = xcalloc (1, sizeof *ctrl);
gpg_init_default_ctrl (ctrl);
#ifndef NO_TRUST_MODELS
switch (cmd)
{
case aPrimegen:
case aPrintMD:
case aPrintMDs:
case aGenRandom:
case aDeArmor:
case aEnArmor:
case aListConfig:
case aListGcryptConfig:
break;
case aFixTrustDB:
case aExportOwnerTrust:
rc = setup_trustdb (0, trustdb_name);
break;
case aListTrustDB:
rc = setup_trustdb (argc? 1:0, trustdb_name);
break;
case aKeygen:
case aFullKeygen:
case aQuickKeygen:
rc = setup_trustdb (1, trustdb_name);
break;
default:
/* If we are using TM_ALWAYS, we do not need to create the
trustdb. */
rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name);
break;
}
if (rc)
log_error (_("failed to initialize the TrustDB: %s\n"),
gpg_strerror (rc));
#endif /*!NO_TRUST_MODELS*/
switch (cmd)
{
case aStore:
case aSym:
case aSign:
case aSignSym:
case aClearsign:
if (!opt.quiet && any_explicit_recipient)
log_info (_("WARNING: recipients (-r) given "
"without using public key encryption\n"));
break;
default:
break;
}
/* Check for certain command whether we need to migrate a
secring.gpg to the gpg-agent. */
switch (cmd)
{
case aListSecretKeys:
case aSign:
case aSignEncr:
case aSignEncrSym:
case aSignSym:
case aClearsign:
case aDecrypt:
case aSignKey:
case aLSignKey:
case aEditKey:
case aPasswd:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickRevUid:
case aFullKeygen:
case aKeygen:
case aImport:
case aExportSecret:
case aExportSecretSub:
case aGenRevoke:
case aDesigRevoke:
case aCardEdit:
case aChangePIN:
migrate_secring (ctrl);
break;
case aListKeys:
if (opt.with_secret)
migrate_secring (ctrl);
break;
default:
break;
}
/* The command dispatcher. */
switch( cmd )
{
case aServer:
gpg_server (ctrl);
break;
case aStore: /* only store the file */
if( argc > 1 )
wrong_args("--store [filename]");
if( (rc = encrypt_store(fname)) )
{
write_status_failure ("store", rc);
log_error ("storing '%s' failed: %s\n",
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aSym: /* encrypt the given file only with the symmetric cipher */
if( argc > 1 )
wrong_args("--symmetric [filename]");
if( (rc = encrypt_symmetric(fname)) )
{
write_status_failure ("symencrypt", rc);
log_error (_("symmetric encryption of '%s' failed: %s\n"),
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aEncr: /* encrypt the given file */
if(multifile)
encrypt_crypt_files (ctrl, argc, argv, remusr);
else
{
if( argc > 1 )
wrong_args("--encrypt [filename]");
if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) )
{
write_status_failure ("encrypt", rc);
log_error("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aEncrSym:
/* This works with PGP 8 in the sense that it acts just like a
symmetric message. It doesn't work at all with 2 or 6. It
might work with 7, but alas, I don't have a copy to test
with right now. */
if( argc > 1 )
wrong_args("--symmetric --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --encrypt"
" with --s2k-mode 0\n"));
else if(PGP6 || PGP7)
log_error(_("you cannot use --symmetric --encrypt"
" while in %s mode\n"),compliance_option_string());
else
{
if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) )
{
write_status_failure ("encrypt", rc);
log_error ("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aSign: /* sign the given file */
sl = NULL;
if( detached_sig ) { /* sign all files */
for( ; argc; argc--, argv++ )
add_to_strlist( &sl, *argv );
}
else {
if( argc > 1 )
wrong_args("--sign [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
}
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL)))
{
write_status_failure ("sign", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncr: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--sign --encrypt [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncrSym: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--symmetric --sign --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --sign --encrypt"
" with --s2k-mode 0\n"));
else if(PGP6 || PGP7)
log_error(_("you cannot use --symmetric --sign --encrypt"
" while in %s mode\n"),compliance_option_string());
else
{
if( argc )
{
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr,
2, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: symmetric+sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
}
break;
case aSignSym: /* sign and conventionally encrypt the given file */
if (argc > 1)
wrong_args("--sign --symmetric [filename]");
rc = sign_symencrypt_file (ctrl, fname, locusr);
if (rc)
{
write_status_failure ("sign-symencrypt", rc);
log_error("%s: sign+symmetric failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aClearsign: /* make a clearsig */
if( argc > 1 )
wrong_args("--clear-sign [filename]");
if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) )
{
write_status_failure ("sign", rc);
log_error("%s: clear-sign failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aVerify:
if (multifile)
{
if ((rc = verify_files (ctrl, argc, argv)))
log_error("verify files failed: %s\n", gpg_strerror (rc) );
}
else
{
if ((rc = verify_signatures (ctrl, argc, argv)))
log_error("verify signatures failed: %s\n", gpg_strerror (rc) );
}
if (rc)
write_status_failure ("verify", rc);
break;
case aDecrypt:
if (multifile)
decrypt_messages (ctrl, argc, argv);
else
{
if( argc > 1 )
wrong_args("--decrypt [filename]");
if( (rc = decrypt_message (ctrl, fname) ))
{
write_status_failure ("decrypt", rc);
log_error("decrypt_message failed: %s\n", gpg_strerror (rc) );
}
}
break;
case aQuickSignKey:
case aQuickLSignKey:
{
const char *fpr;
if (argc < 1)
wrong_args ("--quick-[l]sign-key fingerprint [userids]");
fpr = *argv++; argc--;
sl = NULL;
for( ; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey));
free_strlist (sl);
}
break;
case aSignKey:
if( argc != 1 )
wrong_args("--sign-key user-id");
/* fall through */
case aLSignKey:
if( argc != 1 )
wrong_args("--lsign-key user-id");
/* fall through */
sl=NULL;
if(cmd==aSignKey)
append_to_strlist(&sl,"sign");
else if(cmd==aLSignKey)
append_to_strlist(&sl,"lsign");
else
BUG();
append_to_strlist( &sl, "save" );
username = make_username( fname );
keyedit_menu (ctrl, username, locusr, sl, 0, 0 );
xfree(username);
free_strlist(sl);
break;
case aEditKey: /* Edit a key signature */
if( !argc )
wrong_args("--edit-key user-id [commands]");
username = make_username( fname );
if( argc > 1 ) {
sl = NULL;
for( argc--, argv++ ; argc; argc--, argv++ )
append_to_strlist( &sl, *argv );
keyedit_menu (ctrl, username, locusr, sl, 0, 1 );
free_strlist(sl);
}
else
keyedit_menu (ctrl, username, locusr, NULL, 0, 1 );
xfree(username);
break;
case aPasswd:
if (argc != 1)
wrong_args("--change-passphrase <user-id>");
else
{
username = make_username (fname);
keyedit_passwd (ctrl, username);
xfree (username);
}
break;
case aDeleteKeys:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
sl = NULL;
/* I'm adding these in reverse order as add_to_strlist2
reverses them again, and it's easier to understand in the
proper order :) */
for( ; argc; argc-- )
add_to_strlist2( &sl, argv[argc-1], utf8_strings );
delete_keys(sl,cmd==aDeleteSecretKeys,cmd==aDeleteSecretAndPublicKeys);
free_strlist(sl);
break;
case aCheckKeys:
opt.check_sigs = 1;
case aListSigs:
opt.list_sigs = 1;
case aListKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
public_key_list (ctrl, sl, 0);
free_strlist(sl);
break;
case aListSecretKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
secret_key_list (ctrl, sl);
free_strlist(sl);
break;
case aLocateKeys:
sl = NULL;
for (; argc; argc--, argv++)
add_to_strlist2( &sl, *argv, utf8_strings );
public_key_list (ctrl, sl, 1);
free_strlist (sl);
break;
case aQuickKeygen:
{
const char *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]");
username = make_username (fname);
argv++, argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire);
xfree (username);
}
break;
case aKeygen: /* generate a key */
if( opt.batch ) {
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else {
if (opt.command_fd != -1 && argc)
{
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
opt.batch = 1;
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else if (argc)
wrong_args ("--generate-key");
else
generate_keypair (ctrl, 0, NULL, NULL, 0);
}
break;
case aFullKeygen: /* Generate a key with all options. */
if (opt.batch)
{
if (argc > 1)
wrong_args ("--full-generate-key [parameterfile]");
generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0);
}
else
{
if (argc)
wrong_args("--full-generate-key");
generate_keypair (ctrl, 1, NULL, NULL, 0);
}
break;
case aQuickAddUid:
{
const char *uid, *newuid;
if (argc != 2)
wrong_args ("--quick-add-uid USER-ID NEW-USER-ID");
uid = *argv++; argc--;
newuid = *argv++; argc--;
keyedit_quick_adduid (ctrl, uid, newuid);
}
break;
case aQuickAddKey:
{
const char *x_fpr, *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]");
x_fpr = *argv++; argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire);
}
break;
case aQuickRevUid:
{
const char *uid, *uidtorev;
if (argc != 2)
wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE");
uid = *argv++; argc--;
uidtorev = *argv++; argc--;
keyedit_quick_revuid (ctrl, uid, uidtorev);
}
break;
case aQuickSetExpire:
{
const char *x_fpr, *x_expire;
if (argc != 2)
wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE");
x_fpr = *argv++; argc--;
x_expire = *argv++; argc--;
keyedit_quick_set_expire (ctrl, x_fpr, x_expire);
}
break;
case aFastImport:
opt.import_options |= IMPORT_FAST;
case aImport:
import_keys (ctrl, argc? argv:NULL, argc, NULL, opt.import_options);
break;
/* TODO: There are a number of command that use this same
"make strlist, call function, report error, free strlist"
pattern. Join them together here and avoid all that
duplicated code. */
case aExport:
case aSendKeys:
case aRecvKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
if( cmd == aSendKeys )
rc = keyserver_export (ctrl, sl );
else if( cmd == aRecvKeys )
rc = keyserver_import (ctrl, sl );
else
{
export_stats_t stats = export_new_stats ();
rc = export_pubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
if(rc)
{
if(cmd==aSendKeys)
{
write_status_failure ("send-keys", rc);
log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc));
}
else if(cmd==aRecvKeys)
{
write_status_failure ("recv-keys", rc);
log_error (_("keyserver receive failed: %s\n"),
gpg_strerror (rc));
}
else
{
write_status_failure ("export", rc);
log_error (_("key export failed: %s\n"), gpg_strerror (rc));
}
}
free_strlist(sl);
break;
case aExportSshKey:
if (argc != 1)
wrong_args ("--export-ssh-key <user-id>");
rc = export_ssh_key (ctrl, argv[0]);
if (rc)
{
write_status_failure ("export-ssh-key", rc);
log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc));
}
break;
case aSearchKeys:
sl = NULL;
for (; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
rc = keyserver_search (ctrl, sl);
if (rc)
{
write_status_failure ("search-keys", rc);
log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc));
}
free_strlist (sl);
break;
case aRefreshKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_refresh (ctrl, sl);
if(rc)
{
write_status_failure ("refresh-keys", rc);
log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc));
}
free_strlist(sl);
break;
case aFetchKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_fetch (ctrl, sl);
if(rc)
{
write_status_failure ("fetch-keys", rc);
log_error ("key fetch failed: %s\n",gpg_strerror (rc));
}
free_strlist(sl);
break;
case aExportSecret:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_seckeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aExportSecretSub:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_secsubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aGenRevoke:
if( argc != 1 )
wrong_args("--generate-revocation user-id");
username = make_username(*argv);
gen_revoke( username );
xfree( username );
break;
case aDesigRevoke:
if (argc != 1)
wrong_args ("--generate-designated-revocation user-id");
username = make_username (*argv);
gen_desig_revoke (ctrl, username, locusr);
xfree (username);
break;
case aDeArmor:
if( argc > 1 )
wrong_args("--dearmor [file]");
rc = dearmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("dearmor", rc);
log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aEnArmor:
if( argc > 1 )
wrong_args("--enarmor [file]");
rc = enarmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("enarmor", rc);
log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aPrimegen:
#if 0 /*FIXME*/
{ int mode = argc < 2 ? 0 : atoi(*argv);
if( mode == 1 && argc == 2 ) {
mpi_print (es_stdout,
generate_public_prime( atoi(argv[1]) ), 1);
}
else if( mode == 2 && argc == 3 ) {
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), NULL,NULL ), 1);
}
else if( mode == 3 && argc == 3 ) {
MPI *factors;
mpi_print (es_stdout, generate_elg_prime(
1, atoi(argv[1]),
atoi(argv[2]), NULL,&factors ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, factors[0], 1 ); /* print q */
}
else if( mode == 4 && argc == 3 ) {
MPI g = mpi_alloc(1);
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), g, NULL ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, g, 1 );
mpi_free (g);
}
else
wrong_args("--gen-prime mode bits [qbits] ");
es_putc ('\n', es_stdout);
}
#endif
wrong_args("--gen-prime not yet supported ");
break;
case aGenRandom:
{
int level = argc ? atoi(*argv):0;
int count = argc > 1 ? atoi(argv[1]): 0;
int endless = !count;
if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 )
wrong_args("--gen-random 0|1|2 [count]");
while( endless || count ) {
byte *p;
/* Wee need a multiple of 3, so that in case of
armored output we get a correct string. No
linefolding is done, as it is best to levae this to
other tools */
size_t n = !endless && count < 99? count : 99;
p = gcry_random_bytes (n, level);
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(stdout), O_BINARY );
#endif
if (opt.armor) {
char *tmp = make_radix64_string (p, n);
es_fputs (tmp, es_stdout);
xfree (tmp);
if (n%3 == 1)
es_putc ('=', es_stdout);
if (n%3)
es_putc ('=', es_stdout);
} else {
es_fwrite( p, n, 1, es_stdout );
}
xfree(p);
if( !endless )
count -= n;
}
if (opt.armor)
es_putc ('\n', es_stdout);
}
break;
case aPrintMD:
if( argc < 1)
wrong_args("--print-md algo [files]");
{
int all_algos = (**argv=='*' && !(*argv)[1]);
int algo = all_algos? 0 : gcry_md_map_name (*argv);
if( !algo && !all_algos )
log_error(_("invalid hash algorithm '%s'\n"), *argv );
else {
argc--; argv++;
if( !argc )
print_mds(NULL, algo);
else {
for(; argc; argc--, argv++ )
print_mds(*argv, algo);
}
}
}
break;
case aPrintMDs: /* old option */
if( !argc )
print_mds(NULL,0);
else {
for(; argc; argc--, argv++ )
print_mds(*argv,0);
}
break;
#ifndef NO_TRUST_MODELS
case aListTrustDB:
if( !argc )
list_trustdb (es_stdout, NULL);
else {
for( ; argc; argc--, argv++ )
list_trustdb (es_stdout, *argv );
}
break;
case aUpdateTrustDB:
if( argc )
wrong_args("--update-trustdb");
update_trustdb (ctrl);
break;
case aCheckTrustDB:
/* Old versions allowed for arguments - ignore them */
check_trustdb (ctrl);
break;
case aFixTrustDB:
how_to_fix_the_trustdb ();
break;
case aListTrustPath:
if( !argc )
wrong_args("--list-trust-path <user-ids>");
for( ; argc; argc--, argv++ ) {
username = make_username( *argv );
list_trust_path( username );
xfree(username);
}
break;
case aExportOwnerTrust:
if( argc )
wrong_args("--export-ownertrust");
export_ownertrust();
break;
case aImportOwnerTrust:
if( argc > 1 )
wrong_args("--import-ownertrust [file]");
import_ownertrust( argc? *argv:NULL );
break;
#endif /*!NO_TRUST_MODELS*/
case aRebuildKeydbCaches:
if (argc)
wrong_args ("--rebuild-keydb-caches");
keydb_rebuild_caches (1);
break;
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
if (argc)
wrong_args ("--card-status");
card_status (es_stdout, NULL, 0);
break;
case aCardEdit:
if (argc) {
sl = NULL;
for (argc--, argv++ ; argc; argc--, argv++)
append_to_strlist (&sl, *argv);
card_edit (ctrl, sl);
free_strlist (sl);
}
else
card_edit (ctrl, NULL);
break;
case aChangePIN:
if (!argc)
change_pin (0,1);
else if (argc == 1)
change_pin (atoi (*argv),1);
else
wrong_args ("--change-pin [no]");
break;
#endif /* ENABLE_CARD_SUPPORT*/
case aListConfig:
{
char *str=collapse_args(argc,argv);
list_config(str);
xfree(str);
}
break;
case aListGcryptConfig:
/* Fixme: It would be nice to integrate that with
--list-config but unfortunately there is no way yet to have
libgcrypt print it to an estream for further parsing. */
gcry_control (GCRYCTL_PRINT_CONFIG, stdout);
break;
case aTOFUPolicy:
#ifdef USE_TOFU
{
int policy;
int i;
KEYDB_HANDLE hd;
if (argc < 2)
wrong_args ("--tofu-policy POLICY KEYID [KEYID...]");
policy = parse_tofu_policy (argv[0]);
hd = keydb_new ();
if (! hd)
g10_exit (1);
tofu_begin_batch_update (ctrl);
for (i = 1; i < argc; i ++)
{
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
rc = classify_user_id (argv[i], &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
argv[i], gpg_strerror (rc));
g10_exit (1);
}
if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID
|| desc.mode == KEYDB_SEARCH_MODE_LONG_KID
|| desc.mode == KEYDB_SEARCH_MODE_FPR16
|| desc.mode == KEYDB_SEARCH_MODE_FPR20
|| desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_KEYGRIP))
{
log_error (_("'%s' does not appear to be a valid"
" key ID, fingerprint or keygrip\n"),
argv[i]);
g10_exit (1);
}
rc = keydb_search_reset (hd);
if (rc)
{
/* This should not happen, thus no need to tranalate
the string. */
log_error ("keydb_search_reset failed: %s\n",
gpg_strerror (rc));
g10_exit (1);
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), argv[i],
gpg_strerror (rc));
g10_exit (1);
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
g10_exit (1);
}
merge_keys_and_selfsig (kb);
if (tofu_set_policy (ctrl, kb, policy))
g10_exit (1);
release_kbnode (kb);
}
tofu_end_batch_update (ctrl);
keydb_release (hd);
}
#endif /*USE_TOFU*/
break;
default:
if (!opt.quiet)
log_info (_("WARNING: no command supplied."
" Trying to guess what you mean ...\n"));
/*FALLTHU*/
case aListPackets:
if( argc > 1 )
wrong_args("[filename]");
/* Issue some output for the unix newbie */
if (!fname && !opt.outfile
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)))
log_info(_("Go ahead and type your message ...\n"));
a = iobuf_open(fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if( !a )
log_error(_("can't open '%s'\n"), print_fname_stdin(fname));
else {
if( !opt.no_armor ) {
if( use_armor_filter( a ) ) {
afx = new_armor_context ();
push_armor_filter (afx, a);
}
}
if( cmd == aListPackets ) {
opt.list_packets=1;
set_packet_list_mode(1);
}
rc = proc_packets (ctrl, NULL, a );
if( rc )
{
write_status_failure ("-", rc);
log_error ("processing message failed: %s\n",
gpg_strerror (rc));
}
iobuf_close(a);
}
break;
}
/* cleanup */
gpg_deinit_default_ctrl (ctrl);
xfree (ctrl);
release_armor_context (afx);
FREE_STRLIST(remusr);
FREE_STRLIST(locusr);
g10_exit(0);
return 8; /*NEVER REACHED*/
}
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
g10_exit( int rc )
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (DBG_CLOCK)
log_clock ("stop");
if ( (opt.debug & DBG_MEMSTAT_VALUE) )
{
keydb_dump_stats ();
gcry_control (GCRYCTL_DUMP_MEMORY_STATS);
gcry_control (GCRYCTL_DUMP_RANDOM_STATS);
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0;
exit (rc);
}
/* Pretty-print hex hashes. This assumes at least an 80-character
display, but there are a few other similar assumptions in the
display code. */
static void
print_hex (gcry_md_hd_t md, int algo, const char *fname)
{
int i,n,count,indent=0;
const byte *p;
if (fname)
indent = es_printf("%s: ",fname);
if (indent>40)
{
es_printf ("\n");
indent=0;
}
if (algo==DIGEST_ALGO_RMD160)
indent += es_printf("RMD160 = ");
else if (algo>0)
indent += es_printf("%6s = ", gcry_md_algo_name (algo));
else
algo = abs(algo);
count = indent;
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
count += es_printf ("%02X",*p++);
for(i=1;i<n;i++,p++)
{
if(n==16)
{
if(count+2>79)
{
es_printf ("\n%*s",indent," ");
count = indent;
}
else
count += es_printf(" ");
if (!(i%8))
count += es_printf(" ");
}
else if (n==20)
{
if(!(i%2))
{
if(count+4>79)
{
es_printf ("\n%*s",indent," ");
count=indent;
}
else
count += es_printf(" ");
}
if (!(i%10))
count += es_printf(" ");
}
else
{
if(!(i%4))
{
if (count+8>79)
{
es_printf ("\n%*s",indent," ");
count=indent;
}
else
count += es_printf(" ");
}
}
count += es_printf("%02X",*p);
}
es_printf ("\n");
}
static void
print_hashline( gcry_md_hd_t md, int algo, const char *fname )
{
int i, n;
const byte *p;
if ( fname )
{
for (p = fname; *p; p++ )
{
if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' )
es_printf ("%%%02X", *p );
else
es_putc (*p, es_stdout);
}
}
es_putc (':', es_stdout);
es_printf ("%d:", algo);
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
for(i=0; i < n ; i++, p++ )
es_printf ("%02X", *p);
es_fputs (":\n", es_stdout);
}
static void
print_mds( const char *fname, int algo )
{
estream_t fp;
char buf[1024];
size_t n;
gcry_md_hd_t md;
if (!fname)
{
fp = es_stdin;
es_set_binary (fp);
}
else
{
fp = es_fopen (fname, "rb" );
if (fp && is_secured_file (es_fileno (fp)))
{
es_fclose (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
}
if (!fp)
{
log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) );
return;
}
gcry_md_open (&md, 0, 0);
if (algo)
gcry_md_enable (md, algo);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
gcry_md_enable (md, GCRY_MD_MD5);
gcry_md_enable (md, GCRY_MD_SHA1);
if (!gcry_md_test_algo (GCRY_MD_RMD160))
gcry_md_enable (md, GCRY_MD_RMD160);
if (!gcry_md_test_algo (GCRY_MD_SHA224))
gcry_md_enable (md, GCRY_MD_SHA224);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
gcry_md_enable (md, GCRY_MD_SHA256);
if (!gcry_md_test_algo (GCRY_MD_SHA384))
gcry_md_enable (md, GCRY_MD_SHA384);
if (!gcry_md_test_algo (GCRY_MD_SHA512))
gcry_md_enable (md, GCRY_MD_SHA512);
}
while ((n=es_fread (buf, 1, DIM(buf), fp)))
gcry_md_write (md, buf, n);
if (es_ferror(fp))
log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno));
else
{
gcry_md_final (md);
if (opt.with_colons)
{
if ( algo )
print_hashline (md, algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hashline( md, GCRY_MD_MD5, fname );
print_hashline( md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hashline( md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hashline (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hashline( md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hashline ( md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hashline ( md, GCRY_MD_SHA512, fname );
}
}
else
{
if (algo)
print_hex (md, -algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hex (md, GCRY_MD_MD5, fname);
print_hex (md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hex (md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hex (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hex (md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hex (md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hex (md, GCRY_MD_SHA512, fname );
}
}
}
gcry_md_close (md);
if (fp != es_stdin)
es_fclose (fp);
}
/****************
* Check the supplied name,value string and add it to the notation
* data to be used for signatures. which==0 for sig notations, and 1
* for cert notations.
*/
static void
add_notation_data( const char *string, int which )
{
struct notation *notation;
notation=string_to_notation(string,utf8_strings);
if(notation)
{
if(which)
{
notation->next=opt.cert_notations;
opt.cert_notations=notation;
}
else
{
notation->next=opt.sig_notations;
opt.sig_notations=notation;
}
}
}
static void
add_policy_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
log_error(_("the given certification policy URL is invalid\n"));
else
log_error(_("the given signature policy URL is invalid\n"));
}
if(which)
sl=add_to_strlist( &opt.cert_policy_url, string );
else
sl=add_to_strlist( &opt.sig_policy_url, string );
if(critical)
sl->flags |= 1;
}
static void
add_keyserver_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
BUG();
else
log_error(_("the given preferred keyserver URL is invalid\n"));
}
if(which)
BUG();
else
sl=add_to_strlist( &opt.sig_keyserver_url, string );
if(critical)
sl->flags |= 1;
}
static void
read_sessionkey_from_fd (int fd)
{
int i, len;
char *line;
if (! gnupg_fd_valid (fd))
log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno));
for (line = NULL, i = len = 100; ; i++ )
{
if (i >= len-1 )
{
char *tmp = line;
len += 100;
line = xmalloc_secure (len);
if (tmp)
{
memcpy (line, tmp, i);
xfree (tmp);
}
else
i=0;
}
if (read (fd, line + i, 1) != 1 || line[i] == '\n')
break;
}
line[i] = 0;
log_debug ("seskey: %s\n", line);
gpgrt_annotate_leaked_object (line);
opt.override_session_key = line;
}
diff --git a/g10/gpgsql.c b/g10/gpgsql.c
index 661bd3543..5b7556912 100644
--- a/g10/gpgsql.c
+++ b/g10/gpgsql.c
@@ -1,251 +1,251 @@
/* gpgsql.c - SQLite helper functions.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
-#include "logging.h"
+#include "../common/util.h"
+#include "../common/logging.h"
#include "gpgsql.h"
/* This is a convenience function that combines sqlite3_mprintf and
sqlite3_exec. */
int
gpgsql_exec_printf (sqlite3 *db,
int (*callback)(void*,int,char**,char**), void *cookie,
char **errmsg,
const char *sql, ...)
{
va_list ap;
int rc;
char *sql2;
va_start (ap, sql);
sql2 = sqlite3_vmprintf (sql, ap);
va_end (ap);
#if 0
log_debug ("tofo db: executing: '%s'\n", sql2);
#endif
rc = sqlite3_exec (db, sql2, callback, cookie, errmsg);
sqlite3_free (sql2);
return rc;
}
int
gpgsql_stepx (sqlite3 *db,
sqlite3_stmt **stmtp,
gpgsql_stepx_callback callback,
void *cookie,
char **errmsg,
const char *sql, ...)
{
int rc;
int err = 0;
sqlite3_stmt *stmt = NULL;
va_list va;
int args;
enum gpgsql_arg_type t;
int i;
int cols;
/* Names of the columns. We initialize this lazily to avoid the
overhead in case the query doesn't return any results. */
const char **azColName = 0;
int callback_initialized = 0;
const char **azVals = 0;
callback_initialized = 0;
if (stmtp && *stmtp)
{
stmt = *stmtp;
/* Make sure this statement is associated with the supplied db. */
log_assert (db == sqlite3_db_handle (stmt));
#if DEBUG_TOFU_CACHE
prepares_saved ++;
#endif
}
else
{
const char *tail = NULL;
rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, &tail);
if (rc)
log_fatal ("failed to prepare SQL: %s", sql);
/* We can only process a single statement. */
if (tail)
{
while (*tail == ' ' || *tail == ';' || *tail == '\n')
tail ++;
if (*tail)
log_fatal
("sqlite3_stepx can only process a single SQL statement."
" Second statement starts with: '%s'\n",
tail);
}
if (stmtp)
*stmtp = stmt;
}
#if DEBUG_TOFU_CACHE
queries ++;
#endif
args = sqlite3_bind_parameter_count (stmt);
va_start (va, sql);
if (args)
{
for (i = 1; i <= args; i ++)
{
t = va_arg (va, enum gpgsql_arg_type);
switch (t)
{
case GPGSQL_ARG_INT:
{
int value = va_arg (va, int);
err = sqlite3_bind_int (stmt, i, value);
break;
}
case GPGSQL_ARG_LONG_LONG:
{
long long value = va_arg (va, long long);
err = sqlite3_bind_int64 (stmt, i, value);
break;
}
case GPGSQL_ARG_STRING:
{
char *text = va_arg (va, char *);
err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC);
break;
}
case GPGSQL_ARG_BLOB:
{
char *blob = va_arg (va, void *);
long long length = va_arg (va, long long);
err = sqlite3_bind_blob (stmt, i, blob, length, SQLITE_STATIC);
break;
}
default:
/* Internal error. Likely corruption. */
log_fatal ("Bad value for parameter type %d.\n", t);
}
if (err)
{
log_fatal ("Error binding parameter %d\n", i);
goto out;
}
}
}
t = va_arg (va, enum gpgsql_arg_type);
log_assert (t == GPGSQL_ARG_END);
va_end (va);
for (;;)
{
rc = sqlite3_step (stmt);
if (rc != SQLITE_ROW)
/* No more data (SQLITE_DONE) or an error occurred. */
break;
if (! callback)
continue;
if (! callback_initialized)
{
cols = sqlite3_column_count (stmt);
azColName = xmalloc (2 * cols * sizeof (const char *) + 1);
for (i = 0; i < cols; i ++)
azColName[i] = sqlite3_column_name (stmt, i);
callback_initialized = 1;
}
azVals = &azColName[cols];
for (i = 0; i < cols; i ++)
{
azVals[i] = sqlite3_column_text (stmt, i);
if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL)
/* Out of memory. */
{
err = SQLITE_NOMEM;
break;
}
}
if (callback (cookie, cols, (char **) azVals, (char **) azColName, stmt))
/* A non-zero result means to abort. */
{
err = SQLITE_ABORT;
break;
}
}
out:
xfree (azColName);
if (stmtp)
rc = sqlite3_reset (stmt);
else
rc = sqlite3_finalize (stmt);
if (rc == SQLITE_OK && err)
/* Local error. */
{
rc = err;
if (errmsg)
{
const char *e = sqlite3_errstr (err);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
}
else if (rc != SQLITE_OK && errmsg)
/* Error reported by sqlite. */
{
const char * e = sqlite3_errmsg (db);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
return rc;
}
diff --git a/g10/gpgv.c b/g10/gpgv.c
index a6d1addd0..5ee26b46c 100644
--- a/g10/gpgv.c
+++ b/g10/gpgv.c
@@ -1,767 +1,767 @@
/* gpgv.c - The GnuPG signature verify utility
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2005, 2006,
* 2008, 2009, 2012 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_DOSISH_SYSTEM
#include <fcntl.h> /* for setmode() */
#endif
#ifdef HAVE_LIBREADLINE
#define GNUPG_LIBREADLINE_H_INCLUDED
#include <readline/readline.h>
#endif
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "main.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "filter.h"
-#include "ttyio.h"
-#include "i18n.h"
-#include "sysutils.h"
-#include "status.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/status.h"
#include "call-agent.h"
#include "../common/init.h"
enum cmd_and_opt_values {
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oBatch = 500,
oKeyring,
oIgnoreTimeConflict,
oStatusFD,
oLoggerFD,
oLoggerFile,
oHomedir,
oWeakDigest,
oEnableSpecialFilenames,
oDebug,
aTest
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oKeyring, "keyring",
N_("|FILE|take the keys from the keyring FILE")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict",
N_("make timestamp conflicts only a warning")),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oLoggerFile, "log-file", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oWeakDigest, "weak-digest",
N_("|ALGO|reject signatures made with ALGO")),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_PACKET_VALUE , "packet" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_FILTER_VALUE , "filter" },
{ DBG_IOBUF_VALUE , "iobuf" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_TRUST_VALUE , "trust" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
int g10_errors_seen = 0;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage( int level )
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@GPG@v (GnuPG)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: gpgv [options] [files] (-h for help)");
break;
case 41: p = _("Syntax: gpgv [options] [files]\n"
"Check signatures against known trusted keys\n");
break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
default: p = NULL;
}
return p;
}
int
main( int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int rc=0;
strlist_t sl;
strlist_t nrings = NULL;
unsigned configlineno;
ctrl_t ctrl;
early_system_init ();
set_strusage (my_strusage);
log_set_prefix ("gpgv", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
gnupg_init_signals (0, NULL);
opt.command_fd = -1; /* no command fd */
opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE;
opt.trust_model = TM_ALWAYS;
opt.no_sig_cache = 1;
opt.flags.require_cross_cert = 1;
opt.batch = 1;
opt.answer_yes = 1;
opt.weak_digests = NULL;
tty_no_terminal(1);
tty_batchmode(1);
dotlock_disable ();
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
additional_weak_digest("MD5");
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
while (optfile_parse( NULL, NULL, &configlineno, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose:
opt.verbose++;
opt.list_sigs=1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oLoggerFD:
log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oLoggerFile:
log_set_file (pargs.r.ret_str);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID) );
break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oWeakDigest:
additional_weak_digest(pargs.r.ret_str);
break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
default : pargs.err = ARGPARSE_PRINT_ERROR; break;
}
}
if (log_get_errorcount (0))
g10_exit(2);
if (opt.verbose > 1)
set_packet_list_mode(1);
/* Note: We open all keyrings in read-only mode. */
if (!nrings) /* No keyring given: use default one. */
keydb_add_resource ("trustedkeys" EXTSEP_S "kbx",
(KEYDB_RESOURCE_FLAG_READONLY
|KEYDB_RESOURCE_FLAG_GPGVDEF));
for (sl = nrings; sl; sl = sl->next)
keydb_add_resource (sl->d, KEYDB_RESOURCE_FLAG_READONLY);
FREE_STRLIST (nrings);
ctrl = xcalloc (1, sizeof *ctrl);
if ((rc = verify_signatures (ctrl, argc, argv)))
log_error("verify signatures failed: %s\n", gpg_strerror (rc) );
xfree (ctrl);
/* cleanup */
g10_exit (0);
return 8; /*NOTREACHED*/
}
void
g10_exit( int rc )
{
rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0;
exit(rc );
}
/* Stub:
* We have to override the trustcheck from pkclist.c because
* this utility assumes that all keys in the keyring are trustworthy
*/
int
check_signatures_trust (ctrl_t ctrl, PKT_signature *sig)
{
(void)ctrl;
(void)sig;
return 0;
}
void
read_trust_options(byte *trust_model, ulong *created, ulong *nextcheck,
byte *marginals, byte *completes, byte *cert_depth,
byte *min_cert_level)
{
(void)trust_model;
(void)created;
(void)nextcheck;
(void)marginals;
(void)completes;
(void)cert_depth;
(void)min_cert_level;
}
/* Stub:
* We don't have the trustdb , so we have to provide some stub functions
* instead
*/
int
cache_disabled_value(PKT_public_key *pk)
{
(void)pk;
return 0;
}
void
check_trustdb_stale (ctrl_t ctrl)
{
(void)ctrl;
}
int
get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
PKT_user_id *uid)
{
(void)ctrl;
(void)kb;
(void)pk;
(void)uid;
return '?';
}
unsigned int
get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
PKT_signature *sig, int may_ask)
{
(void)ctrl;
(void)kb;
(void)pk;
(void)uid;
(void)sig;
(void)may_ask;
return 0;
}
const char *
trust_value_to_string (unsigned int value)
{
(void)value;
return "err";
}
const char *
uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid)
{
(void)ctrl;
(void)key;
(void)uid;
return "err";
}
int
get_ownertrust_info (PKT_public_key *pk, int no_create)
{
(void)pk;
(void)no_create;
return '?';
}
unsigned int
get_ownertrust (PKT_public_key *pk)
{
(void)pk;
return TRUST_UNKNOWN;
}
/* Stubs:
* Because we only work with trusted keys, it does not make sense to
* get them from a keyserver
*/
struct keyserver_spec *
keyserver_match (struct keyserver_spec *spec)
{
(void)spec;
return NULL;
}
int
keyserver_any_configured (ctrl_t ctrl)
{
(void)ctrl;
return 0;
}
int
keyserver_import_keyid (u32 *keyid, void *dummy, int quick)
{
(void)keyid;
(void)dummy;
(void)quick;
return -1;
}
int
keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len,
struct keyserver_spec *keyserver, int quick)
{
(void)ctrl;
(void)fprint;
(void)fprint_len;
(void)keyserver;
(void)quick;
return -1;
}
int
keyserver_import_cert (const char *name)
{
(void)name;
return -1;
}
int
keyserver_import_pka (const char *name,unsigned char *fpr)
{
(void)name;
(void)fpr;
return -1;
}
gpg_error_t
keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick,
unsigned char **fpr, size_t *fpr_len)
{
(void)ctrl;
(void)name;
(void)quick;
(void)fpr;
(void)fpr_len;
return GPG_ERR_BUG;
}
int
keyserver_import_name (const char *name,struct keyserver_spec *spec)
{
(void)name;
(void)spec;
return -1;
}
int
keyserver_import_ldap (const char *name)
{
(void)name;
return -1;
}
gpg_error_t
read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock)
{
(void)ctrl;
(void)fname;
(void)r_keyblock;
return -1;
}
/* Stub:
* No encryption here but mainproc links to these functions.
*/
gpg_error_t
get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek)
{
(void)ctrl;
(void)k;
(void)dek;
return GPG_ERR_GENERAL;
}
/* Stub: */
gpg_error_t
get_override_session_key (DEK *dek, const char *string)
{
(void)dek;
(void)string;
return GPG_ERR_GENERAL;
}
/* Stub: */
int
decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
{
(void)ctrl;
(void)procctx;
(void)ed;
(void)dek;
return GPG_ERR_GENERAL;
}
/* Stub:
* No interactive commands, so we don't need the helptexts
*/
void
display_online_help (const char *keyword)
{
(void)keyword;
}
/* Stub:
* We don't use secret keys, but getkey.c links to this
*/
int
check_secret_key (PKT_public_key *pk, int n)
{
(void)pk;
(void)n;
return GPG_ERR_GENERAL;
}
/* Stub:
* No secret key, so no passphrase needed
*/
DEK *
passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache,
const char *tmp, int *canceled)
{
(void)cipher_algo;
(void)s2k;
(void)create;
(void)nocache;
(void)tmp;
if (canceled)
*canceled = 0;
return NULL;
}
void
passphrase_clear_cache (const char *cacheid)
{
(void)cacheid;
}
struct keyserver_spec *
parse_preferred_keyserver(PKT_signature *sig)
{
(void)sig;
return NULL;
}
struct keyserver_spec *
parse_keyserver_uri (const char *uri, int require_scheme,
const char *configname, unsigned int configlineno)
{
(void)uri;
(void)require_scheme;
(void)configname;
(void)configlineno;
return NULL;
}
void
free_keyserver_spec (struct keyserver_spec *keyserver)
{
(void)keyserver;
}
/* Stubs to avoid linking to photoid.c */
void
show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk)
{
(void)attrs;
(void)count;
(void)pk;
}
int
parse_image_header (const struct user_attribute *attr, byte *type, u32 *len)
{
(void)attr;
(void)type;
(void)len;
return 0;
}
char *
image_type_to_string (byte type, int string)
{
(void)type;
(void)string;
return NULL;
}
#ifdef ENABLE_CARD_SUPPORT
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
(void)name;
(void)info;
return 0;
}
#endif /* ENABLE_CARD_SUPPORT */
/* We do not do any locking, so use these stubs here */
void
dotlock_disable (void)
{
}
dotlock_t
dotlock_create (const char *file_to_lock, unsigned int flags)
{
(void)file_to_lock;
(void)flags;
return NULL;
}
void
dotlock_destroy (dotlock_t h)
{
(void)h;
}
int
dotlock_take (dotlock_t h, long timeout)
{
(void)h;
(void)timeout;
return 0;
}
int
dotlock_release (dotlock_t h)
{
(void)h;
return 0;
}
void
dotlock_remove_lockfiles (void)
{
}
gpg_error_t
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
(void)ctrl;
(void)pk;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
(void)ctrl;
(void)keyblock;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
(void)ctrl;
(void)hexkeygrip;
(void)r_cleartext;
*r_serialno = NULL;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
unsigned char **r_fpr, size_t *r_fprlen,
char **r_url)
{
(void)ctrl;
(void)userid;
if (r_fpr)
*r_fpr = NULL;
if (r_fprlen)
*r_fprlen = 0;
if (r_url)
*r_url = NULL;
return gpg_error (GPG_ERR_NOT_FOUND);
}
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
(void)ctrl;
(void)keyspec;
(void)options;
(void)stats;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
gpg_error_t
tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, const char *user_id)
{
(void)ctrl;
(void)fp;
(void)pk;
(void)user_id;
return gpg_error (GPG_ERR_GENERAL);
}
gpg_error_t
tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
(void)ctrl;
(void)pk;
(void)user_id;
(void)policy;
return gpg_error (GPG_ERR_GENERAL);
}
const char *
tofu_policy_str (enum tofu_policy policy)
{
(void)policy;
return "unknown";
}
void
tofu_begin_batch_update (ctrl_t ctrl)
{
(void)ctrl;
}
void
tofu_end_batch_update (ctrl_t ctrl)
{
(void)ctrl;
}
gpg_error_t
tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
{
(void) ctrl;
(void) kb;
return 0;
}
diff --git a/g10/helptext.c b/g10/helptext.c
index 730f699e3..8b851019a 100644
--- a/g10/helptext.c
+++ b/g10/helptext.c
@@ -1,86 +1,86 @@
/* helptext.c - English help texts
* Copyright (C) 1998, 1999, 2000, 2001, 2002,
* 2004, 2007 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
-#include "ttyio.h"
+#include "../common/util.h"
+#include "../common/ttyio.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/* Helper to get the help through the configurable GnuPG help
system. */
static char *
get_help_from_file (const char *keyword)
{
char *key, *result;
key = xtrymalloc (4 + strlen (keyword) + 1);
if (key)
{
strcpy (stpcpy (key, "gpg."), keyword);
result = gnupg_get_help_string (key, 0);
xfree (key);
if (result && !is_native_utf8 ())
{
char *tmp = utf8_to_native (result, strlen (result), -1);
if (tmp)
{
xfree (result);
result = tmp;
}
}
}
else
result = NULL;
return result;
}
void
display_online_help( const char *keyword )
{
char *result;
int need_final_lf = 1;
tty_kill_prompt();
if ( !keyword )
tty_printf (_("No help available") );
else if ( (result = get_help_from_file (keyword)) )
{
tty_printf ("%s", result);
if (*result && result[strlen (result)-1] == '\n')
need_final_lf = 0;
xfree (result);
}
else
{
tty_printf (_("No help available for '%s'"), keyword );
}
if (need_final_lf)
tty_printf("\n");
}
diff --git a/g10/import.c b/g10/import.c
index 6383d7083..640618af6 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -1,3407 +1,3406 @@
/* import.c - import a key into our key storage.
* Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "trustdb.h"
#include "main.h"
-#include "i18n.h"
-#include "ttyio.h"
-#include "status.h"
-#include "recsel.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
+#include "../common/recsel.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/membuf.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
struct import_stats_s
{
ulong count;
ulong no_user_id;
ulong imported;
ulong n_uids;
ulong n_sigs;
ulong n_subk;
ulong unchanged;
ulong n_revoc;
ulong secret_read;
ulong secret_imported;
ulong secret_dups;
ulong skipped_new_keys;
ulong not_imported;
ulong n_sigs_cleaned;
ulong n_uids_cleaned;
ulong v3keys; /* Number of V3 keys seen. */
};
/* Node flag to indicate that a user ID or a subkey has a
* valid self-signature. */
#define NODE_GOOD_SELFSIG 1
/* Node flag to indicate that a user ID or subkey has
* an invalid self-signature. */
#define NODE_BAD_SELFSIG 2
/* Node flag to indicate that the node shall be deleted. */
#define NODE_DELETION_MARK 4
/* A node flag used to temporary mark a node. */
#define NODE_FLAG_A 8
/* An object and a global instance to store selectors created from
* --import-filter keep-uid=EXPR.
* --import-filter drop-sig=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now. For now we use save and restore
* function to temporary change them.
*/
/* Definition of the import filters. */
struct import_filter_s
{
recsel_expr_t keep_uid;
recsel_expr_t drop_sig;
};
/* The current instance. */
struct import_filter_s import_filter;
static int import (ctrl_t ctrl,
IOBUF inp, const char* fname, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg);
static int read_block (IOBUF a, PACKET **pending_pkt, kbnode_t *ret_root,
int *r_v3keys);
static void revocation_present (ctrl_t ctrl, kbnode_t keyblock);
static int import_one (ctrl_t ctrl,
kbnode_t keyblock,
struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len,
unsigned int options, int from_sk, int silent,
import_screener_t screener, void *screener_arg);
static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg);
static int import_revoke_cert (ctrl_t ctrl,
kbnode_t node, struct import_stats_s *stats);
static int chk_self_sigs (kbnode_t keyblock, u32 *keyid, int *non_self);
static int delete_inv_parts (kbnode_t keyblock,
u32 *keyid, unsigned int options);
static int any_uid_left (kbnode_t keyblock);
static int merge_blocks (kbnode_t keyblock_orig,
kbnode_t keyblock, u32 *keyid,
int *n_uids, int *n_sigs, int *n_subk );
static int append_uid (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static void
release_import_filter (import_filter_t filt)
{
recsel_release (filt->keep_uid);
filt->keep_uid = NULL;
recsel_release (filt->drop_sig);
filt->drop_sig = NULL;
}
static void
cleanup_import_globals (void)
{
release_import_filter (&import_filter);
}
int
parse_import_options(char *str,unsigned int *options,int noisy)
{
struct parse_options import_opts[]=
{
{"import-local-sigs",IMPORT_LOCAL_SIGS,NULL,
N_("import signatures that are marked as local-only")},
{"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,
N_("repair damage from the pks keyserver during import")},
{"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL,
N_("do not clear the ownertrust values during import")},
{"fast-import",IMPORT_FAST,NULL,
N_("do not update the trustdb after import")},
{"import-show",IMPORT_SHOW,NULL,
N_("show key during import")},
{"merge-only",IMPORT_MERGE_ONLY,NULL,
N_("only accept updates to existing keys")},
{"import-clean",IMPORT_CLEAN,NULL,
N_("remove unusable parts from key after import")},
{"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL,
N_("remove as much as possible from key after import")},
{"import-export", IMPORT_EXPORT, NULL,
N_("run import filters and export key immediately")},
{"restore", IMPORT_RESTORE, NULL,
N_("assume the GnuPG key backup format")},
{"import-restore", IMPORT_RESTORE, NULL, NULL},
/* Aliases for backward compatibility */
{"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL},
{"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL},
/* dummy */
{"import-unusable-sigs",0,NULL,NULL},
{"import-clean-sigs",0,NULL,NULL},
{"import-clean-uids",0,NULL,NULL},
{"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to
the new design. */
{NULL,0,NULL,NULL}
};
int rc;
rc = parse_options (str, options, import_opts, noisy);
if (rc && (*options & IMPORT_RESTORE))
{
/* Alter other options we want or don't want for restore. */
*options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST);
*options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_MERGE_ONLY);
}
return rc;
}
/* Parse and set an import filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is all called
* several times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be imported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*/
gpg_error_t
parse_and_set_import_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_import_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&import_filter.keep_uid, string+9);
else if (!strncmp (string, "drop-sig=", 9))
err = recsel_parse_expr (&import_filter.drop_sig, string+9);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Save the current import filters, return them, and clear the current
* filters. Returns NULL on error and sets ERRNO. */
import_filter_t
save_and_clear_import_filter (void)
{
import_filter_t filt;
filt = xtrycalloc (1, sizeof *filt);
if (!filt)
return NULL;
*filt = import_filter;
memset (&import_filter, 0, sizeof import_filter);
return filt;
}
/* Release the current import filters and restore them from NEWFILT.
* Ownership of NEWFILT is moved to this function. */
void
restore_import_filter (import_filter_t filt)
{
if (filt)
{
release_import_filter (&import_filter);
import_filter = *filt;
xfree (filt);
}
}
import_stats_t
import_new_stats_handle (void)
{
return xmalloc_clear ( sizeof (struct import_stats_s) );
}
void
import_release_stats_handle (import_stats_t p)
{
xfree (p);
}
/* Read a key from a file. Only the first key in the file is
* considered and stored at R_KEYBLOCK. FNAME is the name of the
* file.
*/
gpg_error_t
read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL;
u32 keyid[2];
int v3keys; /* Dummy */
int non_self; /* Dummy */
(void)ctrl;
*r_keyblock = NULL;
inp = iobuf_open (fname);
if (!inp)
err = gpg_error_from_syserror ();
else if (is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
err = gpg_error (GPG_ERR_EPERM);
}
else
err = 0;
if (err)
{
log_error (_("can't open '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Push the armor filter. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
/* Read the first non-v3 keyblock. */
while (!(err = read_block (inp, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
break;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
release_kbnode (keyblock);
keyblock = NULL;
}
if (err)
{
if (gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
goto leave;
}
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
if (!find_next_kbnode (keyblock, PKT_USER_ID))
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
collapse_uids (&keyblock);
clear_kbnode_flags (keyblock);
if (chk_self_sigs (keyblock, keyid, &non_self))
{
err = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (!delete_inv_parts (keyblock, keyid, 0) )
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
leave:
if (inp)
{
iobuf_close (inp);
/* Must invalidate that ugly cache to actually close the file. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
}
release_kbnode (keyblock);
/* FIXME: Do we need to free PENDING_PKT ? */
return err;
}
/*
* Import the public keys from the given filename. Input may be armored.
* This function rejects all keys which are not validly self signed on at
* least one userid. Only user ids which are self signed will be imported.
* Other signatures are not checked.
*
* Actually this function does a merge. It works like this:
*
* - get the keyblock
* - check self-signatures and remove all userids and their signatures
* without/invalid self-signatures.
* - reject the keyblock, if we have no valid userid.
* - See whether we have this key already in one of our pubrings.
* If not, simply add it to the default keyring.
* - Compare the key and the self-signatures of the new and the one in
* our keyring. If they are different something weird is going on;
* ask what to do.
* - See whether we have only non-self-signature on one user id; if not
* ask the user what to do.
* - compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* (consider looking at the timestamp and use the newest?)
* - Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* - Proceed with next signature.
*
* Key revocation certificates have special handling.
*/
static int
import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg)
{
int i;
int rc = 0;
struct import_stats_s *stats = stats_handle;
if (!stats)
stats = import_new_stats_handle ();
if (inp)
{
rc = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options,
screener, screener_arg);
}
else
{
if (!fnames && !nnames)
nnames = 1; /* Ohh what a ugly hack to jump into the loop */
for (i=0; i < nnames; i++)
{
const char *fname = fnames? fnames[i] : NULL;
IOBUF inp2 = iobuf_open(fname);
if (!fname)
fname = "[stdin]";
if (inp2 && is_secured_file (iobuf_get_fd (inp2)))
{
iobuf_close (inp2);
inp2 = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp2)
log_error (_("can't open '%s': %s\n"), fname, strerror (errno));
else
{
rc = import (ctrl, inp2, fname, stats, fpr, fpr_len, options,
screener, screener_arg);
iobuf_close (inp2);
/* Must invalidate that ugly cache to actually close it. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
if (rc)
log_error ("import from '%s' failed: %s\n",
fname, gpg_strerror (rc) );
}
if (!fname)
break;
}
}
if (!stats_handle)
{
import_print_stats (stats);
import_release_stats_handle (stats);
}
/* If no fast import and the trustdb is dirty (i.e. we added a key
or userID that had something other than a selfsig, a signature
that was other than a selfsig, or any revocation), then
update/check the trustdb if the user specified by setting
interactive or by not setting no-auto-check-trustdb */
if (!(options & IMPORT_FAST))
check_or_update_trustdb (ctrl);
return rc;
}
void
import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_handle, unsigned int options )
{
import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle,
NULL, NULL, options, NULL, NULL);
}
int
import_keys_stream (ctrl_t ctrl, IOBUF inp, import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len, unsigned int options)
{
return import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options, NULL, NULL);
}
/* Variant of import_keys_stream reading from an estream_t. */
int
import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg)
{
int rc;
iobuf_t inp;
inp = iobuf_esopen (fp, "rb", 1);
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error ("iobuf_esopen failed: %s\n", gpg_strerror (rc));
return rc;
}
rc = import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options,
screener, screener_arg);
iobuf_close (inp);
return rc;
}
static int
import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
unsigned char **fpr,size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg)
{
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
int rc = 0;
int v3keys;
getkey_disable_caches ();
if (!opt.no_armor) /* Armored reading is not disabled. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
while (!(rc = read_block (inp, &pending_pkt, &keyblock, &v3keys)))
{
stats->v3keys += v3keys;
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
rc = import_one (ctrl, keyblock,
stats, fpr, fpr_len, options, 0, 0,
screener, screener_arg);
else if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
rc = import_secret_one (ctrl, keyblock, stats,
opt.batch, options, 0,
screener, screener_arg);
else if (keyblock->pkt->pkttype == PKT_SIGNATURE
&& keyblock->pkt->pkt.signature->sig_class == 0x20 )
rc = import_revoke_cert (ctrl, keyblock, stats);
else
{
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
}
release_kbnode (keyblock);
/* fixme: we should increment the not imported counter but
this does only make sense if we keep on going despite of
errors. For now we do this only if the imported key is too
large. */
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
stats->not_imported++;
}
else if (rc)
break;
if (!(++stats->count % 100) && !opt.quiet)
log_info (_("%lu keys processed so far\n"), stats->count );
}
stats->v3keys += v3keys;
if (rc == -1)
rc = 0;
else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc));
return rc;
}
/* Helper to migrate secring.gpg to GnuPG 2.1. */
gpg_error_t
import_old_secring (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
struct import_stats_s *stats;
int v3keys;
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
getkey_disable_caches();
stats = import_new_stats_handle ();
while (!(err = read_block (inp, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1,
NULL, NULL);
release_kbnode (keyblock);
if (err)
break;
}
import_release_stats_handle (stats);
if (err == -1)
err = 0;
else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
else if (err)
log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err));
iobuf_close (inp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
return err;
}
void
import_print_stats (import_stats_t stats)
{
if (!opt.quiet)
{
log_info(_("Total number processed: %lu\n"),
stats->count + stats->v3keys);
if (stats->v3keys)
log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys);
if (stats->skipped_new_keys )
log_info(_(" skipped new keys: %lu\n"),
stats->skipped_new_keys );
if (stats->no_user_id )
log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id );
if (stats->imported)
{
log_info(_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged )
log_info(_(" unchanged: %lu\n"), stats->unchanged );
if (stats->n_uids )
log_info(_(" new user IDs: %lu\n"), stats->n_uids );
if (stats->n_subk )
log_info(_(" new subkeys: %lu\n"), stats->n_subk );
if (stats->n_sigs )
log_info(_(" new signatures: %lu\n"), stats->n_sigs );
if (stats->n_revoc )
log_info(_(" new key revocations: %lu\n"), stats->n_revoc );
if (stats->secret_read )
log_info(_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported )
log_info(_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups )
log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported )
log_info(_(" not imported: %lu\n"), stats->not_imported );
if (stats->n_sigs_cleaned)
log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned);
if (stats->n_uids_cleaned)
log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned);
}
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf,
"%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count + stats->v3keys,
stats->no_user_id,
stats->imported,
stats->unchanged,
stats->n_uids,
stats->n_subk,
stats->n_sigs,
stats->n_revoc,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
stats->skipped_new_keys,
stats->not_imported,
stats->v3keys );
write_status_text (STATUS_IMPORT_RES, buf);
}
}
/* Return true if PKTTYPE is valid in a keyblock. */
static int
valid_keyblock_packet (int pkttype)
{
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
return 1;
default:
return 0;
}
}
/****************
* Read the next keyblock from stream A.
* PENDING_PKT should be initialzed to NULL
* and not changed by the caller.
* Return: 0 = okay, -1 no more blocks or another errorcode.
* The int at at R_V3KEY counts the number of unsupported v3
* keyblocks.
*/
static int
read_block( IOBUF a, PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys)
{
int rc;
PACKET *pkt;
kbnode_t root = NULL;
int in_cert, in_v3key;
*r_v3keys = 0;
if (*pending_pkt)
{
root = new_kbnode( *pending_pkt );
*pending_pkt = NULL;
in_cert = 1;
}
else
in_cert = 0;
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
in_v3key = 0;
while ((rc=parse_packet(a, pkt)) != -1)
{
if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
&& (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)))
{
in_v3key = 1;
++*r_v3keys;
free_packet (pkt);
init_packet (pkt);
continue;
}
else if (rc ) /* (ignore errors) */
{
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET)
; /* Do not show a diagnostic. */
else
{
log_error("read_block: read error: %s\n", gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
goto ready;
}
free_packet( pkt );
init_packet(pkt);
continue;
}
if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
free_packet( pkt );
init_packet(pkt);
continue;
}
in_v3key = 0;
if (!root && pkt->pkttype == PKT_SIGNATURE
&& pkt->pkt.signature->sig_class == 0x20 )
{
/* This is a revocation certificate which is handled in a
* special way. */
root = new_kbnode( pkt );
pkt = NULL;
goto ready;
}
/* Make a linked list of all packets. */
switch (pkt->pkttype)
{
case PKT_COMPRESSED:
if (check_compress_algo (pkt->pkt.compressed->algorithm))
{
rc = GPG_ERR_COMPR_ALGO;
goto ready;
}
else
{
compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx );
pkt->pkt.compressed->buf = NULL;
push_compress_filter2(a,cfx,pkt->pkt.compressed->algorithm,1);
}
free_packet( pkt );
init_packet(pkt);
break;
case PKT_RING_TRUST:
/* Skip those packets unless we are in restore mode. */
if ((opt.import_options & IMPORT_RESTORE))
goto x_default;
free_packet( pkt );
init_packet(pkt);
break;
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
if (in_cert ) /* Store this packet. */
{
*pending_pkt = pkt;
pkt = NULL;
goto ready;
}
in_cert = 1;
default:
x_default:
if (in_cert && valid_keyblock_packet (pkt->pkttype))
{
if (!root )
root = new_kbnode (pkt);
else
add_kbnode (root, new_kbnode (pkt));
pkt = xmalloc (sizeof *pkt);
}
init_packet(pkt);
break;
}
}
ready:
if (rc == -1 && root )
rc = 0;
if (rc )
release_kbnode( root );
else
*ret_root = root;
free_packet( pkt );
xfree( pkt );
return rc;
}
/* Walk through the subkeys on a pk to find if we have the PKS
disease: multiple subkeys with their binding sigs stripped, and the
sig for the first subkey placed after the last subkey. That is,
instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have
"pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2
and sub3, as they are already lost, but we can try and rescue sub1
by reordering the keyblock so that it reads "pk uid sig sub1 bind1
sub2 sub3". Returns TRUE if the keyblock was modified. */
static int
fix_pks_corruption (kbnode_t keyblock)
{
int changed = 0;
int keycount = 0;
kbnode_t node;
kbnode_t last = NULL;
kbnode_t sknode=NULL;
/* First determine if we have the problem at all. Look for 2 or
more subkeys in a row, followed by a single binding sig. */
for (node=keyblock; node; last=node, node=node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keycount++;
if(!sknode)
sknode=node;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& node->pkt->pkt.signature->sig_class == 0x18
&& keycount >= 2
&& !node->next)
{
/* We might have the problem, as this key has two subkeys in
a row without any intervening packets. */
/* Sanity check */
if (!last)
break;
/* Temporarily attach node to sknode. */
node->next = sknode->next;
sknode->next = node;
last->next = NULL;
/* Note we aren't checking whether this binding sig is a
selfsig. This is not necessary here as the subkey and
binding sig will be rejected later if that is the
case. */
if (check_key_signature (keyblock,node,NULL))
{
/* Not a match, so undo the changes. */
sknode->next = node->next;
last->next = node;
node->next = NULL;
break;
}
else
{
/* Mark it good so we don't need to check it again */
sknode->flag |= NODE_GOOD_SELFSIG;
changed = 1;
break;
}
}
else
keycount = 0;
}
return changed;
}
/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus
direct key signatures. A side effect of this was that a later
import of the same good direct key signatures was not possible
because the cmp_signature check in merge_blocks considered them
equal. Although direct key signatures are now checked during
import, there might still be bogus signatures sitting in a keyring.
We need to detect and delete them before doing a merge. This
function returns the number of removed sigs. */
static int
fix_bad_direct_key_sigs (kbnode_t keyblock, u32 *keyid)
{
gpg_error_t err;
kbnode_t node;
int count = 0;
for (node = keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
err = check_key_signature (keyblock, node, NULL);
if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO )
{
/* If we don't know the error, we can't decide; this is
not a problem because cmp_signature can't compare the
signature either. */
log_info ("key %s: invalid direct key signature removed\n",
keystr (keyid));
delete_kbnode (node);
count++;
}
}
}
return count;
}
static void
print_import_ok (PKT_public_key *pk, unsigned int reason)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char buf[MAX_FINGERPRINT_LEN*2+30], *p;
size_t i, n;
snprintf (buf, sizeof buf, "%u ", reason);
p = buf + strlen (buf);
fingerprint_from_pk (pk, array, &n);
s = array;
for (i=0; i < n ; i++, s++, p += 2)
sprintf (p, "%02X", *s);
write_status_text (STATUS_IMPORT_OK, buf);
}
static void
print_import_check (PKT_public_key * pk, PKT_user_id * id)
{
char * buf;
byte fpr[24];
u32 keyid[2];
size_t i, n;
size_t pos = 0;
buf = xmalloc (17+41+id->len+32);
keyid_from_pk (pk, keyid);
sprintf (buf, "%08X%08X ", keyid[0], keyid[1]);
pos = 17;
fingerprint_from_pk (pk, fpr, &n);
for (i = 0; i < n; i++, pos += 2)
sprintf (buf+pos, "%02X", fpr[i]);
strcat (buf, " ");
strcat (buf, id->name);
write_status_text (STATUS_IMPORT_CHECK, buf);
xfree (buf);
}
static void
check_prefs_warning(PKT_public_key *pk)
{
log_info(_("WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"), keystr_from_pk(pk));
}
static void
check_prefs (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int problem=0;
merge_keys_and_selfsig(keyblock);
pk=keyblock->pkt->pkt.public_key;
for(node=keyblock;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->created
&& node->pkt->pkt.user_id->prefs)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
char *user = utf8_to_native(uid->name,strlen(uid->name),0);
for(;prefs->type;prefs++)
{
char num[10]; /* prefs->value is a byte, so we're over
safe here */
sprintf(num,"%u",prefs->value);
if(prefs->type==PREFTYPE_SYM)
{
if (openpgp_cipher_test_algo (prefs->value))
{
const char *algo =
(openpgp_cipher_test_algo (prefs->value)
? num
: openpgp_cipher_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for cipher"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_HASH)
{
if(openpgp_md_test_algo(prefs->value))
{
const char *algo =
(gcry_md_test_algo (prefs->value)
? num
: gcry_md_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for digest"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_ZIP)
{
if(check_compress_algo (prefs->value))
{
const char *algo=compress_algo_to_string(prefs->value);
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for compression"
" algorithm %s\n"),user,algo?algo:num);
problem=1;
}
}
}
xfree(user);
}
}
if(problem)
{
log_info(_("it is strongly suggested that you update"
" your preferences and\n"));
log_info(_("re-distribute this key to avoid potential algorithm"
" mismatch problems\n"));
if(!opt.batch)
{
strlist_t sl = NULL;
strlist_t locusr = NULL;
size_t fprlen=0;
byte fpr[MAX_FINGERPRINT_LEN], *p;
char username[(MAX_FINGERPRINT_LEN*2)+1];
unsigned int i;
p = fingerprint_from_pk (pk,fpr,&fprlen);
for(i=0;i<fprlen;i++,p++)
sprintf(username+2*i,"%02X",*p);
add_to_strlist(&locusr,username);
append_to_strlist(&sl,"updpref");
append_to_strlist(&sl,"save");
keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
free_strlist(sl);
free_strlist(locusr);
}
else if(!opt.quiet)
log_info(_("you can update your preferences with:"
" gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
}
}
/* Helper for apply_*_filter in import.c and export.c. */
const char *
impex_filter_getval (void *cookie, const char *propname)
{
/* FIXME: Malloc our static buffers and access them via the cookie. */
kbnode_t node = cookie;
static char numbuf[20];
const char *result;
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!strcmp (propname, "uid"))
result = uid->name;
else if (!strcmp (propname, "mbox"))
{
if (!uid->mbox)
{
uid->mbox = mailbox_from_userid (uid->name);
}
result = uid->mbox;
}
else if (!strcmp (propname, "primary"))
{
result = uid->is_primary? "1":"0";
}
else if (!strcmp (propname, "expired"))
{
result = uid->is_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = uid->is_revoked? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (!strcmp (propname, "sig_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "sig_created_d"))
{
result = datestr_from_sig (sig);
}
else if (!strcmp (propname, "sig_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "sig_digest_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo);
result = numbuf;
}
else if (!strcmp (propname, "expired"))
{
result = sig->flags.expired? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (!strcmp (propname, "secret"))
{
result = (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0";
}
else if (!strcmp (propname, "key_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo);
result = numbuf;
}
if (!strcmp (propname, "key_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "key_created_d"))
{
result = datestr_from_pk (pk);
}
else if (!strcmp (propname, "expired"))
{
result = pk->has_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = pk->flags.revoked? "1":"0";
}
else if (!strcmp (propname, "disabled"))
{
result = pk_is_disabled (pk)? "1":"0";
}
else
result = NULL;
}
else
result = NULL;
return result;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (!recsel_select (selector, impex_filter_getval, node))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-sig filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_sig_filter (kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
int active = 0;
u32 main_keyid[2];
PKT_signature *sig;
keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid);
/* Loop over all signatures for user id and attribute packets which
* are not self signatures. */
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready. */
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
active = 1;
if (!active)
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1])
continue; /* Skip self-signatures. */
if (IS_UID_SIG(sig) || IS_UID_REV(sig))
{
if (recsel_select (selector, impex_filter_getval, node))
delete_kbnode (node);
}
}
}
/*
* Try to import one keyblock. Return an error only in serious cases,
* but never for an invalid keyblock. It uses log_error to increase
* the internal errorcount, so that invalid input can be detected by
* programs which called gpg. If SILENT is no messages are printed -
* even most error messages are suppressed.
*/
static int
import_one (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg)
{
PKT_public_key *pk;
PKT_public_key *pk_orig = NULL;
kbnode_t node, uidnode;
kbnode_t keyblock_orig = NULL;
byte fpr2[MAX_FINGERPRINT_LEN];
size_t fpr2len;
u32 keyid[2];
int rc = 0;
int new_key = 0;
int mod_key = 0;
int same_key = 0;
int non_self = 0;
size_t an;
char pkstrbuf[PUBKEY_STRING_SIZE];
int merge_keys_done = 0;
int any_filter = 0;
/* Get the key and print some info about it. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node )
BUG();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr2, &fpr2len);
for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++)
fpr2[an] = 0;
keyid_from_pk( pk, keyid );
uidnode = find_next_kbnode( keyblock, PKT_USER_ID );
if (opt.verbose && !opt.interactive && !silent)
{
log_info( "pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk), datestr_from_pk(pk) );
if (uidnode)
print_utf8_buffer (log_get_stream (),
uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len );
log_printf ("\n");
}
if (!uidnode )
{
if (!silent)
log_error( _("key %s: no user ID\n"), keystr_from_pk(pk));
return 0;
}
if (screener && screener (keyblock, screener_arg))
{
log_error (_("key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.interactive && !silent)
{
if (is_status_enabled())
print_import_check (pk, uidnode->pkt->pkt.user_id);
merge_keys_and_selfsig (keyblock);
tty_printf ("\n");
show_basic_key_info (keyblock);
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("import.okay",
"Do you want to import this key? (y/N) "))
return 0;
}
collapse_uids(&keyblock);
/* Clean the key that we're about to import, to cut down on things
that we have to clean later. This has no practical impact on the
end result, but does result in less logging which might confuse
the user. */
if (options&IMPORT_CLEAN)
clean_key (keyblock,opt.verbose,options&IMPORT_MINIMAL,NULL,NULL);
clear_kbnode_flags( keyblock );
if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG) && fix_pks_corruption(keyblock)
&& opt.verbose)
log_info (_("key %s: PKS subkey corruption repaired\n"),
keystr_from_pk(pk));
if (chk_self_sigs (keyblock, keyid, &non_self))
return 0; /* Invalid keyblock - error already printed. */
/* If we allow such a thing, mark unsigned uids as valid */
if (opt.allow_non_selfsigned_uid)
{
for (node=keyblock; node; node = node->next )
if (node->pkt->pkttype == PKT_USER_ID
&& !(node->flag & NODE_GOOD_SELFSIG)
&& !(node->flag & NODE_BAD_SELFSIG) )
{
char *user=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
/* Fake a good signature status for the user id. */
node->flag |= NODE_GOOD_SELFSIG;
log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"),
keystr_from_pk(pk),user);
xfree(user);
}
}
if (!delete_inv_parts (keyblock, keyid, options ) )
{
if (!silent)
{
log_error( _("key %s: no valid user IDs\n"), keystr_from_pk(pk));
if (!opt.quiet )
log_info(_("this may be caused by a missing self-signature\n"));
}
stats->no_user_id++;
return 0;
}
/* Get rid of deleted nodes. */
commit_kbnode (&keyblock);
/* Apply import filter. */
if (import_filter.keep_uid)
{
apply_keep_uid_filter (keyblock, import_filter.keep_uid);
commit_kbnode (&keyblock);
any_filter = 1;
}
if (import_filter.drop_sig)
{
apply_drop_sig_filter (keyblock, import_filter.drop_sig);
commit_kbnode (&keyblock);
any_filter = 1;
}
/* If we ran any filter we need to check that at least one user id
* is left in the keyring. Note that we do not use log_error in
* this case. */
if (any_filter && !any_uid_left (keyblock))
{
if (!opt.quiet )
log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk));
stats->no_user_id++;
return 0;
}
/* Show the key in the form it is merged or inserted. We skip this
* if "import-export" is also active without --armor or the output
* file has explicily been given. */
if ((options & IMPORT_SHOW)
&& !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile))
{
merge_keys_and_selfsig (keyblock);
merge_keys_done = 1;
/* Note that we do not want to show the validity because the key
* has not yet imported. */
list_keyblock_direct (ctrl, keyblock, 0, 0, 1, 1);
es_fflush (es_stdout);
}
/* Write the keyblock to the output and do not actually import. */
if ((options & IMPORT_EXPORT))
{
if (!merge_keys_done)
{
merge_keys_and_selfsig (keyblock);
merge_keys_done = 1;
}
rc = write_keyblock_to_output (keyblock, opt.armor, opt.export_options);
goto leave;
}
if (opt.dry_run)
goto leave;
/* Do we have this key already in one of our pubrings ? */
pk_orig = xmalloc_clear( sizeof *pk_orig );
rc = get_pubkey_byfprint_fast (pk_orig, fpr2, fpr2len);
if (rc && gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
&& gpg_err_code (rc) != GPG_ERR_UNUSABLE_PUBKEY )
{
if (!silent)
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
}
else if ( rc && (opt.import_options&IMPORT_MERGE_ONLY) )
{
if (opt.verbose && !silent )
log_info( _("key %s: new key - skipped\n"), keystr(keyid));
rc = 0;
stats->skipped_new_keys++;
}
else if (rc ) /* Insert this key. */
{
KEYDB_HANDLE hd;
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_locate_writable (hd);
if (rc)
{
log_error (_("no writable keyring found: %s\n"), gpg_strerror (rc));
keydb_release (hd);
return GPG_ERR_GENERAL;
}
if (opt.verbose > 1 )
log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) );
rc = keydb_insert_keyblock (hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc));
else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST))
{
/* This should not be possible since we delete the
ownertrust when a key is deleted, but it can happen if
the keyring and trustdb are out of sync. It can also
be made to happen with the trusted-key command and by
importing and locally exported key. */
clear_ownertrusts (pk);
if (non_self)
revalidation_mark ();
}
keydb_release (hd);
/* We are ready. */
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (fpr2);
log_info (_("key %s: public key \"%s\" imported\n"),
keystr(keyid), p);
xfree(p);
}
if (is_status_enabled())
{
char *us = get_long_user_id_string( keyid );
write_status_text( STATUS_IMPORTED, us );
xfree(us);
print_import_ok (pk, 1);
}
stats->imported++;
new_key = 1;
}
else /* merge */
{
KEYDB_HANDLE hd;
int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned;
/* Compare the original against the new key; just to be sure nothing
* weird is going on */
if (cmp_public_keys( pk_orig, pk ) )
{
if (!silent)
log_error( _("key %s: doesn't match our copy\n"),keystr(keyid));
goto leave;
}
/* Now read the original keyblock again so that we can use
that handle for updating the keyblock. */
hd = keydb_new ();
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
keydb_disable_caching (hd);
rc = keydb_search_fpr (hd, fpr2);
if (rc )
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
keydb_release (hd);
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock_orig);
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
keydb_release (hd);
goto leave;
}
/* Make sure the original direct key sigs are all sane. */
n_sigs_cleaned = fix_bad_direct_key_sigs (keyblock_orig, keyid);
if (n_sigs_cleaned)
commit_kbnode (&keyblock_orig);
/* and try to merge the block */
clear_kbnode_flags( keyblock_orig );
clear_kbnode_flags( keyblock );
n_uids = n_sigs = n_subk = n_uids_cleaned = 0;
rc = merge_blocks (keyblock_orig, keyblock,
keyid, &n_uids, &n_sigs, &n_subk );
if (rc )
{
keydb_release (hd);
goto leave;
}
if ((options & IMPORT_CLEAN))
clean_key (keyblock_orig,opt.verbose,options&IMPORT_MINIMAL,
&n_uids_cleaned,&n_sigs_cleaned);
if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned)
{
mod_key = 1;
/* KEYBLOCK_ORIG has been updated; write */
rc = keydb_update_keyblock (ctrl, hd, keyblock_orig);
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
else if (non_self)
revalidation_mark ();
/* We are ready. */
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (fpr2);
if (n_uids == 1 )
log_info( _("key %s: \"%s\" 1 new user ID\n"),
keystr(keyid),p);
else if (n_uids )
log_info( _("key %s: \"%s\" %d new user IDs\n"),
keystr(keyid),p,n_uids);
if (n_sigs == 1 )
log_info( _("key %s: \"%s\" 1 new signature\n"),
keystr(keyid), p);
else if (n_sigs )
log_info( _("key %s: \"%s\" %d new signatures\n"),
keystr(keyid), p, n_sigs );
if (n_subk == 1 )
log_info( _("key %s: \"%s\" 1 new subkey\n"),
keystr(keyid), p);
else if (n_subk )
log_info( _("key %s: \"%s\" %d new subkeys\n"),
keystr(keyid), p, n_subk );
if (n_sigs_cleaned==1)
log_info(_("key %s: \"%s\" %d signature cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
else if (n_sigs_cleaned)
log_info(_("key %s: \"%s\" %d signatures cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
if (n_uids_cleaned==1)
log_info(_("key %s: \"%s\" %d user ID cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
else if (n_uids_cleaned)
log_info(_("key %s: \"%s\" %d user IDs cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
xfree(p);
}
stats->n_uids +=n_uids;
stats->n_sigs +=n_sigs;
stats->n_subk +=n_subk;
stats->n_sigs_cleaned +=n_sigs_cleaned;
stats->n_uids_cleaned +=n_uids_cleaned;
if (is_status_enabled () && !silent)
print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
}
else
{
same_key = 1;
if (is_status_enabled ())
print_import_ok (pk, 0);
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (fpr2);
log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p);
xfree(p);
}
stats->unchanged++;
}
keydb_release (hd); hd = NULL;
}
leave:
if (mod_key || new_key || same_key)
{
/* A little explanation for this: we fill in the fingerprint
when importing keys as it can be useful to know the
fingerprint in certain keyserver-related cases (a keyserver
asked for a particular name, but the key doesn't have that
name). However, in cases where we're importing more than
one key at a time, we cannot know which key to fingerprint.
In these cases, rather than guessing, we do not
fingerprinting at all, and we must hope the user ID on the
keys are useful. Note that we need to do this for new
keys, merged keys and even for unchanged keys. This is
required because for example the --auto-key-locate feature
may import an already imported key and needs to know the
fingerprint of the key in all cases. */
if (fpr)
{
xfree (*fpr);
/* Note that we need to compare against 0 here because
COUNT gets only incremented after returning from this
function. */
if (!stats->count)
*fpr = fingerprint_from_pk (pk, NULL, fpr_len);
else
*fpr = NULL;
}
}
/* Now that the key is definitely incorporated into the keydb, we
need to check if a designated revocation is present or if the
prefs are not rational so we can warn the user. */
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
if (!from_sk && have_secret_key_with_kid (keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
if (!from_sk && have_secret_key_with_kid (keyid))
check_prefs (ctrl, keyblock);
}
release_kbnode( keyblock_orig );
free_public_key( pk_orig );
return rc;
}
/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The
function prints diagnostics and returns an error code. If BATCH is
true the secret keys are stored by gpg-agent in the transfer format
(i.e. no re-protection and aksing for passphrases). */
gpg_error_t
transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
kbnode_t ctx = NULL;
kbnode_t node;
PKT_public_key *main_pk, *pk;
struct seckey_info *ski;
int nskey;
membuf_t mbuf;
int i, j;
void *format_args[2*PUBKEY_MAX_NSKEY];
gcry_sexp_t skey, prot, tmpsexp;
gcry_sexp_t curve = NULL;
unsigned char *transferkey = NULL;
size_t transferkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
char *cache_nonce = NULL;
int stub_key_skipped = 0;
/* Get the current KEK. */
err = agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
main_pk = NULL;
while ((node = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (!main_pk)
main_pk = pk;
/* Make sure the keyids are available. */
keyid_from_pk (pk, NULL);
if (node->pkt->pkttype == PKT_SECRET_KEY)
{
pk->main_keyid[0] = pk->keyid[0];
pk->main_keyid[1] = pk->keyid[1];
}
else
{
pk->main_keyid[0] = main_pk->keyid[0];
pk->main_keyid[1] = main_pk->keyid[1];
}
ski = pk->seckey_info;
if (!ski)
BUG ();
if (stats)
{
stats->count++;
stats->secret_read++;
}
/* We ignore stub keys. The way we handle them in other parts
of the code is by asking the agent whether any secret key is
available for a given keyblock and then concluding that we
have a secret key; all secret (sub)keys of the keyblock the
agent does not know of are then stub keys. This works also
for card stub keys. The learn command or the card-status
command may be used to check with the agent whether a card
has been inserted and a stub key is in turn generated by the
agent. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
stub_key_skipped = 1;
continue;
}
/* Convert our internal secret key object into an S-expression. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
if (!nskey || nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
log_error ("internal error: %s\n", gpg_strerror (err));
goto leave;
}
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
/* The ECC case. */
char *curvestr = openpgp_oid_to_str (pk->pkey[0]);
if (!curvestr)
err = gpg_error_from_syserror ();
else
{
const char *curvename = openpgp_oid_to_curve (curvestr, 1);
gcry_sexp_release (curve);
err = gcry_sexp_build (&curve, NULL, "(curve %s)",
curvename?curvename:curvestr);
xfree (curvestr);
if (!err)
{
j = 0;
/* Append the public key element Q. */
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + 1;
/* Append the secret key element D. For ECDH we
skip PKEY[2] because this holds the KEK which is
not needed by gpg-agent. */
i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
}
else
{
/* Standard case for the old (non-ECC) algorithms. */
for (i=j=0; i < nskey; i++)
{
if (!pk->pkey[i])
continue; /* Protected keys only have NPKEY+1 elements. */
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
put_membuf_str (&mbuf, ")");
put_membuf (&mbuf, "", 1);
if (err)
xfree (get_membuf (&mbuf, NULL));
else
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&skey, NULL, format, format_args);
xfree (format);
}
if (err)
{
log_error ("error building skey array: %s\n", gpg_strerror (err));
goto leave;
}
if (ski->is_protected)
{
char countbuf[35];
/* Note that the IVLEN may be zero if we are working on a
dummy key. We can't express that in an S-expression and
thus we send dummy data for the IV. */
snprintf (countbuf, sizeof countbuf, "%lu",
(unsigned long)ski->s2k.count);
err = gcry_sexp_build
(&prot, NULL,
" (protection %s %s %b %d %s %b %s)\n",
ski->sha1chk? "sha1":"sum",
openpgp_cipher_algo_name (ski->algo),
ski->ivlen? (int)ski->ivlen:1,
ski->ivlen? ski->iv: (const unsigned char*)"X",
ski->s2k.mode,
openpgp_md_algo_name (ski->s2k.hash_algo),
(int)sizeof (ski->s2k.salt), ski->s2k.salt,
countbuf);
}
else
err = gcry_sexp_build (&prot, NULL, " (protection none)\n");
tmpsexp = NULL;
xfree (transferkey);
transferkey = NULL;
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version %d)\n"
" (algo %s)\n"
" %S%S\n"
" (csum %d)\n"
" %S)\n",
pk->version,
openpgp_pk_algo_name (pk->pubkey_algo),
curve, skey,
(int)(unsigned long)ski->csum, prot);
gcry_sexp_release (skey);
gcry_sexp_release (prot);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen);
gcry_sexp_release (tmpsexp);
if (err)
{
log_error ("error building transfer key: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
wrappedkeylen = transferkeylen + 8;
xfree (wrappedkey);
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
err = gpg_error_from_syserror ();
else
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
transferkey, transferkeylen);
if (err)
goto leave;
xfree (transferkey);
transferkey = NULL;
/* Send the wrapped key to the agent. */
{
char *desc = gpg_format_keydesc (pk, FORMAT_KEYDESC_IMPORT, 1);
err = agent_import_key (ctrl, desc, &cache_nonce,
wrappedkey, wrappedkeylen, batch, force);
xfree (desc);
}
if (!err)
{
if (opt.verbose)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk_with_sub (main_pk, pk));
if (stats)
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
if (opt.verbose)
log_info (_("key %s: secret key already exists\n"),
keystr_from_pk_with_sub (main_pk, pk));
err = 0;
if (stats)
stats->secret_dups++;
}
else
{
log_error (_("key %s: error sending to agent: %s\n"),
keystr_from_pk_with_sub (main_pk, pk),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try the other subkeys. */
}
}
if (!err && stub_key_skipped)
/* We need to notify user how to migrate stub keys. */
err = gpg_error (GPG_ERR_NOT_PROCESSED);
leave:
gcry_sexp_release (curve);
xfree (cache_nonce);
xfree (wrappedkey);
xfree (transferkey);
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
}
/* Walk a secret keyblock and produce a public keyblock out of it.
Returns a new node or NULL on error. */
static kbnode_t
sec_to_pub_keyblock (kbnode_t sec_keyblock)
{
kbnode_t pub_keyblock = NULL;
kbnode_t ctx = NULL;
kbnode_t secnode, pubnode;
while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (secnode->pkt->pkttype == PKT_SECRET_KEY
|| secnode->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* Make a public key. */
PACKET *pkt;
PKT_public_key *pk;
pkt = xtrycalloc (1, sizeof *pkt);
pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL;
if (!pk)
{
xfree (pkt);
release_kbnode (pub_keyblock);
return NULL;
}
if (secnode->pkt->pkttype == PKT_SECRET_KEY)
pkt->pkttype = PKT_PUBLIC_KEY;
else
pkt->pkttype = PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
pubnode = new_kbnode (pkt);
}
else
{
pubnode = clone_kbnode (secnode);
}
if (!pub_keyblock)
pub_keyblock = pubnode;
else
add_kbnode (pub_keyblock, pubnode);
}
return pub_keyblock;
}
/****************
* Ditto for secret keys. Handling is simpler than for public keys.
* We allow secret key importing only when allow is true, this is so
* that a secret key can not be imported accidentally and thereby tampering
* with the trust calculation.
*/
static int
import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch, unsigned int options,
int for_migration,
import_screener_t screener, void *screener_arg)
{
PKT_public_key *pk;
struct seckey_info *ski;
kbnode_t node, uidnode;
u32 keyid[2];
int rc = 0;
int nr_prev;
kbnode_t pub_keyblock;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Get the key and print some info about it */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
uidnode = find_next_kbnode (keyblock, PKT_USER_ID);
if (screener && screener (keyblock, screener_arg))
{
log_error (_("secret key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.verbose && !for_migration)
{
log_info ("sec %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk), datestr_from_pk (pk));
if (uidnode)
print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len);
log_printf ("\n");
}
stats->secret_read++;
if ((options & IMPORT_NO_SECKEY))
{
if (!for_migration)
log_error (_("importing secret keys not allowed\n"));
return 0;
}
if (!uidnode)
{
if (!for_migration)
log_error( _("key %s: no user ID\n"), keystr_from_pk (pk));
return 0;
}
ski = pk->seckey_info;
if (!ski)
{
/* Actually an internal error. */
log_error ("key %s: secret key info missing\n", keystr_from_pk (pk));
return 0;
}
/* A quick check to not import keys with an invalid protection
cipher algorithm (only checks the primary key, though). */
if (ski->algo > 110)
{
if (!for_migration)
log_error (_("key %s: secret key with invalid cipher %d"
" - skipped\n"), keystr_from_pk (pk), ski->algo);
return 0;
}
#ifdef ENABLE_SELINUX_HACKS
if (1)
{
/* We don't allow importing secret keys because that may be used
to put a secret key into the keyring and the user might later
be tricked into signing stuff with that key. */
log_error (_("importing secret keys not allowed\n"));
return 0;
}
#endif
clear_kbnode_flags (keyblock);
nr_prev = stats->skipped_new_keys;
/* Make a public key out of the key. */
pub_keyblock = sec_to_pub_keyblock (keyblock);
if (!pub_keyblock)
log_error ("key %s: failed to create public key from secret key\n",
keystr_from_pk (pk));
else
{
/* Note that this outputs an IMPORT_OK status message for the
public key block, and below we will output another one for
the secret keys. FIXME? */
import_one (ctrl, pub_keyblock, stats,
NULL, NULL, options, 1, for_migration,
screener, screener_arg);
/* Fixme: We should check for an invalid keyblock and
cancel the secret key import in this case. */
release_kbnode (pub_keyblock);
/* At least we cancel the secret key import when the public key
import was skipped due to MERGE_ONLY option and a new
key. */
if (stats->skipped_new_keys <= nr_prev)
{
/* Read the keyblock again to get the effects of a merge. */
/* Fixme: we should do this based on the fingerprint or
even better let import_one return the merged
keyblock. */
node = get_pubkeyblock (keyid);
if (!node)
log_error ("key %s: failed to re-lookup public key\n",
keystr_from_pk (pk));
else
{
gpg_error_t err;
/* transfer_secret_keys collects subkey stats. */
struct import_stats_s subkey_stats = {0};
err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
batch, 0);
if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
{
/* TRANSLATORS: For smartcard, each private key on
host has a reference (stub) to a smartcard and
actual private key data is stored on the card. A
single smartcard can have up to three private key
data. Importing private key stub is always
skipped in 2.1, and it returns
GPG_ERR_NOT_PROCESSED. Instead, user should be
suggested to run 'gpg --card-status', then,
references to a card will be automatically
created again. */
log_info (_("To migrate '%s', with each smartcard, "
"run: %s\n"), "secring.gpg", "gpg --card-status");
err = 0;
}
if (!err)
{
int status = 16;
if (!opt.quiet)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk (pk));
if (subkey_stats.secret_imported)
{
status |= 1;
stats->secret_imported += 1;
}
if (subkey_stats.secret_dups)
stats->secret_dups += 1;
if (is_status_enabled ())
print_import_ok (pk, status);
check_prefs (ctrl, node);
}
release_kbnode (node);
}
}
}
return rc;
}
/****************
* Import a revocation certificate; this is a single signature packet.
*/
static int
import_revoke_cert (ctrl_t ctrl, kbnode_t node, struct import_stats_s *stats)
{
PKT_public_key *pk = NULL;
kbnode_t onode;
kbnode_t keyblock = NULL;
KEYDB_HANDLE hd = NULL;
u32 keyid[2];
int rc = 0;
log_assert (!node->next );
log_assert (node->pkt->pkttype == PKT_SIGNATURE );
log_assert (node->pkt->pkt.signature->sig_class == 0x20 );
keyid[0] = node->pkt->pkt.signature->keyid[0];
keyid[1] = node->pkt->pkt.signature->keyid[1];
pk = xmalloc_clear( sizeof *pk );
rc = get_pubkey( pk, keyid );
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY )
{
log_error(_("key %s: no public key -"
" can't apply revocation certificate\n"), keystr(keyid));
rc = 0;
goto leave;
}
else if (rc )
{
log_error(_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* Read the original keyblock. */
hd = keydb_new ();
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
fingerprint_from_pk (pk, afp, &an);
while (an < MAX_FINGERPRINT_LEN)
afp[an++] = 0;
rc = keydb_search_fpr (hd, afp);
}
if (rc)
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock );
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* it is okay, that node is not in keyblock because
* check_key_signature works fine for sig_class 0x20 in this
* special case. */
rc = check_key_signature( keyblock, node, NULL);
if (rc )
{
log_error( _("key %s: invalid revocation certificate"
": %s - rejected\n"), keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* check whether we already have this */
for(onode=keyblock->next; onode; onode=onode->next ) {
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& !cmp_signatures(node->pkt->pkt.signature,
onode->pkt->pkt.signature))
{
rc = 0;
goto leave; /* yes, we already know about it */
}
}
/* insert it */
insert_kbnode( keyblock, clone_kbnode(node), 0 );
/* and write the keyblock back */
rc = keydb_update_keyblock (ctrl, hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
keydb_release (hd);
hd = NULL;
/* we are ready */
if (!opt.quiet )
{
char *p=get_user_id_native (keyid);
log_info( _("key %s: \"%s\" revocation certificate imported\n"),
keystr(keyid),p);
xfree(p);
}
stats->n_revoc++;
/* If the key we just revoked was ultimately trusted, remove its
ultimate trust. This doesn't stop the user from putting the
ultimate trust back, but is a reasonable solution for now. */
if(get_ownertrust(pk)==TRUST_ULTIMATE)
clear_ownertrusts(pk);
revalidation_mark ();
leave:
keydb_release (hd);
release_kbnode( keyblock );
free_public_key( pk );
return rc;
}
/* Loop over the keyblock and check all self signatures. On return
* the following bis in the node flags are set:
*
* - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature
* - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature
* - NODE_DELETION_MARK :: This node shall be deleted
*
* NON_SELF is set to true if there are any sigs other than self-sigs
* in this keyblock.
*
* Returns 0 on success or -1 (but not an error code) if the keyblock
* is invalid.
*/
static int
chk_self_sigs (kbnode_t keyblock, u32 *keyid, int *non_self )
{
kbnode_t n, knode = NULL;
PKT_signature *sig;
int rc;
u32 bsdate=0, rsdate=0;
kbnode_t bsnode = NULL, rsnode = NULL;
for (n=keyblock; (n = find_next_kbnode (n, 0)); )
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
knode = n;
bsdate = 0;
rsdate = 0;
bsnode = NULL;
rsnode = NULL;
continue;
}
if ( n->pkt->pkttype != PKT_SIGNATURE )
continue;
sig = n->pkt->pkt.signature;
if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] )
{
*non_self = 1;
continue;
}
/* This just caches the sigs for later use. That way we
import a fully-cached key which speeds things up. */
if (!opt.no_sig_cache)
check_key_signature (keyblock, n, NULL);
if ( IS_UID_SIG(sig) || IS_UID_REV(sig) )
{
kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID );
if ( !unode )
{
log_error( _("key %s: no user ID for signature\n"),
keystr(keyid));
return -1; /* The complete keyblock is invalid. */
}
/* If it hasn't been marked valid yet, keep trying. */
if (!(unode->flag & NODE_GOOD_SELFSIG))
{
rc = check_key_signature (keyblock, n, NULL);
if ( rc )
{
if ( opt.verbose )
{
char *p = utf8_to_native
(unode->pkt->pkt.user_id->name,
strlen (unode->pkt->pkt.user_id->name),0);
log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key "
"algorithm on user ID \"%s\"\n"):
_("key %s: invalid self-signature "
"on user ID \"%s\"\n"),
keystr (keyid),p);
xfree (p);
}
}
else
unode->flag |= NODE_GOOD_SELFSIG;
}
}
else if (IS_KEY_SIG (sig))
{
rc = check_key_signature (keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key algorithm\n"):
_("key %s: invalid direct key signature\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
}
else if ( IS_SUBKEY_SIG (sig) )
{
/* Note that this works based solely on the timestamps like
the rest of gpg. If the standard gets revocation
targets, this may need to be revised. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key"
" algorithm\n"):
_("key %s: invalid subkey binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= bsdate)
{
knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */
if (bsnode)
{
/* Delete the last binding sig since this
one is newer */
bsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" binding\n"),keystr(keyid));
}
bsnode = n;
bsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
else if ( IS_SUBKEY_REV (sig) )
{
/* We don't actually mark the subkey as revoked right now,
so just check that the revocation sig is the most recent
valid one. Note that we don't care if the binding sig is
newer than the revocation sig. See the comment in
getkey.c:merge_selfsigs_subkey for more. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (keyblock, n, NULL);
if ( rc )
{
if(opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public"
" key algorithm\n"):
_("key %s: invalid subkey revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= rsdate)
{
if (rsnode)
{
/* Delete the last revocation sig since
this one is newer. */
rsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" revocation\n"),keystr(keyid));
}
rsnode = n;
rsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
}
return 0;
}
/* Delete all parts which are invalid and those signatures whose
* public key algorithm is not available in this implemenation; but
* consider RSA as valid, because parse/build_packets knows about it.
*
* Returns: True if at least one valid user-id is left over.
*/
static int
delete_inv_parts (kbnode_t keyblock, u32 *keyid, unsigned int options)
{
kbnode_t node;
int nvalid=0, uid_seen=0, subkey_seen=0;
for (node=keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid_seen = 1;
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
char *p=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
log_info( _("key %s: skipped user ID \"%s\"\n"),
keystr(keyid),p);
xfree(p);
}
delete_kbnode( node ); /* the user-id */
/* and all following packets up to the next user-id */
while (node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){
delete_kbnode( node->next );
node = node->next;
}
}
else
nvalid++;
}
else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
log_info( _("key %s: skipped subkey\n"),keystr(keyid));
delete_kbnode( node ); /* the subkey */
/* and all following signature packets */
while (node->next
&& node->next->pkt->pkttype == PKT_SIGNATURE ) {
delete_kbnode( node->next );
node = node->next;
}
}
else
subkey_seen = 1;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo)
&& node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA )
{
delete_kbnode( node ); /* build_packet() can't handle this */
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
&& !have_secret_key_with_kid (node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
* the secret key used to create this signature - it
* seems that this makes sense */
if(opt.verbose)
log_info( _("key %s: non exportable signature"
" (class 0x%02X) - skipped\n"),
keystr(keyid), node->pkt->pkt.signature->sig_class );
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& node->pkt->pkt.signature->sig_class == 0x20)
{
if (uid_seen )
{
if(opt.verbose)
log_info( _("key %s: revocation certificate"
" at wrong place - skipped\n"),keystr(keyid));
delete_kbnode( node );
}
else
{
/* If the revocation cert is from a different key than
the one we're working on don't check it - it's
probably from a revocation key and won't be
verifiable with this key anyway. */
if(node->pkt->pkt.signature->keyid[0]==keyid[0]
&& node->pkt->pkt.signature->keyid[1]==keyid[1])
{
int rc = check_key_signature( keyblock, node, NULL);
if (rc )
{
if(opt.verbose)
log_info( _("key %s: invalid revocation"
" certificate: %s - skipped\n"),
keystr(keyid), gpg_strerror (rc));
delete_kbnode( node );
}
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class == 0x18
|| node->pkt->pkt.signature->sig_class == 0x28)
&& !subkey_seen )
{
if(opt.verbose)
log_info( _("key %s: subkey signature"
" in wrong place - skipped\n"), keystr(keyid));
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !IS_CERT(node->pkt->pkt.signature))
{
if(opt.verbose)
log_info(_("key %s: unexpected signature class (0x%02X) -"
" skipped\n"),keystr(keyid),
node->pkt->pkt.signature->sig_class);
delete_kbnode(node);
}
else if ((node->flag & NODE_DELETION_MARK))
delete_kbnode( node );
}
/* note: because keyblock is the public key, it is never marked
* for deletion and so keyblock cannot change */
commit_kbnode( &keyblock );
return nvalid;
}
/* This function returns true if any UID is left in the keyring. */
static int
any_uid_left (kbnode_t keyblock)
{
kbnode_t node;
for (node=keyblock->next; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
return 1;
return 0;
}
/****************
* It may happen that the imported keyblock has duplicated user IDs.
* We check this here and collapse those user IDs together with their
* sigs into one.
* Returns: True if the keyblock has changed.
*/
int
collapse_uids( kbnode_t *keyblock )
{
kbnode_t uid1;
int any=0;
for(uid1=*keyblock;uid1;uid1=uid1->next)
{
kbnode_t uid2;
if(is_deleted_kbnode(uid1))
continue;
if(uid1->pkt->pkttype!=PKT_USER_ID)
continue;
for(uid2=uid1->next;uid2;uid2=uid2->next)
{
if(is_deleted_kbnode(uid2))
continue;
if(uid2->pkt->pkttype!=PKT_USER_ID)
continue;
if(cmp_user_ids(uid1->pkt->pkt.user_id,
uid2->pkt->pkt.user_id)==0)
{
/* We have a duplicated uid */
kbnode_t sig1,last;
any=1;
/* Now take uid2's signatures, and attach them to
uid1 */
for(last=uid2;last->next;last=last->next)
{
if(is_deleted_kbnode(last))
continue;
if(last->next->pkt->pkttype==PKT_USER_ID
|| last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| last->next->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
}
/* Snip out uid2 */
(find_prev_kbnode(*keyblock,uid2,0))->next=last->next;
/* Now put uid2 in place as part of uid1 */
last->next=uid1->next;
uid1->next=uid2;
delete_kbnode(uid2);
/* Now dedupe uid1 */
for(sig1=uid1->next;sig1;sig1=sig1->next)
{
kbnode_t sig2;
if(is_deleted_kbnode(sig1))
continue;
if(sig1->pkt->pkttype==PKT_USER_ID
|| sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig1->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig1->pkt->pkttype!=PKT_SIGNATURE)
continue;
for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next)
{
if(is_deleted_kbnode(sig2))
continue;
if(sig2->pkt->pkttype==PKT_USER_ID
|| sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig2->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig2->pkt->pkttype!=PKT_SIGNATURE)
continue;
if(cmp_signatures(sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature)==0)
{
/* We have a match, so delete the second
signature */
delete_kbnode(sig2);
sig2=last;
}
}
}
}
}
}
commit_kbnode(keyblock);
if(any && !opt.quiet)
{
const char *key="???";
if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
log_info (_("key %s: duplicated user ID detected - merged\n"), key);
}
return any;
}
/* Check for a 0x20 revocation from a revocation key that is not
present. This may be called without the benefit of merge_xxxx so
you can't rely on pk->revkey and friends. */
static void
revocation_present (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t onode, inode;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
for(onode=keyblock->next;onode;onode=onode->next)
{
/* If we reach user IDs, we're done. */
if(onode->pkt->pkttype==PKT_USER_ID)
break;
if(onode->pkt->pkttype==PKT_SIGNATURE &&
onode->pkt->pkt.signature->sig_class==0x1F &&
onode->pkt->pkt.signature->revkey)
{
int idx;
PKT_signature *sig=onode->pkt->pkt.signature;
for(idx=0;idx<sig->numrevkeys;idx++)
{
u32 keyid[2];
keyid_from_fingerprint(sig->revkey[idx].fpr,
MAX_FINGERPRINT_LEN,keyid);
for(inode=keyblock->next;inode;inode=inode->next)
{
/* If we reach user IDs, we're done. */
if(inode->pkt->pkttype==PKT_USER_ID)
break;
if(inode->pkt->pkttype==PKT_SIGNATURE &&
inode->pkt->pkt.signature->sig_class==0x20 &&
inode->pkt->pkt.signature->keyid[0]==keyid[0] &&
inode->pkt->pkt.signature->keyid[1]==keyid[1])
{
/* Okay, we have a revocation key, and a
revocation issued by it. Do we have the key
itself? */
int rc;
rc=get_pubkey_byfprint_fast (NULL,sig->revkey[idx].fpr,
MAX_FINGERPRINT_LEN);
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (rc) == GPG_ERR_UNUSABLE_PUBKEY)
{
char *tempkeystr=xstrdup(keystr_from_pk(pk));
/* No, so try and get it */
if ((opt.keyserver_options.options
& KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (ctrl))
{
log_info(_("WARNING: key %s may be revoked:"
" fetching revocation key %s\n"),
tempkeystr,keystr(keyid));
keyserver_import_fprint (ctrl,
sig->revkey[idx].fpr,
MAX_FINGERPRINT_LEN,
opt.keyserver, 0);
/* Do we have it now? */
rc=get_pubkey_byfprint_fast (NULL,
sig->revkey[idx].fpr,
MAX_FINGERPRINT_LEN);
}
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (rc) == GPG_ERR_UNUSABLE_PUBKEY)
log_info(_("WARNING: key %s may be revoked:"
" revocation key %s not present.\n"),
tempkeystr,keystr(keyid));
xfree(tempkeystr);
}
}
}
}
}
}
}
/*
* compare and merge the blocks
*
* o compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* o Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* Note: We indicate newly inserted packets with NODE_FLAG_A.
*/
static int
merge_blocks (kbnode_t keyblock_orig, kbnode_t keyblock,
u32 *keyid, int *n_uids, int *n_sigs, int *n_subk )
{
kbnode_t onode, node;
int rc, found;
/* 1st: handle revocation certificates */
for (node=keyblock->next; node; node=node->next )
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& node->pkt->pkt.signature->sig_class == 0x20)
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& onode->pkt->pkt.signature->sig_class == 0x20
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found)
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
{
char *p=get_user_id_native (keyid);
log_info(_("key %s: \"%s\" revocation"
" certificate added\n"), keystr(keyid),p);
xfree(p);
}
}
}
}
/* 2nd: merge in any direct key (0x1F) sigs */
for(node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& node->pkt->pkt.signature->sig_class == 0x1F)
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID)
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& onode->pkt->pkt.signature->sig_class == 0x1F
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found )
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
log_info( _("key %s: direct key signature added\n"),
keystr(keyid));
}
}
}
/* 3rd: try to merge new certificates in */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID)
{
/* find the user id in the imported keyblock */
for (node=keyblock->next; node; node=node->next)
if (node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (node ) /* found: merge */
{
rc = merge_sigs (onode, node, n_sigs);
if (rc )
return rc;
}
}
}
/* 4th: add new user-ids */
for (node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
/* do we have this in the original keyblock */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (!onode ) /* this is a new user id: append */
{
rc = append_uid (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_uids;
}
}
}
/* 5th: add new subkeys */
for (node=keyblock->next; node; node=node->next)
{
onode = NULL;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
/* do we have this in the original keyblock? */
for(onode=keyblock_orig->next; onode; onode=onode->next)
if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key))
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc)
return rc;
++*n_subk;
}
}
else if (node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* do we have this in the original keyblock? */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_SECRET_SUBKEY
&& !cmp_public_keys (onode->pkt->pkt.public_key,
node->pkt->pkt.public_key) )
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_subk;
}
}
}
/* 6th: merge subkey certificates */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A)
&& (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| onode->pkt->pkttype == PKT_SECRET_SUBKEY))
{
/* find the subkey in the imported keyblock */
for(node=keyblock->next; node; node=node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key ) )
break;
}
if (node) /* Found: merge. */
{
rc = merge_keysigs( onode, node, n_sigs);
if (rc )
return rc;
}
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the userid starting with NODE and all signatures to KEYBLOCK.
*/
static int
append_uid (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
kbnode_t n_where = NULL;
log_assert (node->pkt->pkttype == PKT_USER_ID );
/* find the position */
for (n = keyblock; n; n_where = n, n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY )
break;
}
if (!n)
n_where = NULL;
/* and append/insert */
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
if (n_where)
{
insert_kbnode( n_where, n, 0 );
n_where = n;
}
else
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID.
* (how should we handle comment packets here?)
*/
static int
merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_USER_ID);
log_assert (src->pkt->pkttype == PKT_USER_ID);
for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next)
{
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
if (n->pkt->pkt.signature->sig_class == 0x18
|| n->pkt->pkt.signature->sig_class == 0x28 )
continue; /* skip signatures which are only valid on subkeys */
found = 0;
for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next)
if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature))
{
found++;
break;
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY.
*/
static int
merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| dst->pkt->pkttype == PKT_SECRET_SUBKEY);
for (n=src->next; n ; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
found = 0;
for (n2=dst->next; n2; n2 = n2->next)
{
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n2->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n2->pkt->pkttype == PKT_SIGNATURE
&& (n->pkt->pkt.signature->keyid[0]
== n2->pkt->pkt.signature->keyid[0])
&& (n->pkt->pkt.signature->keyid[1]
== n2->pkt->pkt.signature->keyid[1])
&& (n->pkt->pkt.signature->timestamp
<= n2->pkt->pkt.signature->timestamp)
&& (n->pkt->pkt.signature->sig_class
== n2->pkt->pkt.signature->sig_class))
{
found++;
break;
}
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the subkey starting with NODE and all signatures to KEYBLOCK.
* Mark all new and copied packets by setting flag bit 0.
*/
static int
append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
diff --git a/g10/kbnode.c b/g10/kbnode.c
index 6700dc026..153dce2b6 100644
--- a/g10/kbnode.c
+++ b/g10/kbnode.c
@@ -1,431 +1,431 @@
/* kbnode.c - keyblock node utility functions
* Copyright (C) 1998, 1999, 2000, 2001, 2002,
* 2005, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "../common/init.h"
#include "packet.h"
#include "keydb.h"
#define USE_UNUSED_NODES 1
static int cleanup_registered;
static KBNODE unused_nodes;
static void
release_unused_nodes (void)
{
#if USE_UNUSED_NODES
while (unused_nodes)
{
kbnode_t next = unused_nodes->next;
xfree (unused_nodes);
unused_nodes = next;
}
#endif /*USE_UNUSED_NODES*/
}
static kbnode_t
alloc_node (void)
{
kbnode_t n;
n = unused_nodes;
if (n)
unused_nodes = n->next;
else
{
if (!cleanup_registered)
{
cleanup_registered = 1;
register_mem_cleanup_func (release_unused_nodes);
}
n = xmalloc (sizeof *n);
}
n->next = NULL;
n->pkt = NULL;
n->flag = 0;
n->private_flag=0;
n->recno = 0;
return n;
}
static void
free_node( KBNODE n )
{
if (n)
{
#if USE_UNUSED_NODES
n->next = unused_nodes;
unused_nodes = n;
#else
xfree (n);
#endif
}
}
KBNODE
new_kbnode( PACKET *pkt )
{
KBNODE n = alloc_node();
n->pkt = pkt;
return n;
}
KBNODE
clone_kbnode( KBNODE node )
{
KBNODE n = alloc_node();
n->pkt = node->pkt;
n->private_flag = node->private_flag | 2; /* mark cloned */
return n;
}
void
release_kbnode( KBNODE n )
{
KBNODE n2;
while( n ) {
n2 = n->next;
if( !is_cloned_kbnode(n) ) {
free_packet( n->pkt );
xfree( n->pkt );
}
free_node( n );
n = n2;
}
}
/****************
* Delete NODE.
* Note: This only works with walk_kbnode!!
*/
void
delete_kbnode( KBNODE node )
{
node->private_flag |= 1;
}
/****************
* Append NODE to ROOT. ROOT must exist!
*/
void
add_kbnode( KBNODE root, KBNODE node )
{
KBNODE n1;
for(n1=root; n1->next; n1 = n1->next)
;
n1->next = node;
}
/****************
* Insert NODE into the list after root but before a packet which is not of
* type PKTTYPE
* (only if PKTTYPE != 0)
*/
void
insert_kbnode( KBNODE root, KBNODE node, int pkttype )
{
if( !pkttype ) {
node->next = root->next;
root->next = node;
}
else {
KBNODE n1;
for(n1=root; n1->next; n1 = n1->next)
if( pkttype != n1->next->pkt->pkttype ) {
node->next = n1->next;
n1->next = node;
return;
}
/* no such packet, append */
node->next = NULL;
n1->next = node;
}
}
/****************
* Find the previous node (if PKTTYPE = 0) or the previous node
* with pkttype PKTTYPE in the list starting with ROOT of NODE.
*/
KBNODE
find_prev_kbnode( KBNODE root, KBNODE node, int pkttype )
{
KBNODE n1;
for (n1=NULL; root && root != node; root = root->next ) {
if (!pkttype ||root->pkt->pkttype == pkttype)
n1 = root;
}
return n1;
}
/****************
* Ditto, but find the next packet. The behaviour is trivial if
* PKTTYPE is 0 but if it is specified, the next node with a packet
* of this type is returned. The function has some knowledge about
* the valid ordering of packets: e.g. if the next signature packet
* is requested, the function will not return one if it encounters
* a user-id.
*/
KBNODE
find_next_kbnode( KBNODE node, int pkttype )
{
for( node=node->next ; node; node = node->next ) {
if( !pkttype )
return node;
else if( pkttype == PKT_USER_ID
&& ( node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY ) )
return NULL;
else if( pkttype == PKT_SIGNATURE
&& ( node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY ) )
return NULL;
else if( node->pkt->pkttype == pkttype )
return node;
}
return NULL;
}
KBNODE
find_kbnode( KBNODE node, int pkttype )
{
for( ; node; node = node->next ) {
if( node->pkt->pkttype == pkttype )
return node;
}
return NULL;
}
/****************
* Walk through a list of kbnodes. This function returns
* the next kbnode for each call; before using the function the first
* time, the caller must set CONTEXT to NULL (This has simply the effect
* to start with ROOT).
*/
KBNODE
walk_kbnode( KBNODE root, KBNODE *context, int all )
{
KBNODE n;
do {
if( !*context ) {
*context = root;
n = root;
}
else {
n = (*context)->next;
*context = n;
}
} while( !all && n && is_deleted_kbnode(n) );
return n;
}
void
clear_kbnode_flags( KBNODE n )
{
for( ; n; n = n->next ) {
n->flag = 0;
}
}
/****************
* Commit changes made to the kblist at ROOT. Note that ROOT my change,
* and it is therefore passed by reference.
* The function has the effect of removing all nodes marked as deleted.
* returns true if any node has been changed
*/
int
commit_kbnode( KBNODE *root )
{
KBNODE n, nl;
int changed = 0;
for( n = *root, nl=NULL; n; n = nl->next ) {
if( is_deleted_kbnode(n) ) {
if( n == *root )
*root = nl = n->next;
else
nl->next = n->next;
if( !is_cloned_kbnode(n) ) {
free_packet( n->pkt );
xfree( n->pkt );
}
free_node( n );
changed = 1;
}
else
nl = n;
}
return changed;
}
void
remove_kbnode( KBNODE *root, KBNODE node )
{
KBNODE n, nl;
for( n = *root, nl=NULL; n; n = nl->next ) {
if( n == node ) {
if( n == *root )
*root = nl = n->next;
else
nl->next = n->next;
if( !is_cloned_kbnode(n) ) {
free_packet( n->pkt );
xfree( n->pkt );
}
free_node( n );
}
else
nl = n;
}
}
/****************
* Move NODE behind right after WHERE or to the beginning if WHERE is NULL.
*/
void
move_kbnode( KBNODE *root, KBNODE node, KBNODE where )
{
KBNODE tmp, prev;
if( !root || !*root || !node )
return; /* sanity check */
for( prev = *root; prev && prev->next != node; prev = prev->next )
;
if( !prev )
return; /* node is not in the list */
if( !where ) { /* move node before root */
if( node == *root ) /* move to itself */
return;
prev->next = node->next;
node->next = *root;
*root = node;
return;
}
/* move it after where */
if( node == where )
return;
tmp = node->next;
node->next = where->next;
where->next = node;
prev->next = tmp;
}
void
dump_kbnode (KBNODE node)
{
for (; node; node = node->next )
{
const char *s;
switch (node->pkt->pkttype)
{
case 0: s="empty"; break;
case PKT_PUBLIC_KEY: s="public-key"; break;
case PKT_SECRET_KEY: s="secret-key"; break;
case PKT_SECRET_SUBKEY: s= "secret-subkey"; break;
case PKT_PUBKEY_ENC: s="public-enc"; break;
case PKT_SIGNATURE: s="signature"; break;
case PKT_ONEPASS_SIG: s="onepass-sig"; break;
case PKT_USER_ID: s="user-id"; break;
case PKT_PUBLIC_SUBKEY: s="public-subkey"; break;
case PKT_COMMENT: s="comment"; break;
case PKT_RING_TRUST: s="trust"; break;
case PKT_PLAINTEXT: s="plaintext"; break;
case PKT_COMPRESSED: s="compressed"; break;
case PKT_ENCRYPTED: s="encrypted"; break;
case PKT_GPG_CONTROL: s="gpg-control"; break;
default: s="unknown"; break;
}
log_debug ("node %p %02x/%02x type=%s",
node, node->flag, node->private_flag, s);
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
log_printf (" \"");
es_write_sanitized (log_get_stream (), uid->name, uid->len,
NULL, NULL);
log_printf ("\" %c%c%c%c\n",
uid->is_expired? 'e':'.',
uid->is_revoked? 'r':'.',
uid->created? 'v':'.',
uid->is_primary? 'p':'.' );
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
log_printf (" class=%02x keyid=%08lX ts=%lu\n",
node->pkt->pkt.signature->sig_class,
(ulong)node->pkt->pkt.signature->keyid[1],
(ulong)node->pkt->pkt.signature->timestamp);
}
else if (node->pkt->pkttype == PKT_GPG_CONTROL)
{
log_printf (" ctrl=%d len=%u\n",
node->pkt->pkt.gpg_control->control,
(unsigned int)node->pkt->pkt.gpg_control->datalen);
}
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
log_printf (" keyid=%08lX a=%d u=%d %c%c%c%c\n",
(ulong)keyid_from_pk( pk, NULL ),
pk->pubkey_algo, pk->pubkey_usage,
pk->has_expired? 'e':'.',
pk->flags.revoked? 'r':'.',
pk->flags.valid? 'v':'.',
pk->flags.mdc? 'm':'.');
}
else
log_printf ("\n");
log_flush ();
}
}
diff --git a/g10/keydb.c b/g10/keydb.c
index 4c5149da2..27dacf2d5 100644
--- a/g10/keydb.c
+++ b/g10/keydb.c
@@ -1,2097 +1,2097 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001-2013 Free Software Foundation, Inc.
* Coyrright (C) 2001-2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
#include "main.h" /*try_make_homedir ()*/
#include "packet.h"
#include "keyring.h"
#include "../kbx/keybox.h"
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
static int active_handles;
typedef enum
{
KEYDB_RESOURCE_TYPE_NONE = 0,
KEYDB_RESOURCE_TYPE_KEYRING,
KEYDB_RESOURCE_TYPE_KEYBOX
} KeydbResourceType;
#define MAX_KEYDB_RESOURCES 40
struct resource_item
{
KeydbResourceType type;
union {
KEYRING_HANDLE kr;
KEYBOX_HANDLE kb;
} u;
void *token;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* A pointer used to check for the primary key database by comparing
to the struct resource_item's TOKEN. */
static void *primary_keydb;
/* Whether we have successfully registered any resource. */
static int any_registered;
/* This is a simple cache used to return the last result of a
successful fingerprint search. This works only for keybox resources
because (due to lack of a copy_keyblock function) we need to store
an image of the keyblock which is fortunately instantly available
for keyboxes. */
enum keyblock_cache_states {
KEYBLOCK_CACHE_EMPTY,
KEYBLOCK_CACHE_PREPARED,
KEYBLOCK_CACHE_FILLED
};
struct keyblock_cache {
enum keyblock_cache_states state;
byte fpr[MAX_FINGERPRINT_LEN];
iobuf_t iobuf; /* Image of the keyblock. */
u32 *sigstatus;
int pk_no;
int uid_no;
/* Offset of the record in the keybox. */
int resource;
off_t offset;
};
struct keydb_handle
{
/* When we locked all of the resources in ACTIVE (using keyring_lock
/ keybox_lock, as appropriate). */
int locked;
/* The index into ACTIVE of the resources in which the last search
result was found. Initially -1. */
int found;
/* Initially -1 (invalid). This is used to save a search result and
later restore it as the selected result. */
int saved_found;
/* The number of skipped long blobs since the last search
(keydb_search_reset). */
unsigned long skipped_long_blobs;
/* If set, this disables the use of the keyblock cache. */
int no_caching;
/* Whether the next search will be from the beginning of the
database (and thus consider all records). */
int is_reset;
/* The "file position." In our case, this is index of the current
resource in ACTIVE. */
int current;
/* The number of resources in ACTIVE. */
int used;
/* Cache of the last found and parsed key block (only used for
keyboxes, not keyrings). */
struct keyblock_cache keyblock_cache;
/* Copy of ALL_RESOURCES when keydb_new is called. */
struct resource_item active[MAX_KEYDB_RESOURCES];
};
/* Looking up keys is expensive. To hide the cost, we cache whether
keys exist in the key database. Then, if we know a key does not
exist, we don't have to spend time looking it up. This
particularly helps the --list-sigs and --check-sigs commands.
The cache stores the results in a hash using separate chaining.
Concretely: we use the LSB of the keyid to index the hash table and
each bucket consists of a linked list of entries. An entry
consists of the 64-bit key id. If a key id is not in the cache,
then we don't know whether it is in the DB or not.
To simplify the cache consistency protocol, we simply flush the
whole cache whenever a key is inserted or updated. */
#define KID_NOT_FOUND_CACHE_BUCKETS 256
static struct kid_not_found_cache_bucket *
kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS];
/* The total number of entries in the hash table. */
static unsigned int kid_not_found_cache_count;
struct kid_not_found_cache_bucket
{
struct kid_not_found_cache_bucket *next;
u32 kid[2];
};
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
/* Check whether the keyid KID is in key id is definitely not in the
database.
Returns:
0 - Indeterminate: the key id is not in the cache; we don't know
whether the key is in the database or not. If you want a
definitive answer, you'll need to perform a lookup.
1 - There is definitely no key with this key id in the database.
We searched for a key with this key id previously, but we
didn't find it in the database. */
static int
kid_not_found_p (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
{
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n",
(ulong)kid[0], (ulong)kid[1]);
return 1;
}
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n",
(ulong)kid[0], (ulong)kid[1]);
return 0;
}
/* Insert the keyid KID into the kid_not_found_cache. FOUND is whether
the key is in the key database or not.
Note this function does not check whether the key id is already in
the cache. As such, kid_not_found_p() should be called first. */
static void
kid_not_found_insert (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n",
(ulong)kid[0], (ulong)kid[1]);
k = xmalloc (sizeof *k);
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS];
kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k;
kid_not_found_cache_count++;
}
/* Flush the kid not found cache. */
static void
kid_not_found_flush (void)
{
struct kid_not_found_cache_bucket *k, *knext;
int i;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_flush\n");
if (!kid_not_found_cache_count)
return;
for (i=0; i < DIM(kid_not_found_cache); i++)
{
for (k = kid_not_found_cache[i]; k; k = knext)
{
knext = k->next;
xfree (k);
}
kid_not_found_cache[i] = NULL;
}
kid_not_found_cache_count = 0;
}
static void
keyblock_cache_clear (struct keydb_handle *hd)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
xfree (hd->keyblock_cache.sigstatus);
hd->keyblock_cache.sigstatus = NULL;
iobuf_close (hd->keyblock_cache.iobuf);
hd->keyblock_cache.iobuf = NULL;
hd->keyblock_cache.resource = -1;
hd->keyblock_cache.offset = -1;
}
/* Handle the creation of a keyring or a keybox if it does not yet
exist. Take into account that other processes might have the
keyring/keybox already locked. This lock check does not work if
the directory itself is not yet available. If IS_BOX is true the
filename is expected to refer to a keybox. If FORCE_CREATE is true
the keyring or keybox will be created.
Return 0 if it is okay to access the specified file. */
static gpg_error_t
maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
{
dotlock_t lockhd = NULL;
IOBUF iobuf;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
char *bak_fname = NULL;
char *tmp_fname = NULL;
int save_slash;
/* A quick test whether the filename already exists. */
if (!access (filename, F_OK))
return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force_create)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for certain home
directory name pattern. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if (access (filename, F_OK))
{
rc = gpg_error_from_syserror ();
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keyring (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
rc = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (rc));
if (!force_create)
return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */
else
return rc;
}
if ( dotlock_take (lockhd, -1) )
{
rc = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc));
goto leave;
}
/* Now the real test while we are locked. */
/* Gpg either uses pubring.gpg or pubring.kbx and thus different
* lock files. Now, when one gpg process is updating a pubring.gpg
* and thus holding the corresponding lock, a second gpg process may
* get to here at the time between the two rename operation used by
* the first process to update pubring.gpg. The lock taken above
* may not protect the second process if it tries to create a
* pubring.kbx file which would be protected by a different lock
* file.
*
* We can detect this case by checking that the two temporary files
* used by the update code exist at the same time. In that case we
* do not create a new file but act as if FORCE_CREATE has not been
* given. Obviously there is a race between our two checks but the
* worst thing is that we won't create a new file, which is better
* than to accidentally creating one. */
rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname);
if (rc)
goto leave;
if (!access (filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
if (!access (bak_fname, F_OK) && !access (tmp_fname, F_OK))
{
/* Very likely another process is updating a pubring.gpg and we
should not create a pubring.kbx. */
rc = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
if (is_secured_filename (filename))
{
iobuf = NULL;
gpg_err_set_errno (EPERM);
}
else
iobuf = iobuf_create (filename, 0);
umask (oldmask);
if (!iobuf)
{
rc = gpg_error_from_syserror ();
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
iobuf_close (iobuf);
/* Must invalidate that ugly cache */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic will work the next time it is used. */
if (is_box)
{
FILE *fp = fopen (filename, "wb");
if (!fp)
rc = gpg_error_from_syserror ();
else
{
rc = _keybox_write_header_blob (fp, 1);
fclose (fp);
}
if (rc)
{
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
}
if (!opt.quiet)
{
if (is_box)
log_info (_("keybox '%s' created\n"), filename);
else
log_info (_("keyring '%s' created\n"), filename);
}
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
xfree (bak_fname);
xfree (tmp_fname);
return rc;
}
/* Helper for keydb_add_resource. Opens FILENAME to figure out the
resource type.
Returns the specified file's likely type. If the file does not
exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0.
Otherwise, tries to figure out the file's type. This is either
KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or
KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has
the OpenPGP flag set, then R_OPENPGP is also set. */
static KeydbResourceType
rt_from_file (const char *filename, int *r_found, int *r_openpgp)
{
u32 magic;
unsigned char verbuf[4];
FILE *fp;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
*r_found = *r_openpgp = 0;
fp = fopen (filename, "rb");
if (fp)
{
*r_found = 1;
if (fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - not anymore supported. */
else if (fread (&verbuf, 4, 1, fp) == 1
&& verbuf[0] == 1
&& fread (&magic, 4, 1, fp) == 1
&& !memcmp (&magic, "KBXf", 4))
{
if ((verbuf[3] & 0x02))
*r_openpgp = 1;
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else
rt = KEYDB_RESOURCE_TYPE_KEYRING;
}
else /* Maybe empty: assume keyring. */
rt = KEYDB_RESOURCE_TYPE_KEYRING;
fclose (fp);
}
return rt;
}
char *
keydb_search_desc_dump (struct keydb_search_desc *desc)
{
char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
char fpr[2 * MAX_FINGERPRINT_LEN + 1];
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
return xasprintf ("EXACT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SUBSTR:
return xasprintf ("SUBSTR: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAIL:
return xasprintf ("MAIL: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILSUB:
return xasprintf ("MAILSUB: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILEND:
return xasprintf ("MAILEND: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_WORDS:
return xasprintf ("WORDS: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SHORT_KID:
return xasprintf ("SHORT_KID: '%s'",
format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b)));
case KEYDB_SEARCH_MODE_LONG_KID:
return xasprintf ("LONG_KID: '%s'",
format_keyid (desc->u.kid, KF_LONG, b, sizeof (b)));
case KEYDB_SEARCH_MODE_FPR16:
bin2hex (desc->u.fpr, 16, fpr);
return xasprintf ("FPR16: '%s'",
format_hexfingerprint (fpr, b, sizeof (b)));
case KEYDB_SEARCH_MODE_FPR20:
bin2hex (desc->u.fpr, 20, fpr);
return xasprintf ("FPR20: '%s'",
format_hexfingerprint (fpr, b, sizeof (b)));
case KEYDB_SEARCH_MODE_FPR:
bin2hex (desc->u.fpr, 20, fpr);
return xasprintf ("FPR: '%s'",
format_hexfingerprint (fpr, b, sizeof (b)));
case KEYDB_SEARCH_MODE_ISSUER:
return xasprintf ("ISSUER: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_ISSUER_SN:
return xasprintf ("ISSUER_SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SN:
return xasprintf ("SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
return xasprintf ("KEYGRIP: %s", desc->u.grip);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
return xasprintf ("NEXT");
default:
return xasprintf ("Bad search mode (%d)", desc->mode);
}
}
/* Register a resource (keyring or keybox). The first keyring or
* keybox that is added using this function is created if it does not
* already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set.
*
* FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants.
*
* URL must have the following form:
*
* gnupg-ring:filename = plain keyring
* gnupg-kbx:filename = keybox file
* filename = check file's type (create as a plain keyring)
*
* Note: on systems with drive letters (Windows) invalid URLs (i.e.,
* those with an unrecognized part before the ':' such as "c:\...")
* will silently be treated as bare filenames. On other systems, such
* URLs will cause this function to return GPG_ERR_GENERAL.
*
* If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring
* and the file ends in ".gpg", then this function also checks if a
* file with the same name, but the extension ".kbx" exists, is a
* keybox and the OpenPGP flag is set. If so, this function opens
* that resource instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and
* the URL ends in ".kbx", then this function will try opening the
* same URL, but with the extension ".gpg". If that file is a keybox
* with the OpenPGP flag set or it is a keyring, then we use that
* instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the
* file should be created and the file's extension is ".gpg" then we
* replace the extension with ".kbx".
*
* If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a
* keyring (not a keybox), then this resource is considered the
* primary resource. This is used by keydb_locate_writable(). If
* another primary keyring is set, then that keyring is considered the
* primary.
*
* If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a
* keyring (not a keybox), then the keyring is marked as read only and
* operations just as keyring_insert_keyblock will return
* GPG_ERR_ACCESS. */
gpg_error_t
keydb_add_resource (const char *url, unsigned int flags)
{
/* The file named by the URL (i.e., without the prototype). */
const char *resname = url;
char *filename = NULL;
int create;
int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
void *token;
/* Create the resource if it is the first registered one. */
create = (!read_only && !any_registered);
if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) )
{
rt = KEYDB_RESOURCE_TYPE_KEYRING;
resname += 11;
}
else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kbx:", 10) )
{
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
resname += 10;
}
#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
else if (strchr (resname, ':'))
{
log_error ("invalid key resource URL '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
if (*resname != DIRSEP_C
#ifdef HAVE_W32_SYSTEM
&& *resname != '/' /* Fixme: does not handle drive letters. */
#endif
)
{
/* Do tilde expansion etc. */
if (strchr (resname, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (resname, '/') /* Windows also accepts this. */
#endif
)
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
/* See whether we can determine the filetype. */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
int found, openpgp_flag;
int pass = 0;
size_t filenamelen;
check_again:
filenamelen = strlen (filename);
rt = rt_from_file (filename, &found, &openpgp_flag);
if (found)
{
/* The file exists and we have the resource type in RT.
Now let us check whether in addition to the "pubring.gpg"
a "pubring.kbx with openpgp keys exists. This is so that
GPG 2.1 will use an existing "pubring.kbx" by default iff
that file has been created or used by 2.1. This check is
needed because after creation or use of the kbx file with
2.1 an older version of gpg may have created a new
pubring.gpg for its own use. */
if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
strcpy (filename+filenamelen-4, ".kbx");
if ((rt_from_file (filename, &found, &openpgp_flag)
== KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag)
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".gpg");
}
}
else if (!pass && is_gpgvdef
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx"))
{
/* Not found but gpgv's default "trustedkeys.kbx" file has
been requested. We did not found it so now check whether
a "trustedkeys.gpg" file exists and use that instead. */
KeydbResourceType rttmp;
strcpy (filename+filenamelen-4, ".gpg");
rttmp = rt_from_file (filename, &found, &openpgp_flag);
if (found
&& ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag)
|| (rttmp == KEYDB_RESOURCE_TYPE_KEYRING)))
rt = rttmp;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".kbx");
}
else if (!pass
&& is_default && create
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
/* The file does not exist, the default resource has been
requested, the file shall be created, and the file has a
".gpg" suffix. Change the suffix to ".kbx" and try once
more. This way we achieve that we open an existing
".gpg" keyring, but create a new keybox file with an
".kbx" suffix. */
strcpy (filename+filenamelen-4, ".kbx");
pass++;
goto check_again;
}
else /* No file yet: create keybox. */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = maybe_create_keyring_or_box (filename, 0, create);
if (err)
goto leave;
if (keyring_register_filename (filename, read_only, &token))
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
used_resources++;
}
}
else
{
/* This keyring was already registered, so ignore it.
However, we can still mark it as primary even if it was
already registered. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
err = maybe_create_keyring_or_box (filename, 1, create);
if (err)
goto leave;
err = keybox_register_file (filename, 0, &token);
if (!err)
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kb = NULL; /* Not used here */
all_resources[used_resources].token = token;
/* FIXME: Do a compress run if needed and no other
user is currently using the keybox. */
used_resources++;
}
}
else if (gpg_err_code (err) == GPG_ERR_EEXIST)
{
/* Already registered. We will mark it as the primary key
if requested. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error (_("keyblock resource '%s': %s\n"),
filename, gpg_strerror (err));
write_status_error ("add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
void
keydb_dump_stats (void)
{
if (kid_not_found_cache_count)
log_info ("keydb: kid_not_found_cache: total: %u\n",
kid_not_found_cache_count);
}
/* Create a new database handle. A database handle is similar to a
file handle: it contains a local file position. This is used when
searching: subsequent searches resume where the previous search
left off. To rewind the position, use keydb_search_reset(). This
function returns NULL on error, sets ERRNO, and prints an error
diagnostic. */
KEYDB_HANDLE
keydb_new (void)
{
KEYDB_HANDLE hd;
int i, j;
int die = 0;
int reterrno;
if (DBG_CLOCK)
log_clock ("keydb_new");
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
goto leave;
hd->found = -1;
hd->saved_found = -1;
hd->is_reset = 1;
log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; ! die && i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kr = keyring_new (all_resources[i].token);
if (!hd->active[j].u.kr)
{
reterrno = errno;
die = 1;
}
j++;
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kb = keybox_new_openpgp (all_resources[i].token, 0);
if (!hd->active[j].u.kb)
{
reterrno = errno;
die = 1;
}
j++;
break;
}
}
hd->used = j;
active_handles++;
if (die)
{
keydb_release (hd);
gpg_err_set_errno (reterrno);
hd = NULL;
}
leave:
if (!hd)
log_error (_("error opening key DB: %s\n"),
gpg_strerror (gpg_error_from_syserror()));
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
int i;
if (!hd)
return;
log_assert (active_handles > 0);
active_handles--;
unlock_all (hd);
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_release (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kb);
break;
}
}
keyblock_cache_clear (hd);
xfree (hd);
}
/* Set a flag on the handle to suppress use of cached results. This
* is required for updating a keyring and for key listings. Fixme:
* Using a new parameter for keydb_new might be a better solution. */
void
keydb_disable_caching (KEYDB_HANDLE hd)
{
if (hd)
hd->no_caching = 1;
}
/* Return the file name of the resource in which the current search
* result was found or, if there is no search result, the filename of
* the current resource (i.e., the resource that the file position
* points to). Note: the filename is not necessarily the URL used to
* open it!
*
* This function only returns NULL if no handle is specified, in all
* other error cases an empty string is returned. */
const char *
keydb_get_resource_name (KEYDB_HANDLE hd)
{
int idx;
const char *s = NULL;
if (!hd)
return NULL;
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
idx = 0;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
s = NULL;
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
s = keyring_get_resource_name (hd->active[idx].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kb);
break;
}
return s? s: "";
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
/* Fixme: This locking scheme may lead to a deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem.
[Oops: Who claimed the latter]
To fix this we need to use a lock file to protect lock_all. */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_lock (hd->active[i].u.kr, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_lock (hd->active[i].u.kb, 1);
break;
}
}
if (rc)
{
/* Revert the already taken locks. */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kb, 0);
break;
}
}
}
else
hd->locked = 1;
return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (!hd->locked)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kb, 0);
break;
}
}
hd->locked = 0;
}
/* Save the last found state and invalidate the current selection
* (i.e., the entry selected by keydb_search() is invalidated and
* something like keydb_get_keyblock() will return an error). This
* does not change the file position. This makes it possible to do
* something like:
*
* keydb_search (hd, ...); // Result 1.
* keydb_push_found_state (hd);
* keydb_search_reset (hd);
* keydb_search (hd, ...); // Result 2.
* keydb_pop_found_state (hd);
* keydb_get_keyblock (hd, ...); // -> Result 1.
*
* Note: it is only possible to save a single save state at a time.
* In other words, the save stack only has room for a single
* instance of the state. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_push_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kb);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
}
/* Restore the previous save state. If the saved state is NULL or
invalid, this is a NOP. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_pop_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kb);
break;
}
}
static gpg_error_t
parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
const u32 *sigstatus, kbnode_t *r_keyblock)
{
gpg_error_t err;
PACKET *pkt;
kbnode_t keyblock = NULL;
kbnode_t node, *tail;
int in_cert, save_mode;
u32 n_sigs;
int pk_count, uid_count;
*r_keyblock = NULL;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
return gpg_error_from_syserror ();
init_packet (pkt);
save_mode = set_packet_list_mode (0);
in_cert = 0;
n_sigs = 0;
tail = NULL;
pk_count = uid_count = 0;
while ((err = parse_packet (iobuf, pkt)) != -1)
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
{
free_packet (pkt);
init_packet (pkt);
continue;
}
if (err)
{
log_error ("parse_keyblock_image: read error: %s\n",
gpg_strerror (err));
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
break; /* Allowed per RFC. */
default:
/* Note that can't allow ring trust packets here and some of
the other GPG specific packets don't make sense either. */
log_error ("skipped packet of type %d in keybox\n",
(int)pkt->pkttype);
free_packet(pkt);
init_packet(pkt);
continue;
}
/* Other sanity checks. */
if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
{
log_error ("parse_keyblock_image: first packet in a keybox blob "
"is not a public key packet\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
log_error ("parse_keyblock_image: "
"multiple keyblocks in a keybox blob\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
in_cert = 1;
if (pkt->pkttype == PKT_SIGNATURE && sigstatus)
{
PKT_signature *sig = pkt->pkt.signature;
n_sigs++;
if (n_sigs > sigstatus[0])
{
log_error ("parse_keyblock_image: "
"more signatures than found in the meta data\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
if (sigstatus[n_sigs])
{
sig->flags.checked = 1;
if (sigstatus[n_sigs] == 1 )
; /* missing key */
else if (sigstatus[n_sigs] == 2 )
; /* bad signature */
else if (sigstatus[n_sigs] < 0x10000000)
; /* bad flag */
else
{
sig->flags.valid = 1;
/* Fixme: Shall we set the expired flag here? */
}
}
}
node = new_kbnode (pkt);
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_count == pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_count == uid_no)
node->flag |= 2;
break;
default:
break;
}
if (!keyblock)
keyblock = node;
else
*tail = node;
tail = &node->next;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
break;
}
init_packet (pkt);
}
set_packet_list_mode (save_mode);
if (err == -1 && keyblock)
err = 0; /* Got the entire keyblock. */
if (!err && sigstatus && n_sigs != sigstatus[0])
{
log_error ("parse_keyblock_image: signature count does not match\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
}
if (err)
release_kbnode (keyblock);
else
*r_keyblock = keyblock;
free_packet (pkt);
xfree (pkt);
return err;
}
/* Return the keyblock last found by keydb_search() in *RET_KB.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
* code.
*
* The returned keyblock has the kbnode flag bit 0 set for the node
* with the public key used to locate the keyblock or flag bit 1 set
* for the user ID node. */
gpg_error_t
keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
{
gpg_error_t err = 0;
*ret_kb = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("keydb_get_keybock enter");
if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
{
err = iobuf_seek (hd->keyblock_cache.iobuf, 0);
if (err)
{
log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n");
keyblock_cache_clear (hd);
}
else
{
err = parse_keyblock_image (hd->keyblock_cache.iobuf,
hd->keyblock_cache.pk_no,
hd->keyblock_cache.uid_no,
hd->keyblock_cache.sigstatus,
ret_kb);
if (err)
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock (err? "keydb_get_keyblock leave (cached, failed)"
: "keydb_get_keyblock leave (cached)");
return err;
}
}
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
u32 *sigstatus;
int pk_no, uid_no;
err = keybox_get_keyblock (hd->active[hd->found].u.kb,
&iobuf, &pk_no, &uid_no, &sigstatus);
if (!err)
{
err = parse_keyblock_image (iobuf, pk_no, uid_no, sigstatus,
ret_kb);
if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
hd->keyblock_cache.sigstatus = sigstatus;
hd->keyblock_cache.iobuf = iobuf;
hd->keyblock_cache.pk_no = pk_no;
hd->keyblock_cache.uid_no = uid_no;
}
else
{
xfree (sigstatus);
iobuf_close (iobuf);
}
}
}
break;
}
if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock (err? "keydb_get_keyblock leave (failed)"
: "keydb_get_keyblock leave");
return err;
}
/* Build a keyblock image from KEYBLOCK. Returns 0 on success and
only then stores a new iobuf object at R_IOBUF and a signature
status vecotor at R_SIGSTATUS. */
static gpg_error_t
build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
{
gpg_error_t err;
iobuf_t iobuf;
kbnode_t kbctx, node;
u32 n_sigs;
u32 *sigstatus;
*r_iobuf = NULL;
if (r_sigstatus)
*r_sigstatus = NULL;
/* Allocate a vector for the signature cache. This is an array of
u32 values with the first value giving the number of elements to
follow and each element descriping the cache status of the
signature. */
if (r_sigstatus)
{
for (kbctx=NULL, n_sigs=0; (node = walk_kbnode (keyblock, &kbctx, 0));)
if (node->pkt->pkttype == PKT_SIGNATURE)
n_sigs++;
sigstatus = xtrycalloc (1+n_sigs, sizeof *sigstatus);
if (!sigstatus)
return gpg_error_from_syserror ();
}
else
sigstatus = NULL;
iobuf = iobuf_temp ();
for (kbctx = NULL, n_sigs = 0; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
/* Make sure to use only packets valid on a keyblock. */
switch (node->pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
/* Note that we don't want the ring trust packets. They are
not useful. */
break;
default:
continue;
}
err = build_packet (iobuf, node->pkt);
if (err)
{
iobuf_close (iobuf);
return err;
}
/* Build signature status vector. */
if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
n_sigs++;
/* Fixme: Detect the "missing key" status. */
if (sig->flags.checked && sigstatus)
{
if (sig->flags.valid)
{
if (!sig->expiredate)
sigstatus[n_sigs] = 0xffffffff;
else if (sig->expiredate < 0x1000000)
sigstatus[n_sigs] = 0x10000000;
else
sigstatus[n_sigs] = sig->expiredate;
}
else
sigstatus[n_sigs] = 0x00000002; /* Bad signature. */
}
}
}
if (sigstatus)
sigstatus[0] = n_sigs;
*r_iobuf = iobuf;
if (r_sigstatus)
*r_sigstatus = sigstatus;
return 0;
}
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
*
* This doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. Note:
* if there isn't a keyblock in the keyring corresponding to KB, then
* this function returns GPG_ERR_VALUE_NOT_FOUND.
*
* This function selects the matching record and modifies the current
* file position to point to the record just after the selected entry.
* Thus, if you do a subsequent search using HD, you should first do a
* keydb_search_reset. Further, if the selected record is important,
* you should use keydb_push_found_state and keydb_pop_found_state to
* save and restore it. */
gpg_error_t
keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
PKT_public_key *pk;
KEYDB_SEARCH_DESC desc;
size_t len;
log_assert (kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (opt.dry_run)
return 0;
err = lock_all (hd);
if (err)
return err;
#ifdef USE_TOFU
tofu_notice_key_changed (ctrl, kb);
#endif
memset (&desc, 0, sizeof (desc));
fingerprint_from_pk (pk, desc.u.fpr, &len);
if (len == 20)
desc.mode = KEYDB_SEARCH_MODE_FPR20;
else
log_bug ("%s: Unsupported key length: %zu\n", __func__, len);
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (err)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
log_assert (hd->found >= 0 && hd->found < hd->used);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
err = build_keyblock_image (kb, &iobuf, NULL);
if (!err)
{
err = keybox_update_keyblock (hd->active[hd->found].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf));
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
return err;
}
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
*
* Be default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
* keydb_search_reset was called), then the keyring / keybox where the
* next search would start is used (i.e., the current file position).
*
* Note: this doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
int idx;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (opt.dry_run)
return 0;
if (hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if (hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
return gpg_error (GPG_ERR_GENERAL);
err = lock_all (hd);
if (err)
return err;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_insert_keyblock (hd->active[idx].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{ /* We need to turn our kbnode_t list of packets into a proper
keyblock first. This is required by the OpenPGP key parser
included in the keybox code. Eventually we can change this
kludge to have the caller pass the image. */
iobuf_t iobuf;
u32 *sigstatus;
err = build_keyblock_image (kb, &iobuf, &sigstatus);
if (!err)
{
err = keybox_insert_keyblock (hd->active[idx].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf),
sigstatus);
xfree (sigstatus);
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
return err;
}
/* Delete the currently selected keyblock. If you haven't done a
* search yet on this database handle (or called keydb_search_reset),
* then this will return an error.
*
* Returns 0 on success or an error code, if an error occurs. */
gpg_error_t
keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t rc;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
if (opt.dry_run)
return 0;
rc = lock_all (hd);
if (rc)
return rc;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_delete_keyblock (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_delete (hd->active[hd->found].u.kb);
break;
}
unlock_all (hd);
return rc;
}
/* A database may consists of multiple keyrings / key boxes. This
* sets the "file position" to the start of the first keyring / key
* box that is writable (i.e., doesn't have the read-only flag set).
*
* This first tries the primary keyring (the last keyring (not
* keybox!) added using keydb_add_resource() and with
* KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it
* tries the keyrings / keyboxes in the order in which they were
* added. */
gpg_error_t
keydb_locate_writable (KEYDB_HANDLE hd)
{
gpg_error_t rc;
if (!hd)
return GPG_ERR_INV_ARG;
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
/* If we have a primary set, try that one first */
if (primary_keydb)
{
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
if(hd->active[hd->current].token == primary_keydb)
{
if(keyring_is_writable (hd->active[hd->current].token))
return 0;
else
break;
}
}
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
}
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG();
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
if (keyring_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (keybox_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
}
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Rebuild the on-disk caches of all key resources. */
void
keydb_rebuild_caches (int noisy)
{
int i, rc;
for (i=0; i < used_resources; i++)
{
if (!keyring_is_writable (all_resources[i].token))
continue;
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_rebuild_cache (all_resources[i].token,noisy);
if (rc)
log_error (_("failed to rebuild keyring cache: %s\n"),
gpg_strerror (rc));
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* N/A. */
break;
}
}
}
/* Return the number of skipped blocks (because they were to large to
read from a keybox) since the last search reset. */
unsigned long
keydb_get_skipped_counter (KEYDB_HANDLE hd)
{
return hd ? hd->skipped_long_blobs : 0;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database
* (the start of the first resource).
*
* Returns 0 on success and an error code if an error occurred.
* (Currently, this function always returns 0 if HD is valid.) */
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t rc = 0;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock ("keydb_search_reset");
if (DBG_CACHE)
log_debug ("keydb_search: reset (hd=%p)", hd);
hd->skipped_long_blobs = 0;
hd->current = 0;
hd->found = -1;
/* Now reset all resources. */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_search_reset (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_search_reset (hd->active[i].u.kb);
break;
}
}
hd->is_reset = 1;
return rc;
}
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, returns
* GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
* occurred, returns an error code.
*
* The returned key is considered to be selected and the raw data can,
* for instance, be returned by calling keydb_get_keyblock(). */
gpg_error_t
keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex)
{
int i;
gpg_error_t rc;
int was_reset = hd->is_reset;
/* If an entry is already in the cache, then don't add it again. */
int already_in_cache = 0;
if (descindex)
*descindex = 0; /* Make sure it is always set on return. */
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!any_registered)
{
write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (DBG_CLOCK)
log_clock ("keydb_search enter");
if (DBG_LOOKUP)
{
log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
{
if (DBG_CLOCK)
log_clock ("keydb_search leave (not found, cached)");
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* NB: If one of the exact search modes below is used in a loop to
walk over all keys (with the same fingerprint) the caching must
have been disabled for the handle. */
if (!hd->no_caching
&& ndesc == 1
&& (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[0].mode == KEYDB_SEARCH_MODE_FPR)
&& hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED
&& !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20)
/* Make sure the current file position occurs before the cached
result to avoid an infinite loop. */
&& (hd->current < hd->keyblock_cache.resource
|| (hd->current == hd->keyblock_cache.resource
&& (keybox_offset (hd->active[hd->current].u.kb)
<= hd->keyblock_cache.offset))))
{
/* (DESCINDEX is already set). */
if (DBG_CLOCK)
log_clock ("keydb_search leave (cached)");
hd->current = hd->keyblock_cache.resource;
/* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
Seek just beyond that. */
keybox_seek (hd->active[hd->current].u.kb,
hd->keyblock_cache.offset + 1);
return 0;
}
rc = -1;
while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
&& hd->current >= 0 && hd->current < hd->used)
{
if (DBG_LOOKUP)
log_debug ("%s: searching %s (resource %d of %d)\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
hd->current, hd->used);
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG(); /* we should never see it here */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_search (hd->active[hd->current].u.kr, desc,
ndesc, descindex, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
do
rc = keybox_search (hd->active[hd->current].u.kb, desc,
ndesc, KEYBOX_BLOBTYPE_PGP,
descindex, &hd->skipped_long_blobs);
while (rc == GPG_ERR_LEGACY_KEY);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (resource %d of %d) => %s\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
hd->current, hd->used,
rc == -1 ? "EOF" : gpg_strerror (rc));
if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
/* EOF -> switch to next resource */
hd->current++;
}
else if (!rc)
hd->found = hd->current;
}
hd->is_reset = 0;
rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
? gpg_error (GPG_ERR_NOT_FOUND)
: rc);
keyblock_cache_clear (hd);
if (!hd->no_caching
&& !rc
&& ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[0].mode == KEYDB_SEARCH_MODE_FPR)
&& hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
hd->keyblock_cache.resource = hd->current;
/* The current offset is at the start of the next record. Since
a record is at least 1 byte, we just use offset - 1, which is
within the record. */
hd->keyblock_cache.offset
= keybox_offset (hd->active[hd->current].u.kb) - 1;
memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20);
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND
&& ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset
&& !already_in_cache)
kid_not_found_insert (desc[0].u.kid);
if (DBG_CLOCK)
log_clock (rc? "keydb_search leave (not found)"
: "keydb_search leave (found)");
return rc;
}
/* Return the first non-legacy key in the database.
*
* If you want the very first key in the database, you can directly
* call keydb_search with the search description
* KEYDB_SEARCH_MODE_FIRST. */
gpg_error_t
keydb_search_first (KEYDB_HANDLE hd)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
err = keydb_search_reset (hd);
if (err)
return err;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (hd, &desc, 1, NULL);
}
/* Return the next key (not the next matching key!).
*
* Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this
* function silently skips legacy keys. */
gpg_error_t
keydb_search_next (KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (hd, &desc, 1, NULL);
}
/* This is a convenience function for searching for keys with a long
* key id.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = kid[0];
desc.u.kid[1] = kid[1];
return keydb_search (hd, &desc, 1, NULL);
}
/* This is a convenience function for searching for keys with a long
* (20 byte) fingerprint.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN);
return keydb_search (hd, &desc, 1, NULL);
}
diff --git a/g10/keydb.h b/g10/keydb.h
index c9f5b1c4a..6f57583fd 100644
--- a/g10/keydb.h
+++ b/g10/keydb.h
@@ -1,478 +1,478 @@
/* keydb.h - Key database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_KEYDB_H
#define G10_KEYDB_H
-#include "types.h"
-#include "util.h"
+#include "../common/types.h"
+#include "../common/util.h"
#include "packet.h"
/* What qualifies as a certification (rather than a signature?) */
#define IS_CERT(s) (IS_KEY_SIG(s) || IS_UID_SIG(s) || IS_SUBKEY_SIG(s) \
|| IS_KEY_REV(s) || IS_UID_REV(s) || IS_SUBKEY_REV(s))
#define IS_SIG(s) (!IS_CERT(s))
#define IS_KEY_SIG(s) ((s)->sig_class == 0x1f)
#define IS_UID_SIG(s) (((s)->sig_class & ~3) == 0x10)
#define IS_SUBKEY_SIG(s) ((s)->sig_class == 0x18)
#define IS_KEY_REV(s) ((s)->sig_class == 0x20)
#define IS_UID_REV(s) ((s)->sig_class == 0x30)
#define IS_SUBKEY_REV(s) ((s)->sig_class == 0x28)
struct getkey_ctx_s;
typedef struct getkey_ctx_s *GETKEY_CTX;
typedef struct getkey_ctx_s *getkey_ctx_t;
/****************
* A Keyblock is all packets which form an entire certificate;
* i.e. the public key, certificate, trust packets, user ids,
* signatures, and subkey.
*
* This structure is also used to bind arbitrary packets together.
*/
struct kbnode_struct {
KBNODE next;
PACKET *pkt;
int flag;
int private_flag;
ulong recno; /* used while updating the trustdb */
};
#define is_deleted_kbnode(a) ((a)->private_flag & 1)
#define is_cloned_kbnode(a) ((a)->private_flag & 2)
/* Bit flags used with build_pk_list. */
enum
{
PK_LIST_ENCRYPT_TO = 1, /* This is an encrypt-to recipient. */
PK_LIST_HIDDEN = 2, /* This is a hidden recipient. */
PK_LIST_CONFIG = 4, /* Specified via config file. */
PK_LIST_FROM_FILE = 8 /* Take key from file with that name. */
};
/* To store private data in the flags the private data must be left
* shifted by this value. */
enum
{
PK_LIST_SHIFT = 4
};
/* Structure to hold a couple of public key certificates. */
typedef struct pk_list *PK_LIST; /* Deprecated. */
typedef struct pk_list *pk_list_t;
struct pk_list
{
PK_LIST next;
PKT_public_key *pk;
int flags; /* See PK_LIST_ constants. */
};
/* Structure to hold a list of secret key certificates. */
typedef struct sk_list *SK_LIST;
struct sk_list
{
SK_LIST next;
PKT_public_key *pk;
int mark; /* not used */
};
/* structure to collect all information which can be used to
* identify a public key */
typedef struct pubkey_find_info *PUBKEY_FIND_INFO;
struct pubkey_find_info {
u32 keyid[2];
unsigned nbits;
byte pubkey_algo;
byte fingerprint[MAX_FINGERPRINT_LEN];
char userid[1];
};
typedef struct keydb_handle *KEYDB_HANDLE;
/* Helper type for preference fucntions. */
union pref_hint
{
int digest_length;
};
/*-- keydb.c --*/
#define KEYDB_RESOURCE_FLAG_PRIMARY 2 /* The primary resource. */
#define KEYDB_RESOURCE_FLAG_DEFAULT 4 /* The default one. */
#define KEYDB_RESOURCE_FLAG_READONLY 8 /* Open in read only mode. */
#define KEYDB_RESOURCE_FLAG_GPGVDEF 16 /* Default file for gpgv. */
/* Format a search term for debugging output. The caller must free
the result. */
char *keydb_search_desc_dump (struct keydb_search_desc *desc);
/* Register a resource (keyring or keybox). */
gpg_error_t keydb_add_resource (const char *url, unsigned int flags);
/* Dump some statistics to the log. */
void keydb_dump_stats (void);
/* Create a new database handle. Returns NULL on error, sets ERRNO,
and prints an error diagnostic. */
KEYDB_HANDLE keydb_new (void);
/* Free all resources owned by the database handle. */
void keydb_release (KEYDB_HANDLE hd);
/* Set a flag on the handle to suppress use of cached results. This
is required for updating a keyring and for key listings. Fixme:
Using a new parameter for keydb_new might be a better solution. */
void keydb_disable_caching (KEYDB_HANDLE hd);
/* Save the last found state and invalidate the current selection. */
void keydb_push_found_state (KEYDB_HANDLE hd);
/* Restore the previous save state. */
void keydb_pop_found_state (KEYDB_HANDLE hd);
/* Return the file name of the resource. */
const char *keydb_get_resource_name (KEYDB_HANDLE hd);
/* Return the keyblock last found by keydb_search. */
gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
/* Update the keyblock KB. */
gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb);
/* Insert a keyblock into one of the underlying keyrings or keyboxes. */
gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
/* Delete the currently selected keyblock. */
gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd);
/* Find the first writable resource. */
gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd);
/* Rebuild the on-disk caches of all key resources. */
void keydb_rebuild_caches (int noisy);
/* Return the number of skipped blocks (because they were to large to
read from a keybox) since the last search reset. */
unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd);
/* Clears the current search result and resets the handle's position. */
gpg_error_t keydb_search_reset (KEYDB_HANDLE hd);
/* Search the database for keys matching the search description. */
gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex);
/* Return the first non-legacy key in the database. */
gpg_error_t keydb_search_first (KEYDB_HANDLE hd);
/* Return the next key (not the next matching key!). */
gpg_error_t keydb_search_next (KEYDB_HANDLE hd);
/* This is a convenience function for searching for keys with a long
key id. */
gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid);
/* This is a convenience function for searching for keys with a long
(20 byte) fingerprint. */
gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr);
/*-- pkclist.c --*/
void show_revocation_reason( PKT_public_key *pk, int mode );
int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig);
void release_pk_list (PK_LIST pk_list);
int build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list);
gpg_error_t find_and_check_key (ctrl_t ctrl,
const char *name, unsigned int use,
int mark_hidden, int from_file,
pk_list_t *pk_list_addr);
int algo_available( preftype_t preftype, int algo,
const union pref_hint *hint );
int select_algo_from_prefs( PK_LIST pk_list, int preftype,
int request, const union pref_hint *hint);
int select_mdc_from_pklist (PK_LIST pk_list);
void warn_missing_mdc_from_pklist (PK_LIST pk_list);
void warn_missing_aes_from_pklist (PK_LIST pk_list);
/*-- skclist.c --*/
int random_is_faked (void);
void release_sk_list( SK_LIST sk_list );
gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr,
SK_LIST *ret_sk_list, unsigned use);
/*-- passphrase.h --*/
unsigned char encode_s2k_iterations (int iterations);
int have_static_passphrase(void);
const char *get_static_passphrase (void);
void set_passphrase_from_string(const char *pass);
void read_passphrase_from_fd( int fd );
void passphrase_clear_cache (const char *cacheid);
DEK *passphrase_to_dek_ext(u32 *keyid, int pubkey_algo,
int cipher_algo, STRING2KEY *s2k, int mode,
const char *tryagain_text,
const char *custdesc, const char *custprompt,
int *canceled);
DEK *passphrase_to_dek (int cipher_algo, STRING2KEY *s2k,
int create, int nocache,
const char *tryagain_text, int *canceled);
void set_next_passphrase( const char *s );
char *get_last_passphrase(void);
void next_to_last_passphrase(void);
void emit_status_need_passphrase (u32 *keyid, u32 *mainkeyid, int pubkey_algo);
#define FORMAT_KEYDESC_NORMAL 0
#define FORMAT_KEYDESC_IMPORT 1
#define FORMAT_KEYDESC_EXPORT 2
#define FORMAT_KEYDESC_DELKEY 3
char *gpg_format_keydesc (PKT_public_key *pk, int mode, int escaped);
/*-- getkey.c --*/
/* Cache a copy of a public key in the public key cache. */
void cache_public_key( PKT_public_key *pk );
/* Disable and drop the public key cache. */
void getkey_disable_caches(void);
/* Return the public key with the key id KEYID and store it at PK. */
int get_pubkey( PKT_public_key *pk, u32 *keyid );
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
account nor does it merge in the self-signed data. This function
also only considers primary keys. */
int get_pubkey_fast (PKT_public_key *pk, u32 *keyid);
/* Return the key block for the key with KEYID. */
kbnode_t get_pubkeyblock (u32 *keyid);
/* A list used by get_pubkeys to gather all of the matches. */
struct pubkey_s
{
struct pubkey_s *next;
/* The key to use (either the public key or the subkey). */
PKT_public_key *pk;
kbnode_t keyblock;
};
typedef struct pubkey_s *pubkey_t;
/* Free a single key. This does not remove key from any list! */
void pubkey_free (pubkey_t key);
/* Free a list of public keys. */
void pubkeys_free (pubkey_t keys);
/* Returns all keys that match the search specfication SEARCH_TERMS.
The returned keys should be freed using pubkeys_free. */
gpg_error_t
get_pubkeys (ctrl_t ctrl,
char *search_terms, int use, int include_unusable, char *source,
int warn_possibly_ambiguous,
pubkey_t *r_keys);
/* Find a public key identified by NAME. */
int get_pubkey_byname (ctrl_t ctrl,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name,
KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd,
int include_unusable, int no_akl );
/* Likewise, but only return the best match if NAME resembles a mail
* address. */
int get_best_pubkey_byname (ctrl_t ctrl,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable, int no_akl);
/* Get a public key directly from file FNAME. */
gpg_error_t get_pubkey_fromfile (ctrl_t ctrl,
PKT_public_key *pk, const char *fname);
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. */
gpg_error_t get_seckey (PKT_public_key *pk, u32 *keyid);
/* Lookup a key with the specified fingerprint. */
int get_pubkey_byfprint (PKT_public_key *pk, kbnode_t *r_keyblock,
const byte *fprint, size_t fprint_len);
/* This function is similar to get_pubkey_byfprint, but it doesn't
merge the self-signed data into the public key and subkeys or into
the user ids. */
int get_pubkey_byfprint_fast (PKT_public_key *pk,
const byte *fprint, size_t fprint_len);
/* Returns true if a secret key is available for the public key with
key id KEYID. */
int have_secret_key_with_kid (u32 *keyid);
/* Parse the --default-key parameter. Returns the last key (in terms
of when the option is given) that is available. */
const char *parse_def_secret_key (ctrl_t ctrl);
/* Look up a secret key. */
gpg_error_t get_seckey_default (ctrl_t ctrl, PKT_public_key *pk);
/* Search for keys matching some criteria. */
gpg_error_t getkey_bynames (getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret,
kbnode_t *ret_keyblock);
/* Search for one key matching some criteria. */
gpg_error_t getkey_byname (ctrl_t ctrl,
getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret,
kbnode_t *ret_keyblock);
/* Return the next search result. */
gpg_error_t getkey_next (getkey_ctx_t ctx, PKT_public_key *pk,
kbnode_t *ret_keyblock);
/* Release any resources used by a key listing context. */
void getkey_end (getkey_ctx_t ctx);
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE get_ctx_handle(GETKEY_CTX ctx);
/* Enumerate some secret keys. */
gpg_error_t enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *pk);
/* Set the mainkey_id fields for all keys in KEYBLOCK. */
void setup_main_keyids (kbnode_t keyblock);
/* This function merges information from the self-signed data into the
data structures. */
void merge_keys_and_selfsig (kbnode_t keyblock);
char*get_user_id_string_native( u32 *keyid );
char*get_long_user_id_string( u32 *keyid );
char*get_user_id( u32 *keyid, size_t *rn );
char*get_user_id_native( u32 *keyid );
char *get_user_id_byfpr (const byte *fpr, size_t *rn);
char *get_user_id_byfpr_native (const byte *fpr);
void release_akl(void);
int parse_auto_key_locate(char *options);
/*-- keyid.c --*/
int pubkey_letter( int algo );
char *pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize);
#define PUBKEY_STRING_SIZE 32
u32 v3_keyid (gcry_mpi_t a, u32 *ki);
void hash_public_key( gcry_md_hd_t md, PKT_public_key *pk );
char *format_keyid (u32 *keyid, int format, char *buffer, int len);
/* Return PK's keyid. The memory is owned by PK. */
u32 *pk_keyid (PKT_public_key *pk);
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *pk_main_keyid (PKT_public_key *pk);
/* Order A and B. If A < B then return -1, if A == B then return 0,
and if A > B then return 1. */
static int GPGRT_ATTR_UNUSED
keyid_cmp (const u32 *a, const u32 *b)
{
if (a[0] < b[0])
return -1;
if (a[0] > b[0])
return 1;
if (a[1] < b[1])
return -1;
if (a[1] > b[1])
return 1;
return 0;
}
/* Return whether PK is a primary key. */
static int GPGRT_ATTR_UNUSED
pk_is_primary (PKT_public_key *pk)
{
return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *keyid_copy (u32 *dest, const u32 *src);
size_t keystrlen(void);
const char *keystr(u32 *keyid);
const char *keystr_with_sub (u32 *main_kid, u32 *sub_kid);
const char *keystr_from_pk(PKT_public_key *pk);
const char *keystr_from_pk_with_sub (PKT_public_key *main_pk,
PKT_public_key *sub_pk);
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *pk_keyid_str (PKT_public_key *pk);
const char *keystr_from_desc(KEYDB_SEARCH_DESC *desc);
u32 keyid_from_pk( PKT_public_key *pk, u32 *keyid );
u32 keyid_from_sig( PKT_signature *sig, u32 *keyid );
u32 keyid_from_fingerprint(const byte *fprint, size_t fprint_len, u32 *keyid);
byte *namehash_from_uid(PKT_user_id *uid);
unsigned nbits_from_pk( PKT_public_key *pk );
const char *datestr_from_pk( PKT_public_key *pk );
const char *datestr_from_sig( PKT_signature *sig );
const char *expirestr_from_pk( PKT_public_key *pk );
const char *expirestr_from_sig( PKT_signature *sig );
const char *revokestr_from_pk( PKT_public_key *pk );
const char *usagestr_from_pk (PKT_public_key *pk, int fill);
const char *colon_strtime (u32 t);
const char *colon_datestr_from_pk (PKT_public_key *pk);
const char *colon_datestr_from_sig (PKT_signature *sig);
const char *colon_expirestr_from_sig (PKT_signature *sig);
byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len );
char *hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen);
char *format_hexfingerprint (const char *fingerprint,
char *buffer, size_t buflen);
gpg_error_t keygrip_from_pk (PKT_public_key *pk, unsigned char *array);
gpg_error_t hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip);
/*-- kbnode.c --*/
KBNODE new_kbnode( PACKET *pkt );
KBNODE clone_kbnode( KBNODE node );
void release_kbnode( KBNODE n );
void delete_kbnode( KBNODE node );
void add_kbnode( KBNODE root, KBNODE node );
void insert_kbnode( KBNODE root, KBNODE node, int pkttype );
void move_kbnode( KBNODE *root, KBNODE node, KBNODE where );
void remove_kbnode( KBNODE *root, KBNODE node );
KBNODE find_prev_kbnode( KBNODE root, KBNODE node, int pkttype );
KBNODE find_next_kbnode( KBNODE node, int pkttype );
KBNODE find_kbnode( KBNODE node, int pkttype );
KBNODE walk_kbnode( KBNODE root, KBNODE *context, int all );
void clear_kbnode_flags( KBNODE n );
int commit_kbnode( KBNODE *root );
void dump_kbnode( KBNODE node );
#endif /*G10_KEYDB_H*/
diff --git a/g10/keyedit.c b/g10/keyedit.c
index c591600a5..c10a011d1 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -1,6656 +1,6656 @@
/* keyedit.c - Edit properties of a key
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2016 Werner Koch
* Copyright (C) 2015, 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
#include "photoid.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
-#include "ttyio.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/ttyio.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
-#include "host2net.h"
+#include "../common/host2net.h"
#include "tofu.h"
static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
int verbose);
static void show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk,
unsigned int flag, int with_prefs);
static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked,
int with_revoker, int with_fpr,
int with_subkeys, int with_prefs,
int nowarn);
static void show_key_and_fingerprint (kbnode_t keyblock, int with_subkeys);
static void show_key_and_grip (kbnode_t keyblock);
static void subkey_expire_warning (kbnode_t keyblock);
static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock,
int photo, const char *photo_name, const char *uidstr);
static void menu_deluid (KBNODE pub_keyblock);
static int menu_delsig (KBNODE pub_keyblock);
static int menu_clean (KBNODE keyblock, int self_only);
static void menu_delkey (KBNODE pub_keyblock);
static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
static gpg_error_t menu_expire (kbnode_t pub_keyblock,
int force_mainkey, u32 newexpiration);
static int menu_changeusage (kbnode_t keyblock);
static int menu_backsign (KBNODE pub_keyblock);
static int menu_set_primary_uid (KBNODE pub_keyblock);
static int menu_set_preferences (KBNODE pub_keyblock);
static int menu_set_keyserver_url (const char *url, KBNODE pub_keyblock);
static int menu_set_notation (const char *string, KBNODE pub_keyblock);
static int menu_select_uid (KBNODE keyblock, int idx);
static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash);
static int menu_select_key (KBNODE keyblock, int idx, char *p);
static int count_uids (KBNODE keyblock);
static int count_uids_with_flag (KBNODE keyblock, unsigned flag);
static int count_keys_with_flag (KBNODE keyblock, unsigned flag);
static int count_selected_uids (KBNODE keyblock);
static int real_uids_left (KBNODE keyblock);
static int count_selected_keys (KBNODE keyblock);
static int menu_revsig (KBNODE keyblock);
static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock);
static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason,
int *modified);
static int menu_revkey (KBNODE pub_keyblock);
static int menu_revsubkey (KBNODE pub_keyblock);
#ifndef NO_TRUST_MODELS
static int enable_disable_key (KBNODE keyblock, int disable);
#endif /*!NO_TRUST_MODELS*/
static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock);
static int update_trust = 0;
#define CONTROL_D ('D' - 'A' + 1)
#define NODFLG_BADSIG (1<<0) /* Bad signature. */
#define NODFLG_NOKEY (1<<1) /* No public key. */
#define NODFLG_SIGERR (1<<2) /* Other sig error. */
#define NODFLG_MARK_A (1<<4) /* Temporary mark. */
#define NODFLG_DELSIG (1<<5) /* To be deleted. */
#define NODFLG_SELUID (1<<8) /* Indicate the selected userid. */
#define NODFLG_SELKEY (1<<9) /* Indicate the selected key. */
#define NODFLG_SELSIG (1<<10) /* Indicate a selected signature. */
struct sign_attrib
{
int non_exportable, non_revocable;
struct revocation_reason_info *reason;
byte trust_depth, trust_value;
char *trust_regexp;
};
/* TODO: Fix duplicated code between here and the check-sigs/list-sigs
code in keylist.c. */
static int
print_and_check_one_sig_colon (KBNODE keyblock, KBNODE node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key)
{
PKT_signature *sig = node->pkt->pkt.signature;
int rc, sigrc;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
rc = check_key_signature (keyblock, node, is_selfsig);
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:",
sigrc, sig->pubkey_algo, (ulong) sig->keyid[0],
(ulong) sig->keyid[1], (ulong) sig->timestamp,
(ulong) sig->expiredate);
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d", sig->trust_depth, sig->trust_value);
es_printf (":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout,
sig->trust_regexp, strlen (sig->trust_regexp),
":", NULL);
es_printf ("::%02x%c\n", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
return (sigrc == '!');
}
/*
* Print information about a signature (rc is its status), check it
* and return true if the signature is okay. NODE must be a signature
* packet. With EXTENDED set all possible signature list options will
* always be printed.
*/
static int
print_one_sig (int rc, KBNODE keyblock, KBNODE node,
int *inv_sigs, int *no_key, int *oth_err,
int is_selfsig, int print_without_key, int extended)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
int is_rev = sig->sig_class == 0x30;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
tty_printf ("%s%c%c %c%c%c%c%c%c %s %s",
is_rev ? "rev" : "sig", sigrc,
(sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ',
keystr (sig->keyid),
datestr_from_sig (sig));
if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended )
tty_printf (" %s", expirestr_from_sig (sig));
tty_printf (" ");
if (sigrc == '%')
tty_printf ("[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (is_selfsig)
{
tty_printf (is_rev ? _("[revocation]") : _("[self-signature]"));
if (extended && sig->flags.chosen_selfsig)
tty_printf ("*");
}
else
{
size_t n;
char *p = get_user_id (sig->keyid, &n);
tty_print_utf8_string2 (NULL, p, n,
opt.screen_columns - keystrlen () - 26 -
((opt.
list_options & LIST_SHOW_SIG_EXPIRE) ? 11
: 0));
xfree (p);
}
tty_printf ("\n");
if (sig->flags.policy_url
&& ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended))
show_policy_url (sig, 3, -1);
if (sig->flags.notation
&& ((opt.list_options & LIST_SHOW_NOTATIONS) || extended))
show_notation (sig, 3, -1,
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) +
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended))
show_keyserver_url (sig, 3, -1);
if (extended)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
const unsigned char *s;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL);
if (s && *s)
tty_printf (" [primary]\n");
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (s && buf32_to_u32 (s))
tty_printf (" [expires: %s]\n",
isotimestamp (pk->timestamp + buf32_to_u32 (s)));
}
}
return (sigrc == '!');
}
static int
print_and_check_one_sig (KBNODE keyblock, KBNODE node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key, int extended)
{
int rc;
rc = check_key_signature (keyblock, node, is_selfsig);
return print_one_sig (rc,
keyblock, node, inv_sigs, no_key, oth_err,
*is_selfsig, print_without_key, extended);
}
/* Order two signatures. The actual ordering isn't important. Our
goal is to ensure that identical signatures occur together. */
static int
sig_comparison (const void *av, const void *bv)
{
const KBNODE an = *(const KBNODE *) av;
const KBNODE bn = *(const KBNODE *) bv;
const PKT_signature *a;
const PKT_signature *b;
int ndataa;
int ndatab;
int i;
log_assert (an->pkt->pkttype == PKT_SIGNATURE);
log_assert (bn->pkt->pkttype == PKT_SIGNATURE);
a = an->pkt->pkt.signature;
b = bn->pkt->pkt.signature;
if (a->digest_algo < b->digest_algo)
return -1;
if (a->digest_algo > b->digest_algo)
return 1;
ndataa = pubkey_get_nsig (a->pubkey_algo);
ndatab = pubkey_get_nsig (b->pubkey_algo);
if (ndataa != ndatab)
return (ndataa < ndatab)? -1 : 1;
for (i = 0; i < ndataa; i ++)
{
int c = gcry_mpi_cmp (a->data[i], b->data[i]);
if (c != 0)
return c;
}
/* Okay, they are equal. */
return 0;
}
/* Perform a few sanity checks on a keyblock is okay and possibly
repair some damage. Concretely:
- Detect duplicate signatures and remove them.
- Detect out of order signatures and relocate them (e.g., a sig
over user id X located under subkey Y).
Note: this function does not remove signatures that don't belong or
components that are not signed! (Although it would be trivial to
do so.)
If ONLY_SELFSIGS is true, then this function only reorders self
signatures (it still checks all signatures for duplicates,
however).
Returns 1 if the keyblock was modified, 0 otherwise. */
static int
check_all_keysigs (KBNODE kb, int only_selected, int only_selfsigs)
{
gpg_error_t err;
PKT_public_key *pk;
KBNODE n, n_next, *n_prevp, n2;
char *pending_desc = NULL;
PKT_public_key *issuer;
KBNODE last_printed_component;
KBNODE current_component = NULL;
int dups = 0;
int missing_issuer = 0;
int reordered = 0;
int bad_signature = 0;
int missing_selfsig = 0;
int modified = 0;
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
/* First we look for duplicates. */
{
int nsigs;
kbnode_t *sigs;
int i;
int last_i;
/* Count the sigs. */
for (nsigs = 0, n = kb; n; n = n->next)
{
if (is_deleted_kbnode (n))
continue;
else if (n->pkt->pkttype == PKT_SIGNATURE)
nsigs ++;
}
if (!nsigs)
return 0; /* No signatures at all. */
/* Add them all to the SIGS array. */
sigs = xtrycalloc (nsigs, sizeof *sigs);
if (!sigs)
{
log_error (_("error allocating memory: %s\n"),
gpg_strerror (gpg_error_from_syserror ()));
return 0;
}
i = 0;
for (n = kb; n; n = n->next)
{
if (is_deleted_kbnode (n))
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sigs[i] = n;
i ++;
}
log_assert (i == nsigs);
qsort (sigs, nsigs, sizeof (sigs[0]), sig_comparison);
last_i = 0;
for (i = 1; i < nsigs; i ++)
{
log_assert (sigs[last_i]);
log_assert (sigs[last_i]->pkt->pkttype == PKT_SIGNATURE);
log_assert (sigs[i]);
log_assert (sigs[i]->pkt->pkttype == PKT_SIGNATURE);
if (sig_comparison (&sigs[last_i], &sigs[i]) == 0)
/* They are the same. Kill the latter. */
{
if (DBG_PACKET)
{
PKT_signature *sig = sigs[i]->pkt->pkt.signature;
log_debug ("Signature appears multiple times, "
"deleting duplicate:\n");
log_debug (" sig: class 0x%x, issuer: %s,"
" timestamp: %s (%lld), digest: %02x %02x\n",
sig->sig_class, keystr (sig->keyid),
isotimestamp (sig->timestamp),
(long long) sig->timestamp,
sig->digest_start[0], sig->digest_start[1]);
}
/* Remove sigs[i] from the keyblock. */
{
KBNODE z, *prevp;
int to_kill = last_i;
last_i = i;
for (prevp = &kb, z = kb; z; prevp = &z->next, z = z->next)
if (z == sigs[to_kill])
break;
*prevp = sigs[to_kill]->next;
sigs[to_kill]->next = NULL;
release_kbnode (sigs[to_kill]);
sigs[to_kill] = NULL;
dups ++;
modified = 1;
}
}
else
last_i = i;
}
xfree (sigs);
}
/* Make sure the sigs occur after the component (public key, subkey,
user id) that they sign. */
issuer = NULL;
last_printed_component = NULL;
for (n_prevp = &kb, n = kb;
n;
/* If we moved n, then n_prevp is need valid. */
n_prevp = (n->next == n_next ? &n->next : n_prevp), n = n_next)
{
PACKET *p;
int processed_current_component;
PKT_signature *sig;
int rc;
int dump_sig_params = 0;
n_next = n->next;
if (is_deleted_kbnode (n))
continue;
p = n->pkt;
if (issuer && issuer != pk)
{
free_public_key (issuer);
issuer = NULL;
}
xfree (pending_desc);
pending_desc = NULL;
switch (p->pkttype)
{
case PKT_PUBLIC_KEY:
log_assert (p->pkt.public_key == pk);
if (only_selected && ! (n->flag & NODFLG_SELKEY))
{
current_component = NULL;
break;
}
if (DBG_PACKET)
log_debug ("public key %s: timestamp: %s (%lld)\n",
pk_keyid_str (pk),
isotimestamp (pk->timestamp),
(long long) pk->timestamp);
current_component = n;
break;
case PKT_PUBLIC_SUBKEY:
if (only_selected && ! (n->flag & NODFLG_SELKEY))
{
current_component = NULL;
break;
}
if (DBG_PACKET)
log_debug ("subkey %s: timestamp: %s (%lld)\n",
pk_keyid_str (p->pkt.public_key),
isotimestamp (p->pkt.public_key->timestamp),
(long long) p->pkt.public_key->timestamp);
current_component = n;
break;
case PKT_USER_ID:
if (only_selected && ! (n->flag & NODFLG_SELUID))
{
current_component = NULL;
break;
}
if (DBG_PACKET)
log_debug ("user id: %s\n",
p->pkt.user_id->attrib_data
? "[ photo id ]"
: p->pkt.user_id->name);
current_component = n;
break;
case PKT_SIGNATURE:
if (! current_component)
/* The current component is not selected, don't check the
sigs under it. */
break;
sig = n->pkt->pkt.signature;
pending_desc = xasprintf (" sig: class: 0x%x, issuer: %s,"
" timestamp: %s (%lld), digest: %02x %02x",
sig->sig_class,
keystr (sig->keyid),
isotimestamp (sig->timestamp),
(long long) sig->timestamp,
sig->digest_start[0], sig->digest_start[1]);
if (keyid_cmp (pk_keyid (pk), sig->keyid) == 0)
issuer = pk;
else
/* Issuer is a different key. */
{
if (only_selfsigs)
continue;
issuer = xmalloc (sizeof (*issuer));
err = get_pubkey (issuer, sig->keyid);
if (err)
{
xfree (issuer);
issuer = NULL;
if (DBG_PACKET)
{
if (pending_desc)
log_debug ("%s", pending_desc);
log_debug (" Can't check signature allegedly"
" issued by %s: %s\n",
keystr (sig->keyid), gpg_strerror (err));
}
missing_issuer ++;
break;
}
}
if ((err = openpgp_pk_test_algo (sig->pubkey_algo)))
{
if (DBG_PACKET && pending_desc)
log_debug ("%s", pending_desc);
tty_printf (_("can't check signature with unsupported"
" public-key algorithm (%d): %s.\n"),
sig->pubkey_algo, gpg_strerror (err));
break;
}
if ((err = openpgp_md_test_algo (sig->digest_algo)))
{
if (DBG_PACKET && pending_desc)
log_debug ("%s", pending_desc);
tty_printf (_("can't check signature with unsupported"
" message-digest algorithm %d: %s.\n"),
sig->digest_algo, gpg_strerror (err));
break;
}
/* We iterate over the keyblock. Most likely, the matching
component is the current component so always try that
first. */
processed_current_component = 0;
for (n2 = current_component;
n2;
n2 = (processed_current_component ? n2->next : kb),
processed_current_component = 1)
if (is_deleted_kbnode (n2))
continue;
else if (processed_current_component && n2 == current_component)
/* Don't process it twice. */
continue;
else
{
err = check_signature_over_key_or_uid (issuer, sig, kb, n2->pkt,
NULL, NULL);
if (! err)
break;
}
/* n/sig is a signature and n2 is the component (public key,
subkey or user id) that it signs, if any.
current_component is that component that it appears to
apply to (according to the ordering). */
if (current_component == n2)
{
if (DBG_PACKET)
{
log_debug ("%s", pending_desc);
log_debug (" Good signature over last key or uid!\n");
}
rc = 0;
}
else if (n2)
{
log_assert (n2->pkt->pkttype == PKT_USER_ID
|| n2->pkt->pkttype == PKT_PUBLIC_KEY
|| n2->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (DBG_PACKET)
{
log_debug ("%s", pending_desc);
log_debug (" Good signature out of order!"
" (Over %s (%d) '%s')\n",
n2->pkt->pkttype == PKT_USER_ID
? "user id"
: n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
? "subkey"
: "primary key",
n2->pkt->pkttype,
n2->pkt->pkttype == PKT_USER_ID
? n2->pkt->pkt.user_id->name
: pk_keyid_str (n2->pkt->pkt.public_key));
}
/* Reorder the packets: move the signature n to be just
after n2. */
/* Unlink the signature. */
log_assert (n_prevp);
*n_prevp = n->next;
/* Insert the sig immediately after the component. */
n->next = n2->next;
n2->next = n;
reordered ++;
modified = 1;
rc = 0;
}
else
{
if (DBG_PACKET)
{
log_debug ("%s", pending_desc);
log_debug (" Bad signature.\n");
}
if (DBG_PACKET)
dump_sig_params = 1;
bad_signature ++;
rc = GPG_ERR_BAD_SIGNATURE;
}
/* We don't cache the result here, because we haven't
completely checked that the signature is legitimate. For
instance, if we have a revocation certificate on Alice's
key signed by Bob, the signature may be good, but we
haven't checked that Bob is a designated revoker. */
/* cache_sig_result (sig, rc); */
{
int has_selfsig = 0;
if (! rc && issuer == pk)
{
if (n2->pkt->pkttype == PKT_PUBLIC_KEY
&& (/* Direct key signature. */
sig->sig_class == 0x1f
/* Key revocation signature. */
|| sig->sig_class == 0x20))
has_selfsig = 1;
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (/* Subkey binding sig. */
sig->sig_class == 0x18
/* Subkey revocation sig. */
|| sig->sig_class == 0x28))
has_selfsig = 1;
if (n2->pkt->pkttype == PKT_USER_ID
&& (/* Certification sigs. */
sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13
/* Certification revocation sig. */
|| sig->sig_class == 0x30))
has_selfsig = 1;
}
if ((n2 && n2 != last_printed_component)
|| (! n2 && last_printed_component != current_component))
{
int is_reordered = n2 && n2 != current_component;
if (n2)
last_printed_component = n2;
else
last_printed_component = current_component;
if (!modified)
;
else if (last_printed_component->pkt->pkttype == PKT_USER_ID)
{
tty_printf ("uid ");
tty_print_utf8_string (last_printed_component
->pkt->pkt.user_id->name,
last_printed_component
->pkt->pkt.user_id->len);
}
else if (last_printed_component->pkt->pkttype
== PKT_PUBLIC_KEY)
tty_printf ("pub %s",
pk_keyid_str (last_printed_component
->pkt->pkt.public_key));
else
tty_printf ("sub %s",
pk_keyid_str (last_printed_component
->pkt->pkt.public_key));
if (modified)
{
if (is_reordered)
tty_printf (_(" (reordered signatures follow)"));
tty_printf ("\n");
}
}
if (modified)
print_one_sig (rc, kb, n, NULL, NULL, NULL, has_selfsig,
0, only_selfsigs);
}
if (dump_sig_params)
{
int i;
for (i = 0; i < pubkey_get_nsig (sig->pubkey_algo); i ++)
{
char buffer[1024];
size_t len;
char *printable;
gcry_mpi_print (GCRYMPI_FMT_USG,
buffer, sizeof (buffer), &len,
sig->data[i]);
printable = bin2hex (buffer, len, NULL);
log_info (" %d: %s\n", i, printable);
xfree (printable);
}
}
break;
default:
if (DBG_PACKET)
log_debug ("unhandled packet: %d\n", p->pkttype);
break;
}
}
xfree (pending_desc);
pending_desc = NULL;
if (issuer != pk)
free_public_key (issuer);
issuer = NULL;
/* Identify keys / uids that don't have a self-sig. */
{
int has_selfsig = 0;
PACKET *p;
PKT_signature *sig;
current_component = NULL;
for (n = kb; n; n = n->next)
{
if (is_deleted_kbnode (n))
continue;
p = n->pkt;
switch (p->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_USER_ID:
if (current_component && ! has_selfsig)
missing_selfsig ++;
current_component = n;
has_selfsig = 0;
break;
case PKT_SIGNATURE:
if (! current_component || has_selfsig)
break;
sig = n->pkt->pkt.signature;
if (! (sig->flags.checked && sig->flags.valid))
break;
if (keyid_cmp (pk_keyid (pk), sig->keyid) != 0)
/* Different issuer, couldn't be a self-sig. */
break;
if (current_component->pkt->pkttype == PKT_PUBLIC_KEY
&& (/* Direct key signature. */
sig->sig_class == 0x1f
/* Key revocation signature. */
|| sig->sig_class == 0x20))
has_selfsig = 1;
if (current_component->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (/* Subkey binding sig. */
sig->sig_class == 0x18
/* Subkey revocation sig. */
|| sig->sig_class == 0x28))
has_selfsig = 1;
if (current_component->pkt->pkttype == PKT_USER_ID
&& (/* Certification sigs. */
sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13
/* Certification revocation sig. */
|| sig->sig_class == 0x30))
has_selfsig = 1;
break;
default:
if (current_component && ! has_selfsig)
missing_selfsig ++;
current_component = NULL;
}
}
}
if (dups || missing_issuer || bad_signature || reordered)
tty_printf (_("key %s:\n"), pk_keyid_str (pk));
if (dups)
tty_printf (ngettext ("%d duplicate signature removed\n",
"%d duplicate signatures removed\n", dups), dups);
if (missing_issuer)
tty_printf (ngettext ("%d signature not checked due to a missing key\n",
"%d signatures not checked due to missing keys\n",
missing_issuer), missing_issuer);
if (bad_signature)
tty_printf (ngettext ("%d bad signature\n",
"%d bad signatures\n",
bad_signature), bad_signature);
if (reordered)
tty_printf (ngettext ("%d signature reordered\n",
"%d signatures reordered\n",
reordered), reordered);
if (only_selfsigs && (bad_signature || reordered))
tty_printf (_("Warning: errors found and only checked self-signatures,"
" run '%s' to check all signatures.\n"), "check");
return modified;
}
static int
sign_mk_attrib (PKT_signature * sig, void *opaque)
{
struct sign_attrib *attrib = opaque;
byte buf[8];
if (attrib->non_exportable)
{
buf[0] = 0; /* not exportable */
build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1);
}
if (attrib->non_revocable)
{
buf[0] = 0; /* not revocable */
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
}
if (attrib->reason)
revocation_reason_build_cb (sig, attrib->reason);
if (attrib->trust_depth)
{
/* Not critical. If someone doesn't understand trust sigs,
this can still be a valid regular signature. */
buf[0] = attrib->trust_depth;
buf[1] = attrib->trust_value;
build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2);
/* Critical. If someone doesn't understands regexps, this
whole sig should be invalid. Note the +1 for the length -
regexps are null terminated. */
if (attrib->trust_regexp)
build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP,
attrib->trust_regexp,
strlen (attrib->trust_regexp) + 1);
}
return 0;
}
static void
trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp)
{
char *p;
*trust_value = 0;
*trust_depth = 0;
*regexp = NULL;
/* Same string as pkclist.c:do_edit_ownertrust */
tty_printf (_
("Please decide how far you trust this user to correctly verify"
" other users' keys\n(by looking at passports, checking"
" fingerprints from different sources, etc.)\n"));
tty_printf ("\n");
tty_printf (_(" %d = I trust marginally\n"), 1);
tty_printf (_(" %d = I trust fully\n"), 2);
tty_printf ("\n");
while (*trust_value == 0)
{
p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
/* 60 and 120 are as per RFC2440 */
if (p[0] == '1' && !p[1])
*trust_value = 60;
else if (p[0] == '2' && !p[1])
*trust_value = 120;
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter the depth of this trust signature.\n"
"A depth greater than 1 allows the key you are"
" signing to make\n"
"trust signatures on your behalf.\n"));
tty_printf ("\n");
while (*trust_depth == 0)
{
p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
*trust_depth = atoi (p);
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter a domain to restrict this signature, "
"or enter for none.\n"));
tty_printf ("\n");
p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
if (strlen (p) > 0)
{
char *q = p;
int regexplen = 100, ind;
*regexp = xmalloc (regexplen);
/* Now mangle the domain the user entered into a regexp. To do
this, \-escape everything that isn't alphanumeric, and attach
"<[^>]+[@.]" to the front, and ">$" to the end. */
strcpy (*regexp, "<[^>]+[@.]");
ind = strlen (*regexp);
while (*q)
{
if (!((*q >= 'A' && *q <= 'Z')
|| (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9')))
(*regexp)[ind++] = '\\';
(*regexp)[ind++] = *q;
if ((regexplen - ind) < 3)
{
regexplen += 100;
*regexp = xrealloc (*regexp, regexplen);
}
q++;
}
(*regexp)[ind] = '\0';
strcat (*regexp, ">$");
}
xfree (p);
tty_printf ("\n");
}
/*
* Loop over all LOCUSR and sign the uids after asking. If no
* user id is marked, all user ids will be signed; if some user_ids
* are marked only those will be signed. If QUICK is true the
* function won't ask the user and use sensible defaults.
*/
static int
sign_uids (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, strlist_t locusr, int *ret_modified,
int local, int nonrevocable, int trust, int interactive,
int quick)
{
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
PKT_public_key *pk = NULL;
KBNODE node, uidnode;
PKT_public_key *primary_pk = NULL;
int select_all = !count_selected_uids (keyblock) || interactive;
/* Build a list of all signators.
*
* We use the CERT flag to request the primary which must always
* be one which is capable of signing keys. I can't see a reason
* why to sign keys using a subkey. Implementation of USAGE_CERT
* is just a hack in getkey.c and does not mean that a subkey
* marked as certification capable will be used. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if (rc)
goto leave;
/* Loop over all signators. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
u32 sk_keyid[2], pk_keyid[2];
char *p, *trust_regexp = NULL;
int class = 0, selfsig = 0;
u32 duration = 0, timestamp = 0;
byte trust_depth = 0, trust_value = 0;
pk = sk_rover->pk;
keyid_from_pk (pk, sk_keyid);
/* Set mark A for all selected user ids. */
for (node = keyblock; node; node = node->next)
{
if (select_all || (node->flag & NODFLG_SELUID))
node->flag |= NODFLG_MARK_A;
else
node->flag &= ~NODFLG_MARK_A;
}
/* Reset mark for uids which are already signed. */
uidnode = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
primary_pk = node->pkt->pkt.public_key;
keyid_from_pk (primary_pk, pk_keyid);
/* Is this a self-sig? */
if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1])
selfsig = 1;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL;
if (uidnode)
{
int yesreally = 0;
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
if (opt.only_sign_text_ids
&& uidnode->pkt->pkt.user_id->attribs)
{
tty_fprintf (fp, _("Skipping user ID \"%s\","
" which is not a text ID.\n"),
user);
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (uidnode->pkt->pkt.user_id->is_revoked)
{
tty_fprintf (fp, _("User ID \"%s\" is revoked."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (uidnode->pkt->pkt.user_id->is_expired)
{
tty_fprintf (fp, _("User ID \"%s\" is expired."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.expire_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (!uidnode->pkt->pkt.user_id->created && !selfsig)
{
tty_fprintf (fp, _("User ID \"%s\" is not self-signed."),
user);
if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
if (uidnode && interactive && !yesreally && !quick)
{
tty_fprintf (fp,
_("User ID \"%s\" is signable. "), user);
if (!cpr_get_answer_is_yes ("sign_uid.sign_okay",
_("Sign it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
}
xfree (user);
}
}
else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class & ~3) == 0x10)
{
if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0]
&& sk_keyid[1] == node->pkt->pkt.signature->keyid[1])
{
char buf[50];
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
/* It's a v3 self-sig. Make it into a v4 self-sig? */
if (node->pkt->pkt.signature->version < 4
&& selfsig && !quick)
{
tty_fprintf (fp,
_("The self-signature on \"%s\"\n"
"is a PGP 2.x-style signature.\n"), user);
/* Note that the regular PGP2 warning below
still applies if there are no v4 sigs on
this key at all. */
if (opt.expert)
if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay",
_("Do you want to promote "
"it to an OpenPGP self-"
"signature? (y/N) ")))
{
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Is the current signature expired? */
if (node->pkt->pkt.signature->flags.expired)
{
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"has expired.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.replace_expired_okay",
_("Do you want to issue a "
"new signature to replace "
"the expired one? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
if (!node->pkt->pkt.signature->flags.exportable && !local)
{
/* It's a local sig, and we want to make a
exportable sig. */
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"is a local signature.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.local_promote_okay",
_("Do you want to promote "
"it to a full exportable " "signature? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Fixme: see whether there is a revocation in which
* case we should allow signing it again. */
if (!node->pkt->pkt.signature->flags.exportable && local)
tty_fprintf ( fp,
_("\"%s\" was already locally signed by key %s\n"),
user, keystr_from_pk (pk));
else
tty_fprintf (fp,
_("\"%s\" was already signed by key %s\n"),
user, keystr_from_pk (pk));
if (opt.expert && !quick
&& cpr_get_answer_is_yes ("sign_uid.dupe_okay",
_("Do you want to sign it "
"again anyway? (y/N) ")))
{
/* Don't delete the old sig here since this is
an --expert thing. */
xfree (user);
continue;
}
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong) pk->keyid[0], (ulong) pk->keyid[1]);
write_status_text (STATUS_ALREADY_SIGNED, buf);
uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */
xfree (user);
}
}
}
/* Check whether any uids are left for signing. */
if (!count_uids_with_flag (keyblock, NODFLG_MARK_A))
{
tty_fprintf (fp, _("Nothing to sign with key %s\n"),
keystr_from_pk (pk));
continue;
}
/* Ask whether we really should sign these user id(s). */
tty_fprintf (fp, "\n");
show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0);
tty_fprintf (fp, "\n");
if (primary_pk->expiredate && !selfsig)
{
/* Static analyzer note: A claim that PRIMARY_PK might be
NULL is not correct because it set from the public key
packet which is always the first packet in a keyblock and
parsed in the above loop over the keyblock. In case the
keyblock has no packets at all and thus the loop was not
entered the above count_uids_with_flag would have
detected this case. */
u32 now = make_timestamp ();
if (primary_pk->expiredate <= now)
{
tty_fprintf (fp, _("This key has expired!"));
if (opt.expert && !quick)
{
tty_fprintf (fp, " ");
if (!cpr_get_answer_is_yes ("sign_uid.expired_okay",
_("Are you sure you still "
"want to sign it? (y/N) ")))
continue;
}
else
{
tty_fprintf (fp, _(" Unable to sign.\n"));
continue;
}
}
else
{
tty_fprintf (fp, _("This key is due to expire on %s.\n"),
expirestr_from_pk (primary_pk));
if (opt.ask_cert_expire && !quick)
{
char *answer = cpr_get ("sign_uid.expire",
_("Do you want your signature to "
"expire at the same time? (Y/n) "));
if (answer_is_yes_no_default (answer, 1))
{
/* This fixes the signature timestamp we're
going to make as now. This is so the
expiration date is exactly correct, and not
a few seconds off (due to the time it takes
to answer the questions, enter the
passphrase, etc). */
timestamp = now;
duration = primary_pk->expiredate - now;
}
cpr_kill_prompt ();
xfree (answer);
}
}
}
/* Only ask for duration if we haven't already set it to match
the expiration of the pk */
if (!duration && !selfsig)
{
if (opt.ask_cert_expire && !quick)
duration = ask_expire_interval (1, opt.def_cert_expire);
else
duration = parse_expire_string (opt.def_cert_expire);
}
if (selfsig)
;
else
{
if (opt.batch || !opt.ask_cert_level || quick)
class = 0x10 + opt.def_cert_level;
else
{
char *answer;
tty_fprintf (fp,
_("How carefully have you verified the key you are "
"about to sign actually belongs\nto the person "
"named above? If you don't know what to "
"answer, enter \"0\".\n"));
tty_fprintf (fp, "\n");
tty_fprintf (fp, _(" (0) I will not answer.%s\n"),
opt.def_cert_level == 0 ? " (default)" : "");
tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"),
opt.def_cert_level == 1 ? " (default)" : "");
tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"),
opt.def_cert_level == 2 ? " (default)" : "");
tty_fprintf (fp,
_(" (3) I have done very careful checking.%s\n"),
opt.def_cert_level == 3 ? " (default)" : "");
tty_fprintf (fp, "\n");
while (class == 0)
{
answer = cpr_get ("sign_uid.class",
_("Your selection? "
"(enter '?' for more information): "));
if (answer[0] == '\0')
class = 0x10 + opt.def_cert_level; /* Default */
else if (ascii_strcasecmp (answer, "0") == 0)
class = 0x10; /* Generic */
else if (ascii_strcasecmp (answer, "1") == 0)
class = 0x11; /* Persona */
else if (ascii_strcasecmp (answer, "2") == 0)
class = 0x12; /* Casual */
else if (ascii_strcasecmp (answer, "3") == 0)
class = 0x13; /* Positive */
else
tty_fprintf (fp, _("Invalid selection.\n"));
xfree (answer);
}
}
if (trust && !quick)
trustsig_prompt (&trust_value, &trust_depth, &trust_regexp);
}
if (!quick)
{
p = get_user_id_native (sk_keyid);
tty_fprintf (fp,
_("Are you sure that you want to sign this key with your\n"
"key \"%s\" (%s)\n"), p, keystr_from_pk (pk));
xfree (p);
}
if (selfsig)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("This will be a self-signature.\n"));
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-revocable.\n"));
}
}
else
{
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-revocable.\n"));
}
switch (class)
{
case 0x11:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have not checked this key at all.\n"));
break;
case 0x12:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key casually.\n"));
break;
case 0x13:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key very carefully.\n"));
break;
}
}
tty_fprintf (fp, "\n");
if (opt.batch && opt.answer_yes)
;
else if (quick)
;
else if (!cpr_get_answer_is_yes ("sign_uid.okay",
_("Really sign? (y/N) ")))
continue;
/* Now we can sign the user ids. */
reloop: /* (Must use this, because we are modifing the list.) */
primary_pk = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
primary_pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID
&& (node->flag & NODFLG_MARK_A))
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
log_assert (primary_pk);
memset (&attrib, 0, sizeof attrib);
attrib.non_exportable = local;
attrib.non_revocable = nonrevocable;
attrib.trust_depth = trust_depth;
attrib.trust_value = trust_value;
attrib.trust_regexp = trust_regexp;
node->flag &= ~NODFLG_MARK_A;
/* We force creation of a v4 signature for local
* signatures, otherwise we would not generate the
* subpacket with v3 keys and the signature becomes
* exportable. */
if (selfsig)
rc = make_keysig_packet (&sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
0x13, 0, 0, 0,
keygen_add_std_prefs, primary_pk,
NULL);
else
rc = make_keysig_packet (&sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
class, 0,
timestamp, duration,
sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto leave;
}
*ret_modified = 1; /* We changed the keyblock. */
update_trust = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE);
goto reloop;
}
}
/* Delete any sigs that got promoted */
for (node = keyblock; node; node = node->next)
if (node->flag & NODFLG_DELSIG)
delete_kbnode (node);
} /* End loop over signators. */
leave:
release_sk_list (sk_list);
return rc;
}
/*
* Change the passphrase of the primary and all secondary keys. Note
* that it is common to use only one passphrase for the primary and
* all subkeys. However, this is now (since GnuPG 2.1) all up to the
* gpg-agent. Returns 0 on success or an error code.
*/
static gpg_error_t
change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
kbnode_t node;
PKT_public_key *pk;
int any;
u32 keyid[2], subid[2];
char *hexgrip = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key missing!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* Check whether it is likely that we will be able to change the
passphrase for any subkey. */
for (any = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *serialno;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
if (!err && serialno)
; /* Key on card. */
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Maybe stub key. */
else if (!err)
any = 1; /* Key is known. */
else
log_error ("key %s: error getting keyinfo from agent: %s\n",
keystr_with_sub (keyid, subid), gpg_strerror (err));
xfree (serialno);
}
}
err = 0;
if (!any)
{
tty_printf (_("Key has only stub or on-card key items - "
"no passphrase to change.\n"));
goto leave;
}
/* Change the passphrase for all keys. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *desc;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
desc = gpg_format_keydesc (pk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, 0,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (err)
log_log ((gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
? GPGRT_LOG_INFO : GPGRT_LOG_ERROR,
_("key %s: error changing passphrase: %s\n"),
keystr_with_sub (keyid, subid),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break;
}
}
leave:
xfree (hexgrip);
xfree (cache_nonce);
xfree (passwd_nonce);
return err;
}
/* Fix various problems in the keyblock. Returns true if the keyblock
was changed. Note that a pointer to the keyblock must be given and
the function may change it (i.e. replacing the first node). */
static int
fix_keyblock (kbnode_t *keyblockp)
{
int changed = 0;
if (collapse_uids (keyblockp))
changed++;
if (check_all_keysigs (*keyblockp, 0, 1))
changed++;
reorder_keyblock (*keyblockp);
/* If we modified the keyblock, make sure the flags are right. */
if (changed)
merge_keys_and_selfsig (*keyblockp);
return changed;
}
static int
parse_sign_type (const char *str, int *localsig, int *nonrevokesig,
int *trustsig)
{
const char *p = str;
while (*p)
{
if (ascii_strncasecmp (p, "l", 1) == 0)
{
*localsig = 1;
p++;
}
else if (ascii_strncasecmp (p, "nr", 2) == 0)
{
*nonrevokesig = 1;
p += 2;
}
else if (ascii_strncasecmp (p, "t", 1) == 0)
{
*trustsig = 1;
p++;
}
else
return 0;
}
return 1;
}
/*
* Menu driven key editor. If seckey_check is true, then a secret key
* that matches username will be looked for. If it is false, not all
* commands will be available.
*
* Note: to keep track of certain selections we use node->mark MARKBIT_xxxx.
*/
/* Need an SK for this command */
#define KEYEDIT_NEED_SK 1
/* Cannot be viewing the SK for this command */
#define KEYEDIT_NOT_SK 2
/* Must be viewing the SK for this command */
#define KEYEDIT_ONLY_SK 4
/* Match the tail of the string */
#define KEYEDIT_TAIL_MATCH 8
enum cmdids
{
cmdNONE = 0,
cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN,
cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG,
cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY,
cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF,
cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN,
#ifndef NO_TRUST_MODELS
cmdENABLEKEY, cmdDISABLEKEY,
#endif /*!NO_TRUST_MODELS*/
cmdSHOWPREF,
cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST,
cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD,
cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP
};
static struct
{
const char *name;
enum cmdids id;
int flags;
const char *desc;
} cmds[] =
{
{ "quit", cmdQUIT, 0, N_("quit this menu")},
{ "q", cmdQUIT, 0, NULL},
{ "save", cmdSAVE, 0, N_("save and quit")},
{ "help", cmdHELP, 0, N_("show this help")},
{ "?", cmdHELP, 0, NULL},
{ "fpr", cmdFPR, 0, N_("show key fingerprint")},
{ "grip", cmdGRIP, 0, N_("show the keygrip")},
{ "list", cmdLIST, 0, N_("list key and user IDs")},
{ "l", cmdLIST, 0, NULL},
{ "uid", cmdSELUID, 0, N_("select user ID N")},
{ "key", cmdSELKEY, 0, N_("select subkey N")},
{ "check", cmdCHECK, 0, N_("check signatures")},
{ "c", cmdCHECK, 0, NULL},
{ "change-usage", cmdCHANGEUSAGE, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
{ "cross-certify", cmdBACKSIGN, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
{ "backsign", cmdBACKSIGN, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
{ "sign", cmdSIGN, KEYEDIT_NOT_SK | KEYEDIT_TAIL_MATCH,
N_("sign selected user IDs [* see below for related commands]")},
{ "s", cmdSIGN, KEYEDIT_NOT_SK, NULL},
/* "lsign" and friends will never match since "sign" comes first
and it is a tail match. They are just here so they show up in
the help menu. */
{ "lsign", cmdNOP, 0, N_("sign selected user IDs locally")},
{ "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")},
{ "nrsign", cmdNOP, 0,
N_("sign selected user IDs with a non-revocable signature")},
{ "debug", cmdDEBUG, 0, NULL},
{ "adduid", cmdADDUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a user ID")},
{ "addphoto", cmdADDPHOTO, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("add a photo ID")},
{ "deluid", cmdDELUID, KEYEDIT_NOT_SK, N_("delete selected user IDs")},
/* delphoto is really deluid in disguise */
{ "delphoto", cmdDELUID, KEYEDIT_NOT_SK, NULL},
{ "addkey", cmdADDKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a subkey")},
#ifdef ENABLE_CARD_SUPPORT
{ "addcardkey", cmdADDCARDKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("add a key to a smartcard")},
{ "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_ONLY_SK,
N_("move a key to a smartcard")},
{ "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_ONLY_SK,
N_("move a backup key to a smartcard")},
#endif /*ENABLE_CARD_SUPPORT */
{ "delkey", cmdDELKEY, KEYEDIT_NOT_SK, N_("delete selected subkeys")},
{ "addrevoker", cmdADDREVOKER, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("add a revocation key")},
{ "delsig", cmdDELSIG, KEYEDIT_NOT_SK,
N_("delete signatures from the selected user IDs")},
{ "expire", cmdEXPIRE, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("change the expiration date for the key or selected subkeys")},
{ "primary", cmdPRIMARY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("flag the selected user ID as primary")},
{ "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */
{ "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL},
{ "pref", cmdPREF, KEYEDIT_NOT_SK, N_("list preferences (expert)")},
{ "showpref", cmdSHOWPREF, KEYEDIT_NOT_SK, N_("list preferences (verbose)")},
{ "setpref", cmdSETPREF, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("set preference list for the selected user IDs")},
{ "updpref", cmdSETPREF, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
{ "keyserver", cmdPREFKS, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("set the preferred keyserver URL for the selected user IDs")},
{ "notation", cmdNOTATION, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("set a notation for the selected user IDs")},
{ "passwd", cmdPASSWD, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("change the passphrase")},
{ "password", cmdPASSWD, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
#ifndef NO_TRUST_MODELS
{ "trust", cmdTRUST, KEYEDIT_NOT_SK, N_("change the ownertrust")},
#endif /*!NO_TRUST_MODELS*/
{ "revsig", cmdREVSIG, KEYEDIT_NOT_SK,
N_("revoke signatures on the selected user IDs")},
{ "revuid", cmdREVUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("revoke selected user IDs")},
{ "revphoto", cmdREVUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL},
{ "revkey", cmdREVKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK,
N_("revoke key or selected subkeys")},
#ifndef NO_TRUST_MODELS
{ "enable", cmdENABLEKEY, KEYEDIT_NOT_SK, N_("enable key")},
{ "disable", cmdDISABLEKEY, KEYEDIT_NOT_SK, N_("disable key")},
#endif /*!NO_TRUST_MODELS*/
{ "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")},
{ "clean", cmdCLEAN, KEYEDIT_NOT_SK,
N_("compact unusable user IDs and remove unusable signatures from key")},
{ "minimize", cmdMINIMIZE, KEYEDIT_NOT_SK,
N_("compact unusable user IDs and remove all signatures from key")},
{ NULL, cmdNONE, 0, NULL}
};
#ifdef HAVE_LIBREADLINE
/*
These two functions are used by readline for command completion.
*/
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text */
if (cmds[list_index++].desc && strncmp (name, text, len) == 0)
return strdup (name);
}
return NULL;
}
static char **
keyedit_completion (const char *text, int start, int end)
{
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
(void) end;
if (start == 0)
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /* HAVE_LIBREADLINE */
/* Main function of the menu driven key editor. */
void
keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check)
{
enum cmdids cmd = 0;
gpg_error_t err = 0;
KBNODE keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int have_seckey = 0;
char *answer = NULL;
int redisplay = 1;
int modified = 0;
int sec_shadowing = 0;
int run_subkey_warnings = 0;
int have_commands = !!commands;
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error (_("can't do this in batch mode\n"));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* Due to Windows peculiarities we need to make sure that the
trustdb stale check is done before we open another file
(i.e. by searching for a key). In theory we could make sure
that the files are closed after use but the open/close caches
inhibits that and flushing the cache right before the stale
check is not easy to implement. Thus we take the easy way out
and run the stale check as early as possible. Note, that for
non- W32 platforms it is run indirectly trough a call to
get_validity (). */
check_trustdb_stale (ctrl);
#endif
/* Get the public key */
err = get_pubkey_byname (ctrl, NULL, NULL, username, &keyblock, &kdbhd, 1, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
goto leave;
}
if (fix_keyblock (&keyblock))
modified++;
/* See whether we have a matching secret key. */
if (seckey_check)
{
have_seckey = !agent_probe_any_secret_key (ctrl, keyblock);
if (have_seckey && !quiet)
tty_printf (_("Secret key is available.\n"));
}
/* Main command loop. */
for (;;)
{
int i, arg_number, photo;
const char *arg_string = "";
char *p;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
tty_printf ("\n");
if (redisplay && !quiet)
{
/* Show using flags: with_revoker, with_subkeys. */
show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0);
tty_printf ("\n");
redisplay = 0;
}
if (run_subkey_warnings)
{
run_subkey_warnings = 0;
if (!count_selected_keys (keyblock))
subkey_expire_warning (keyblock);
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
#ifdef HAVE_LIBREADLINE
tty_enable_completion (keyedit_completion);
#endif
answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> ");
cpr_kill_prompt ();
tty_disable_completion ();
}
trim_spaces (answer);
}
while (*answer == '#');
arg_number = 0; /* Here is the init which egcc complains about. */
photo = 0; /* Same here. */
if (!*answer)
cmd = cmdLIST;
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else if (digitp (answer))
{
cmd = cmdSELUID;
arg_number = atoi (answer);
}
else
{
if ((p = strchr (answer, ' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi (p);
arg_string = p;
}
for (i = 0; cmds[i].name; i++)
{
if (cmds[i].flags & KEYEDIT_TAIL_MATCH)
{
size_t l = strlen (cmds[i].name);
size_t a = strlen (answer);
if (a >= l)
{
if (!ascii_strcasecmp (&answer[a - l], cmds[i].name))
{
answer[a - l] = '\0';
break;
}
}
}
else if (!ascii_strcasecmp (answer, cmds[i].name))
break;
}
if ((cmds[i].flags & KEYEDIT_NEED_SK) && !have_seckey)
{
tty_printf (_("Need the secret key to do this.\n"));
cmd = cmdNOP;
}
else
cmd = cmds[i].id;
}
/* Dispatch the command. */
switch (cmd)
{
case cmdHELP:
for (i = 0; cmds[i].name; i++)
{
if ((cmds[i].flags & KEYEDIT_NEED_SK) && !have_seckey)
; /* Skip those item if we do not have the secret key. */
else if (cmds[i].desc)
tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc));
}
tty_printf ("\n");
tty_printf
(_("* The 'sign' command may be prefixed with an 'l' for local "
"signatures (lsign),\n"
" a 't' for trust signatures (tsign), an 'nr' for "
"non-revocable signatures\n"
" (nrsign), or any combination thereof (ltsign, "
"tnrsign, etc.).\n"));
break;
case cmdLIST:
redisplay = 1;
break;
case cmdFPR:
show_key_and_fingerprint
(keyblock, (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1))));
break;
case cmdGRIP:
show_key_and_grip (keyblock);
break;
case cmdSELUID:
if (strlen (arg_string) == NAMEHASH_LEN * 2)
redisplay = menu_select_uid_namehash (keyblock, arg_string);
else
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
redisplay = menu_select_uid (keyblock, arg_number);
}
break;
case cmdSELKEY:
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
if (menu_select_key (keyblock, arg_number, p))
redisplay = 1;
}
break;
case cmdCHECK:
if (check_all_keysigs (keyblock, count_selected_uids (keyblock),
!strcmp (arg_string, "selfsig")))
modified = 1;
break;
case cmdSIGN:
{
int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0;
if (pk->flags.revoked)
{
tty_printf (_("Key is revoked."));
if (opt.expert)
{
tty_printf (" ");
if (!cpr_get_answer_is_yes
("keyedit.sign_revoked.okay",
_("Are you sure you still want to sign it? (y/N) ")))
break;
}
else
{
tty_printf (_(" Unable to sign.\n"));
break;
}
}
if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock))
{
int result;
if (opt.only_sign_text_ids)
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all user IDs? (y/N) "));
else
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all text user IDs? (y/N) "));
if (! result)
{
if (opt.interactive)
interactive = 1;
else
{
tty_printf (_("Hint: Select the user IDs to sign\n"));
have_commands = 0;
break;
}
}
}
/* What sort of signing are we doing? */
if (!parse_sign_type
(answer, &localsig, &nonrevokesig, &trustsig))
{
tty_printf (_("Unknown signature type '%s'\n"), answer);
break;
}
sign_uids (ctrl, NULL, keyblock, locusr, &modified,
localsig, nonrevokesig, trustsig, interactive, 0);
}
break;
case cmdDEBUG:
dump_kbnode (keyblock);
break;
case cmdTOGGLE:
/* The toggle command is a leftover from old gpg versions
where we worked with a secret and a public keyring. It
is not necessary anymore but we keep this command for the
sake of scripts using it. */
redisplay = 1;
break;
case cmdADDPHOTO:
if (RFC2440)
{
tty_printf (_("This command is not allowed while in %s mode.\n"),
compliance_option_string ());
break;
}
photo = 1;
/* fall through */
case cmdADDUID:
if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL))
{
update_trust = 1;
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (keyblock);
}
break;
case cmdDELUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (real_uids_left (keyblock) < 1)
tty_printf (_("You can't delete the last user ID!\n"));
else if (cpr_get_answer_is_yes
("keyedit.remove.uid.okay",
n1 > 1 ? _("Really remove all selected user IDs? (y/N) ")
: _("Really remove this user ID? (y/N) ")))
{
menu_deluid (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdDELSIG:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (menu_delsig (keyblock))
{
/* No redisplay here, because it may scroll away some
* of the status output of this command. */
modified = 1;
}
}
break;
case cmdADDKEY:
if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (keyblock);
}
break;
#ifdef ENABLE_CARD_SUPPORT
case cmdADDCARDKEY:
if (!card_generate_subkey (keyblock))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (keyblock);
}
break;
case cmdKEYTOCARD:
{
KBNODE node = NULL;
switch (count_selected_keys (keyblock))
{
case 0:
if (cpr_get_answer_is_yes
("keyedit.keytocard.use_primary",
/* TRANSLATORS: Please take care: This is about
moving the key and not about removing it. */
_("Really move the primary key? (y/N) ")))
node = keyblock;
break;
case 1:
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& node->flag & NODFLG_SELKEY)
break;
}
break;
default:
tty_printf (_("You must select exactly one key.\n"));
break;
}
if (node)
{
PKT_public_key *xxpk = node->pkt->pkt.public_key;
if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0))
{
redisplay = 1;
sec_shadowing = 1;
}
}
}
break;
case cmdBKUPTOCARD:
{
/* Ask for a filename, check whether this is really a
backup key as generated by the card generation, parse
that key and store it on card. */
KBNODE node;
char *fname;
PACKET *pkt;
IOBUF a;
if (!*arg_string)
{
tty_printf (_("Command expects a filename argument\n"));
break;
}
if (*arg_string == DIRSEP_C)
fname = xstrdup (arg_string);
else if (*arg_string == '~')
fname = make_filename (arg_string, NULL);
else
fname = make_filename (gnupg_homedir (), arg_string, NULL);
/* Open that file. */
a = iobuf_open (fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if (!a)
{
tty_printf (_("Can't open '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
break;
}
/* Parse and check that file. */
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
err = parse_packet (a, pkt);
iobuf_close (a);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname);
if (!err && pkt->pkttype != PKT_SECRET_KEY
&& pkt->pkttype != PKT_SECRET_SUBKEY)
err = GPG_ERR_NO_SECKEY;
if (err)
{
tty_printf (_("Error reading backup key from '%s': %s\n"),
fname, gpg_strerror (err));
xfree (fname);
free_packet (pkt);
xfree (pkt);
break;
}
xfree (fname);
node = new_kbnode (pkt);
/* Transfer it to gpg-agent which handles secret keys. */
err = transfer_secret_keys (ctrl, NULL, node, 1, 1);
/* Treat the pkt as a public key. */
pkt->pkttype = PKT_PUBLIC_KEY;
/* Ask gpg-agent to store the secret key to card. */
if (card_store_subkey (node, 0))
{
redisplay = 1;
sec_shadowing = 1;
}
release_kbnode (node);
}
break;
#endif /* ENABLE_CARD_SUPPORT */
case cmdDELKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
tty_printf (_("You must select at least one key.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "key");
}
else if (!cpr_get_answer_is_yes
("keyedit.remove.subkey.okay",
n1 > 1 ? _("Do you really want to delete the "
"selected keys? (y/N) ")
: _("Do you really want to delete this key? (y/N) ")))
;
else
{
menu_delkey (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdADDREVOKER:
{
int sensitive = 0;
if (ascii_strcasecmp (arg_string, "sensitive") == 0)
sensitive = 1;
if (menu_addrevoker (ctrl, keyblock, sensitive))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (keyblock);
}
}
break;
case cmdREVUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (cpr_get_answer_is_yes
("keyedit.revoke.uid.okay",
n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ")
: _("Really revoke this user ID? (y/N) ")))
{
if (menu_revuid (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdREVKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
_("Do you really want to revoke"
" the entire key? (y/N) ")))
{
if (menu_revkey (keyblock))
modified = 1;
redisplay = 1;
}
}
else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
n1 > 1 ?
_("Do you really want to revoke"
" the selected subkeys? (y/N) ")
: _("Do you really want to revoke"
" this subkey? (y/N) ")))
{
if (menu_revsubkey (keyblock))
modified = 1;
redisplay = 1;
}
if (modified)
merge_keys_and_selfsig (keyblock);
}
break;
case cmdEXPIRE:
if (gpg_err_code (menu_expire (keyblock, 0, 0)) == GPG_ERR_TRUE)
{
merge_keys_and_selfsig (keyblock);
run_subkey_warnings = 1;
modified = 1;
redisplay = 1;
}
break;
case cmdCHANGEUSAGE:
if (menu_changeusage (keyblock))
{
merge_keys_and_selfsig (keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdBACKSIGN:
if (menu_backsign (keyblock))
{
modified = 1;
redisplay = 1;
}
break;
case cmdPRIMARY:
if (menu_set_primary_uid (keyblock))
{
merge_keys_and_selfsig (keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdPASSWD:
change_passphrase (ctrl, keyblock);
break;
#ifndef NO_TRUST_MODELS
case cmdTRUST:
if (opt.trust_model == TM_EXTERNAL)
{
tty_printf (_("Owner trust may not be set while "
"using a user provided trust database\n"));
break;
}
show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0);
tty_printf ("\n");
if (edit_ownertrust (ctrl, find_kbnode (keyblock,
PKT_PUBLIC_KEY)->pkt->pkt.
public_key, 1))
{
redisplay = 1;
/* No real need to set update_trust here as
edit_ownertrust() calls revalidation_mark()
anyway. */
update_trust = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 1);
}
break;
case cmdSHOWPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 2);
}
break;
case cmdSETPREF:
{
PKT_user_id *tempuid;
keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0);
tempuid = keygen_get_std_prefs ();
tty_printf (_("Set preference list to:\n"));
show_prefs (tempuid, NULL, 1);
free_user_id (tempuid);
if (cpr_get_answer_is_yes
("keyedit.setpref.okay",
count_selected_uids (keyblock) ?
_("Really update the preferences"
" for the selected user IDs? (y/N) ")
: _("Really update the preferences? (y/N) ")))
{
if (menu_set_preferences (keyblock))
{
merge_keys_and_selfsig (keyblock);
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdPREFKS:
if (menu_set_keyserver_url (*arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOTATION:
if (menu_set_notation (*arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOP:
break;
case cmdREVSIG:
if (menu_revsig (keyblock))
{
redisplay = 1;
modified = 1;
}
break;
#ifndef NO_TRUST_MODELS
case cmdENABLEKEY:
case cmdDISABLEKEY:
if (enable_disable_key (keyblock, cmd == cmdDISABLEKEY))
{
redisplay = 1;
modified = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdSHOWPHOTO:
menu_showphoto (ctrl, keyblock);
break;
case cmdCLEAN:
if (menu_clean (keyblock, 0))
redisplay = modified = 1;
break;
case cmdMINIMIZE:
if (menu_clean (keyblock, 1))
redisplay = modified = 1;
break;
case cmdQUIT:
if (have_commands)
goto leave;
if (!modified && !sec_shadowing)
goto leave;
if (!cpr_get_answer_is_yes ("keyedit.save.okay",
_("Save changes? (y/N) ")))
{
if (cpr_enabled ()
|| cpr_get_answer_is_yes ("keyedit.cancel.okay",
_("Quit without saving? (y/N) ")))
goto leave;
break;
}
/* fall through */
case cmdSAVE:
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (sec_shadowing)
{
err = agent_scd_learn (NULL, 1);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (!modified && !sec_shadowing)
tty_printf (_("Key not changed so no update needed.\n"));
if (update_trust)
{
revalidation_mark ();
update_trust = 0;
}
goto leave;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
}
} /* End of the main command loop. */
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
xfree (answer);
}
/* Change the passphrase of the secret key identified by USERNAME. */
void
keyedit_passwd (ctrl_t ctrl, const char *username)
{
gpg_error_t err;
PKT_public_key *pk;
kbnode_t keyblock = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock);
if (err)
goto leave;
err = change_passphrase (ctrl, keyblock);
leave:
release_kbnode (keyblock);
free_public_key (pk);
if (err)
{
log_info ("error changing the passphrase for '%s': %s\n",
username, gpg_strerror (err));
write_status_error ("keyedit.passwd", err);
}
else
write_status_text (STATUS_SUCCESS, "keyedit.passwd");
}
/* Unattended adding of a new keyid. USERNAME specifies the
key. NEWUID is the new user id to add to the key. */
void
keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock = NULL;
kbnode_t node;
char *uidstring = NULL;
uidstring = xstrdup (newuid);
trim_spaces (uidstring);
if (!*uidstring)
{
log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
kdbhd = keydb_new ();
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
goto leave;
}
err = classify_user_id (username, &desc, 1);
if (!err)
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
{
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
/* Now with the keyblock retrieved, search again to detect an
ambiguous specification. We need to save the found state so
that we can do an update later. */
keydb_push_found_state (kdbhd);
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
keydb_pop_found_state (kdbhd);
if (!err)
{
/* We require the secret primary key to add a UID. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
BUG ();
err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key);
}
}
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
username, gpg_strerror (err));
goto leave;
}
fix_keyblock (&keyblock);
merge_keys_and_selfsig (keyblock);
if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark ();
}
leave:
xfree (uidstring);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended revocation of a keyid. USERNAME specifies the
key. UIDTOREV is the user id revoke from the key. */
void
keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock = NULL;
kbnode_t node;
int modified = 0;
size_t revlen;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
kdbhd = keydb_new ();
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
goto leave;
}
err = classify_user_id (username, &desc, 1);
if (!err)
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
{
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
/* Now with the keyblock retrieved, search again to detect an
ambiguous specification. We need to save the found state so
that we can do an update later. */
keydb_push_found_state (kdbhd);
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
keydb_pop_found_state (kdbhd);
if (!err)
{
/* We require the secret primary key to revoke a UID. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
BUG ();
err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key);
}
}
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
username, gpg_strerror (err));
goto leave;
}
fix_keyblock (&keyblock);
setup_main_keyids (keyblock);
revlen = strlen (uidtorev);
/* find the right UID */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& revlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen))
{
struct revocation_reason_info *reason;
reason = get_default_uid_revocation_reason ();
err = core_revuid (ctrl, keyblock, node, reason, &modified);
release_revocation_reason_info (reason);
if (err)
{
log_error (_("User ID revocation failed: %s\n"),
gpg_strerror (err));
goto leave;
}
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark ();
goto leave;
}
}
log_error (_("User ID revocation failed: %s\n"), gpg_strerror (GPG_ERR_NOT_FOUND));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Find a keyblock by fingerprint because only this uniquely
* identifies a key and may thus be used to select a key for
* unattended subkey creation os key signing. */
static gpg_error_t
find_by_primary_fpr (ctrl_t ctrl, const char *fpr,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
*r_keyblock = NULL;
*r_kdbhd = NULL;
if (classify_user_id (fpr, &desc, 1)
|| !(desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_FPR16
|| desc.mode == KEYDB_SEARCH_MODE_FPR20))
{
log_error (_("\"%s\" is not a fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
err = get_pubkey_byname (ctrl, NULL, NULL, fpr, &keyblock, &kdbhd, 1, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err));
goto leave;
}
/* Check that the primary fingerprint has been given. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16
&& !memcmp (fprbin, desc.u.fpr, 16))
;
else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR
&& !memcmp (fprbin, desc.u.fpr, 16)
&& !desc.u.fpr[16]
&& !desc.u.fpr[17]
&& !desc.u.fpr[18]
&& !desc.u.fpr[19])
;
else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20
|| desc.mode == KEYDB_SEARCH_MODE_FPR)
&& !memcmp (fprbin, desc.u.fpr, 20))
;
else
{
log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
err = 0;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended key signing function. If the key specifified by FPR is
available and FPR is the primary fingerprint all user ids of the
key are signed using the default signing key. If UIDS is an empty
list all usable UIDs are signed, if it is not empty, only those
user ids matching one of the entries of the list are signed. With
LOCAL being true the signatures are marked as non-exportable. */
void
keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
strlist_t locusr, int local)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int modified = 0;
PKT_public_key *pk;
kbnode_t node;
strlist_t sl;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
key and may thus be used to select a key for unattended key
signing. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (&keyblock))
modified++;
/* Give some info in verbose. */
if (opt.verbose)
{
show_key_with_all_names (ctrl, es_stdout, keyblock, 0,
1/*with_revoker*/, 1/*with_fingerprint*/,
0, 0, 1);
es_fflush (es_stdout);
}
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n"));
goto leave;
}
/* Set the flags according to the UIDS list. Fixme: We may want to
use classify_user_id along with dedicated compare functions so
that we match the same way as in the key lookup. */
any = 0;
menu_select_uid (keyblock, 0); /* Better clear the flags first. */
for (sl=uids; sl; sl = sl->next)
{
const char *name = sl->d;
int count = 0;
sl->flags &= ~(1|2); /* Clear flags used for error reporting. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->attrib_data)
;
else if (*name == '='
&& strlen (name+1) == uid->len
&& !memcmp (uid->name, name + 1, uid->len))
{ /* Exact match - we don't do a check for ambiguity
* in this case. */
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
}
else if (ascii_memistr (uid->name, uid->len,
*name == '*'? name+1:name))
{
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
count++;
}
}
}
if (count > 1)
{
any = -1; /* Force failure at end. */
sl->flags |= 2; /* Report as ambiguous. */
}
}
/* Check whether all given user ids were found. */
for (sl=uids; sl; sl = sl->next)
if (!(sl->flags & 1))
any = -1; /* That user id was not found. */
/* Print an error if there was a problem with the user ids. */
if (uids && any < 1)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
es_fflush (es_stdout);
for (sl=uids; sl; sl = sl->next)
{
if ((sl->flags & 2))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME));
else if (!(sl->flags & 1))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_NOT_FOUND));
}
log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n"));
goto leave;
}
/* Sign. */
sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1);
es_fflush (es_stdout);
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
if (update_trust)
revalidation_mark ();
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended subkey creation function.
*
*/
void
keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended subkey
* creation. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (&keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
goto leave;
}
/* Create the subkey. Note that the called function already prints
* an error message. */
if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr))
modified = 1;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended expiration setting function for the main key.
*
*/
void
keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
u32 expire;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended
* expiration setting. */
err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
if (err)
goto leave;
if (fix_keyblock (&keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
log_error (_("'%s' is not a valid expiration time\n"), expirestr);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (expire)
expire += make_timestamp ();
/* Set the new expiration date. */
err = menu_expire (keyblock, 1, expire);
if (gpg_err_code (err) == GPG_ERR_TRUE)
modified = 1;
else if (err)
goto leave;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark ();
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
if (err)
write_status_error ("set_expire", err);
}
static void
tty_print_notations (int indent, PKT_signature * sig)
{
int first = 1;
struct notation *notation, *nd;
if (indent < 0)
{
first = 0;
indent = -indent;
}
notation = sig_to_notation (sig);
for (nd = notation; nd; nd = nd->next)
{
if (!first)
tty_printf ("%*s", indent, "");
else
first = 0;
tty_print_utf8_string (nd->name, strlen (nd->name));
tty_printf ("=");
tty_print_utf8_string (nd->value, strlen (nd->value));
tty_printf ("\n");
}
free_notation (notation);
}
/*
* Show preferences of a public keyblock.
*/
static void
show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose)
{
const prefitem_t fake = { 0, 0 };
const prefitem_t *prefs;
int i;
if (!uid)
return;
if (uid->prefs)
prefs = uid->prefs;
else if (verbose)
prefs = &fake;
else
return;
if (verbose)
{
int any, des_seen = 0, sha1_seen = 0, uncomp_seen = 0;
tty_printf (" ");
tty_printf (_("Cipher: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_SYM)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!openpgp_cipher_test_algo (prefs[i].value)
&& prefs[i].value < 100)
tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == CIPHER_ALGO_3DES)
des_seen = 1;
}
}
if (!des_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", openpgp_cipher_algo_name (CIPHER_ALGO_3DES));
}
tty_printf ("\n ");
tty_printf (_("Digest: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_HASH)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100)
tty_printf ("%s", gcry_md_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == DIGEST_ALGO_SHA1)
sha1_seen = 1;
}
}
if (!sha1_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", gcry_md_algo_name (DIGEST_ALGO_SHA1));
}
tty_printf ("\n ");
tty_printf (_("Compression: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_ZIP)
{
const char *s = compress_algo_to_string (prefs[i].value);
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (s && prefs[i].value < 100)
tty_printf ("%s", s);
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == COMPRESS_ALGO_NONE)
uncomp_seen = 1;
}
}
if (!uncomp_seen)
{
if (any)
tty_printf (", ");
else
{
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_ZIP));
tty_printf (", ");
}
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_NONE));
}
if (uid->flags.mdc || !uid->flags.ks_modify)
{
tty_printf ("\n ");
tty_printf (_("Features: "));
any = 0;
if (uid->flags.mdc)
{
tty_printf ("MDC");
any = 1;
}
if (!uid->flags.ks_modify)
{
if (any)
tty_printf (", ");
tty_printf (_("Keyserver no-modify"));
}
}
tty_printf ("\n");
if (selfsig)
{
const byte *pref_ks;
size_t pref_ks_len;
pref_ks = parse_sig_subpkt (selfsig->hashed,
SIGSUBPKT_PREF_KS, &pref_ks_len);
if (pref_ks && pref_ks_len)
{
tty_printf (" ");
tty_printf (_("Preferred keyserver: "));
tty_print_utf8_string (pref_ks, pref_ks_len);
tty_printf ("\n");
}
if (selfsig->flags.notation)
{
tty_printf (" ");
tty_printf (_("Notations: "));
tty_print_notations (5 + strlen (_("Notations: ")), selfsig);
}
}
}
else
{
tty_printf (" ");
for (i = 0; prefs[i].type; i++)
{
tty_printf (" %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' :
prefs[i].type == PREFTYPE_HASH ? 'H' :
prefs[i].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[i].value);
}
if (uid->flags.mdc)
tty_printf (" [mdc]");
if (!uid->flags.ks_modify)
tty_printf (" [no-ks-modify]");
tty_printf ("\n");
}
}
/* This is the version of show_key_with_all_names used when
opt.with_colons is used. It prints all available data in a easy to
parse format and does not translate utf8 */
static void
show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
{
KBNODE node;
int i, j, ulti_hack = 0;
byte pk_version = 0;
PKT_public_key *primary = NULL;
int have_seckey;
if (!fp)
fp = es_stdout;
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
u32 keyid[2];
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk_version = pk->version;
primary = pk;
}
keyid_from_pk (pk, keyid);
have_seckey = !agent_probe_secret_key (ctrl, pk);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
es_fputs (have_seckey? "sec:" : "pub:", fp);
else
es_fputs (have_seckey? "ssb:" : "sub:", fp);
if (!pk->flags.valid)
es_putc ('i', fp);
else if (pk->flags.revoked)
es_putc ('r', fp);
else if (pk->has_expired)
es_putc ('e', fp);
else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
{
int trust = get_validity_info (ctrl, keyblock, pk, NULL);
if (trust == 'u')
ulti_hack = 1;
es_putc (trust, fp);
}
es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::",
nbits_from_pk (pk),
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
(ulong) pk->timestamp, (ulong) pk->expiredate);
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& !(opt.fast_list_mode || opt.no_expensive_trust_checks))
es_putc (get_ownertrust_info (pk, 0), fp);
es_putc (':', fp);
es_putc (':', fp);
es_putc (':', fp);
/* Print capabilities. */
if ((pk->pubkey_usage & PUBKEY_USAGE_ENC))
es_putc ('e', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_SIG))
es_putc ('s', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_CERT))
es_putc ('c', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
es_putc ('a', fp);
es_putc ('\n', fp);
print_fingerprint (fp, pk, 0);
print_revokers (fp, pk);
}
}
/* the user ids */
i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (uid->attrib_data)
es_fputs ("uat:", fp);
else
es_fputs ("uid:", fp);
if (uid->is_revoked)
es_fputs ("r::::::::", fp);
else if (uid->is_expired)
es_fputs ("e::::::::", fp);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
es_fputs ("::::::::", fp);
else
{
int uid_validity;
if (primary && !ulti_hack)
uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
else
uid_validity = 'u';
es_fprintf (fp, "%c::::::::", uid_validity);
}
if (uid->attrib_data)
es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (fp, uid->name, uid->len, ":", NULL);
es_putc (':', fp);
/* signature class */
es_putc (':', fp);
/* capabilities */
es_putc (':', fp);
/* preferences */
if (pk_version > 3 || uid->selfsigversion > 3)
{
const prefitem_t *prefs = uid->prefs;
for (j = 0; prefs && prefs[j].type; j++)
{
if (j)
es_putc (' ', fp);
es_fprintf (fp,
"%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' :
prefs[j].type == PREFTYPE_HASH ? 'H' :
prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[j].value);
}
if (uid->flags.mdc)
es_fputs (",mdc", fp);
if (!uid->flags.ks_modify)
es_fputs (",no-ks-modify", fp);
}
es_putc (':', fp);
/* flags */
es_fprintf (fp, "%d,", i);
if (uid->is_primary)
es_putc ('p', fp);
if (uid->is_revoked)
es_putc ('r', fp);
if (uid->is_expired)
es_putc ('e', fp);
if ((node->flag & NODFLG_SELUID))
es_putc ('s', fp);
if ((node->flag & NODFLG_MARK_A))
es_putc ('m', fp);
es_putc (':', fp);
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
#ifdef USE_TOFU
enum tofu_policy policy;
if (! tofu_get_policy (ctrl, primary, uid, &policy)
&& policy != TOFU_POLICY_NONE)
es_fprintf (fp, "%s", tofu_policy_str (policy));
#endif /*USE_TOFU*/
}
es_putc (':', fp);
es_putc ('\n', fp);
}
}
}
static void
show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk, unsigned int flag,
int with_prefs)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (!flag || (flag && (node->flag & flag)))
{
if (!(flag & NODFLG_MARK_A) && pk)
tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid));
if (flag & NODFLG_MARK_A)
tty_fprintf (fp, " ");
else if (node->flag & NODFLG_SELUID)
tty_fprintf (fp, "(%d)* ", i);
else if (uid->is_primary)
tty_fprintf (fp, "(%d). ", i);
else
tty_fprintf (fp, "(%d) ", i);
tty_print_utf8_string2 (fp, uid->name, uid->len, 0);
tty_fprintf (fp, "\n");
if (with_prefs && pk)
{
if (pk->version > 3 || uid->selfsigversion > 3)
{
PKT_signature *selfsig = NULL;
KBNODE signode;
for (signode = node->next;
signode && signode->pkt->pkttype == PKT_SIGNATURE;
signode = signode->next)
{
if (signode->pkt->pkt.signature->
flags.chosen_selfsig)
{
selfsig = signode->pkt->pkt.signature;
break;
}
}
show_prefs (uid, selfsig, with_prefs == 2);
}
else
tty_fprintf (fp, _("There are no preferences on a"
" PGP 2.x-style user ID.\n"));
}
}
}
}
}
/*
* Display the key a the user ids, if only_marked is true, do only so
* for user ids with mark A flag set and do not display the index
* number. If FP is not NULL print to the given stream and not to the
* tty (ignored in with-colons mode).
*/
static void
show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked, int with_revoker,
int with_fpr, int with_subkeys, int with_prefs,
int nowarn)
{
gpg_error_t err;
kbnode_t node;
int i;
int do_warn = 0;
int have_seckey = 0;
char *serialno = NULL;
PKT_public_key *primary = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
if (opt.with_colons)
{
show_key_with_all_names_colon (ctrl, fp, keyblock);
return;
}
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !is_deleted_kbnode (node)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *otrust = "err";
const char *trust = "err";
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* do it here, so that debug messages don't clutter the
* output */
static int did_warn = 0;
trust = get_validity_string (ctrl, pk, NULL);
otrust = get_ownertrust_string (pk, 0);
/* Show a warning once */
if (!did_warn
&& (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
& TRUST_FLAG_PENDING_CHECK))
{
did_warn = 1;
do_warn = 1;
}
primary = pk;
}
if (pk->flags.revoked)
{
char *user = get_user_id_string_native (pk->revoked.keyid);
tty_fprintf (fp,
_("The following key was revoked on"
" %s by %s key %s\n"),
revokestr_from_pk (pk),
gcry_pk_algo_name (pk->revoked.algo), user);
xfree (user);
}
if (with_revoker)
{
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
for (i = 0; i < pk->numrevkeys; i++)
{
u32 r_keyid[2];
char *user;
const char *algo;
algo = gcry_pk_algo_name (pk->revkey[i].algid);
keyid_from_fingerprint (pk->revkey[i].fpr,
MAX_FINGERPRINT_LEN, r_keyid);
user = get_user_id_string_native (r_keyid);
tty_fprintf (fp,
_("This key may be revoked by %s key %s"),
algo ? algo : "?", user);
if (pk->revkey[i].class & 0x40)
{
tty_fprintf (fp, " ");
tty_fprintf (fp, _("(sensitive)"));
}
tty_fprintf (fp, "\n");
xfree (user);
}
}
keyid_from_pk (pk, NULL);
xfree (serialno);
serialno = NULL;
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n",
gpg_strerror (err));
have_seckey = 0;
}
else
have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
xfree (hexgrip);
}
tty_fprintf
(fp, "%s%c %s/%s",
node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" :
node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" :
have_seckey ? "ssb" :
"sub",
(node->flag & NODFLG_SELKEY) ? '*' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (pk->keyid));
if (opt.legacy_list_mode)
tty_fprintf (fp, " ");
else
tty_fprintf (fp, "\n ");
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
if (pk->flags.revoked)
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
else if (pk->has_expired)
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
else
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1));
tty_fprintf (fp, "\n");
if (serialno)
{
/* The agent told us that a secret key is available and
that it has been stored on a card. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s\n",
4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s\n", serialno);
}
else if (pk->seckey_info
&& pk->seckey_info->is_protected
&& pk->seckey_info->s2k.mode == 1002)
{
/* FIXME: Check wether this code path is still used. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (pk->seckey_info->ivlen == 16
&& !memcmp (pk->seckey_info->iv,
"\xD2\x76\x00\x01\x24\x01", 6))
{
/* This is an OpenPGP card. */
for (i = 8; i < 14; i++)
{
if (i == 10)
tty_fprintf (fp, " ");
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
}
else
{
/* Unknown card: Print all. */
for (i = 0; i < pk->seckey_info->ivlen; i++)
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
if (opt.trust_model != TM_ALWAYS)
{
tty_fprintf (fp, "%*s",
opt.legacy_list_mode?
((int) keystrlen () + 13):5, "");
/* Ownertrust is only meaningful for the PGP or
classic trust models, or PGP combined with TOFU */
if (opt.trust_model == TM_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
int width = 14 - strlen (otrust);
if (width <= 0)
width = 1;
tty_fprintf (fp, _("trust: %s"), otrust);
tty_fprintf (fp, "%*s", width, "");
}
tty_fprintf (fp, _("validity: %s"), trust);
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& (get_ownertrust (pk) & TRUST_FLAG_DISABLED))
{
tty_fprintf (fp, "*** ");
tty_fprintf (fp, _("This key has been disabled"));
tty_fprintf (fp, "\n");
}
}
if ((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr)
{
print_fingerprint (fp, pk, 2);
tty_fprintf (fp, "\n");
}
}
}
show_names (ctrl, fp,
keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs);
if (do_warn && !nowarn)
tty_fprintf (fp, _("Please note that the shown key validity"
" is not necessarily correct\n"
"unless you restart the program.\n"));
xfree (serialno);
}
/* Display basic key information. This function is suitable to show
information on the key without any dependencies on the trustdb or
any other internal GnuPG stuff. KEYBLOCK may either be a public or
a secret key. This function may be called with KEYBLOCK containing
secret keys and thus the printing of "pub" vs. "sec" does only
depend on the packet type and not by checking with gpg-agent. */
void
show_basic_key_info (KBNODE keyblock)
{
KBNODE node;
int i;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* The primary key */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
/* Note, we use the same format string as in other show
functions to make the translation job easier. */
tty_printf ("%s %s/%s ",
node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" :
node->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "sub" :
node->pkt->pkttype == PKT_SECRET_KEY ? "sec" :"ssb",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk));
tty_printf (_("created: %s"), datestr_from_pk (pk));
tty_printf (" ");
tty_printf (_("expires: %s"), expirestr_from_pk (pk));
tty_printf ("\n");
print_fingerprint (NULL, pk, 3);
tty_printf ("\n");
}
}
/* The user IDs. */
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
tty_printf (" ");
if (uid->is_revoked)
tty_printf ("[%s] ", _("revoked"));
else if (uid->is_expired)
tty_printf ("[%s] ", _("expired"));
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
}
}
static void
show_key_and_fingerprint (kbnode_t keyblock, int with_subkeys)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk));
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_print_utf8_string (uid->name, uid->len);
break;
}
}
tty_printf ("\n");
if (pk)
print_fingerprint (NULL, pk, 2);
if (with_subkeys)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("sub %s/%s %s [%s]\n",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
print_fingerprint (NULL, pk, 4);
}
}
}
}
/* Show a listing of the primary and its subkeys along with their
keygrips. */
static void
show_key_and_grip (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
char *hexgrip;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("%s %s/%s %s [%s]\n",
node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
if (!hexkeygrip_from_pk (pk, &hexgrip))
{
tty_printf (" Keygrip: %s\n", hexgrip);
xfree (hexgrip);
}
}
}
}
/* Show a warning if no uids on the key have the primary uid flag
set. */
static void
no_primary_warning (KBNODE keyblock)
{
KBNODE node;
int have_primary = 0, uid_count = 0;
/* TODO: if we ever start behaving differently with a primary or
non-primary attribute ID, we will need to check for attributes
here as well. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data == NULL)
{
uid_count++;
if (node->pkt->pkt.user_id->is_primary == 2)
{
have_primary = 1;
break;
}
}
}
if (uid_count > 1 && !have_primary)
log_info (_
("WARNING: no user ID has been marked as primary. This command"
" may\n cause a different user ID to become"
" the assumed primary.\n"));
}
/* Print a warning if the latest encryption subkey expires soon. This
function is called after the expire data of the primary key has
been changed. */
static void
subkey_expire_warning (kbnode_t keyblock)
{
u32 curtime = make_timestamp ();
kbnode_t node;
PKT_public_key *pk;
/* u32 mainexpire = 0; */
u32 subexpire = 0;
u32 latest_date = 0;
for (node = keyblock; node; node = node->next)
{
/* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */
/* { */
/* pk = node->pkt->pkt.public_key; */
/* mainexpire = pk->expiredate; */
/* } */
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (!pk->flags.valid)
continue;
if (pk->flags.revoked)
continue;
if (pk->timestamp > curtime)
continue; /* Ignore future keys. */
if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC))
continue; /* Not an encryption key. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
subexpire = pk->expiredate;
}
}
if (!subexpire)
return; /* No valid subkey with an expiration time. */
if (curtime + (10*86400) > subexpire)
{
log_info (_("WARNING: Your encryption subkey expires soon.\n"));
log_info (_("You may want to change its expiration date too.\n"));
}
}
/*
* Ask for a new user id, add the self-signature, and update the
* keyblock. If UIDSTRING is not NULL the user ID is generated
* unattended using that string. UIDSTRING is expected to be utf-8
* encoded and white space trimmed. Returns true if there is a new
* user id.
*/
static int
menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock,
int photo, const char *photo_name, const char *uidstring)
{
PKT_user_id *uid;
PKT_public_key *pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
KBNODE node;
KBNODE pub_where = NULL;
gpg_error_t err;
if (photo && uidstring)
return 0; /* Not allowed. */
for (node = pub_keyblock; node; pub_where = node, node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break;
}
if (!node) /* No subkey. */
pub_where = NULL;
log_assert (pk);
if (photo)
{
int hasattrib = 0;
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->attrib_data != NULL)
{
hasattrib = 1;
break;
}
/* It is legal but bad for compatibility to add a photo ID to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a photo on a v3 key.
Don't bother to ask this if the key already has a photo - any
damage has already been done at that point. -dms */
if (pk->version == 3 && !hasattrib)
{
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP2-style key. "
"Adding a photo ID may cause some versions\n"
" of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a photo ID to "
"a PGP2-style key.\n"));
return 0;
}
}
uid = generate_photo_id (ctrl, pk, photo_name);
}
else
uid = generate_user_id (pub_keyblock, uidstring);
if (!uid)
{
if (uidstring)
{
write_status_error ("adduid", gpg_error (304));
log_error ("%s", _("Such a user ID already exists on this key!\n"));
}
return 0;
}
err = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x13, 0, 0, 0,
keygen_add_std_prefs, pk, NULL);
if (err)
{
write_status_error ("keysig", err);
log_error ("signing failed: %s\n", gpg_strerror (err));
free_user_id (uid);
return 0;
}
/* Insert/append to public keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = uid;
node = new_kbnode (pkt);
if (pub_where)
insert_kbnode (pub_where, node, 0);
else
add_kbnode (pub_keyblock, node);
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
if (pub_where)
insert_kbnode (node, new_kbnode (pkt), 0);
else
add_kbnode (pub_keyblock, new_kbnode (pkt));
return 1;
}
/*
* Remove all selected userids from the keyring
*/
static void
menu_deluid (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
selected = node->flag & NODFLG_SELUID;
if (selected)
{
/* Only cause a trust update if we delete a
non-revoked user id */
if (!node->pkt->pkt.user_id->is_revoked)
update_trust = 1;
delete_kbnode (node);
}
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
selected = 0;
}
commit_kbnode (&pub_keyblock);
}
static int
menu_delsig (KBNODE pub_keyblock)
{
KBNODE node;
PKT_user_id *uid = NULL;
int changed = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL;
}
else if (uid && node->pkt->pkttype == PKT_SIGNATURE)
{
int okay, valid, selfsig, inv_sig, no_key, other_err;
tty_printf ("uid ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
okay = inv_sig = no_key = other_err = 0;
if (opt.with_colons)
valid = print_and_check_one_sig_colon (pub_keyblock, node,
&inv_sig, &no_key,
&other_err, &selfsig, 1);
else
valid = print_and_check_one_sig (pub_keyblock, node,
&inv_sig, &no_key, &other_err,
&selfsig, 1, 0);
if (valid)
{
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.valid",
_("Delete this good signature? (y/N/q)"));
/* Only update trust if we delete a good signature.
The other two cases do not affect trust. */
if (okay)
update_trust = 1;
}
else if (inv_sig || other_err)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.invalid",
_("Delete this invalid signature? (y/N/q)"));
else if (no_key)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.unknown",
_("Delete this unknown signature? (y/N/q)"));
if (okay == -1)
break;
if (okay && selfsig
&& !cpr_get_answer_is_yes
("keyedit.delsig.selfsig",
_("Really delete this self-signature? (y/N)")))
okay = 0;
if (okay)
{
delete_kbnode (node);
changed++;
}
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
uid = NULL;
}
if (changed)
{
commit_kbnode (&pub_keyblock);
tty_printf (ngettext("Deleted %d signature.\n",
"Deleted %d signatures.\n", changed), changed);
}
else
tty_printf (_("Nothing deleted.\n"));
return changed;
}
static int
menu_clean (KBNODE keyblock, int self_only)
{
KBNODE uidnode;
int modified = 0, select_all = !count_selected_uids (keyblock);
for (uidnode = keyblock->next;
uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY;
uidnode = uidnode->next)
{
if (uidnode->pkt->pkttype == PKT_USER_ID
&& (uidnode->flag & NODFLG_SELUID || select_all))
{
int uids = 0, sigs = 0;
char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len,
0);
clean_one_uid (keyblock, uidnode, opt.verbose, self_only, &uids,
&sigs);
if (uids)
{
const char *reason;
if (uidnode->pkt->pkt.user_id->is_revoked)
reason = _("revoked");
else if (uidnode->pkt->pkt.user_id->is_expired)
reason = _("expired");
else
reason = _("invalid");
tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason);
modified = 1;
}
else if (sigs)
{
tty_printf (ngettext("User ID \"%s\": %d signature removed\n",
"User ID \"%s\": %d signatures removed\n",
sigs), user, sigs);
modified = 1;
}
else
{
tty_printf (self_only == 1 ?
_("User ID \"%s\": already minimized\n") :
_("User ID \"%s\": already clean\n"), user);
}
xfree (user);
}
}
return modified;
}
/*
* Remove some of the secondary keys
*/
static void
menu_delkey (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
selected = node->flag & NODFLG_SELKEY;
if (selected)
delete_kbnode (node);
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else
selected = 0;
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys are no
longer used to certify other keys, so there is no change in
trust when revoking/removing them. */
}
/*
* Ask for a new revoker, create the self-signature and put it into
* the keyblock. Returns true if there is a new revoker.
*/
static int
menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive)
{
PKT_public_key *pk = NULL;
PKT_public_key *revoker_pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
struct revocation_key revkey;
size_t fprlen;
int rc;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
if (pk->numrevkeys == 0 && pk->version == 3)
{
/* It is legal but bad for compatibility to add a revoker to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a revoker on a v3 key.
Don't bother to ask this if the key already has a revoker -
any damage has already been done at that point. -dms */
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP 2.x-style key. "
"Adding a designated revoker may cause\n"
" some versions of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a designated revoker to "
"a PGP 2.x-style key.\n"));
return 0;
}
}
for (;;)
{
char *answer;
free_public_key (revoker_pk);
revoker_pk = xmalloc_clear (sizeof (*revoker_pk));
tty_printf ("\n");
answer = cpr_get_utf8
("keyedit.add_revoker",
_("Enter the user ID of the designated revoker: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
goto fail;
}
/* Note that I'm requesting CERT here, which usually implies
primary keys only, but some casual testing shows that PGP and
GnuPG both can handle a designated revocation from a subkey. */
revoker_pk->req_usage = PUBKEY_USAGE_CERT;
rc = get_pubkey_byname (ctrl, NULL, revoker_pk, answer, NULL, NULL, 1, 1);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (rc));
xfree (answer);
continue;
}
xfree (answer);
fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen);
if (fprlen != 20)
{
log_error (_("cannot appoint a PGP 2.x style key as a "
"designated revoker\n"));
continue;
}
revkey.class = 0x80;
if (sensitive)
revkey.class |= 0x40;
revkey.algid = revoker_pk->pubkey_algo;
if (cmp_public_keys (revoker_pk, pk) == 0)
{
/* This actually causes no harm (after all, a key that
designates itself as a revoker is the same as a
regular key), but it's easy enough to check. */
log_error (_("you cannot appoint a key as its own "
"designated revoker\n"));
continue;
}
keyid_from_pk (pk, NULL);
/* Does this revkey already exist? */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i;
for (i = 0; i < pk->numrevkeys; i++)
{
if (memcmp (&pk->revkey[i], &revkey,
sizeof (struct revocation_key)) == 0)
{
char buf[50];
log_error (_("this key has already been designated "
"as a revoker\n"));
format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf));
write_status_text (STATUS_ALREADY_SIGNED, buf);
break;
}
}
if (i < pk->numrevkeys)
continue;
}
print_pubkey_info (NULL, revoker_pk);
print_fingerprint (NULL, revoker_pk, 2);
tty_printf ("\n");
tty_printf (_("WARNING: appointing a key as a designated revoker "
"cannot be undone!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay",
_("Are you sure you want to appoint this "
"key as a designated revoker? (y/N) ")))
continue;
free_public_key (revoker_pk);
revoker_pk = NULL;
break;
}
rc = make_keysig_packet (&sig, pk, NULL, NULL, pk, 0x1F, 0, 0, 0,
keygen_add_revkey, &revkey, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto fail;
}
/* Insert into public keyblock. */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE);
return 1;
fail:
if (sig)
free_seckey_enc (sig);
free_public_key (revoker_pk);
return 0;
}
/* With FORCE_MAINKEY cleared this function handles the interactive
* menu option "expire". With FORCE_MAINKEY set this functions only
* sets the expiration date of the primary key to NEWEXPIRATION and
* avoid all interactivity. Retirns 0 if nothing was done,
* GPG_ERR_TRUE if the key was modified, or any other error code. */
static gpg_error_t
menu_expire (kbnode_t pub_keyblock, int force_mainkey, u32 newexpiration)
{
int signumber, rc;
u32 expiredate;
int mainkey = 0;
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
if (force_mainkey)
{
mainkey = 1;
expiredate = newexpiration;
}
else
{
int n1 = count_selected_keys (pub_keyblock);
if (n1 > 1)
{
if (!cpr_get_answer_is_yes
("keyedit.expire_multiple_subkeys.okay",
_("Are you sure you want to change the"
" expiration time for multiple subkeys? (y/N) ")))
return gpg_error (GPG_ERR_CANCELED);;
}
else if (n1)
tty_printf (_("Changing expiration time for a subkey.\n"));
else
{
tty_printf (_("Changing expiration time for the primary key.\n"));
mainkey = 1;
no_primary_warning (pub_keyblock);
}
expiredate = ask_expiredate ();
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
signumber = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
main_pk->expiredate = expiredate;
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if ((node->flag & NODFLG_SELKEY) && !force_mainkey)
{
sub_pk = node->pkt->pkt.public_key;
sub_pk->expiredate = expiredate;
}
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is a self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
signumber++;
if ((mainkey && main_pk->version < 4)
|| (!mainkey && sub_pk->version < 4))
{
log_info
(_("You can't change the expiration date of a v3 key\n"));
return gpg_error (GPG_ERR_LEGACY_KEY);
}
if (mainkey)
rc = update_keysig_packet (&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_expire,
main_pk);
else
rc =
update_keysig_packet (&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_expire, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_TRUE)
rc = GPG_ERR_GENERAL;
return rc;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
}
}
}
update_trust = 1;
return gpg_error (GPG_ERR_TRUE);
}
/* Change the capability of a selected key. This command should only
* be used to rectify badly created keys and as such is not suggested
* for general use. */
static int
menu_changeusage (kbnode_t keyblock)
{
int n1, rc;
int mainkey = 0;
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
n1 = count_selected_keys (keyblock);
if (n1 > 1)
{
tty_printf (_("You must select exactly one key.\n"));
return 0;
}
else if (n1)
tty_printf ("Changing usage of a subkey.\n");
else
{
tty_printf ("Changing usage of the primary key.\n");
mainkey = 1;
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->flag & NODFLG_SELKEY)
sub_pk = node->pkt->pkt.public_key;
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is the self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
if ((mainkey && main_pk->version < 4)
|| (!mainkey && sub_pk->version < 4))
{
log_info ("You can't change the capabilities of a v3 key\n");
return 0;
}
if (mainkey)
main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0,
main_pk->pubkey_usage);
else
sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1,
sub_pk->pubkey_usage);
if (mainkey)
rc = update_keysig_packet (&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_flags,
main_pk);
else
rc =
update_keysig_packet (&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_flags, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
break;
}
}
}
return 1;
}
static int
menu_backsign (KBNODE pub_keyblock)
{
int rc, modified = 0;
PKT_public_key *main_pk;
KBNODE node;
u32 timestamp;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
merge_keys_and_selfsig (pub_keyblock);
main_pk = pub_keyblock->pkt->pkt.public_key;
keyid_from_pk (main_pk, NULL);
/* We use the same timestamp for all backsigs so that we don't
reveal information about the used machine. */
timestamp = make_timestamp ();
for (node = pub_keyblock; node; node = node->next)
{
PKT_public_key *sub_pk = NULL;
KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/;
/* char *passphrase; */
/* Find a signing subkey with no backsig */
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG)
{
if (node->pkt->pkt.public_key->flags.backsig)
tty_printf (_
("signing subkey %s is already cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
else
sub_pk = node->pkt->pkt.public_key;
}
else
tty_printf (_("subkey %s does not sign and so does"
" not need to be cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
}
if (!sub_pk)
continue;
/* Find the selected selfsig on this subkey */
for (node2 = node->next;
node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next)
if (node2->pkt->pkt.signature->version >= 4
&& node2->pkt->pkt.signature->flags.chosen_selfsig)
{
sig_pk = node2;
break;
}
if (!sig_pk)
continue;
/* Find the secret subkey that matches the public subkey */
log_debug ("FIXME: Check whether a secret subkey is available.\n");
/* if (!sub_sk) */
/* { */
/* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */
/* keystr_from_pk (sub_pk)); */
/* continue; */
/* } */
/* Now we can get to work. */
rc = make_backsig (sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk,
timestamp, NULL);
if (!rc)
{
PKT_signature *newsig;
PACKET *newpkt;
rc = update_keysig_packet (&newsig, sig_pk->pkt->pkt.signature,
main_pk, NULL, sub_pk, main_pk,
NULL, NULL);
if (!rc)
{
/* Put the new sig into place on the pubkey */
newpkt = xmalloc_clear (sizeof (*newpkt));
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (sig_pk->pkt);
xfree (sig_pk->pkt);
sig_pk->pkt = newpkt;
modified = 1;
}
else
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
break;
}
}
else
{
log_error ("make_backsig failed: %s\n", gpg_strerror (rc));
break;
}
}
return modified;
}
static int
change_primary_uid_cb (PKT_signature * sig, void *opaque)
{
byte buf[1];
/* first clear all primary uid flags so that we are sure none are
* lingering around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID);
/* if opaque is set,we want to set the primary id */
if (opaque)
{
buf[0] = 1;
build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1);
}
return 0;
}
/*
* Set the primary uid flag for the selected UID. We will also reset
* all other primary uid flags. For this to work with have to update
* all the signature timestamps. If we would do this with the current
* time, we lose quite a lot of information, so we use a kludge to
* do this: Just increment the timestamp by one second which is
* sufficient to updated a signature during import.
*/
static int
menu_set_primary_uid (KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected;
int attribute = 0;
int modified = 0;
if (count_selected_uids (pub_keyblock) != 1)
{
tty_printf (_("Please select exactly one user ID.\n"));
return 0;
}
main_pk = NULL;
uid = NULL;
selected = 0;
/* Is our selected uid an attribute packet? */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID)
attribute = (node->pkt->pkt.user_id->attrib_data != NULL);
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = node->flag & NODFLG_SELUID;
}
else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& attribute == (uid->attrib_data != NULL)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced.
We can just ignore v3 signatures because they are
not able to carry the primary ID flag. We also
ignore self-sigs on user IDs that are not of the
same type that we are making primary. That is, if
we are making a user ID primary, we alter user IDs.
If we are making an attribute packet primary, we
alter attribute packets. */
/* FIXME: We must make sure that we only have one
self-signature per user ID here (not counting
revocations) */
PKT_signature *newsig;
PACKET *newpkt;
const byte *p;
int action;
/* See whether this signature has the primary UID flag. */
p = parse_sig_subpkt (sig->hashed,
SIGSUBPKT_PRIMARY_UID, NULL);
if (!p)
p = parse_sig_subpkt (sig->unhashed,
SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p) /* yes */
action = selected ? 0 : -1;
else /* no */
action = selected ? 1 : 0;
if (action)
{
int rc = update_keysig_packet (&newsig, sig,
main_pk, uid, NULL,
main_pk,
change_primary_uid_cb,
action > 0 ? "x" : NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
}
return modified;
}
/*
* Set preferences to new values for the selected user IDs
*/
static int
menu_set_preferences (KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
no_primary_warning (pub_keyblock);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user-ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the preferences. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
rc = update_keysig_packet (&newsig, sig,
main_pk, uid, NULL, main_pk,
keygen_upd_std_prefs, NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
return modified;
}
static int
menu_set_keyserver_url (const char *url, KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer, *uri;
no_primary_warning (pub_keyblock);
if (url)
answer = xstrdup (url);
else
{
answer = cpr_get_utf8 ("keyedit.add_keyserver",
_("Enter your preferred keyserver URL: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (ascii_strcasecmp (answer, "none") == 0)
uri = NULL;
else
{
struct keyserver_spec *keyserver = NULL;
/* Sanity check the format */
keyserver = parse_keyserver_uri (answer, 1);
xfree (answer);
if (!keyserver)
{
log_info (_("could not parse keyserver URL\n"));
return 0;
}
uri = xstrdup (keyserver->uri);
free_keyserver_spec (keyserver);
}
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the subpacket. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
const byte *p;
size_t plen;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &plen);
if (p && plen)
{
tty_printf ("Current preferred keyserver for user"
" ID \"%s\": ", user);
tty_print_utf8_string (p, plen);
tty_printf ("\n");
if (!cpr_get_answer_is_yes
("keyedit.confirm_keyserver",
uri
? _("Are you sure you want to replace it? (y/N) ")
: _("Are you sure you want to delete it? (y/N) ")))
continue;
}
else if (uri == NULL)
{
/* There is no current keyserver URL, so there
is no point in trying to un-set it. */
continue;
}
rc = update_keysig_packet (&newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_keyserver_url, uri);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
xfree (uri);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
xfree (user);
}
}
}
xfree (uri);
return modified;
}
static int
menu_set_notation (const char *string, KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer;
struct notation *notation;
no_primary_warning (pub_keyblock);
if (string)
answer = xstrdup (string);
else
{
answer = cpr_get_utf8 ("keyedit.add_notation",
_("Enter the notation: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (!ascii_strcasecmp (answer, "none")
|| !ascii_strcasecmp (answer, "-"))
notation = NULL; /* Delete them all. */
else
{
notation = string_to_notation (answer, 0);
if (!notation)
{
xfree (answer);
return 0;
}
}
xfree (answer);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
PKT_signature *newsig;
PACKET *newpkt;
int rc, skip = 0, addonly = 1;
if (sig->flags.notation)
{
tty_printf ("Current notations for user ID \"%s\":\n",
user);
tty_print_notations (-9, sig);
}
else
{
tty_printf ("No notations on user ID \"%s\"\n", user);
if (notation == NULL)
{
/* There are no current notations, so there
is no point in trying to un-set them. */
continue;
}
}
if (notation)
{
struct notation *n;
int deleting = 0;
notation->next = sig_to_notation (sig);
for (n = notation->next; n; n = n->next)
if (strcmp (n->name, notation->name) == 0)
{
if (notation->value)
{
if (strcmp (n->value, notation->value) == 0)
{
if (notation->flags.ignore)
{
/* Value match with a delete
flag. */
n->flags.ignore = 1;
deleting = 1;
}
else
{
/* Adding the same notation
twice, so don't add it at
all. */
skip = 1;
tty_printf ("Skipping notation:"
" %s=%s\n",
notation->name,
notation->value);
break;
}
}
}
else
{
/* No value, so it means delete. */
n->flags.ignore = 1;
deleting = 1;
}
if (n->flags.ignore)
{
tty_printf ("Removing notation: %s=%s\n",
n->name, n->value);
addonly = 0;
}
}
if (!notation->flags.ignore && !skip)
tty_printf ("Adding notation: %s=%s\n",
notation->name, notation->value);
/* We tried to delete, but had no matches. */
if (notation->flags.ignore && !deleting)
continue;
}
else
{
tty_printf ("Removing all notations\n");
addonly = 0;
}
if (skip
|| (!addonly
&&
!cpr_get_answer_is_yes ("keyedit.confirm_notation",
_("Proceed? (y/N) "))))
continue;
rc = update_keysig_packet (&newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_notations, notation);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
free_notation (notation);
xfree (user);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
if (notation)
{
/* Snip off the notation list from the sig */
free_notation (notation->next);
notation->next = NULL;
}
xfree (user);
}
}
}
}
free_notation (notation);
return modified;
}
/*
* Select one user id or remove all selection if IDX is 0 or select
* all if IDX is -1. Returns: True if the selection changed.
*/
static int
menu_select_uid (KBNODE keyblock, int idx)
{
KBNODE node;
int i;
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag |= NODFLG_SELUID;
return 1;
}
else if (idx) /* Toggle. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No user ID with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (++i == idx)
{
if ((node->flag & NODFLG_SELUID))
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
}
}
}
}
else /* Unselect all */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag &= ~NODFLG_SELUID;
}
return 1;
}
/* Search in the keyblock for a uid that matches namehash */
static int
menu_select_uid_namehash (KBNODE keyblock, const char *namehash)
{
byte hash[NAMEHASH_LEN];
KBNODE node;
int i;
log_assert (strlen (namehash) == NAMEHASH_LEN * 2);
for (i = 0; i < NAMEHASH_LEN; i++)
hash[i] = hextobyte (&namehash[i * 2]);
for (node = keyblock->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
namehash_from_uid (node->pkt->pkt.user_id);
if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) ==
0)
{
if (node->flag & NODFLG_SELUID)
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
break;
}
}
}
if (!node)
{
tty_printf (_("No user ID with hash %s\n"), namehash);
return 0;
}
return 1;
}
/*
* Select secondary keys
* Returns: True if the selection changed.
*/
static int
menu_select_key (KBNODE keyblock, int idx, char *p)
{
KBNODE node;
int i, j;
int is_hex_digits;
is_hex_digits = p && strlen (p) >= 8;
if (is_hex_digits)
{
/* Skip initial spaces. */
while (spacep (p))
p ++;
/* If the id starts with 0x accept and ignore it. */
if (p[0] == '0' && p[1] == 'x')
p += 2;
for (i = 0, j = 0; p[i]; i ++)
if (hexdigitp (&p[i]))
{
p[j] = toupper (p[i]);
j ++;
}
else if (spacep (&p[i]))
/* Skip spaces. */
{
}
else
{
is_hex_digits = 0;
break;
}
if (is_hex_digits)
/* In case we skipped some spaces, add a new NUL terminator. */
{
p[j] = 0;
/* If we skipped some spaces, make sure that we still have
at least 8 characters. */
is_hex_digits = (/* Short keyid. */
strlen (p) == 8
/* Long keyid. */
|| strlen (p) == 16
/* Fingerprints are (currently) 32 or 40
characters. */
|| strlen (p) >= 32);
}
}
if (is_hex_digits)
{
int found_one = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
int match = 0;
if (strlen (p) == 8 || strlen (p) == 16)
{
u32 kid[2];
char kid_str[17];
keyid_from_pk (node->pkt->pkt.public_key, kid);
format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG,
kid_str, sizeof (kid_str));
if (strcmp (p, kid_str) == 0)
match = 1;
}
else
{
char fp[2*MAX_FINGERPRINT_LEN + 1];
hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp));
if (strcmp (fp, p) == 0)
match = 1;
}
if (match)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
found_one = 1;
}
}
if (found_one)
return 1;
tty_printf (_("No subkey with key ID '%s'.\n"), p);
return 0;
}
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag |= NODFLG_SELKEY;
}
else if (idx) /* Toggle selection. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No subkey with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
}
}
}
else /* Unselect all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag &= ~NODFLG_SELKEY;
}
return 1;
}
static int
count_uids_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag))
i++;
return i;
}
static int
count_keys_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag))
i++;
return i;
}
static int
count_uids (KBNODE keyblock)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
i++;
return i;
}
/*
* Returns true if there is at least one selected user id
*/
static int
count_selected_uids (KBNODE keyblock)
{
return count_uids_with_flag (keyblock, NODFLG_SELUID);
}
static int
count_selected_keys (KBNODE keyblock)
{
return count_keys_with_flag (keyblock, NODFLG_SELKEY);
}
/* Returns how many real (i.e. not attribute) uids are unmarked. */
static int
real_uids_left (KBNODE keyblock)
{
KBNODE node;
int real = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) &&
!node->pkt->pkt.user_id->attrib_data)
real++;
return real;
}
/*
* Ask whether the signature should be revoked. If the user commits this,
* flag bit MARK_A is set on the signature and the user ID.
*/
static void
ask_revoke_sig (KBNODE keyblock, KBNODE node)
{
int doit = 0;
PKT_user_id *uid;
PKT_signature *sig = node->pkt->pkt.signature;
KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
if (!unode)
{
log_error ("Oops: no user ID for signature\n");
return;
}
uid = unode->pkt->pkt.user_id;
if (opt.with_colons)
{
if (uid->attrib_data)
printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len);
else
{
es_printf ("uid:::::::::");
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
}
es_printf ("\n");
print_and_check_one_sig_colon (keyblock, node, NULL, NULL, NULL, NULL,
1);
}
else
{
char *p = utf8_to_native (unode->pkt->pkt.user_id->name,
unode->pkt->pkt.user_id->len, 0);
tty_printf (_("user ID: \"%s\"\n"), p);
xfree (p);
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"), "");
}
if (sig->flags.expired)
{
tty_printf (_("This signature expired on %s.\n"),
expirestr_from_sig (sig));
/* Use a different question so we can have different help text */
doit = cpr_get_answer_is_yes
("ask_revoke_sig.expired",
_("Are you sure you still want to revoke it? (y/N) "));
}
else
doit = cpr_get_answer_is_yes
("ask_revoke_sig.one",
_("Create a revocation certificate for this signature? (y/N) "));
if (doit)
{
node->flag |= NODFLG_MARK_A;
unode->flag |= NODFLG_MARK_A;
}
}
/*
* Display all user ids of the current public key together with signatures
* done by one of our keys. Then walk over all this sigs and ask the user
* whether he wants to revoke this signature.
* Return: True when the keyblock has changed.
*/
static int
menu_revsig (KBNODE keyblock)
{
PKT_signature *sig;
PKT_public_key *primary_pk;
KBNODE node;
int changed = 0;
int rc, any, skip = 1, all = !count_selected_uids (keyblock);
struct revocation_reason_info *reason = NULL;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* First check whether we have any signatures at all. */
any = 0;
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
skip = 0;
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
any = 1;
break;
}
}
}
if (!any)
{
tty_printf (_("Not signed by you.\n"));
return 0;
}
/* FIXME: detect duplicates here */
tty_printf (_("You have signed these user IDs on key %s:\n"),
keystr_from_pk (keyblock->pkt->pkt.public_key));
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
/* Hmmm: Should we show only UIDs with a signature? */
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
skip = 0;
}
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"),
sig->flags.revocable ? "" : _(" (non-revocable)"));
if (sig->flags.revocable)
node->flag |= NODFLG_SELSIG;
}
else if (sig->sig_class == 0x30)
{
tty_printf (" ");
tty_printf (_("revoked by your key %s on %s\n"),
keystr (sig->keyid), datestr_from_sig (sig));
}
}
}
tty_printf ("\n");
/* ask */
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_SELSIG))
continue;
ask_revoke_sig (keyblock, node);
}
/* present selected */
any = 0;
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_MARK_A))
continue;
if (!any)
{
any = 1;
tty_printf (_("You are about to revoke these signatures:\n"));
}
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
sig = node->pkt->pkt.signature;
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig), "",
sig->flags.exportable ? "" : _(" (non-exportable)"));
}
}
if (!any)
return 0; /* none selected */
if (!cpr_get_answer_is_yes
("ask_revoke_sig.okay",
_("Really create the revocation certificates? (y/N) ")))
return 0; /* forget it */
reason = ask_revocation_reason (0, 1, 0);
if (!reason)
{ /* user decided to cancel */
return 0;
}
/* now we can sign the user ids */
reloop: /* (must use this, because we are modifing the list) */
primary_pk = keyblock->pkt->pkt.public_key;
for (node = keyblock; node; node = node->next)
{
KBNODE unode;
PACKET *pkt;
struct sign_attrib attrib;
PKT_public_key *signerkey;
if (!(node->flag & NODFLG_MARK_A)
|| node->pkt->pkttype != PKT_SIGNATURE)
continue;
unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
log_assert (unode); /* we already checked this */
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable;
node->flag &= ~NODFLG_MARK_A;
signerkey = xmalloc_secure_clear (sizeof *signerkey);
if (get_seckey (signerkey, node->pkt->pkt.signature->keyid))
{
log_info (_("no secret key\n"));
free_public_key (signerkey);
continue;
}
rc = make_keysig_packet (&sig, primary_pk,
unode->pkt->pkt.user_id,
NULL, signerkey, 0x30, 0, 0, 0,
sign_mk_attrib, &attrib, NULL);
free_public_key (signerkey);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
update_trust = 1;
/* Are we revoking our own uid? */
if (primary_pk->keyid[0] == sig->keyid[0] &&
primary_pk->keyid[1] == sig->keyid[1])
unode->pkt->pkt.user_id->is_revoked = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (unode, new_kbnode (pkt), 0);
goto reloop;
}
release_revocation_reason_info (reason);
return changed;
}
/* return 0 if revocation of NODE (which must be a User ID) was
successful, non-zero if there was an error. *modified will be set
to 1 if a change was made. */
static int
core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason, int *modified)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
gpg_error_t rc;
if (node->pkt->pkttype != PKT_USER_ID)
{
rc = gpg_error (GPG_ERR_NO_USER_ID);
write_status_error ("keysig", rc);
log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->is_revoked)
{
char *user = utf8_to_native (uid->name, uid->len, 0);
log_info (_("user ID \"%s\" is already revoked\n"), user);
xfree (user);
}
else
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
u32 timestamp = make_timestamp ();
if (uid->created >= timestamp)
{
/* Okay, this is a problem. The user ID selfsig was
created in the future, so we need to warn the user and
set our revocation timestamp one second after that so
everything comes out clean. */
log_info (_("WARNING: a user ID signature is dated %d"
" seconds in the future\n"),
uid->created - timestamp);
timestamp = uid->created + 1;
}
memset (&attrib, 0, sizeof attrib);
/* should not need to cast away const here; but
revocation_reason_build_cb needs to take a non-const
void* in order to meet the function signtuare for the
mksubpkt argument to make_keysig_packet */
attrib.reason = (struct revocation_reason_info *)reason;
rc = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x30, 0,
timestamp, 0,
sign_mk_attrib, &attrib, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
#ifndef NO_TRUST_MODELS
/* If the trustdb has an entry for this key+uid then the
trustdb needs an update. */
if (!update_trust
&& ((get_validity (ctrl, keyblock, pk, uid, NULL, 0)
& TRUST_MASK)
>= TRUST_UNDEFINED))
update_trust = 1;
#endif /*!NO_TRUST_MODELS*/
node->pkt->pkt.user_id->is_revoked = 1;
if (modified)
*modified = 1;
}
}
return 0;
}
}
/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if
keyblock changed. */
static int
menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
/* Note that this is correct as per the RFCs, but nevertheless
somewhat meaningless in the real world. 1991 did define the 0x30
sig class, but PGP 2.x did not actually implement it, so it would
probably be safe to use v4 revocations everywhere. -ds */
for (node = pub_keyblock; node; node = node->next)
if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->selfsigversion > 3))
{
if ((reason = ask_revocation_reason (0, 1, 4)))
break;
else
goto leave;
}
reloop: /* (better this way because we are modifying the keyring) */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID))
{
int modified = 0;
rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified);
if (rc)
goto leave;
if (modified)
{
node->flag &= ~NODFLG_SELUID;
changed = 1;
goto reloop;
}
}
if (changed)
commit_kbnode (&pub_keyblock);
leave:
release_revocation_reason_info (reason);
return changed;
}
/*
* Revoke the whole key.
*/
static int
menu_revkey (KBNODE pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
int rc, changed = 0;
struct revocation_reason_info *reason;
PACKET *pkt;
PKT_signature *sig;
if (pk->flags.revoked)
{
tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk));
return 0;
}
reason = ask_revocation_reason (1, 0, 0);
/* user decided to cancel */
if (!reason)
return 0;
rc = make_keysig_packet (&sig, pk, NULL, NULL, pk,
0x20, 0, 0, 0,
revocation_reason_build_cb, reason, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto scram;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), 0);
commit_kbnode (&pub_keyblock);
update_trust = 1;
scram:
release_revocation_reason_info (reason);
return changed;
}
static int
menu_revsubkey (KBNODE pub_keyblock)
{
PKT_public_key *mainpk;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
reason = ask_revocation_reason (1, 0, 0);
if (!reason)
return 0; /* User decided to cancel. */
reloop: /* (better this way because we are modifing the keyring) */
mainpk = pub_keyblock->pkt->pkt.public_key;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (node->flag & NODFLG_SELKEY))
{
PACKET *pkt;
PKT_signature *sig;
PKT_public_key *subpk = node->pkt->pkt.public_key;
struct sign_attrib attrib;
if (subpk->flags.revoked)
{
tty_printf (_("Subkey %s is already revoked.\n"),
keystr_from_pk (subpk));
continue;
}
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
node->flag &= ~NODFLG_SELKEY;
rc = make_keysig_packet (&sig, mainpk, NULL, subpk, mainpk,
0x28, 0, 0, 0, sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
goto reloop;
}
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys no longer
are used to certify other keys, so there is no change in trust
when revoking/removing them */
release_revocation_reason_info (reason);
return changed;
}
/* Note that update_ownertrust is going to mark the trustdb dirty when
enabling or disabling a key. This is arguably sub-optimal as
disabled keys are still counted in the web of trust, but perhaps
not worth adding extra complexity to change. -ds */
#ifndef NO_TRUST_MODELS
static int
enable_disable_key (KBNODE keyblock, int disable)
{
PKT_public_key *pk =
find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
unsigned int trust, newtrust;
trust = newtrust = get_ownertrust (pk);
newtrust &= ~TRUST_FLAG_DISABLED;
if (disable)
newtrust |= TRUST_FLAG_DISABLED;
if (trust == newtrust)
return 0; /* already in that state */
update_ownertrust (pk, newtrust);
return 0;
}
#endif /*!NO_TRUST_MODELS*/
static void
menu_showphoto (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE node;
int select_all = !count_selected_uids (keyblock);
int count = 0;
PKT_public_key *pk = NULL;
/* Look for the public key first. We have to be really, really,
explicit as to which photo this is, and what key it is a UID on
since people may want to sign it. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
count++;
if ((select_all || (node->flag & NODFLG_SELUID)) &&
uid->attribs != NULL)
{
int i;
for (i = 0; i < uid->numattribs; i++)
{
byte type;
u32 size;
if (uid->attribs[i].type == ATTRIB_IMAGE &&
parse_image_header (&uid->attribs[i], &type, &size))
{
tty_printf (_("Displaying %s photo ID of size %ld for "
"key %s (uid %d)\n"),
image_type_to_string (type, 1),
(ulong) size, keystr_from_pk (pk), count);
show_photos (ctrl, &uid->attribs[i], 1, pk, uid);
}
}
}
}
}
}
diff --git a/g10/keygen.c b/g10/keygen.c
index 201ebaa42..44f139a19 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -1,5322 +1,5322 @@
/* keygen.c - Generate a key pair
* Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2015, 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "packet.h"
-#include "ttyio.h"
+#include "../common/ttyio.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "pkglue.h"
#include "../common/shareddefs.h"
-#include "host2net.h"
-#include "mbox-util.h"
+#include "../common/host2net.h"
+#include "../common/mbox-util.h"
/* The default algorithms. If you change them remember to change them
also in gpg.c:gpgconf_list. You should also check that the value
is inside the bounds enforced by ask_keysize and gen_xxx. See also
get_keysize_range which encodes the allowed ranges. */
#define DEFAULT_STD_KEY_PARAM "rsa2048/cert,sign+rsa2048/encr"
#define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr"
/* When generating keys using the streamlined key generation dialog,
use this as a default expiration interval. */
const char *default_expiration_interval = "2y";
/* Flag bits used during key generation. */
#define KEYGEN_FLAG_NO_PROTECTION 1
#define KEYGEN_FLAG_TRANSIENT_KEY 2
/* Maximum number of supported algorithm preferences. */
#define MAX_PREFS 30
enum para_name {
pKEYTYPE,
pKEYLENGTH,
pKEYCURVE,
pKEYUSAGE,
pSUBKEYTYPE,
pSUBKEYLENGTH,
pSUBKEYCURVE,
pSUBKEYUSAGE,
pAUTHKEYTYPE,
pNAMEREAL,
pNAMEEMAIL,
pNAMECOMMENT,
pPREFERENCES,
pREVOKER,
pUSERID,
pCREATIONDATE,
pKEYCREATIONDATE, /* Same in seconds since epoch. */
pEXPIREDATE,
pKEYEXPIRE, /* in n seconds */
pSUBKEYEXPIRE, /* in n seconds */
pPASSPHRASE,
pSERIALNO,
pCARDBACKUPKEY,
pHANDLE,
pKEYSERVER,
pKEYGRIP
};
struct para_data_s {
struct para_data_s *next;
int lnr;
enum para_name key;
union {
u32 expire;
u32 creation;
unsigned int usage;
struct revocation_key revkey;
char value[1];
} u;
};
struct output_control_s
{
int lnr;
int dryrun;
unsigned int keygen_flags;
int use_files;
struct {
char *fname;
char *newfname;
IOBUF stream;
armor_filter_context_t *afx;
} pub;
};
struct opaque_data_usage_and_pk {
unsigned int usage;
PKT_public_key *pk;
};
static int prefs_initialized = 0;
static byte sym_prefs[MAX_PREFS];
static int nsym_prefs;
static byte hash_prefs[MAX_PREFS];
static int nhash_prefs;
static byte zip_prefs[MAX_PREFS];
static int nzip_prefs;
static int mdc_available,ks_modify;
static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage,
u32 *r_expire,
unsigned int *r_nbits, char **r_curve);
static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card );
static int write_keyblock (iobuf_t out, kbnode_t node);
static gpg_error_t gen_card_key (int keyno, int algo, int is_primary,
kbnode_t pub_root, u32 *timestamp,
u32 expireval);
static unsigned int get_keysize_range (int algo,
unsigned int *min, unsigned int *max);
/* Return the algo string for a default new key. */
const char *
get_default_pubkey_algo (void)
{
if (opt.def_new_key_algo)
{
if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':'))
return opt.def_new_key_algo;
/* To avoid checking that option every time we delay that until
* here. The only thing we really need to make sure is that
* there is no colon in the string so that the --gpgconf-list
* command won't mess up its output. */
log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo");
}
return DEFAULT_STD_KEY_PARAM;
}
static void
print_status_key_created (int letter, PKT_public_key *pk, const char *handle)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char *buf, *p;
size_t i, n;
if (!handle)
handle = "";
buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1);
p = buf;
if (letter || pk)
{
*p++ = letter;
if (pk)
{
*p++ = ' ';
fingerprint_from_pk (pk, array, &n);
s = array;
/* Fixme: Use bin2hex */
for (i=0; i < n ; i++, s++, p += 2)
snprintf (p, 3, "%02X", *s);
}
}
if (*handle)
{
*p++ = ' ';
for (i=0; handle[i] && i < 100; i++)
*p++ = isspace ((unsigned int)handle[i])? '_':handle[i];
}
*p = 0;
write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED,
buf);
xfree (buf);
}
static void
print_status_key_not_created (const char *handle)
{
print_status_key_created (0, NULL, handle);
}
static void
write_uid( KBNODE root, const char *s )
{
PACKET *pkt = xmalloc_clear(sizeof *pkt );
size_t n = strlen(s);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n);
pkt->pkt.user_id->len = n;
pkt->pkt.user_id->ref = 1;
strcpy(pkt->pkt.user_id->name, s);
add_kbnode( root, new_kbnode( pkt ) );
}
static void
do_add_key_flags (PKT_signature *sig, unsigned int use)
{
byte buf[1];
buf[0] = 0;
/* The spec says that all primary keys MUST be able to certify. */
if(sig->sig_class!=0x18)
buf[0] |= 0x01;
if (use & PUBKEY_USAGE_SIG)
buf[0] |= 0x02;
if (use & PUBKEY_USAGE_ENC)
buf[0] |= 0x04 | 0x08;
if (use & PUBKEY_USAGE_AUTH)
buf[0] |= 0x20;
build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1);
}
int
keygen_add_key_expire (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
byte buf[8];
u32 u;
if (pk->expiredate)
{
if (pk->expiredate > pk->timestamp)
u = pk->expiredate - pk->timestamp;
else
u = 1;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4);
}
else
{
/* Make sure we don't leave a key expiration subpacket lying
around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE);
}
return 0;
}
/* Add the key usage (i.e. key flags) in SIG from the public keys
* pubkey_usage field. OPAQUE has the public key. */
int
keygen_add_key_flags (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
return 0;
}
static int
keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque)
{
struct opaque_data_usage_and_pk *oduap = opaque;
do_add_key_flags (sig, oduap->usage);
return keygen_add_key_expire (sig, oduap->pk);
}
static int
set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf)
{
int i;
for (i=0; i < *nbuf; i++ )
if (buf[i] == val)
{
log_info (_("preference '%s' duplicated\n"), item);
return -1;
}
if (*nbuf >= MAX_PREFS)
{
if(type==1)
log_info(_("too many cipher preferences\n"));
else if(type==2)
log_info(_("too many digest preferences\n"));
else if(type==3)
log_info(_("too many compression preferences\n"));
else
BUG();
return -1;
}
buf[(*nbuf)++] = val;
return 0;
}
/*
* Parse the supplied string and use it to set the standard
* preferences. The string may be in a form like the one printed by
* "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual
* cipher/hash/compress names. Use NULL to set the default
* preferences. Returns: 0 = okay
*/
int
keygen_set_std_prefs (const char *string,int personal)
{
byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS];
int nsym=0, nhash=0, nzip=0, val, rc=0;
int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */
char dummy_string[20*4+1]; /* Enough for 20 items. */
if (!string || !ascii_strcasecmp (string, "default"))
{
if (opt.def_preference_list)
string=opt.def_preference_list;
else
{
int any_compress = 0;
dummy_string[0]='\0';
/* The rationale why we use the order AES256,192,128 is
for compatibility reasons with PGP. If gpg would
define AES128 first, we would get the somewhat
confusing situation:
gpg -r pgpkey -r gpgkey ---gives--> AES256
gpg -r gpgkey -r pgpkey ---gives--> AES
Note that by using --personal-cipher-preferences it is
possible to prefer AES128.
*/
/* Make sure we do not add more than 15 items here, as we
could overflow the size of dummy_string. We currently
have at most 12. */
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) )
strcat(dummy_string,"S9 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) )
strcat(dummy_string,"S8 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) )
strcat(dummy_string,"S7 ");
strcat(dummy_string,"S2 "); /* 3DES */
/* The default hash algo order is:
SHA-256, SHA-384, SHA-512, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224))
strcat (dummy_string, "H11 ");
strcat (dummy_string, "H2 "); /* SHA-1 */
if(!check_compress_algo(COMPRESS_ALGO_ZLIB))
{
strcat(dummy_string,"Z2 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_BZIP2))
{
strcat(dummy_string,"Z3 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_ZIP))
{
strcat(dummy_string,"Z1 ");
any_compress = 1;
}
/* In case we have no compress algo at all, declare that
we prefer no compresssion. */
if (!any_compress)
strcat(dummy_string,"Z0 ");
/* Remove the trailing space. */
if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ')
dummy_string[strlen (dummy_string)-1] = 0;
string=dummy_string;
}
}
else if (!ascii_strcasecmp (string, "none"))
string = "";
if(strlen(string))
{
char *prefstringbuf;
char *tok, *prefstring;
/* We need a writable string. */
prefstring = prefstringbuf = xstrdup (string);
while((tok=strsep(&prefstring," ,")))
{
if((val=string_to_cipher_algo (tok)))
{
if(set_one_pref(val,1,tok,sym,&nsym))
rc=-1;
}
else if((val=string_to_digest_algo (tok)))
{
if(set_one_pref(val,2,tok,hash,&nhash))
rc=-1;
}
else if((val=string_to_compress_algo(tok))>-1)
{
if(set_one_pref(val,3,tok,zip,&nzip))
rc=-1;
}
else if (ascii_strcasecmp(tok,"mdc")==0)
mdc=1;
else if (ascii_strcasecmp(tok,"no-mdc")==0)
mdc=0;
else if (ascii_strcasecmp(tok,"ks-modify")==0)
modify=1;
else if (ascii_strcasecmp(tok,"no-ks-modify")==0)
modify=0;
else
{
log_info (_("invalid item '%s' in preference string\n"),tok);
rc=-1;
}
}
xfree (prefstringbuf);
}
if(!rc)
{
if(personal)
{
if(personal==PREFTYPE_SYM)
{
xfree(opt.personal_cipher_prefs);
if(nsym==0)
opt.personal_cipher_prefs=NULL;
else
{
int i;
opt.personal_cipher_prefs=
xmalloc(sizeof(prefitem_t *)*(nsym+1));
for (i=0; i<nsym; i++)
{
opt.personal_cipher_prefs[i].type = PREFTYPE_SYM;
opt.personal_cipher_prefs[i].value = sym[i];
}
opt.personal_cipher_prefs[i].type = PREFTYPE_NONE;
opt.personal_cipher_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_HASH)
{
xfree(opt.personal_digest_prefs);
if(nhash==0)
opt.personal_digest_prefs=NULL;
else
{
int i;
opt.personal_digest_prefs=
xmalloc(sizeof(prefitem_t *)*(nhash+1));
for (i=0; i<nhash; i++)
{
opt.personal_digest_prefs[i].type = PREFTYPE_HASH;
opt.personal_digest_prefs[i].value = hash[i];
}
opt.personal_digest_prefs[i].type = PREFTYPE_NONE;
opt.personal_digest_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_ZIP)
{
xfree(opt.personal_compress_prefs);
if(nzip==0)
opt.personal_compress_prefs=NULL;
else
{
int i;
opt.personal_compress_prefs=
xmalloc(sizeof(prefitem_t *)*(nzip+1));
for (i=0; i<nzip; i++)
{
opt.personal_compress_prefs[i].type = PREFTYPE_ZIP;
opt.personal_compress_prefs[i].value = zip[i];
}
opt.personal_compress_prefs[i].type = PREFTYPE_NONE;
opt.personal_compress_prefs[i].value = 0;
}
}
}
else
{
memcpy (sym_prefs, sym, (nsym_prefs=nsym));
memcpy (hash_prefs, hash, (nhash_prefs=nhash));
memcpy (zip_prefs, zip, (nzip_prefs=nzip));
mdc_available = mdc;
ks_modify = modify;
prefs_initialized = 1;
}
}
return rc;
}
/* Return a fake user ID containing the preferences. Caller must
free. */
PKT_user_id *
keygen_get_std_prefs(void)
{
int i,j=0;
PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id));
if(!prefs_initialized)
keygen_set_std_prefs(NULL,0);
uid->ref=1;
uid->prefs=xmalloc((sizeof(prefitem_t *)*
(nsym_prefs+nhash_prefs+nzip_prefs+1)));
for(i=0;i<nsym_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_SYM;
uid->prefs[j].value=sym_prefs[i];
}
for(i=0;i<nhash_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_HASH;
uid->prefs[j].value=hash_prefs[i];
}
for(i=0;i<nzip_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_ZIP;
uid->prefs[j].value=zip_prefs[i];
}
uid->prefs[j].type=PREFTYPE_NONE;
uid->prefs[j].value=0;
uid->flags.mdc=mdc_available;
uid->flags.ks_modify=ks_modify;
return uid;
}
static void
add_feature_mdc (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x01; /* MDC feature */
else
buf[0] &= ~0x01;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_keyserver_modify (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
/* The keyserver modify flag is a negative flag (i.e. no-modify) */
enabled=!enabled;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x80; /* no-modify flag */
else
buf[0] &= ~0x80;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS);
else
build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n);
xfree (buf);
}
int
keygen_upd_std_prefs (PKT_signature *sig, void *opaque)
{
(void)opaque;
if (!prefs_initialized)
keygen_set_std_prefs (NULL, 0);
if (nsym_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM);
}
if (nhash_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH);
}
if (nzip_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR);
}
/* Make sure that the MDC feature flag is set if needed. */
add_feature_mdc (sig,mdc_available);
add_keyserver_modify (sig,ks_modify);
keygen_add_keyserver_url(sig,NULL);
return 0;
}
/****************
* Add preference to the self signature packet.
* This is only called for packets with version > 3.
*/
int
keygen_add_std_prefs (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
keygen_add_key_expire (sig, opaque );
keygen_upd_std_prefs (sig, opaque);
keygen_add_keyserver_url (sig,NULL);
return 0;
}
int
keygen_add_keyserver_url(PKT_signature *sig, void *opaque)
{
const char *url=opaque;
if(!url)
url=opt.def_keyserver_url;
if(url)
build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url));
else
delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS);
return 0;
}
int
keygen_add_notations(PKT_signature *sig,void *opaque)
{
struct notation *notation;
/* We always start clean */
delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION);
delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION);
sig->flags.notation=0;
for(notation=opaque;notation;notation=notation->next)
if(!notation->flags.ignore)
{
unsigned char *buf;
unsigned int n1,n2;
n1=strlen(notation->name);
if(notation->altvalue)
n2=strlen(notation->altvalue);
else if(notation->bdat)
n2=notation->blen;
else
n2=strlen(notation->value);
buf = xmalloc( 8 + n1 + n2 );
/* human readable or not */
buf[0] = notation->bdat?0:0x80;
buf[1] = buf[2] = buf[3] = 0;
buf[4] = n1 >> 8;
buf[5] = n1;
buf[6] = n2 >> 8;
buf[7] = n2;
memcpy(buf+8, notation->name, n1 );
if(notation->altvalue)
memcpy(buf+8+n1, notation->altvalue, n2 );
else if(notation->bdat)
memcpy(buf+8+n1, notation->bdat, n2 );
else
memcpy(buf+8+n1, notation->value, n2 );
build_sig_subpkt( sig, SIGSUBPKT_NOTATION |
(notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0),
buf, 8+n1+n2 );
xfree(buf);
}
return 0;
}
int
keygen_add_revkey (PKT_signature *sig, void *opaque)
{
struct revocation_key *revkey = opaque;
byte buf[2+MAX_FINGERPRINT_LEN];
buf[0] = revkey->class;
buf[1] = revkey->algid;
memcpy (&buf[2], revkey->fpr, MAX_FINGERPRINT_LEN);
build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+MAX_FINGERPRINT_LEN);
/* All sigs with revocation keys set are nonrevocable. */
sig->flags.revocable = 0;
buf[0] = 0;
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
parse_revkeys (sig);
return 0;
}
/* Create a back-signature. If TIMESTAMP is not NULL, use it for the
signature creation time. */
gpg_error_t
make_backsig (PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PKT_signature *backsig;
cache_public_key (sub_pk);
err = make_keysig_packet (&backsig, pk, NULL, sub_pk, sub_psk, 0x19,
0, timestamp, 0, NULL, NULL, cache_nonce);
if (err)
log_error ("make_keysig_packet failed for backsig: %s\n",
gpg_strerror (err));
else
{
/* Get it into a binary packed form. */
IOBUF backsig_out = iobuf_temp();
PACKET backsig_pkt;
init_packet (&backsig_pkt);
backsig_pkt.pkttype = PKT_SIGNATURE;
backsig_pkt.pkt.signature = backsig;
err = build_packet (backsig_out, &backsig_pkt);
free_packet (&backsig_pkt);
if (err)
log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err));
else
{
size_t pktlen = 0;
byte *buf = iobuf_get_temp_buffer (backsig_out);
/* Remove the packet header. */
if(buf[0]&0x40)
{
if (buf[1] < 192)
{
pktlen = buf[1];
buf += 2;
}
else if(buf[1] < 224)
{
pktlen = (buf[1]-192)*256;
pktlen += buf[2]+192;
buf += 3;
}
else if (buf[1] == 255)
{
pktlen = buf32_to_size_t (buf+2);
buf += 6;
}
else
BUG ();
}
else
{
int mark = 1;
switch (buf[0]&3)
{
case 3:
BUG ();
break;
case 2:
pktlen = (size_t)buf[mark++] << 24;
pktlen |= buf[mark++] << 16;
case 1:
pktlen |= buf[mark++] << 8;
case 0:
pktlen |= buf[mark++];
}
buf += mark;
}
/* Now make the binary blob into a subpacket. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNATURE, buf, pktlen);
iobuf_close (backsig_out);
}
}
return err;
}
/* Write a direct key signature to the first key in ROOT using the key
PSK. REVKEY is describes the direct key signature and TIMESTAMP is
the timestamp to set on the signature. */
static gpg_error_t
write_direct_sig (KBNODE root, PKT_public_key *psk,
struct revocation_key *revkey, u32 timestamp,
const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing direct signature\n"));
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (&sig, pk, NULL,NULL, psk, 0x1F,
0, timestamp, 0,
keygen_add_revkey, revkey, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) );
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write a self-signature to the first user id in ROOT using the key
PSK. USE and TIMESTAMP give the extra data we need for the
signature. */
static gpg_error_t
write_selfsigs (KBNODE root, PKT_public_key *psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
PKT_user_id *uid;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing self signature\n"));
/* Get the uid packet from the list. */
node = find_kbnode (root, PKT_USER_ID);
if (!node)
BUG(); /* No user id packet in tree. */
uid = node->pkt->pkt.user_id;
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pk = node->pkt->pkt.public_key;
/* The usage has not yet been set - do it now. */
pk->pubkey_usage = use;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (&sig, pk, uid, NULL, psk, 0x13,
0, timestamp, 0,
keygen_add_std_prefs, pk, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err));
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write the key binding signature. If TIMESTAMP is not NULL use the
signature creation time. PRI_PSK is the key use for signing.
SUB_PSK is a key used to create a back-signature; that one is only
used if USE has the PUBKEY_USAGE_SIG capability. */
static int
write_keybinding (KBNODE root, PKT_public_key *pri_psk, PKT_public_key *sub_psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pri_pk, *sub_pk;
struct opaque_data_usage_and_pk oduap;
if (opt.verbose)
log_info(_("writing key binding signature\n"));
/* Get the primary pk packet from the tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pri_pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
* signature creation is able to retrieve the public key. */
cache_public_key (pri_pk);
/* Find the last subkey. */
sub_pk = NULL;
for (node = root; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
}
if (!sub_pk)
BUG();
/* Make the signature. */
oduap.usage = use;
oduap.pk = sub_pk;
err = make_keysig_packet (&sig, pri_pk, NULL, sub_pk, pri_psk, 0x18,
0, timestamp, 0,
keygen_add_key_flags_and_expire, &oduap,
cache_nonce);
if (err)
{
log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err));
return err;
}
/* Make a backsig. */
if (use & PUBKEY_USAGE_SIG)
{
err = make_backsig (sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce);
if (err)
return err;
}
pkt = xmalloc_clear ( sizeof *pkt );
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt) );
return err;
}
static gpg_error_t
ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *curve = NULL;
int i;
const char *oidstr;
unsigned int nbits;
array[0] = NULL;
array[1] = NULL;
array[2] = NULL;
list = gcry_sexp_find_token (sexp, "public-key", 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
l2 = gcry_sexp_find_token (list, "curve", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
curve = gcry_sexp_nth_string (l2, 1);
if (!curve)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
gcry_sexp_release (l2);
oidstr = openpgp_curve_to_oid (curve, &nbits);
if (!oidstr)
{
/* That can't happen because we used one of the curves
gpg_curve_to_oid knows about. */
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
err = openpgp_oid_from_str (oidstr, &array[0]);
if (err)
goto leave;
l2 = gcry_sexp_find_token (list, "q", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
array[1] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[1])
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
gcry_sexp_release (list);
if (algo == PUBKEY_ALGO_ECDH)
{
array[2] = pk_ecdh_default_params (nbits);
if (!array[2])
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
xfree (curve);
if (err)
{
for (i=0; i < 3; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
}
return err;
}
/* Extract key parameters from SEXP and store them in ARRAY. ELEMS is
a string where each character denotes a parameter name. TOPNAME is
the name of the top element above the elements. */
static int
key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp,
const char *topname, const char *elems)
{
gcry_sexp_t list, l2;
const char *s;
int i, idx;
int rc = 0;
list = gcry_sexp_find_token (sexp, topname, 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
for (idx=0,s=elems; *s; s++, idx++)
{
l2 = gcry_sexp_find_token (list, s, 1);
if (!l2)
{
rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */
goto leave;
}
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[idx])
{
rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */
goto leave;
}
}
gcry_sexp_release (list);
leave:
if (rc)
{
for (i=0; i<idx; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
gcry_sexp_release (list);
}
return rc;
}
/* Create a keyblock using the given KEYGRIP. ALGO is the OpenPGP
algorithm of that keygrip. */
static int
do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip,
kbnode_t pub_root, u32 timestamp, u32 expireval,
int is_subkey)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
const char *algoelem;
if (hexkeygrip[0] == '&')
hexkeygrip++;
switch (algo)
{
case PUBKEY_ALGO_RSA: algoelem = "ne"; break;
case PUBKEY_ALGO_DSA: algoelem = "pqgy"; break;
case PUBKEY_ALGO_ELGAMAL_E: algoelem = "pgy"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA: algoelem = ""; break;
case PUBKEY_ALGO_EDDSA: algoelem = ""; break;
default: return gpg_error (GPG_ERR_INTERNAL);
}
/* Ask the agent for the public key matching HEXKEYGRIP. */
{
unsigned char *public;
err = agent_readkey (ctrl, 0, hexkeygrip, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL,
public, gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
}
/* Build a public key packet. */
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/* Common code for the key generation function gen_xxx. */
static int
common_gen (const char *keyparms, int algo, const char *algoelem,
kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms,
!!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION),
passphrase,
&s_key);
if (err)
{
log_error ("agent_genkey failed: %s\n", gpg_strerror (err) );
return err;
}
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/*
* Generate an Elgamal key.
*/
static int
gen_elg (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
log_assert (is_ELGAMAL (algo));
if (nbits < 1024)
{
nbits = 2048;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > 4096)
{
nbits = 4096;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
/* Note that we use transient-key only if no-protection has also
been enabled. */
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))",
algo == GCRY_PK_ELG_E ? "openpgp-elg" :
algo == GCRY_PK_ELG ? "elg" : "x-oops" ,
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "pgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an DSA key
*/
static gpg_error_t
gen_dsa (unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
unsigned int qbits;
char *keyparms;
char nbitsstr[35];
char qbitsstr[35];
if (nbits < 768)
{
nbits = 2048;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
else if ( nbits > 3072 )
{
nbits = 3072;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
if( (nbits % 64) )
{
nbits = ((nbits + 63) / 64) * 64;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/* To comply with FIPS rules we round up to the next value unless in
expert mode. */
if (!opt.expert && nbits > 1024 && (nbits % 1024))
{
nbits = ((nbits + 1023) / 1024) * 1024;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/*
Figure out a q size based on the key size. FIPS 180-3 says:
L = 1024, N = 160
L = 2048, N = 224
L = 2048, N = 256
L = 3072, N = 256
2048/256 is an odd pair since there is also a 2048/224 and
3072/256. Matching sizes is not a very exact science.
We'll do 256 qbits for nbits over 2047, 224 for nbits over 1024
but less than 2048, and 160 for 1024 (DSA1).
*/
if (nbits > 2047)
qbits = 256;
else if ( nbits > 1024)
qbits = 224;
else
qbits = 160;
if (qbits != 160 )
log_info (_("WARNING: some OpenPGP programs can't"
" handle a DSA key with this digest size\n"));
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits);
keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
strlen (qbitsstr), qbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, PUBKEY_ALGO_DSA, "pqgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an ECC key
*/
static gpg_error_t
gen_ecc (int algo, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
char *keyparms;
log_assert (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH);
if (!curve || !*curve)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Note that we use the "comp" flag with EdDSA to request the use of
a 0x40 compression prefix octet. */
if (algo == PUBKEY_ALGO_EDDSA)
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519"))
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an RSA key.
*/
static int
gen_rsa (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096);
log_assert (is_RSA(algo));
if (!nbits)
nbits = get_keysize_range (algo, NULL, NULL);
if (nbits < 1024)
{
nbits = 2048;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > maxsize)
{
nbits = maxsize;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "ne",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/****************
* check valid days:
* return 0 on error or the multiplier
*/
static int
check_valid_days( const char *s )
{
if( !digitp(s) )
return 0;
for( s++; *s; s++)
if( !digitp(s) )
break;
if( !*s )
return 1;
if( s[1] )
return 0; /* e.g. "2323wc" */
if( *s == 'd' || *s == 'D' )
return 1;
if( *s == 'w' || *s == 'W' )
return 7;
if( *s == 'm' || *s == 'M' )
return 30;
if( *s == 'y' || *s == 'Y' )
return 365;
return 0;
}
static void
print_key_flags(int flags)
{
if(flags&PUBKEY_USAGE_SIG)
tty_printf("%s ",_("Sign"));
if(flags&PUBKEY_USAGE_CERT)
tty_printf("%s ",_("Certify"));
if(flags&PUBKEY_USAGE_ENC)
tty_printf("%s ",_("Encrypt"));
if(flags&PUBKEY_USAGE_AUTH)
tty_printf("%s ",_("Authenticate"));
}
/* Ask for the key flags and return them. CURRENT gives the current
* usage which should normally be given as 0. */
unsigned int
ask_key_flags (int algo, int subkey, unsigned int current)
{
/* TRANSLATORS: Please use only plain ASCII characters for the
translation. If this is not possible use single digits. The
string needs to 8 bytes long. Here is a description of the
functions:
s = Toggle signing capability
e = Toggle encryption capability
a = Toggle authentication capability
q = Finish
*/
const char *togglers = _("SsEeAaQq");
char *answer = NULL;
const char *s;
unsigned int possible = openpgp_pk_algo_usage(algo);
if ( strlen(togglers) != 8 )
{
tty_printf ("NOTE: Bad translation at %s:%d. "
"Please report.\n", __FILE__, __LINE__);
togglers = "11223300";
}
/* Only primary keys may certify. */
if(subkey)
possible&=~PUBKEY_USAGE_CERT;
/* Preload the current set with the possible set, minus
authentication if CURRENT has been given as 0. If CURRENT has
been has non-zero we mask with all possible usages. */
if (current)
current &= possible;
else
current = (possible&~PUBKEY_USAGE_AUTH);
for(;;)
{
tty_printf("\n");
tty_printf(_("Possible actions for a %s key: "),
(algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
? "ECDSA/EdDSA" : openpgp_pk_algo_name (algo));
print_key_flags(possible);
tty_printf("\n");
tty_printf(_("Current allowed actions: "));
print_key_flags(current);
tty_printf("\n\n");
if(possible&PUBKEY_USAGE_SIG)
tty_printf(_(" (%c) Toggle the sign capability\n"),
togglers[0]);
if(possible&PUBKEY_USAGE_ENC)
tty_printf(_(" (%c) Toggle the encrypt capability\n"),
togglers[2]);
if(possible&PUBKEY_USAGE_AUTH)
tty_printf(_(" (%c) Toggle the authenticate capability\n"),
togglers[4]);
tty_printf(_(" (%c) Finished\n"),togglers[6]);
tty_printf("\n");
xfree(answer);
answer = cpr_get("keygen.flags",_("Your selection? "));
cpr_kill_prompt();
if (*answer == '=')
{
/* Hack to allow direct entry of the capabilities. */
current = 0;
for (s=answer+1; *s; s++)
{
if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG))
current |= PUBKEY_USAGE_SIG;
else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC))
current |= PUBKEY_USAGE_ENC;
else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH))
current |= PUBKEY_USAGE_AUTH;
else if (!subkey && *s == 'c')
{
/* Accept 'c' for the primary key because USAGE_CERT
will be set anyway. This is for folks who
want to experiment with a cert-only primary key. */
current |= PUBKEY_USAGE_CERT;
}
}
break;
}
else if (strlen(answer)>1)
tty_printf(_("Invalid selection.\n"));
else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7])
break;
else if((*answer==togglers[0] || *answer==togglers[1])
&& possible&PUBKEY_USAGE_SIG)
{
if(current&PUBKEY_USAGE_SIG)
current&=~PUBKEY_USAGE_SIG;
else
current|=PUBKEY_USAGE_SIG;
}
else if((*answer==togglers[2] || *answer==togglers[3])
&& possible&PUBKEY_USAGE_ENC)
{
if(current&PUBKEY_USAGE_ENC)
current&=~PUBKEY_USAGE_ENC;
else
current|=PUBKEY_USAGE_ENC;
}
else if((*answer==togglers[4] || *answer==togglers[5])
&& possible&PUBKEY_USAGE_AUTH)
{
if(current&PUBKEY_USAGE_AUTH)
current&=~PUBKEY_USAGE_AUTH;
else
current|=PUBKEY_USAGE_AUTH;
}
else
tty_printf(_("Invalid selection.\n"));
}
xfree(answer);
return current;
}
/* Check whether we have a key for the key with HEXGRIP. Returns 0 if
there is no such key or the OpenPGP algo number for the key. */
static int
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
unsigned char *public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return 0;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
return map_pk_gcry_to_openpgp (algo);
}
/* Ask for an algorithm. The function returns the algorithm id to
* create. If ADDMODE is false the function won't show an option to
* create the primary and subkey combined and won't set R_USAGE
* either. If a combined algorithm has been selected, the subkey
* algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the
* user has the choice to enter the keygrip of an existing key. That
* keygrip is then stored at this address. The caller needs to free
* it. */
static int
ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
char **r_keygrip)
{
char *keygrip = NULL;
char *answer = NULL;
int algo;
int dummy_algo;
if (!r_subkey_algo)
r_subkey_algo = &dummy_algo;
tty_printf (_("Please select what kind of key you want:\n"));
#if GPG_USE_RSA
if (!addmode)
tty_printf (_(" (%d) RSA and RSA (default)\n"), 1 );
#endif
if (!addmode && opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA and Elgamal\n"), 2 );
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (sign only)\n"), 3 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (sign only)\n"), 4 );
#endif
if (addmode)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) Elgamal (encrypt only)\n"), 5 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (encrypt only)\n"), 6 );
#endif
}
if (opt.expert)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (set your own capabilities)\n"), 7 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (set your own capabilities)\n"), 8 );
#endif
}
#if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA
if (opt.expert && !addmode)
tty_printf (_(" (%d) ECC and ECC\n"), 9 );
if (opt.expert)
tty_printf (_(" (%d) ECC (sign only)\n"), 10 );
if (opt.expert)
tty_printf (_(" (%d) ECC (set your own capabilities)\n"), 11 );
if (opt.expert && addmode)
tty_printf (_(" (%d) ECC (encrypt only)\n"), 12 );
#endif
if (opt.expert && r_keygrip)
tty_printf (_(" (%d) Existing key\n"), 13 );
for (;;)
{
*r_usage = 0;
*r_subkey_algo = 0;
xfree (answer);
answer = cpr_get ("keygen.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 1;
if (opt.compliance == CO_DE_VS
&& (algo == 2 || algo == 3 || algo == 5 || algo == 7))
{
tty_printf (_("Invalid selection.\n"));
}
else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_subkey_algo = PUBKEY_ALGO_RSA;
break;
}
else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode)
{
algo = PUBKEY_ALGO_DSA;
*r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E;
break;
}
else if (algo == 3 || !strcmp (answer, "dsa"))
{
algo = PUBKEY_ALGO_DSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if (algo == 4 || !strcmp (answer, "rsa/s"))
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 5 || !strcmp (answer, "elg")) && addmode)
{
algo = PUBKEY_ALGO_ELGAMAL_E;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_DSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 9 || !strcmp (answer, "ecc+ecc"))
&& opt.expert && !addmode)
{
algo = PUBKEY_ALGO_ECDSA;
*r_subkey_algo = PUBKEY_ALGO_ECDH;
break;
}
else if ((algo == 10 || !strcmp (answer, "ecc/s")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 12 || !strcmp (answer, "ecc/e"))
&& opt.expert && addmode)
{
algo = PUBKEY_ALGO_ECDH;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 13 || !strcmp (answer, "keygrip"))
&& opt.expert && r_keygrip)
{
for (;;)
{
xfree (answer);
answer = tty_get (_("Enter the keygrip: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
{
xfree (answer);
answer = NULL;
continue;
}
if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf
(_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(algo = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else
tty_printf (_("Invalid selection.\n"));
}
xfree(answer);
if (r_keygrip)
*r_keygrip = keygrip;
return algo;
}
static unsigned int
get_keysize_range (int algo, unsigned int *min, unsigned int *max)
{
unsigned int def;
unsigned int dummy1, dummy2;
if (!min)
min = &dummy1;
if (!max)
max = &dummy2;
switch(algo)
{
case PUBKEY_ALGO_DSA:
*min = opt.expert? 768 : 1024;
*max=3072;
def=2048;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
*min=256;
*max=521;
def=256;
break;
case PUBKEY_ALGO_EDDSA:
*min=255;
*max=441;
def=255;
break;
default:
*min = opt.compliance == CO_DE_VS ? 2048: 1024;
*max = 4096;
def = 2048;
break;
}
return def;
}
/* Return a fixed up keysize depending on ALGO. */
static unsigned int
fixup_keysize (unsigned int nbits, int algo, int silent)
{
if (algo == PUBKEY_ALGO_DSA && (nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits);
}
else if (algo == PUBKEY_ALGO_EDDSA)
{
if (nbits != 255 && nbits != 441)
{
if (nbits < 256)
nbits = 255;
else
nbits = 441;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
{
if (nbits != 256 && nbits != 384 && nbits != 521)
{
if (nbits < 256)
nbits = 256;
else if (nbits < 384)
nbits = 384;
else
nbits = 521;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits );
}
return nbits;
}
/* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE
is not 0, the function asks for the size of the encryption
subkey. */
static unsigned
ask_keysize (int algo, unsigned int primary_keysize)
{
unsigned int nbits;
unsigned int min, def, max;
int for_subkey = !!primary_keysize;
int autocomp = 0;
def = get_keysize_range (algo, &min, &max);
if (primary_keysize && !opt.expert)
{
/* Deduce the subkey size from the primary key size. */
if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072)
nbits = 3072; /* For performance reasons we don't support more
than 3072 bit DSA. However we won't see this
case anyway because DSA can't be used as an
encryption subkey ;-). */
else
nbits = primary_keysize;
autocomp = 1;
goto leave;
}
tty_printf(_("%s keys may be between %u and %u bits long.\n"),
openpgp_pk_algo_name (algo), min, max);
for (;;)
{
char *prompt, *answer;
if (for_subkey)
prompt = xasprintf (_("What keysize do you want "
"for the subkey? (%u) "), def);
else
prompt = xasprintf (_("What keysize do you want? (%u) "), def);
answer = cpr_get ("keygen.size", prompt);
cpr_kill_prompt ();
nbits = *answer? atoi (answer): def;
xfree(prompt);
xfree(answer);
if(nbits<min || nbits>max)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
openpgp_pk_algo_name (algo), min, max);
else
break;
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
leave:
nbits = fixup_keysize (nbits, algo, autocomp);
return nbits;
}
/* Ask for the curve. ALGO is the selected algorithm which this
function may adjust. Returns a malloced string with the name of
the curve. BOTH tells that gpg creates a primary and subkey. */
static char *
ask_curve (int *algo, int *subkey_algo)
{
/* NB: We always use a complete algo list so that we have stable
numbers in the menu regardless on how Gpg was configured. */
struct {
const char *name;
const char* eddsa_curve; /* Corresponding EdDSA curve. */
const char *pretty_name;
unsigned int supported : 1; /* Supported by gpg. */
unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */
unsigned int expert_only : 1; /* Only with --expert */
unsigned int available : 1; /* Available in Libycrypt (runtime checked) */
} curves[] = {
#if GPG_USE_ECDSA || GPG_USE_ECDH
# define MY_USE_ECDSADH 1
#else
# define MY_USE_ECDSADH 0
#endif
{ "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 },
{ "Curve448", "Ed448", "Curve 448", 0/*reserved*/ , 0, 1, 0 },
{ "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 },
{ "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 },
{ "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
};
#undef MY_USE_ECDSADH
int idx;
char *answer;
char *result = NULL;
gcry_sexp_t keyparms;
tty_printf (_("Please select which elliptic curve you want:\n"));
keyparms = NULL;
for (idx=0; idx < DIM(curves); idx++)
{
int rc;
curves[idx].available = 0;
if (!curves[idx].supported)
continue;
if (opt.compliance==CO_DE_VS)
{
if (!curves[idx].de_vs)
continue; /* Not allowed. */
}
else if (!opt.expert && curves[idx].expert_only)
continue;
/* We need to switch from the ECDH name of the curve to the
EDDSA name of the curve if we want a signing key. */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].eddsa_curve? curves[idx].eddsa_curve
/**/ : curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
if (subkey_algo && curves[idx].eddsa_curve)
{
/* Both Curve 25519 (or 448) keys are to be created. Check that
Libgcrypt also supports the real Curve25519 (or 448). */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
}
curves[idx].available = 1;
tty_printf (" (%d) %s\n", idx + 1,
curves[idx].pretty_name?
curves[idx].pretty_name:curves[idx].name);
}
gcry_sexp_release (keyparms);
for (;;)
{
answer = cpr_get ("keygen.curve", _("Your selection? "));
cpr_kill_prompt ();
idx = *answer? atoi (answer) : 1;
if (*answer && !idx)
{
/* See whether the user entered the name of the curve. */
for (idx=0; idx < DIM(curves); idx++)
{
if (!opt.expert && curves[idx].expert_only)
continue;
if (!stricmp (curves[idx].name, answer)
|| (curves[idx].pretty_name
&& !stricmp (curves[idx].pretty_name, answer)))
break;
}
if (idx == DIM(curves))
idx = -1;
}
else
idx--;
xfree(answer);
answer = NULL;
if (idx < 0 || idx >= DIM (curves) || !curves[idx].available)
tty_printf (_("Invalid selection.\n"));
else
{
/* If the user selected a signing algorithm and Curve25519
we need to set the algo to EdDSA and update the curve name. */
if ((*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA)
&& curves[idx].eddsa_curve)
{
if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA)
*subkey_algo = PUBKEY_ALGO_EDDSA;
*algo = PUBKEY_ALGO_EDDSA;
result = xstrdup (curves[idx].eddsa_curve);
}
else
result = xstrdup (curves[idx].name);
break;
}
}
if (!result)
result = xstrdup (curves[0].name);
return result;
}
/****************
* Parse an expire string and return its value in seconds.
* Returns (u32)-1 on error.
* This isn't perfect since scan_isodatestr returns unix time, and
* OpenPGP actually allows a 32-bit time *plus* a 32-bit offset.
* Because of this, we only permit setting expirations up to 2106, but
* OpenPGP could theoretically allow up to 2242. I think we'll all
* just cope for the next few years until we get a 64-bit time_t or
* similar.
*/
u32
parse_expire_string( const char *string )
{
int mult;
u32 seconds;
u32 abs_date = 0;
u32 curtime = make_timestamp ();
time_t tt;
if (!string || !*string || !strcmp (string, "none")
|| !strcmp (string, "never") || !strcmp (string, "-"))
seconds = 0;
else if (!strncmp (string, "seconds=", 8))
seconds = atoi (string+8);
else if ((abs_date = scan_isodatestr(string))
&& (abs_date+86400/2) > curtime)
seconds = (abs_date+86400/2) - curtime;
else if ((tt = isotime2epoch (string)) != (time_t)(-1))
seconds = (u32)tt - curtime;
else if ((mult = check_valid_days (string)))
seconds = atoi (string) * 86400L * mult;
else
seconds = (u32)(-1);
return seconds;
}
/* Parse a Creation-Date string which is either "1986-04-26" or
"19860426T042640". Returns 0 on error. */
static u32
parse_creation_string (const char *string)
{
u32 seconds;
if (!*string)
seconds = 0;
else if ( !strncmp (string, "seconds=", 8) )
seconds = atoi (string+8);
else if ( !(seconds = scan_isodatestr (string)))
{
time_t tmp = isotime2epoch (string);
seconds = (tmp == (time_t)(-1))? 0 : tmp;
}
return seconds;
}
/* object == 0 for a key, and 1 for a sig */
u32
ask_expire_interval(int object,const char *def_expire)
{
u32 interval;
char *answer;
switch(object)
{
case 0:
if(def_expire)
BUG();
tty_printf(_("Please specify how long the key should be valid.\n"
" 0 = key does not expire\n"
" <n> = key expires in n days\n"
" <n>w = key expires in n weeks\n"
" <n>m = key expires in n months\n"
" <n>y = key expires in n years\n"));
break;
case 1:
if(!def_expire)
BUG();
tty_printf(_("Please specify how long the signature should be valid.\n"
" 0 = signature does not expire\n"
" <n> = signature expires in n days\n"
" <n>w = signature expires in n weeks\n"
" <n>m = signature expires in n months\n"
" <n>y = signature expires in n years\n"));
break;
default:
BUG();
}
/* Note: The elgamal subkey for DSA has no expiration date because
* it must be signed with the DSA key and this one has the expiration
* date */
answer = NULL;
for(;;)
{
u32 curtime;
xfree(answer);
if(object==0)
answer = cpr_get("keygen.valid",_("Key is valid for? (0) "));
else
{
char *prompt;
prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire);
answer = cpr_get("siggen.valid",prompt);
xfree(prompt);
if(*answer=='\0')
answer=xstrdup(def_expire);
}
cpr_kill_prompt();
trim_spaces(answer);
curtime = make_timestamp ();
interval = parse_expire_string( answer );
if( interval == (u32)-1 )
{
tty_printf(_("invalid value\n"));
continue;
}
if( !interval )
{
tty_printf((object==0)
? _("Key does not expire at all\n")
: _("Signature does not expire at all\n"));
}
else
{
tty_printf(object==0
? _("Key expires at %s\n")
: _("Signature expires at %s\n"),
asctimestamp((ulong)(curtime + interval) ) );
#if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T)
if ( (time_t)((ulong)(curtime+interval)) < 0 )
tty_printf (_("Your system can't display dates beyond 2038.\n"
"However, it will be correctly handled up to"
" 2106.\n"));
else
#endif /*SIZEOF_TIME_T*/
if ( (time_t)((unsigned long)(curtime+interval)) < curtime )
{
tty_printf (_("invalid value\n"));
continue;
}
}
if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay",
_("Is this correct? (y/N) ")) )
break;
}
xfree(answer);
return interval;
}
u32
ask_expiredate()
{
u32 x = ask_expire_interval(0,NULL);
return x? make_timestamp() + x : 0;
}
static PKT_user_id *
uid_from_string (const char *string)
{
size_t n;
PKT_user_id *uid;
n = strlen (string);
uid = xmalloc_clear (sizeof *uid + n);
uid->len = n;
strcpy (uid->name, string);
uid->ref = 1;
return uid;
}
/* Return true if the user id UID already exists in the keyblock. */
static int
uid_already_in_keyblock (kbnode_t keyblock, const char *uid)
{
PKT_user_id *uidpkt = uid_from_string (uid);
kbnode_t node;
int result = 0;
for (node=keyblock; node && !result; node=node->next)
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids (uidpkt, node->pkt->pkt.user_id))
result = 1;
free_user_id (uidpkt);
return result;
}
/* Ask for a user ID. With a MODE of 1 an extra help prompt is
printed for use during a new key creation. If KEYBLOCK is not NULL
the function prevents the creation of an already existing user
ID. IF FULL is not set some prompts are not shown. */
static char *
ask_user_id (int mode, int full, KBNODE keyblock)
{
char *answer;
char *aname, *acomment, *amail, *uid;
if ( !mode )
{
/* TRANSLATORS: This is the new string telling the user what
gpg is now going to do (i.e. ask for the parts of the user
ID). Note that if you do not translate this string, a
different string will be used, which might still have
a correct translation. */
const char *s1 =
N_("\n"
"GnuPG needs to construct a user ID to identify your key.\n"
"\n");
const char *s2 = _(s1);
if (!strcmp (s1, s2))
{
/* There is no translation for the string thus we to use
the old info text. gettext has no way to tell whether
a translation is actually available, thus we need to
to compare again. */
/* TRANSLATORS: This string is in general not anymore used
but you should keep your existing translation. In case
the new string is not translated this old string will
be used. */
const char *s3 = N_("\n"
"You need a user ID to identify your key; "
"the software constructs the user ID\n"
"from the Real Name, Comment and Email Address in this form:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n\n");
const char *s4 = _(s3);
if (strcmp (s3, s4))
s2 = s3; /* A translation exists - use it. */
}
tty_printf ("%s", s2) ;
}
uid = aname = acomment = amail = NULL;
for(;;) {
char *p;
int fail=0;
if( !aname ) {
for(;;) {
xfree(aname);
aname = cpr_get("keygen.name",_("Real name: "));
trim_spaces(aname);
cpr_kill_prompt();
if( opt.allow_freeform_uid )
break;
if( strpbrk( aname, "<>" ) )
{
tty_printf(_("Invalid character in name\n"));
tty_printf(_("The characters '%s' and '%s' may not "
"appear in name\n"), "<", ">");
}
else if( digitp(aname) )
tty_printf(_("Name may not start with a digit\n"));
else if (*aname && strlen (aname) < 5)
{
tty_printf(_("Name must be at least 5 characters long\n"));
/* However, we allow an empty name. */
}
else
break;
}
}
if( !amail ) {
for(;;) {
xfree(amail);
amail = cpr_get("keygen.email",_("Email address: "));
trim_spaces(amail);
cpr_kill_prompt();
if( !*amail || opt.allow_freeform_uid )
break; /* no email address is okay */
else if ( !is_valid_mailbox (amail) )
tty_printf(_("Not a valid email address\n"));
else
break;
}
}
if (!acomment) {
if (full) {
for(;;) {
xfree(acomment);
acomment = cpr_get("keygen.comment",_("Comment: "));
trim_spaces(acomment);
cpr_kill_prompt();
if( !*acomment )
break; /* no comment is okay */
else if( strpbrk( acomment, "()" ) )
tty_printf(_("Invalid character in comment\n"));
else
break;
}
}
else {
xfree (acomment);
acomment = xstrdup ("");
}
}
xfree(uid);
uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10);
if (!*aname && *amail && !*acomment && !random_is_faked ())
{ /* Empty name and comment but with mail address. Use
simplified form with only the non-angle-bracketed mail
address. */
p = stpcpy (p, amail);
}
else
{
p = stpcpy (p, aname );
if (*acomment)
p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")");
if (*amail)
p = stpcpy(stpcpy(stpcpy(p," <"), amail),">");
}
/* Append a warning if the RNG is switched into fake mode. */
if ( random_is_faked () )
strcpy(p, " (insecure!)" );
/* print a note in case that UTF8 mapping has to be done */
for(p=uid; *p; p++ ) {
if( *p & 0x80 ) {
tty_printf(_("You are using the '%s' character set.\n"),
get_native_charset() );
break;
}
}
tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid);
if( !*amail && !opt.allow_freeform_uid
&& (strchr( aname, '@' ) || strchr( acomment, '@'))) {
fail = 1;
tty_printf(_("Please don't put the email address "
"into the real name or the comment\n") );
}
if (!fail && keyblock)
{
if (uid_already_in_keyblock (keyblock, uid))
{
tty_printf (_("Such a user ID already exists on this key!\n"));
fail = 1;
}
}
for(;;) {
/* TRANSLATORS: These are the allowed answers in
lower and uppercase. Below you will find the matching
string which should be translated accordingly and the
letter changed to match the one in the answer string.
n = Change name
c = Change comment
e = Change email
o = Okay (ready, continue)
q = Quit
*/
const char *ansstr = _("NnCcEeOoQq");
if( strlen(ansstr) != 10 )
BUG();
if( cpr_enabled() ) {
answer = xstrdup (ansstr + (fail?8:6));
answer[1] = 0;
}
else if (full) {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") :
_("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
else {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (E)mail, or (Q)uit? ") :
_("Change (N)ame, (E)mail, or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
if( strlen(answer) > 1 )
;
else if( *answer == ansstr[0] || *answer == ansstr[1] ) {
xfree(aname); aname = NULL;
break;
}
else if( *answer == ansstr[2] || *answer == ansstr[3] ) {
xfree(acomment); acomment = NULL;
break;
}
else if( *answer == ansstr[4] || *answer == ansstr[5] ) {
xfree(amail); amail = NULL;
break;
}
else if( *answer == ansstr[6] || *answer == ansstr[7] ) {
if( fail ) {
tty_printf(_("Please correct the error first\n"));
}
else {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
break;
}
}
else if( *answer == ansstr[8] || *answer == ansstr[9] ) {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
xfree(uid); uid = NULL;
break;
}
xfree(answer);
}
xfree(answer);
if (!amail && !acomment)
break;
xfree(uid); uid = NULL;
}
if( uid ) {
char *p = native_to_utf8( uid );
xfree( uid );
uid = p;
}
return uid;
}
/* Basic key generation. Here we divert to the actual generation
routines based on the requested algorithm. */
static int
do_create (int algo, unsigned int nbits, const char *curve, KBNODE pub_root,
u32 timestamp, u32 expiredate, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
/* Fixme: The entropy collecting message should be moved to a
libgcrypt progress handler. */
if (!opt.batch)
tty_printf (_(
"We need to generate a lot of random bytes. It is a good idea to perform\n"
"some other action (type on the keyboard, move the mouse, utilize the\n"
"disks) during the prime generation; this gives the random number\n"
"generator a better chance to gain enough entropy.\n") );
if (algo == PUBKEY_ALGO_ELGAMAL_E)
err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_DSA)
err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_RSA)
err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else
BUG();
return err;
}
/* Generate a new user id packet or return NULL if canceled. If
KEYBLOCK is not NULL the function prevents the creation of an
already existing user ID. If UIDSTR is not NULL the user is not
asked but UIDSTR is used to create the user id packet; if the user
id already exists NULL is returned. UIDSTR is expected to be utf-8
encoded and should have already been checked for a valid length
etc. */
PKT_user_id *
generate_user_id (KBNODE keyblock, const char *uidstr)
{
PKT_user_id *uid;
char *p;
if (uidstr)
{
if (uid_already_in_keyblock (keyblock, uidstr))
return NULL; /* Already exists. */
uid = uid_from_string (uidstr);
}
else
{
p = ask_user_id (1, 1, keyblock);
if (!p)
return NULL; /* Canceled. */
uid = uid_from_string (p);
xfree (p);
}
return uid;
}
/* Helper for parse_key_parameter_string for one part of the
* specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty
* success is returned. On error an error code is returned. Note
* that STRING may be modified by this function. NULL may be passed
* for any parameter. FOR_SUBKEY shall be true if this is used as a
* subkey. */
static gpg_error_t
parse_key_parameter_part (char *string, int for_subkey,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve)
{
char *flags;
int algo;
char *endp;
const char *curve = NULL;
int ecdh_or_ecdsa = 0;
unsigned int size;
int keyuse;
int i;
const char *s;
if (!string || !*string)
return 0; /* Success. */
flags = strchr (string, '/');
if (flags)
*flags++ = 0;
algo = 0;
if (strlen (string) >= 3 && (digitp (string+3) || !string[3]))
{
if (!ascii_memcasecmp (string, "rsa", 3))
algo = PUBKEY_ALGO_RSA;
else if (!ascii_memcasecmp (string, "dsa", 3))
algo = PUBKEY_ALGO_DSA;
else if (!ascii_memcasecmp (string, "elg", 3))
algo = PUBKEY_ALGO_ELGAMAL_E;
}
if (algo)
{
if (!string[3])
size = get_keysize_range (algo, NULL, NULL);
else
{
size = strtoul (string+3, &endp, 10);
if (size < 512 || size > 16384 || *endp)
return gpg_error (GPG_ERR_INV_VALUE);
}
}
else if ((curve = openpgp_is_curve_supported (string, &algo, &size)))
{
if (!algo)
{
algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */
ecdh_or_ecdsa = 1; /* We may need to switch the algo. */
}
}
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Parse the flags. */
keyuse = 0;
if (flags)
{
char **tokens = NULL;
tokens = strtokenize (flags, ",");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
keyuse |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
keyuse |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
keyuse |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
keyuse |= PUBKEY_USAGE_CERT;
else if (!ascii_strcasecmp (s, "ecdsa"))
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDSA;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "ecdh"))
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDH;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "eddsa"))
{
/* Not required but we allow it for consistency. */
if (algo == PUBKEY_ALGO_EDDSA)
;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
}
else
{
xfree (tokens);
return gpg_error (GPG_ERR_UNKNOWN_FLAG);
}
}
xfree (tokens);
}
/* If not yet decided switch between ecdh and ecdsa. */
if (ecdh_or_ecdsa && keyuse)
algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
else if (ecdh_or_ecdsa)
algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
/* Set or fix key usage. */
if (!keyuse)
{
if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
keyuse = PUBKEY_USAGE_SIG;
else if (algo == PUBKEY_ALGO_RSA)
keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG;
else
keyuse = PUBKEY_USAGE_ENC;
}
else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
{
keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E)
{
keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */
}
/* Make sure a primary key can certify. */
if (!for_subkey)
keyuse |= PUBKEY_USAGE_CERT;
/* Check that usage is actually possible. */
if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((keyuse & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (keyuse & PUBKEY_USAGE_CERT)))
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
/* Return values. */
if (r_algo)
*r_algo = algo;
if (r_size)
{
unsigned int min, def, max;
/* Make sure the keysize is in the allowed range. */
def = get_keysize_range (algo, &min, &max);
if (!size)
size = def;
else if (size < min)
size = min;
else if (size > max)
size = max;
*r_size = fixup_keysize (size, algo, 1);
}
if (r_keyuse)
*r_keyuse = keyuse;
if (r_curve)
*r_curve = curve;
return 0;
}
/* Parse and return the standard key generation parameter.
* The string is expected to be in this format:
*
* ALGO[/FLAGS][+SUBALGO[/FLAGS]]
*
* Here ALGO is a string in the same format as printed by the
* keylisting. For example:
*
* rsa3072 := RSA with 3072 bit.
* dsa2048 := DSA with 2048 bit.
* elg2048 := Elgamal with 2048 bit.
* ed25519 := EDDSA using curve Ed25519.
* cv25519 := ECDH using curve Curve25519.
* nistp256:= ECDSA or ECDH using curve NIST P-256
*
* All strings with an unknown prefix are considered an elliptic
* curve. Curves which have no implicit algorithm require that FLAGS
* is given to select whether ECDSA or ECDH is used; this can eoither
* be done using an algorithm keyword or usage keywords.
*
* FLAGS is a comma delimited string of keywords:
*
* cert := Allow usage Certify
* sign := Allow usage Sign
* encr := Allow usage Encrypt
* auth := Allow usage Authentication
* encrypt := Alias for "encr"
* ecdsa := Use algorithm ECDSA.
* eddsa := Use algorithm EdDSA.
* ecdh := Use algorithm ECDH.
*
* There are several defaults and fallbacks depending on the
* algorithm. PART can be used to select which part of STRING is
* used:
* -1 := Both parts
* 0 := Only the part of the primary key
* 1 := If there is one part parse that one, if there are
* two parts parse the second part. Always return
* in the args for the primary key (R_ALGO,....).
*
*/
gpg_error_t
parse_key_parameter_string (const char *string, int part,
int *r_algo, unsigned int *r_size,
unsigned *r_keyuse,
char const **r_curve,
int *r_subalgo, unsigned int *r_subsize,
unsigned *r_subkeyuse,
char const **r_subcurve)
{
gpg_error_t err = 0;
char *primary, *secondary;
if (r_algo)
*r_algo = 0;
if (r_size)
*r_size = 0;
if (r_keyuse)
*r_keyuse = 0;
if (r_curve)
*r_curve = NULL;
if (r_subalgo)
*r_subalgo = 0;
if (r_subsize)
*r_subsize = 0;
if (r_subkeyuse)
*r_subkeyuse = 0;
if (r_subcurve)
*r_subcurve = NULL;
if (!string || !*string
|| !strcmp (string, "default") || !strcmp (string, "-"))
string = get_default_pubkey_algo ();
else if (!strcmp (string, "future-default"))
string = FUTURE_STD_KEY_PARAM;
primary = xstrdup (string);
secondary = strchr (primary, '+');
if (secondary)
*secondary++ = 0;
if (part == -1 || part == 0)
{
err = parse_key_parameter_part (primary, 0, r_algo, r_size,
r_keyuse, r_curve);
if (!err && part == -1)
err = parse_key_parameter_part (secondary, 1, r_subalgo, r_subsize,
r_subkeyuse, r_subcurve);
}
else if (part == 1)
{
/* If we have SECONDARY, use that part. If there is only one
* part consider this to be the subkey algo. */
err = parse_key_parameter_part (secondary? secondary : primary, 1,
r_algo, r_size, r_keyuse, r_curve);
}
xfree (primary);
return err;
}
/* Append R to the linked list PARA. */
static void
append_to_parameter (struct para_data_s *para, struct para_data_s *r)
{
log_assert (para);
while (para->next)
para = para->next;
para->next = r;
}
/* Release the parameter list R. */
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
if (r->key == pPASSPHRASE && *r->u.value)
wipememory (r->u.value, strlen (r->u.value));
xfree (r);
}
}
static struct para_data_s *
get_parameter( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r;
for( r = para; r && r->key != key; r = r->next )
;
return r;
}
static const char *
get_parameter_value( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return (r && *r->u.value)? r->u.value : NULL;
}
/* This is similar to get_parameter_value but also returns the empty
string. This is required so that quick_generate_keypair can use an
empty Passphrase to specify no-protection. */
static const char *
get_parameter_passphrase (struct para_data_s *para)
{
struct para_data_s *r = get_parameter (para, pPASSPHRASE);
return r ? r->u.value : NULL;
}
static int
get_parameter_algo( struct para_data_s *para, enum para_name key,
int *r_default)
{
int i;
struct para_data_s *r = get_parameter( para, key );
if (r_default)
*r_default = 0;
if (!r)
return -1;
/* Note that we need to handle the ECC algorithms specified as
strings directly because Libgcrypt folds them all to ECC. */
if (!ascii_strcasecmp (r->u.value, "default"))
{
/* Note: If you change this default algo, remember to change it
* also in gpg.c:gpgconf_list. */
/* FIXME: We only allow the algo here and have a separate thing
* for the curve etc. That is a ugly but demanded for backward
* compatibility with the batch key generation. It would be
* better to make full use of parse_key_parameter_string. */
parse_key_parameter_string (NULL, 0,
&i, NULL, NULL, NULL,
NULL, NULL, NULL, NULL);
if (r_default)
*r_default = 1;
}
else if (digitp (r->u.value))
i = atoi( r->u.value );
else if (!strcmp (r->u.value, "ELG-E")
|| !strcmp (r->u.value, "ELG"))
i = PUBKEY_ALGO_ELGAMAL_E;
else if (!ascii_strcasecmp (r->u.value, "EdDSA"))
i = PUBKEY_ALGO_EDDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDSA"))
i = PUBKEY_ALGO_ECDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDH"))
i = PUBKEY_ALGO_ECDH;
else
i = map_pk_gcry_to_openpgp (gcry_pk_map_name (r->u.value));
if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S)
i = 0; /* we don't want to allow generation of these algorithms */
return i;
}
/* Parse a usage string. The usage keywords "auth", "sign", "encr"
* may be delimited by space, tab, or comma. On error -1 is returned
* instead of the usage flags. */
static int
parse_usagestr (const char *usagestr)
{
gpg_error_t err;
char **tokens = NULL;
const char *s;
int i;
unsigned int use = 0;
tokens = strtokenize (usagestr, " \t,");
if (!tokens)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
return -1;
}
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
use |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
use |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
use |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
use |= PUBKEY_USAGE_CERT;
else
{
xfree (tokens);
return -1; /* error */
}
}
xfree (tokens);
return use;
}
/*
* Parse the usage parameter and set the keyflags. Returns -1 on
* error, 0 for no usage given or 1 for usage available.
*/
static int
parse_parameter_usage (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
int i;
if (!r)
return 0; /* none (this is an optional parameter)*/
i = parse_usagestr (r->u.value);
if (i == -1)
{
log_error ("%s:%d: invalid usage list\n", fname, r->lnr );
return -1; /* error */
}
r->u.usage = i;
return 1;
}
static int
parse_revocation_key (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
struct revocation_key revkey;
char *pn;
int i;
if( !r )
return 0; /* none (this is an optional parameter) */
pn = r->u.value;
revkey.class=0x80;
revkey.algid=atoi(pn);
if(!revkey.algid)
goto fail;
/* Skip to the fpr */
while(*pn && *pn!=':')
pn++;
if(*pn!=':')
goto fail;
pn++;
for(i=0;i<MAX_FINGERPRINT_LEN && *pn;i++,pn+=2)
{
int c=hextobyte(pn);
if(c==-1)
goto fail;
revkey.fpr[i]=c;
}
/* skip to the tag */
while(*pn && *pn!='s' && *pn!='S')
pn++;
if(ascii_strcasecmp(pn,"sensitive")==0)
revkey.class|=0x40;
memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key));
return 0;
fail:
log_error("%s:%d: invalid revocation key\n", fname, r->lnr );
return -1; /* error */
}
static u32
get_parameter_u32( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
if( !r )
return 0;
if( r->key == pKEYCREATIONDATE )
return r->u.creation;
if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE )
return r->u.expire;
if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE )
return r->u.usage;
return (unsigned int)strtoul( r->u.value, NULL, 10 );
}
static unsigned int
get_parameter_uint( struct para_data_s *para, enum para_name key )
{
return get_parameter_u32( para, key );
}
static struct revocation_key *
get_parameter_revkey( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return r? &r->u.revkey : NULL;
}
static int
proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname,
struct output_control_s *outctrl, int card )
{
struct para_data_s *r;
const char *s1, *s2, *s3;
size_t n;
char *p;
int is_default = 0;
int have_user_id = 0;
int err, algo;
/* Check that we have all required parameters. */
r = get_parameter( para, pKEYTYPE );
if(r)
{
algo = get_parameter_algo (para, pKEYTYPE, &is_default);
if (openpgp_pk_test_algo2 (algo, PUBKEY_USAGE_SIG))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
}
else
{
log_error ("%s: no Key-Type specified\n",fname);
return -1;
}
err = parse_parameter_usage (fname, para, pKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if key-usage is not provided and
no default algorithm has been requested. */
r = xmalloc_clear(sizeof(*r));
r->key = pKEYUSAGE;
r->u.usage = (is_default
? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG)
: openpgp_pk_algo_usage(algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n",
fname, r->lnr, algo);
return -1;
}
}
is_default = 0;
r = get_parameter( para, pSUBKEYTYPE );
if(r)
{
algo = get_parameter_algo (para, pSUBKEYTYPE, &is_default);
if (openpgp_pk_test_algo (algo))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
err = parse_parameter_usage (fname, para, pSUBKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if subkey-usage is not
provided */
r = xmalloc_clear (sizeof(*r));
r->key = pSUBKEYUSAGE;
r->u.usage = (is_default
? PUBKEY_USAGE_ENC
: openpgp_pk_algo_usage (algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pSUBKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Subkey-Usage not allowed"
" for algo %d\n", fname, r->lnr, algo);
return -1;
}
}
}
if( get_parameter_value( para, pUSERID ) )
have_user_id=1;
else
{
/* create the formatted user ID */
s1 = get_parameter_value( para, pNAMEREAL );
s2 = get_parameter_value( para, pNAMECOMMENT );
s3 = get_parameter_value( para, pNAMEEMAIL );
if( s1 || s2 || s3 )
{
n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0);
r = xmalloc_clear( sizeof *r + n + 20 );
r->key = pUSERID;
p = r->u.value;
if( s1 )
p = stpcpy(p, s1 );
if( s2 )
p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")");
if( s3 )
p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">");
append_to_parameter (para, r);
have_user_id=1;
}
}
if(!have_user_id)
{
log_error("%s: no User-ID specified\n",fname);
return -1;
}
/* Set preferences, if any. */
keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0);
/* Set keyserver, if any. */
s1=get_parameter_value( para, pKEYSERVER );
if(s1)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (s1, 1);
if(spec)
{
free_keyserver_spec(spec);
opt.def_keyserver_url=s1;
}
else
{
r = get_parameter (para, pKEYSERVER);
log_error("%s:%d: invalid keyserver url\n", fname, r->lnr );
return -1;
}
}
/* Set revoker, if any. */
if (parse_revocation_key (fname, para, pREVOKER))
return -1;
/* Make KEYCREATIONDATE from Creation-Date. */
r = get_parameter (para, pCREATIONDATE);
if (r && *r->u.value)
{
u32 seconds;
seconds = parse_creation_string (r->u.value);
if (!seconds)
{
log_error ("%s:%d: invalid creation date\n", fname, r->lnr );
return -1;
}
r->u.creation = seconds;
r->key = pKEYCREATIONDATE; /* Change that entry. */
}
/* Make KEYEXPIRE from Expire-Date. */
r = get_parameter( para, pEXPIREDATE );
if( r && *r->u.value )
{
u32 seconds;
seconds = parse_expire_string( r->u.value );
if( seconds == (u32)-1 )
{
log_error("%s:%d: invalid expire date\n", fname, r->lnr );
return -1;
}
r->u.expire = seconds;
r->key = pKEYEXPIRE; /* change hat entry */
/* also set it for the subkey */
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYEXPIRE;
r->u.expire = seconds;
append_to_parameter (para, r);
}
do_generate_keypair (ctrl, para, outctrl, card );
return 0;
}
/****************
* Kludge to allow non interactive key generation controlled
* by a parameter file.
* Note, that string parameters are expected to be in UTF-8
*/
static void
read_parameter_file (ctrl_t ctrl, const char *fname )
{
static struct { const char *name;
enum para_name key;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Curve", pKEYCURVE },
{ "Key-Usage", pKEYUSAGE },
{ "Subkey-Type", pSUBKEYTYPE },
{ "Subkey-Length", pSUBKEYLENGTH },
{ "Subkey-Curve", pSUBKEYCURVE },
{ "Subkey-Usage", pSUBKEYUSAGE },
{ "Name-Real", pNAMEREAL },
{ "Name-Email", pNAMEEMAIL },
{ "Name-Comment", pNAMECOMMENT },
{ "Expire-Date", pEXPIREDATE },
{ "Creation-Date", pCREATIONDATE },
{ "Passphrase", pPASSPHRASE },
{ "Preferences", pPREFERENCES },
{ "Revoker", pREVOKER },
{ "Handle", pHANDLE },
{ "Keyserver", pKEYSERVER },
{ "Keygrip", pKEYGRIP },
{ NULL, 0 }
};
IOBUF fp;
byte *line;
unsigned int maxlen, nline;
char *p;
int lnr;
const char *err = NULL;
struct para_data_s *para, *r;
int i;
struct output_control_s outctrl;
memset( &outctrl, 0, sizeof( outctrl ) );
outctrl.pub.afx = new_armor_context ();
if( !fname || !*fname)
fname = "-";
fp = iobuf_open (fname);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp) {
log_error (_("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
lnr = 0;
err = NULL;
para = NULL;
maxlen = 1024;
line = NULL;
while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) {
char *keyword, *value;
lnr++;
if( !maxlen ) {
err = "line too long";
break;
}
for( p = line; isspace(*(byte*)p); p++ )
;
if( !*p || *p == '#' )
continue;
keyword = p;
if( *keyword == '%' ) {
for( ; !isspace(*(byte*)p); p++ )
;
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
value = p;
trim_trailing_ws( value, strlen(value) );
if( !ascii_strcasecmp( keyword, "%echo" ) )
log_info("%s\n", value );
else if( !ascii_strcasecmp( keyword, "%dry-run" ) )
outctrl.dryrun = 1;
else if( !ascii_strcasecmp( keyword, "%ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-protection" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION;
else if( !ascii_strcasecmp( keyword, "%transient-key" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY;
else if( !ascii_strcasecmp( keyword, "%commit" ) ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else if( !ascii_strcasecmp( keyword, "%pubring" ) ) {
if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) )
; /* still the same file - ignore it */
else {
xfree( outctrl.pub.newfname );
outctrl.pub.newfname = xstrdup( value );
outctrl.use_files = 1;
}
}
else if( !ascii_strcasecmp( keyword, "%secring" ) ) {
/* Ignore this command. */
}
else
log_info("skipping control '%s' (%s)\n", keyword, value );
continue;
}
if( !(p = strchr( p, ':' )) || p == keyword ) {
err = "missing colon";
break;
}
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
if( !*p ) {
err = "missing argument";
break;
}
value = p;
trim_trailing_ws( value, strlen(value) );
for(i=0; keywords[i].name; i++ ) {
if( !ascii_strcasecmp( keywords[i].name, keyword ) )
break;
}
if( !keywords[i].name ) {
err = "unknown keyword";
break;
}
if( keywords[i].key != pKEYTYPE && !para ) {
err = "parameter block does not start with \"Key-Type\"";
break;
}
if( keywords[i].key == pKEYTYPE && para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else {
for( r = para; r; r = r->next ) {
if( r->key == keywords[i].key )
break;
}
if( r ) {
err = "duplicate keyword";
break;
}
}
r = xmalloc_clear( sizeof *r + strlen( value ) );
r->lnr = lnr;
r->key = keywords[i].key;
strcpy( r->u.value, value );
r->next = para;
para = r;
}
if( err )
log_error("%s:%d: %s\n", fname, lnr, err );
else if( iobuf_error (fp) ) {
log_error("%s:%d: read error\n", fname, lnr);
}
else if( para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created (get_parameter_value (para, pHANDLE));
}
if( outctrl.use_files ) { /* close open streams */
iobuf_close( outctrl.pub.stream );
/* Must invalidate that ugly cache to actually close it. */
if (outctrl.pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl.pub.fname);
xfree( outctrl.pub.fname );
xfree( outctrl.pub.newfname );
}
xfree (line);
release_parameter_list( para );
iobuf_close (fp);
release_armor_context (outctrl.pub.afx);
}
/* Helper for quick_generate_keypair. */
static struct para_data_s *
quickgen_set_para (struct para_data_s *para, int for_subkey,
int algo, int nbits, const char *curve, unsigned int use)
{
struct para_data_s *r;
r = xmalloc_clear (sizeof *r + 30);
r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE;
if (use)
snprintf (r->u.value, 30, "%s%s%s%s",
(use & PUBKEY_USAGE_ENC)? "encr " : "",
(use & PUBKEY_USAGE_SIG)? "sign " : "",
(use & PUBKEY_USAGE_AUTH)? "auth " : "",
(use & PUBKEY_USAGE_CERT)? "cert " : "");
else
strcpy (r->u.value, for_subkey ? "encr" : "sign");
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE;
snprintf (r->u.value, 20, "%d", algo);
r->next = para;
para = r;
if (curve)
{
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH;
sprintf (r->u.value, "%u", nbits);
r->next = para;
para = r;
}
return para;
}
/*
* Unattended generation of a standard key.
*/
void
quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
int use_tty;
memset (&outctrl, 0, sizeof outctrl);
use_tty = (!opt.batch && !opt.answer_yes
&& !*algostr && !*usagestr && !*expirestr
&& !cpr_enabled ()
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)));
r = xmalloc_clear (sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
uid = trim_spaces (r->u.value);
if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid)))
{
log_error (_("Key generation failed: %s\n"),
gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
/* If gpg is directly used on the console ask whether a key with the
given user id shall really be created. */
if (use_tty)
{
tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid);
if (!cpr_get_answer_is_yes_def ("quick_keygen.okay",
_("Continue? (Y/n) "), 1))
goto leave;
}
/* Check whether such a user ID already exists. */
{
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = uid;
kdbhd = keydb_new ();
if (!kdbhd)
goto leave;
err = keydb_search (kdbhd, &desc, 1, NULL);
keydb_release (kdbhd);
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_info (_("A key for \"%s\" already exists\n"), uid);
if (opt.answer_yes)
;
else if (!use_tty
|| !cpr_get_answer_is_yes_def ("quick_keygen.force",
_("Create anyway? (y/N) "), 0))
{
write_status_error ("genkey", gpg_error (304));
log_inc_errorcount (); /* we used log_info */
goto leave;
}
log_info (_("creating anyway\n"));
}
}
if (!*expirestr || strcmp (expirestr, "-") == 0)
expirestr = default_expiration_interval;
if ((!*algostr || !strcmp (algostr, "default")
|| !strcmp (algostr, "future-default"))
&& (!*usagestr || !strcmp (usagestr, "default")
|| !strcmp (usagestr, "-")))
{
/* Use default key parameters. */
int algo, subalgo;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
err = parse_key_parameter_string (algostr, -1,
&algo, &size, &keyuse, &curve,
&subalgo, &subsize, &subkeyuse,
&subcurve);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse);
if (*expirestr)
{
u32 expire;
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
}
else
{
/* Extended unattended mode. Creates only the primary key. */
int algo;
unsigned int use;
u32 expire;
unsigned int nbits;
char *curve;
err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
goto leave;
}
para = quickgen_set_para (para, 0, algo, nbits, curve, use);
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new key. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
r = xmalloc_clear (sizeof *r + strlen (s));
r->key = pPASSPHRASE;
strcpy (r->u.value, s);
r->next = para;
para = r;
}
proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0);
leave:
release_parameter_list (para);
}
/*
* Generate a keypair (fname is only used in batch mode) If
* CARD_SERIALNO is not NULL the function will create the keys on an
* OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is
* NOT NULL, the encryption key for the card is generated on the host,
* imported to the card and a backup file created by gpg-agent. If
* FULL is not set only the basic prompts are used (except for batch
* mode).
*/
void
generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key)
{
gpg_error_t err;
unsigned int nbits;
char *uid = NULL;
int algo;
unsigned int use;
int both = 0;
u32 expire;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
#ifndef ENABLE_CARD_SUPPORT
(void)card_backup_key;
#endif
memset( &outctrl, 0, sizeof( outctrl ) );
if (opt.batch && card_serialno)
{
/* We don't yet support unattended key generation. */
log_error (_("can't do this in batch mode\n"));
return;
}
if (opt.batch)
{
read_parameter_file (ctrl, fname);
return;
}
if (card_serialno)
{
#ifdef ENABLE_CARD_SUPPORT
struct agent_card_info_s info;
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"),
gpg_strerror (err));
return;
}
r = xcalloc (1, sizeof *r + strlen (card_serialno) );
r->key = pSERIALNO;
strcpy( r->u.value, card_serialno);
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[0].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy (r->u.value, "sign");
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[1].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy (r->u.value, "encrypt");
r->next = para;
para = r;
if (info.key_attr[1].algo == PUBKEY_ALGO_RSA)
{
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYLENGTH;
sprintf( r->u.value, "%u", info.key_attr[1].nbits);
r->next = para;
para = r;
}
else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_EDDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_ECDH)
{
r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, info.key_attr[1].curve);
r->next = para;
para = r;
}
r = xcalloc (1, sizeof *r + 20 );
r->key = pAUTHKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[2].algo );
r->next = para;
para = r;
if (card_backup_key)
{
r = xcalloc (1, sizeof *r + 1);
r->key = pCARDBACKUPKEY;
strcpy (r->u.value, "1");
r->next = para;
para = r;
}
#endif /*ENABLE_CARD_SUPPORT*/
}
else if (full) /* Full featured key generation. */
{
int subkey_algo;
char *key_from_hexgrip = NULL;
algo = ask_algo (ctrl, 0, &subkey_algo, &use, &key_from_hexgrip);
if (key_from_hexgrip)
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 40 );
r->key = pKEYGRIP;
strcpy (r->u.value, key_from_hexgrip);
r->next = para;
para = r;
xfree (key_from_hexgrip);
}
else
{
char *curve = NULL;
if (subkey_algo)
{
/* Create primary and subkey at once. */
both = 1;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, &subkey_algo);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = 0;
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = ask_keysize (algo, 0);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy( r->u.value, "sign" );
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", subkey_algo);
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy( r->u.value, "encrypt" );
r->next = para;
para = r;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
if (algo == PUBKEY_ALGO_EDDSA
&& subkey_algo == PUBKEY_ALGO_ECDH)
{
/* Need to switch to a different curve for the
encryption key. */
xfree (curve);
curve = xstrdup ("Curve25519");
}
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
}
else /* Create only a single key. */
{
/* For ECC we need to ask for the curve before storing the
algo because ask_curve may change the algo. */
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, NULL);
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo );
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
nbits = 0;
}
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
/* The curve has already been set. */
}
else
{
nbits = ask_keysize (both? subkey_algo : algo, nbits);
r = xmalloc_clear( sizeof *r + 20 );
r->key = both? pSUBKEYLENGTH : pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
xfree (curve);
}
}
else /* Default key generation. */
{
int subalgo;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
tty_printf ( _("Note: Use \"%s %s\""
" for a full featured key generation dialog.\n"),
#if USE_GPG2_HACK
GPG_NAME "2"
#else
GPG_NAME
#endif
, "--full-generate-key" );
err = parse_key_parameter_string (NULL, -1,
&algo, &size, &keyuse, &curve,
&subalgo, &subsize,
&subkeyuse, &subcurve);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
return;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse);
}
expire = full? ask_expire_interval (0, NULL)
: parse_expire_string (default_expiration_interval);
r = xcalloc (1, sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20);
r->key = pSUBKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
uid = ask_user_id (0, full, NULL);
if (!uid)
{
log_error(_("Key generation canceled.\n"));
release_parameter_list( para );
return;
}
r = xcalloc (1, sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno);
release_parameter_list (para);
}
/* Create and delete a dummy packet to start off a list of kbnodes. */
static void
start_tree(KBNODE *tree)
{
PACKET *pkt;
pkt=xmalloc_clear(sizeof(*pkt));
pkt->pkttype=PKT_NONE;
*tree=new_kbnode(pkt);
delete_kbnode(*tree);
}
/* Write the *protected* secret key to the file. */
static gpg_error_t
card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir)
{
gpg_error_t err = 0;
int rc;
char keyid_buffer[2 * 8 + 1];
char name_buffer[50];
char *fname;
IOBUF fp;
mode_t oldmask;
PACKET *pkt = NULL;
format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer));
snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer);
fname = make_filename (backup_dir, name_buffer, NULL);
/* Note that the umask call is not anymore needed because
iobuf_create now takes care of it. However, it does not harm
and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_create (fname, 1);
umask (oldmask);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) );
goto leave;
}
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SECRET_KEY;
pkt->pkt.secret_key = sk;
rc = build_packet (fp, pkt);
if (rc)
{
log_error ("build packet failed: %s\n", gpg_strerror (rc));
iobuf_cancel (fp);
}
else
{
char *fprbuf;
iobuf_close (fp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
log_info (_("Note: backup of card key saved to '%s'\n"), fname);
fprbuf = hexfingerprint (sk, NULL, 0);
write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf,
fname, strlen (fname), 0);
xfree (fprbuf);
}
leave:
xfree (pkt);
xfree (fname);
return err;
}
/* Store key to card and make a backup file in OpenPGP format. */
static gpg_error_t
card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk,
const char *backup_dir)
{
PKT_public_key *sk;
gnupg_isotime_t timestamp;
gpg_error_t err;
char *hexgrip;
int rc;
struct agent_card_info_s info;
gcry_cipher_hd_t cipherhd = NULL;
char *cache_nonce = NULL;
void *kek = NULL;
size_t keklen;
sk = copy_public_key (NULL, sub_psk);
if (!sk)
return gpg_error_from_syserror ();
epoch2isotime (timestamp, (time_t)sk->timestamp);
err = hexkeygrip_from_pk (sk, &hexgrip);
if (err)
return err;
memset(&info, 0, sizeof (info));
rc = agent_scd_getattr ("SERIALNO", &info);
if (rc)
return (gpg_error_t)rc;
rc = agent_keytocard (hexgrip, 2, 1, info.serialno, timestamp);
xfree (info.serialno);
if (rc)
{
err = (gpg_error_t)rc;
goto leave;
}
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
err = receive_seckey_from_agent (ctrl, cipherhd, 0,
&cache_nonce, hexgrip, sk);
if (err)
{
log_error ("error getting secret key from agent: %s\n",
gpg_strerror (err));
goto leave;
}
err = card_write_key_to_backup_file (sk, backup_dir);
if (err)
log_error ("writing card key to backup file: %s\n", gpg_strerror (err));
else
/* Remove secret key data in agent side. */
agent_scd_learn (NULL, 1);
leave:
xfree (cache_nonce);
gcry_cipher_close (cipherhd);
xfree (kek);
xfree (hexgrip);
free_public_key (sk);
return err;
}
static void
do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card)
{
gpg_error_t err;
KBNODE pub_root = NULL;
const char *s;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
struct revocation_key *revkey;
int did_sub = 0;
u32 timestamp;
char *cache_nonce = NULL;
int algo;
u32 expire;
const char *key_from_hexgrip = NULL;
if (outctrl->dryrun)
{
log_info("dry-run mode - key generation skipped\n");
return;
}
if ( outctrl->use_files )
{
if ( outctrl->pub.newfname )
{
iobuf_close(outctrl->pub.stream);
outctrl->pub.stream = NULL;
if (outctrl->pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl->pub.fname);
xfree( outctrl->pub.fname );
outctrl->pub.fname = outctrl->pub.newfname;
outctrl->pub.newfname = NULL;
if (is_secured_filename (outctrl->pub.fname) )
{
outctrl->pub.stream = NULL;
gpg_err_set_errno (EPERM);
}
else
outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0);
if (!outctrl->pub.stream)
{
log_error(_("can't create '%s': %s\n"), outctrl->pub.newfname,
strerror(errno) );
return;
}
if (opt.armor)
{
outctrl->pub.afx->what = 1;
push_armor_filter (outctrl->pub.afx, outctrl->pub.stream);
}
}
log_assert( outctrl->pub.stream );
if (opt.verbose)
log_info (_("writing public key to '%s'\n"), outctrl->pub.fname );
}
/* We create the packets as a tree of kbnodes. Because the
structure we create is known in advance we simply generate a
linked list. The first packet is a dummy packet which we flag as
deleted. The very first packet must always be a KEY packet. */
start_tree (&pub_root);
timestamp = get_parameter_u32 (para, pKEYCREATIONDATE);
if (!timestamp)
timestamp = make_timestamp ();
/* Note that, depending on the backend (i.e. the used scdaemon
version), the card key generation may update TIMESTAMP for each
key. Thus we need to pass TIMESTAMP to all signing function to
make sure that the binding signature is done using the timestamp
of the corresponding (sub)key and not that of the primary key.
An alternative implementation could tell the signing function the
node of the subkey but that is more work than just to pass the
current timestamp. */
algo = get_parameter_algo( para, pKEYTYPE, NULL );
expire = get_parameter_u32( para, pKEYEXPIRE );
key_from_hexgrip = get_parameter_value (para, pKEYGRIP);
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip,
pub_root, timestamp, expire, 0);
else if (!card)
err = do_create (algo,
get_parameter_uint( para, pKEYLENGTH ),
get_parameter_value (para, pKEYCURVE),
pub_root,
timestamp,
expire, 0,
outctrl->keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
else
err = gen_card_key (1, algo,
1, pub_root, &timestamp,
expire);
/* Get the pointer to the generated public key packet. */
if (!err)
{
pri_psk = pub_root->next->pkt->pkt.public_key;
log_assert (pri_psk);
/* Make sure a few fields are correctly set up before going
further. */
pri_psk->flags.primary = 1;
keyid_from_pk (pri_psk, NULL);
/* We don't use pk_keyid to get keyid, because it also asserts
that main_keyid is set! */
keyid_copy (pri_psk->main_keyid, pri_psk->keyid);
}
if (!err && (revkey = get_parameter_revkey (para, pREVOKER)))
err = write_direct_sig (pub_root, pri_psk, revkey, timestamp, cache_nonce);
if (!err && (s = get_parameter_value (para, pUSERID)))
{
write_uid (pub_root, s );
err = write_selfsigs (pub_root, pri_psk,
get_parameter_uint (para, pKEYUSAGE), timestamp,
cache_nonce);
}
/* Write the auth key to the card before the encryption key. This
is a partial workaround for a PGP bug (as of this writing, all
versions including 8.1), that causes it to try and encrypt to
the most recent subkey regardless of whether that subkey is
actually an encryption type. In this case, the auth key is an
RSA key so it succeeds. */
if (!err && card && get_parameter (para, pAUTHKEYTYPE))
{
err = gen_card_key (3, get_parameter_algo( para, pAUTHKEYTYPE, NULL ),
0, pub_root, &timestamp,
get_parameter_u32 (para, pKEYEXPIRE));
if (!err)
err = write_keybinding (pub_root, pri_psk, NULL,
PUBKEY_USAGE_AUTH, timestamp, cache_nonce);
}
if (!err && get_parameter (para, pSUBKEYTYPE))
{
sub_psk = NULL;
s = NULL;
if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY)))
{
err = do_create (get_parameter_algo (para, pSUBKEYTYPE, NULL),
get_parameter_uint (para, pSUBKEYLENGTH),
get_parameter_value (para, pSUBKEYCURVE),
pub_root,
timestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE), 1,
s ? KEYGEN_FLAG_NO_PROTECTION : outctrl->keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
kbnode_t node;
for (node = pub_root; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
log_assert (sub_psk);
if (s)
err = card_store_key_with_backup (ctrl,
sub_psk, gnupg_homedir ());
}
}
else
{
err = gen_card_key (2, get_parameter_algo (para, pSUBKEYTYPE, NULL),
0, pub_root, &timestamp,
get_parameter_u32 (para, pKEYEXPIRE));
}
if (!err)
err = write_keybinding (pub_root, pri_psk, sub_psk,
get_parameter_uint (para, pSUBKEYUSAGE),
timestamp, cache_nonce);
did_sub = 1;
}
if (!err && outctrl->use_files) /* Direct write to specified files. */
{
err = write_keyblock (outctrl->pub.stream, pub_root);
if (err)
log_error ("can't write public key: %s\n", gpg_strerror (err));
}
else if (!err) /* Write to the standard keyrings. */
{
KEYDB_HANDLE pub_hd;
pub_hd = keydb_new ();
if (!pub_hd)
err = gpg_error_from_syserror ();
else
{
err = keydb_locate_writable (pub_hd);
if (err)
log_error (_("no writable public keyring found: %s\n"),
gpg_strerror (err));
}
if (!err && opt.verbose)
{
log_info (_("writing public key to '%s'\n"),
keydb_get_resource_name (pub_hd));
}
if (!err)
{
err = keydb_insert_keyblock (pub_hd, pub_root);
if (err)
log_error (_("error writing public keyring '%s': %s\n"),
keydb_get_resource_name (pub_hd), gpg_strerror (err));
}
keydb_release (pub_hd);
if (!err)
{
int no_enc_rsa;
PKT_public_key *pk;
no_enc_rsa = ((get_parameter_algo (para, pKEYTYPE, NULL)
== PUBKEY_ALGO_RSA)
&& get_parameter_uint (para, pKEYUSAGE)
&& !((get_parameter_uint (para, pKEYUSAGE)
& PUBKEY_USAGE_ENC)) );
pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
keyid_from_pk (pk, pk->main_keyid);
register_trusted_keyid (pk->main_keyid);
update_ownertrust (pk, ((get_ownertrust (pk) & ~TRUST_MASK)
| TRUST_ULTIMATE ));
gen_standard_revoke (pk, cache_nonce);
/* Get rid of the first empty packet. */
commit_kbnode (&pub_root);
if (!opt.batch)
{
tty_printf (_("public and secret key created and signed.\n") );
tty_printf ("\n");
merge_keys_and_selfsig (pub_root);
list_keyblock_direct (ctrl, pub_root, 0, 1, 1, 1);
}
if (!opt.batch
&& (get_parameter_algo (para, pKEYTYPE, NULL) == PUBKEY_ALGO_DSA
|| no_enc_rsa )
&& !get_parameter (para, pSUBKEYTYPE) )
{
tty_printf(_("Note that this key cannot be used for "
"encryption. You may want to use\n"
"the command \"--edit-key\" to generate a "
"subkey for this purpose.\n") );
}
}
}
if (err)
{
if (opt.batch)
log_error ("key generation failed: %s\n", gpg_strerror (err) );
else
tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) );
write_status_error (card? "card_key_generate":"key_generate", err);
print_status_key_not_created ( get_parameter_value (para, pHANDLE) );
}
else
{
PKT_public_key *pk = find_kbnode (pub_root,
PKT_PUBLIC_KEY)->pkt->pkt.public_key;
print_status_key_created (did_sub? 'B':'P', pk,
get_parameter_value (para, pHANDLE));
}
release_kbnode (pub_root);
xfree (cache_nonce);
}
static gpg_error_t
parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage, u32 *r_expire,
unsigned int *r_nbits, char **r_curve)
{
gpg_error_t err;
int algo;
unsigned int use, nbits;
u32 expire;
int wantuse;
const char *curve = NULL;
*r_curve = NULL;
nbits = 0;
/* Parse the algo string. */
if (algostr && *algostr == '&' && strlen (algostr) == 41)
{
/* Take algo from existing key. */
algo = check_keygrip (ctrl, algostr+1);
/* FIXME: We need the curve name as well. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
err = parse_key_parameter_string (algostr, for_subkey? 1 : 0,
&algo, &nbits, &use, &curve,
NULL, NULL, NULL, NULL);
if (err)
return err;
/* Parse the usage string. */
if (!usagestr || !*usagestr
|| !strcmp (usagestr, "default") || !strcmp (usagestr, "-"))
; /* Keep usage from parse_key_parameter_string. */
else if ((wantuse = parse_usagestr (usagestr)) != -1)
use = wantuse;
else
return gpg_error (GPG_ERR_INV_VALUE);
/* Make sure a primary key has the CERT usage. */
if (!for_subkey)
use |= PUBKEY_USAGE_CERT;
/* Check that usage is possible. NB: We have the same check in
* parse_key_parameter_string but need it here again in case the
* separate usage value has been given. */
if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((use & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (use & PUBKEY_USAGE_CERT)))
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
/* Parse the expire string. */
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
return gpg_error (GPG_ERR_INV_VALUE);
if (curve)
{
*r_curve = xtrystrdup (curve);
if (!*r_curve)
return gpg_error_from_syserror ();
}
*r_algo = algo;
*r_usage = use;
*r_expire = expire;
*r_nbits = nbits;
return 0;
}
/* Add a new subkey to an existing key. Returns 0 if a new key has
been generated and put into the keyblocks. If any of ALGOSTR,
USAGESTR, or EXPIRESTR is NULL interactive mode is used. */
gpg_error_t
generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err = 0;
int interactive;
kbnode_t node;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
int algo;
unsigned int use;
u32 expire;
unsigned int nbits = 0;
char *curve = NULL;
u32 cur_time;
char *key_from_hexgrip = NULL;
char *hexgrip = NULL;
char *serialno = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
interactive = (!algostr || !usagestr || !expirestr);
/* Break out the primary key. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; primary key missing in keyblock!\n");
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
pri_psk = node->pkt->pkt.public_key;
cur_time = make_timestamp ();
if (pri_psk->timestamp > cur_time)
{
ulong d = pri_psk->timestamp - cur_time;
log_info ( d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_psk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
err = hexkeygrip_from_pk (pri_psk, &hexgrip);
if (err)
goto leave;
if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
{
if (interactive)
tty_printf (_("Secret parts of primary key are not available.\n"));
else
log_info ( _("Secret parts of primary key are not available.\n"));
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
if (serialno)
{
if (interactive)
tty_printf (_("Secret parts of primary key are stored on-card.\n"));
else
log_info ( _("Secret parts of primary key are stored on-card.\n"));
}
if (interactive)
{
algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip);
log_assert (algo);
if (key_from_hexgrip)
nbits = 0;
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
curve = ask_curve (&algo, NULL);
else
nbits = ask_keysize (algo, 0);
expire = ask_expire_interval (0, NULL);
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else /* Unattended mode. */
{
err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve);
if (err)
goto leave;
}
/* Verify the passphrase now so that we get a cache item for the
* primary key passphrase. The agent also returns a passphrase
* nonce, which we can use to set the passphrase for the subkey to
* that of the primary key. */
{
char *desc = gpg_format_keydesc (pri_psk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/,
&cache_nonce, &passwd_nonce);
xfree (desc);
}
/* Start creation. */
if (key_from_hexgrip)
{
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip,
keyblock, cur_time, expire, 1);
}
else
{
const char *passwd;
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new subkey. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
passwd = get_static_passphrase ();
else
passwd = NULL;
err = do_create (algo, nbits, curve,
keyblock, cur_time, expire, 1, 0,
passwd, &cache_nonce, &passwd_nonce);
}
if (err)
goto leave;
/* Get the pointer to the generated public subkey packet. */
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
/* Write the binding signature. */
err = write_keybinding (keyblock, pri_psk, sub_psk, use, cur_time,
cache_nonce);
if (err)
goto leave;
print_status_key_created ('S', sub_psk, NULL);
leave:
xfree (key_from_hexgrip);
xfree (curve);
xfree (hexgrip);
xfree (serialno);
xfree (cache_nonce);
xfree (passwd_nonce);
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
return err;
}
#ifdef ENABLE_CARD_SUPPORT
/* Generate a subkey on a card. */
gpg_error_t
generate_card_subkeypair (kbnode_t pub_keyblock,
int keyno, const char *serialno)
{
gpg_error_t err = 0;
kbnode_t node;
PKT_public_key *pri_pk = NULL;
unsigned int use;
u32 expire;
u32 cur_time;
struct para_data_s *para = NULL;
PKT_public_key *sub_pk = NULL;
int algo;
struct agent_card_info_s info;
log_assert (keyno >= 1 && keyno <= 3);
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"), gpg_strerror (err));
return err;
}
algo = info.key_attr[keyno-1].algo;
para = xtrycalloc (1, sizeof *para + strlen (serialno) );
if (!para)
{
err = gpg_error_from_syserror ();
goto leave;
}
para->key = pSERIALNO;
strcpy (para->u.value, serialno);
/* Break out the primary secret key */
node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key lost!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pri_pk = node->pkt->pkt.public_key;
cur_time = make_timestamp();
if (pri_pk->timestamp > cur_time)
{
ulong d = pri_pk->timestamp - cur_time;
log_info (d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_pk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
expire = ask_expire_interval (0, NULL);
if (keyno == 1)
use = PUBKEY_USAGE_SIG;
else if (keyno == 2)
use = PUBKEY_USAGE_ENC;
else
use = PUBKEY_USAGE_AUTH;
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
/* Note, that depending on the backend, the card key generation may
update CUR_TIME. */
err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
log_assert (sub_pk);
err = write_keybinding (pub_keyblock, pri_pk, sub_pk,
use, cur_time, NULL);
}
leave:
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
else
print_status_key_created ('S', sub_pk, NULL);
release_parameter_list (para);
return err;
}
#endif /* !ENABLE_CARD_SUPPORT */
/*
* Write a keyblock to an output stream
*/
static int
write_keyblock( IOBUF out, KBNODE node )
{
for( ; node ; node = node->next )
{
if(!is_deleted_kbnode(node))
{
int rc = build_packet( out, node->pkt );
if( rc )
{
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
}
return 0;
}
/* Note that timestamp is an in/out arg. */
static gpg_error_t
gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root,
u32 *timestamp, u32 expireval)
{
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t err;
PACKET *pkt;
PKT_public_key *pk;
char keyid[10];
unsigned char *public;
gcry_sexp_t s_key;
snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno);
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return gpg_error_from_syserror ();
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
xfree (pk);
return gpg_error_from_syserror ();
}
/* Note: SCD knows the serialnumber, thus there is no point in passing it. */
err = agent_scd_genkey (keyno, 1, timestamp);
/* The code below is not used because we force creation of
* the a card key (3rd arg).
* if (gpg_err_code (rc) == GPG_ERR_EEXIST)
* {
* tty_printf ("\n");
* log_error ("WARNING: key does already exists!\n");
* tty_printf ("\n");
* if ( cpr_get_answer_is_yes( "keygen.card.replace_key",
* _("Replace existing key? ")))
* rc = agent_scd_genkey (keyno, 1, timestamp);
* }
*/
if (err)
{
log_error ("key generation failed: %s\n", gpg_strerror (err));
xfree (pkt);
xfree (pk);
return err;
}
/* Send the READKEY command so that the agent creates a shadow key for
card key. We need to do that now so that we are able to create
the self-signatures. */
err = agent_readkey (NULL, 1, keyid, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
if (algo == PUBKEY_ALGO_RSA)
err = key_from_sexp (pk->pkey, s_key, "public-key", "ne");
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
gcry_sexp_release (s_key);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
free_public_key (pk);
return err;
}
pk->timestamp = *timestamp;
pk->version = 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
#else
(void)keyno;
(void)is_primary;
(void)pub_root;
(void)timestamp;
(void)expireval;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!ENABLE_CARD_SUPPORT*/
}
diff --git a/g10/keyid.c b/g10/keyid.c
index 6e8d97fa2..1d3f46cb4 100644
--- a/g10/keyid.c
+++ b/g10/keyid.c
@@ -1,981 +1,981 @@
/* keyid.c - key ID and fingerprint handling
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2004, 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "options.h"
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "rmd160.h"
-#include "host2net.h"
+#include "../common/host2net.h"
#define KEYID_STR_SIZE 19
#ifdef HAVE_UNSIGNED_TIME_T
# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1))
#else
/* Error or 32 bit time_t and value after 2038-01-19. */
# define IS_INVALID_TIME_T(a) ((a) < 0)
#endif
/* Return a letter describing the public key algorithms. */
int
pubkey_letter( int algo )
{
switch (algo)
{
case PUBKEY_ALGO_RSA: return 'R' ;
case PUBKEY_ALGO_RSA_E: return 'r' ;
case PUBKEY_ALGO_RSA_S: return 's' ;
case PUBKEY_ALGO_ELGAMAL_E: return 'g' ;
case PUBKEY_ALGO_ELGAMAL: return 'G' ;
case PUBKEY_ALGO_DSA: return 'D' ;
case PUBKEY_ALGO_ECDH: return 'e' ; /* ECC DH (encrypt only) */
case PUBKEY_ALGO_ECDSA: return 'E' ; /* ECC DSA (sign only) */
case PUBKEY_ALGO_EDDSA: return 'E' ; /* ECC EdDSA (sign only) */
default: return '?';
}
}
/* Return a string describing the public key algorithm and the
keysize. For elliptic curves the functions prints the name of the
curve because the keysize is a property of the curve. The string
is copied to the supplied buffer up a length of BUFSIZE-1.
Examples for the output are:
"rsa2048" - RSA with 2048 bit
"elg1024" - Elgamal with 1024 bit
"ed25519" - ECC using the curve Ed25519.
"E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
"E_1.3.6.1.4.1.11591.2.12242973" ECC with a bogus OID.
"unknown_N" - Unknown OpenPGP algorithm N.
If the option --legacy-list-mode is active, the output use the
legacy format:
"2048R" - RSA with 2048 bit
"1024g" - Elgamal with 1024 bit
"256E" - ECDSA using a curve with 256 bit
The macro PUBKEY_STRING_SIZE may be used to allocate a buffer with
a suitable size.*/
char *
pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize)
{
const char *prefix = NULL;
if (opt.legacy_list_mode)
{
snprintf (buffer, bufsize, "%4u%c",
nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo));
return buffer;
}
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: prefix = "rsa"; break;
case PUBKEY_ALGO_ELGAMAL_E: prefix = "elg"; break;
case PUBKEY_ALGO_DSA: prefix = "dsa"; break;
case PUBKEY_ALGO_ELGAMAL: prefix = "xxx"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA: prefix = ""; break;
}
if (prefix && *prefix)
snprintf (buffer, bufsize, "%s%u", prefix, nbits_from_pk (pk));
else if (prefix)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
if (name)
snprintf (buffer, bufsize, "%s", name);
else if (curve)
snprintf (buffer, bufsize, "E_%s", curve);
else
snprintf (buffer, bufsize, "E_error");
xfree (curve);
}
else
snprintf (buffer, bufsize, "unknown_%u", (unsigned int)pk->pubkey_algo);
return buffer;
}
/* Hash a public key. This function is useful for v4 fingerprints and
for v3 or v4 key signing. */
void
hash_public_key (gcry_md_hd_t md, PKT_public_key *pk)
{
unsigned int n = 6;
unsigned int nn[PUBKEY_MAX_NPKEY];
byte *pp[PUBKEY_MAX_NPKEY];
int i;
unsigned int nbits;
size_t nbytes;
int npkey = pubkey_get_npkey (pk->pubkey_algo);
/* FIXME: We can avoid the extra malloc by calling only the first
mpi_print here which computes the required length and calling the
real mpi_print only at the end. The speed advantage would only be
for ECC (opaque MPIs) or if we could implement an mpi_print
variant with a callback handler to do the hashing. */
if (npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
pp[0] = gcry_mpi_get_opaque (pk->pkey[0], &nbits);
nn[0] = (nbits+7)/8;
n+=nn[0];
}
else
{
for (i=0; i < npkey; i++ )
{
if (!pk->pkey[i])
{
/* This case may only happen if the parsing of the MPI
failed but the key was anyway created. May happen
during "gpg KEYFILE". */
pp[i] = NULL;
nn[i] = 0;
}
else if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const void *p;
p = gcry_mpi_get_opaque (pk->pkey[i], &nbits);
pp[i] = xmalloc ((nbits+7)/8);
if (p)
memcpy (pp[i], p, (nbits+7)/8);
else
pp[i] = NULL;
nn[i] = (nbits+7)/8;
n += nn[i];
}
else
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0,
&nbytes, pk->pkey[i]))
BUG ();
pp[i] = xmalloc (nbytes);
if (gcry_mpi_print (GCRYMPI_FMT_PGP, pp[i], nbytes,
&nbytes, pk->pkey[i]))
BUG ();
nn[i] = nbytes;
n += nn[i];
}
}
}
gcry_md_putc ( md, 0x99 ); /* ctb */
/* What does it mean if n is greater than 0xFFFF ? */
gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
gcry_md_putc ( md, pk->timestamp >> 24 );
gcry_md_putc ( md, pk->timestamp >> 16 );
gcry_md_putc ( md, pk->timestamp >> 8 );
gcry_md_putc ( md, pk->timestamp );
gcry_md_putc ( md, pk->pubkey_algo );
if(npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
if (pp[0])
gcry_md_write (md, pp[0], nn[0]);
}
else
{
for(i=0; i < npkey; i++ )
{
if (pp[i])
gcry_md_write ( md, pp[i], nn[i] );
xfree(pp[i]);
}
}
}
static gcry_md_hd_t
do_fingerprint_md( PKT_public_key *pk )
{
gcry_md_hd_t md;
if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0))
BUG ();
hash_public_key(md,pk);
gcry_md_final( md );
return md;
}
/* fixme: Check whether we can replace this function or if not
describe why we need it. */
u32
v3_keyid (gcry_mpi_t a, u32 *ki)
{
byte *buffer, *p;
size_t nbytes;
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, a ))
BUG ();
/* fixme: allocate it on the stack */
buffer = xmalloc (nbytes);
if (gcry_mpi_print( GCRYMPI_FMT_USG, buffer, nbytes, NULL, a ))
BUG ();
if (nbytes < 8) /* oops */
ki[0] = ki[1] = 0;
else
{
p = buffer + nbytes - 8;
ki[0] = buf32_to_u32 (p);
p += 4;
ki[1] = buf32_to_u32 (p);
}
xfree (buffer);
return ki[1];
}
/* Return PK's keyid. The memory is owned by PK. */
u32 *
pk_keyid (PKT_public_key *pk)
{
keyid_from_pk (pk, NULL);
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) == 0
&& ! pk->flags.primary)
log_bug ("keyid and main_keyid are the same, but primary flag not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) != 0
&& pk->flags.primary)
log_bug ("keyid and main_keyid are different, but primary flag set!\n");
#endif
return pk->keyid;
}
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *
pk_main_keyid (PKT_public_key *pk)
{
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
#endif
return pk->main_keyid;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *
keyid_copy (u32 *dest, const u32 *src)
{
dest[0] = src[0];
dest[1] = src[1];
return dest;
}
char *
format_keyid (u32 *keyid, int format, char *buffer, int len)
{
char tmp[KEYID_STR_SIZE];
if (! buffer)
{
buffer = tmp;
len = sizeof (tmp);
}
if (format == KF_DEFAULT)
format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch (format)
{
case KF_NONE:
if (len)
*buffer = 0;
break;
case KF_SHORT:
snprintf (buffer, len, "%08lX", (ulong)keyid[1]);
break;
case KF_LONG:
snprintf (buffer, len, "%08lX%08lX", (ulong)keyid[0], (ulong)keyid[1]);
break;
case KF_0xSHORT:
snprintf (buffer, len, "0x%08lX", (ulong)keyid[1]);
break;
case KF_0xLONG:
snprintf (buffer, len, "0x%08lX%08lX", (ulong)keyid[0],(ulong)keyid[1]);
break;
default:
BUG();
}
if (buffer == tmp)
return xstrdup (buffer);
return buffer;
}
size_t
keystrlen(void)
{
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch(format)
{
case KF_NONE:
return 0;
case KF_SHORT:
return 8;
case KF_LONG:
return 16;
case KF_0xSHORT:
return 10;
case KF_0xLONG:
return 18;
default:
BUG();
}
}
const char *
keystr (u32 *keyid)
{
static char keyid_str[KEYID_STR_SIZE];
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
if (format == KF_NONE)
format = KF_LONG;
return format_keyid (keyid, format, keyid_str, sizeof (keyid_str));
}
/* This function returns the key id of the main and possible the
* subkey as one string. It is used by error messages. */
const char *
keystr_with_sub (u32 *main_kid, u32 *sub_kid)
{
static char buffer[KEYID_STR_SIZE+1+KEYID_STR_SIZE];
char *p;
int format = opt.keyid_format;
if (format == KF_NONE)
format = KF_LONG;
format_keyid (main_kid, format, buffer, KEYID_STR_SIZE);
if (sub_kid)
{
p = buffer + strlen (buffer);
*p++ = '/';
format_keyid (sub_kid, format, p, KEYID_STR_SIZE);
}
return buffer;
}
const char *
keystr_from_pk(PKT_public_key *pk)
{
keyid_from_pk(pk,NULL);
return keystr(pk->keyid);
}
const char *
keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk)
{
keyid_from_pk (main_pk, NULL);
if (sub_pk)
keyid_from_pk (sub_pk, NULL);
return keystr_with_sub (main_pk->keyid, sub_pk? sub_pk->keyid:NULL);
}
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *
pk_keyid_str (PKT_public_key *pk)
{
return keystr (pk_keyid (pk));
}
const char *
keystr_from_desc(KEYDB_SEARCH_DESC *desc)
{
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
case KEYDB_SEARCH_MODE_SHORT_KID:
return keystr(desc->u.kid);
case KEYDB_SEARCH_MODE_FPR20:
{
u32 keyid[2];
keyid[0] = buf32_to_u32 (desc->u.fpr+12);
keyid[1] = buf32_to_u32 (desc->u.fpr+16);
return keystr(keyid);
}
case KEYDB_SEARCH_MODE_FPR16:
return "?v3 fpr?";
default:
BUG();
}
}
/*
* Get the keyid from the public key and put it into keyid
* if this is not NULL. Return the 32 low bits of the keyid.
*/
u32
keyid_from_pk (PKT_public_key *pk, u32 *keyid)
{
u32 lowbits;
u32 dummy_keyid[2];
if (!keyid)
keyid = dummy_keyid;
if( pk->keyid[0] || pk->keyid[1] )
{
keyid[0] = pk->keyid[0];
keyid[1] = pk->keyid[1];
lowbits = keyid[1];
}
else
{
const byte *dp;
gcry_md_hd_t md;
md = do_fingerprint_md(pk);
if(md)
{
dp = gcry_md_read ( md, 0 );
keyid[0] = buf32_to_u32 (dp+12);
keyid[1] = buf32_to_u32 (dp+16);
lowbits = keyid[1];
gcry_md_close (md);
pk->keyid[0] = keyid[0];
pk->keyid[1] = keyid[1];
}
else
pk->keyid[0]=pk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF;
}
return lowbits;
}
/*
* Get the keyid from the fingerprint. This function is simple for most
* keys, but has to do a keylookup for old stayle keys.
*/
u32
keyid_from_fingerprint( const byte *fprint, size_t fprint_len, u32 *keyid )
{
u32 dummy_keyid[2];
if( !keyid )
keyid = dummy_keyid;
if (fprint_len != 20)
{
/* This is special as we have to lookup the key first. */
PKT_public_key pk;
int rc;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_byfprint (&pk, NULL, fprint, fprint_len);
if( rc )
{
log_error("Oops: keyid_from_fingerprint: no pubkey\n");
keyid[0] = 0;
keyid[1] = 0;
}
else
keyid_from_pk (&pk, keyid);
}
else
{
const byte *dp = fprint;
keyid[0] = buf32_to_u32 (dp+12);
keyid[1] = buf32_to_u32 (dp+16);
}
return keyid[1];
}
u32
keyid_from_sig (PKT_signature *sig, u32 *keyid)
{
if( keyid )
{
keyid[0] = sig->keyid[0];
keyid[1] = sig->keyid[1];
}
return sig->keyid[1];
}
byte *
namehash_from_uid (PKT_user_id *uid)
{
if (!uid->namehash)
{
uid->namehash = xmalloc (20);
if (uid->attrib_data)
rmd160_hash_buffer (uid->namehash, uid->attrib_data, uid->attrib_len);
else
rmd160_hash_buffer (uid->namehash, uid->name, uid->len);
}
return uid->namehash;
}
/*
* Return the number of bits used in PK.
*/
unsigned int
nbits_from_pk (PKT_public_key *pk)
{
return pubkey_nbits (pk->pubkey_algo, pk->pkey);
}
static const char *
mk_datestr (char *buffer, time_t atime)
{
struct tm *tp;
if (IS_INVALID_TIME_T (atime))
strcpy (buffer, "????" "-??" "-??"); /* Mark this as invalid. */
else
{
tp = gmtime (&atime);
sprintf (buffer,"%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/*
* return a string with the creation date of the pk
* Note: this is alloced in a static buffer.
* Format is: yyyy-mm-dd
*/
const char *
datestr_from_pk (PKT_public_key *pk)
{
static char buffer[11+5];
time_t atime = pk->timestamp;
return mk_datestr (buffer, atime);
}
const char *
datestr_from_sig (PKT_signature *sig )
{
static char buffer[11+5];
time_t atime = sig->timestamp;
return mk_datestr (buffer, atime);
}
const char *
expirestr_from_pk (PKT_public_key *pk)
{
static char buffer[11+5];
time_t atime;
if (!pk->expiredate)
return _("never ");
atime = pk->expiredate;
return mk_datestr (buffer, atime);
}
const char *
expirestr_from_sig (PKT_signature *sig)
{
static char buffer[11+5];
time_t atime;
if (!sig->expiredate)
return _("never ");
atime=sig->expiredate;
return mk_datestr (buffer, atime);
}
const char *
revokestr_from_pk( PKT_public_key *pk )
{
static char buffer[11+5];
time_t atime;
if(!pk->revoked.date)
return _("never ");
atime=pk->revoked.date;
return mk_datestr (buffer, atime);
}
const char *
usagestr_from_pk (PKT_public_key *pk, int fill)
{
static char buffer[10];
int i = 0;
unsigned int use = pk->pubkey_usage;
if ( use & PUBKEY_USAGE_SIG )
buffer[i++] = 'S';
if ( use & PUBKEY_USAGE_CERT )
buffer[i++] = 'C';
if ( use & PUBKEY_USAGE_ENC )
buffer[i++] = 'E';
if ( (use & PUBKEY_USAGE_AUTH) )
buffer[i++] = 'A';
while (fill && i < 4)
buffer[i++] = ' ';
buffer[i] = 0;
return buffer;
}
const char *
colon_strtime (u32 t)
{
static char buf[20];
if (!t)
return "";
snprintf (buf, sizeof buf, "%lu", (ulong)t);
return buf;
}
const char *
colon_datestr_from_pk (PKT_public_key *pk)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)pk->timestamp);
return buf;
}
const char *
colon_datestr_from_sig (PKT_signature *sig)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)sig->timestamp);
return buf;
}
const char *
colon_expirestr_from_sig (PKT_signature *sig)
{
static char buf[20];
if (!sig->expiredate)
return "";
snprintf (buf, sizeof buf,"%lu", (ulong)sig->expiredate);
return buf;
}
/*
* Return a byte array with the fingerprint for the given PK/SK
* The length of the array is returned in ret_len. Caller must free
* the array or provide an array of length MAX_FINGERPRINT_LEN.
*/
byte *
fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len)
{
const byte *dp;
size_t len;
gcry_md_hd_t md;
md = do_fingerprint_md(pk);
dp = gcry_md_read( md, 0 );
len = gcry_md_get_algo_dlen (gcry_md_get_algo (md));
log_assert( len <= MAX_FINGERPRINT_LEN );
if (!array)
array = xmalloc ( len );
memcpy (array, dp, len );
pk->keyid[0] = buf32_to_u32 (dp+12);
pk->keyid[1] = buf32_to_u32 (dp+16);
gcry_md_close( md);
if (ret_len)
*ret_len = len;
return array;
}
/* Return an allocated buffer with the fingerprint of PK formatted as
a plain hexstring. If BUFFER is NULL the result is a malloc'd
string. If BUFFER is not NULL the result will be copied into this
buffer. In the latter case BUFLEN describes the length of the
buffer; if this is too short the function terminates the process.
Returns a malloc'ed string or BUFFER. A suitable length for BUFFER
is (2*MAX_FINGERPRINT_LEN + 1). */
char *
hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen)
{
unsigned char fpr[MAX_FINGERPRINT_LEN];
size_t len;
fingerprint_from_pk (pk, fpr, &len);
if (!buffer)
buffer = xmalloc (2 * len + 1);
else if (buflen < 2*len+1)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
bin2hex (fpr, len, buffer);
return buffer;
}
/* Pretty print a hex fingerprint. If BUFFER is NULL the result is a
malloc'd string. If BUFFER is not NULL the result will be copied
into this buffer. In the latter case BUFLEN describes the length
of the buffer; if this is too short the function terminates the
process. Returns a malloc'ed string or BUFFER. A suitable length
for BUFFER is (MAX_FORMATTED_FINGERPRINT_LEN + 1). */
char *
format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen)
{
int hexlen = strlen (fingerprint);
int space;
int i, j;
if (hexlen == 40) /* v4 fingerprint */
{
space = (/* The characters and the NUL. */
40 + 1
/* After every fourth character, we add a space (except
the last). */
+ 40 / 4 - 1
/* Half way through we add a second space. */
+ 1);
}
else /* Other fingerprint versions - print as is. */
{
space = hexlen + 1;
}
if (!buffer)
buffer = xmalloc (space);
else if (buflen < space)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
if (hexlen == 40) /* v4 fingerprint */
{
for (i = 0, j = 0; i < 40; i ++)
{
if (i && i % 4 == 0)
buffer[j ++] = ' ';
if (i == 40 / 2)
buffer[j ++] = ' ';
buffer[j ++] = fingerprint[i];
}
buffer[j ++] = 0;
log_assert (j == space);
}
else
{
strcpy (buffer, fingerprint);
}
return buffer;
}
/* Return the so called KEYGRIP which is the SHA-1 hash of the public
key parameters expressed as an canoncial encoded S-Exp. ARRAY must
be 20 bytes long. Returns 0 on success or an error code. */
gpg_error_t
keygrip_from_pk (PKT_public_key *pk, unsigned char *array)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
if (DBG_PACKET)
log_debug ("get_keygrip for public key\n");
switch (pk->pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1],
pk->pkey[2], pk->pkey[3]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1], pk->pkey[2]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_S:
case GCRY_PK_RSA_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pk->pkey[0], pk->pkey[1]);
break;
case PUBKEY_ALGO_EDDSA:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
if (!curve)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_build (&s_pkey, NULL,
pk->pubkey_algo == PUBKEY_ALGO_EDDSA?
"(public-key(ecc(curve%s)(flags eddsa)(q%m)))":
(pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& openpgp_oid_is_cv25519 (pk->pkey[0]))?
"(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))":
"(public-key(ecc(curve%s)(q%m)))",
curve, pk->pkey[1]);
xfree (curve);
}
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (err)
return err;
if (!gcry_pk_get_keygrip (s_pkey, array))
{
log_info ("error computing keygrip\n");
memset (array, 0, 20);
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
if (DBG_PACKET)
log_printhex ("keygrip=", array, 20);
/* FIXME: Save the keygrip in PK. */
}
gcry_sexp_release (s_pkey);
return err;
}
/* Store an allocated buffer with the keygrip of PK encoded as a
hexstring at r_GRIP. Returns 0 on success. */
gpg_error_t
hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip)
{
gpg_error_t err;
unsigned char grip[20];
*r_grip = NULL;
err = keygrip_from_pk (pk, grip);
if (!err)
{
char * buf = xtrymalloc (20*2+1);
if (!buf)
err = gpg_error_from_syserror ();
else
{
bin2hex (grip, 20, buf);
*r_grip = buf;
}
}
return err;
}
diff --git a/g10/keylist.c b/g10/keylist.c
index 2684f59db..93b5ee6f6 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -1,1922 +1,1922 @@
/* keylist.c - Print information about OpenPGP keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2008, 2010, 2012 Free Software Foundation, Inc.
* Copyright (C) 2013, 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
#include "photoid.h"
-#include "util.h"
-#include "ttyio.h"
+#include "../common/util.h"
+#include "../common/ttyio.h"
#include "trustdb.h"
#include "main.h"
-#include "i18n.h"
-#include "status.h"
+#include "../common/i18n.h"
+#include "../common/status.h"
#include "call-agent.h"
-#include "mbox-util.h"
-#include "zb32.h"
+#include "../common/mbox-util.h"
+#include "../common/zb32.h"
#include "tofu.h"
static void list_all (ctrl_t, int, int);
static void list_one (ctrl_t ctrl,
strlist_t names, int secret, int mark_secret);
static void locate_one (ctrl_t ctrl, strlist_t names);
static void print_card_serialno (const char *serialno);
struct keylist_context
{
int check_sigs; /* If set signatures shall be verified. */
int good_sigs; /* Counter used if CHECK_SIGS is set. */
int inv_sigs; /* Counter used if CHECK_SIGS is set. */
int no_key; /* Counter used if CHECK_SIGS is set. */
int oth_err; /* Counter used if CHECK_SIGS is set. */
int no_validity; /* Do not show validity. */
};
static void list_keyblock (ctrl_t ctrl,
kbnode_t keyblock, int secret, int has_secret,
int fpr, struct keylist_context *listctx);
/* The stream used to write attribute packets to. */
static estream_t attrib_fp;
/* Release resources from a keylist context. */
static void
keylist_context_release (struct keylist_context *listctx)
{
(void)listctx; /* Nothing to release. */
}
/* List the keys. If list is NULL, all available keys are listed.
With LOCATE_MODE set the locate algorithm is used to find a
key. */
void
public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode)
{
#ifndef NO_TRUST_MODELS
if (opt.with_colons)
{
byte trust_model, marginals, completes, cert_depth, min_cert_level;
ulong created, nextcheck;
read_trust_options (&trust_model, &created, &nextcheck,
&marginals, &completes, &cert_depth, &min_cert_level);
es_fprintf (es_stdout, "tru:");
if (nextcheck && nextcheck <= make_timestamp ())
es_fprintf (es_stdout, "o");
if (trust_model != opt.trust_model)
es_fprintf (es_stdout, "t");
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
if (marginals != opt.marginals_needed)
es_fprintf (es_stdout, "m");
if (completes != opt.completes_needed)
es_fprintf (es_stdout, "c");
if (cert_depth != opt.max_cert_depth)
es_fprintf (es_stdout, "d");
if (min_cert_level != opt.min_cert_level)
es_fprintf (es_stdout, "l");
}
es_fprintf (es_stdout, ":%d:%lu:%lu", trust_model, created, nextcheck);
/* Only show marginals, completes, and cert_depth in the classic
or PGP trust models since they are not meaningful
otherwise. */
if (trust_model == TM_PGP || trust_model == TM_CLASSIC)
es_fprintf (es_stdout, ":%d:%d:%d", marginals, completes, cert_depth);
es_fprintf (es_stdout, "\n");
}
#endif /*!NO_TRUST_MODELS*/
/* We need to do the stale check right here because it might need to
update the keyring while we already have the keyring open. This
is very bad for W32 because of a sharing violation. For real OSes
it might lead to false results if we are later listing a keyring
which is associated with the inode of a deleted file. */
check_trustdb_stale (ctrl);
#ifdef USE_TOFU
tofu_begin_batch_update (ctrl);
#endif
if (locate_mode)
locate_one (ctrl, list);
else if (!list)
list_all (ctrl, 0, opt.with_secret);
else
list_one (ctrl, list, 0, opt.with_secret);
#ifdef USE_TOFU
tofu_end_batch_update (ctrl);
#endif
}
void
secret_key_list (ctrl_t ctrl, strlist_t list)
{
(void)ctrl;
check_trustdb_stale (ctrl);
if (!list)
list_all (ctrl, 1, 0);
else /* List by user id */
list_one (ctrl, list, 1, 0);
}
char *
format_seckey_info (PKT_public_key *pk)
{
u32 keyid[2];
char *p;
char pkstrbuf[PUBKEY_STRING_SIZE];
char *info;
keyid_from_pk (pk, keyid);
p = get_user_id_native (keyid);
info = xtryasprintf ("sec %s/%s %s %s",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (keyid), datestr_from_pk (pk), p);
xfree (p);
return info;
}
void
print_seckey_info (PKT_public_key *pk)
{
char *p = format_seckey_info (pk);
tty_printf ("\n%s\n", p);
xfree (p);
}
/* Print information about the public key. With FP passed as NULL,
the tty output interface is used, otherwise output is directted to
the given stream. */
void
print_pubkey_info (estream_t fp, PKT_public_key *pk)
{
u32 keyid[2];
char *p;
char pkstrbuf[PUBKEY_STRING_SIZE];
keyid_from_pk (pk, keyid);
/* If the pk was chosen by a particular user ID, that is the one to
print. */
if (pk->user_id)
p = utf8_to_native (pk->user_id->name, pk->user_id->len, 0);
else
p = get_user_id_native (keyid);
if (fp)
tty_printf ("\n");
tty_fprintf (fp, "%s %s/%s %s %s\n",
pk->flags.primary? "pub":"sub",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (keyid), datestr_from_pk (pk), p);
xfree (p);
}
/* Print basic information of a secret key including the card serial
number information. */
#ifdef ENABLE_CARD_SUPPORT
void
print_card_key_info (estream_t fp, kbnode_t keyblock)
{
kbnode_t node;
char *hexgrip;
char *serialno;
int s2k_char;
char pkstrbuf[PUBKEY_STRING_SIZE];
int indent;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
int rc;
PKT_public_key *pk = node->pkt->pkt.public_key;
serialno = NULL;
rc = hexkeygrip_from_pk (pk, &hexgrip);
if (rc)
{
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
s2k_char = '?';
}
else if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
s2k_char = serialno? '>':' ';
else
s2k_char = '#'; /* Key not found. */
tty_fprintf (fp, "%s%c %s/%s %n",
node->pkt->pkttype == PKT_PUBLIC_KEY ? "sec" : "ssb",
s2k_char,
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk),
&indent);
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
if (serialno)
{
tty_fprintf (fp, "\n%*s%s", indent, "", _("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s", 4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s", serialno);
}
tty_fprintf (fp, "\n");
xfree (hexgrip);
xfree (serialno);
}
}
}
#endif /*ENABLE_CARD_SUPPORT*/
/* Flags = 0x01 hashed 0x02 critical. */
static void
status_one_subpacket (sigsubpkttype_t type, size_t len, int flags,
const byte * buf)
{
char status[40];
/* Don't print these. */
if (len > 256)
return;
snprintf (status, sizeof status,
"%d %u %u ", type, flags, (unsigned int) len);
write_status_text_and_buffer (STATUS_SIG_SUBPACKET, status, buf, len, 0);
}
/* Print a policy URL. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*/
void
show_policy_url (PKT_signature * sig, int indent, int mode)
{
const byte *p;
size_t len;
int seq = 0, crit;
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
while ((p =
enum_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, &len, &seq, &crit)))
{
if (mode != 2)
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (crit)
str = _("Critical signature policy: ");
else
str = _("Signature policy: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (fp, "%s", str);
tty_print_utf8_string2 (fp, p, len, 0);
tty_fprintf (fp, "\n");
}
if (mode > 0)
write_status_buffer (STATUS_POLICY_URL, p, len, 0);
}
}
/* Print a keyserver URL. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*/
void
show_keyserver_url (PKT_signature * sig, int indent, int mode)
{
const byte *p;
size_t len;
int seq = 0, crit;
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
while ((p =
enum_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &len, &seq,
&crit)))
{
if (mode != 2)
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (crit)
str = _("Critical preferred keyserver: ");
else
str = _("Preferred keyserver: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (es_stdout, "%s", str);
tty_print_utf8_string2 (fp, p, len, 0);
tty_fprintf (fp, "\n");
}
if (mode > 0)
status_one_subpacket (SIGSUBPKT_PREF_KS, len,
(crit ? 0x02 : 0) | 0x01, p);
}
}
/* Print notation data. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*
* Defined bits in WHICH:
* 1 - standard notations
* 2 - user notations
*/
void
show_notation (PKT_signature * sig, int indent, int mode, int which)
{
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
notation_t nd, notations;
if (which == 0)
which = 3;
notations = sig_to_notation (sig);
/* There may be multiple notations in the same sig. */
for (nd = notations; nd; nd = nd->next)
{
if (mode != 2)
{
int has_at = !!strchr (nd->name, '@');
if ((which & 1 && !has_at) || (which & 2 && has_at))
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (nd->flags.critical)
str = _("Critical signature notation: ");
else
str = _("Signature notation: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (es_stdout, "%s", str);
/* This is all UTF8 */
tty_print_utf8_string2 (fp, nd->name, strlen (nd->name), 0);
tty_fprintf (fp, "=");
tty_print_utf8_string2 (fp, nd->value, strlen (nd->value), 0);
/* (We need to use log_printf so that the next call to a
log function does not insert an extra LF.) */
if (mode > 0)
log_printf ("\n");
else
tty_fprintf (fp, "\n");
}
}
if (mode > 0)
{
write_status_buffer (STATUS_NOTATION_NAME,
nd->name, strlen (nd->name), 0);
if (nd->flags.critical || nd->flags.human)
write_status_text (STATUS_NOTATION_FLAGS,
nd->flags.critical && nd->flags.human? "1 1" :
nd->flags.critical? "1 0" : "0 1");
write_status_buffer (STATUS_NOTATION_DATA,
nd->value, strlen (nd->value), 50);
}
}
free_notation (notations);
}
static void
print_signature_stats (struct keylist_context *s)
{
if (!s->check_sigs)
return; /* Signature checking was not requested. */
if (s->good_sigs)
log_info (ngettext("%d good signature\n",
"%d good signatures\n", s->good_sigs), s->good_sigs);
if (s->inv_sigs)
log_info (ngettext("%d bad signature\n",
"%d bad signatures\n", s->inv_sigs), s->inv_sigs);
if (s->no_key)
log_info (ngettext("%d signature not checked due to a missing key\n",
"%d signatures not checked due to missing keys\n",
s->no_key), s->no_key);
if (s->oth_err)
log_info (ngettext("%d signature not checked due to an error\n",
"%d signatures not checked due to errors\n",
s->oth_err), s->oth_err);
}
/* List all keys. If SECRET is true only secret keys are listed. If
MARK_SECRET is true secret keys are indicated in a public key
listing. */
static void
list_all (ctrl_t ctrl, int secret, int mark_secret)
{
KEYDB_HANDLE hd;
KBNODE keyblock = NULL;
int rc = 0;
int any_secret;
const char *lastresname, *resname;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (opt.check_sigs)
listctx.check_sigs = 1;
hd = keydb_new ();
if (!hd)
rc = gpg_error_from_syserror ();
else
rc = keydb_search_first (hd);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search_first failed: %s\n", gpg_strerror (rc));
goto leave;
}
lastresname = NULL;
do
{
rc = keydb_get_keyblock (hd, &keyblock);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
continue; /* Skip legacy keys. */
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (secret || mark_secret)
any_secret = !agent_probe_any_secret_key (NULL, keyblock);
else
any_secret = 0;
if (secret && !any_secret)
; /* Secret key listing requested but this isn't one. */
else
{
if (!opt.with_colons)
{
resname = keydb_get_resource_name (hd);
if (lastresname != resname)
{
int i;
es_fprintf (es_stdout, "%s\n", resname);
for (i = strlen (resname); i; i--)
es_putc ('-', es_stdout);
es_putc ('\n', es_stdout);
lastresname = resname;
}
}
merge_keys_and_selfsig (keyblock);
list_keyblock (ctrl, keyblock, secret, any_secret, opt.fingerprint,
&listctx);
}
release_kbnode (keyblock);
keyblock = NULL;
}
while (!(rc = keydb_search_next (hd)));
es_fflush (es_stdout);
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
if (keydb_get_skipped_counter (hd))
log_info (ngettext("Warning: %lu key skipped due to its large size\n",
"Warning: %lu keys skipped due to their large sizes\n",
keydb_get_skipped_counter (hd)),
keydb_get_skipped_counter (hd));
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
leave:
keylist_context_release (&listctx);
release_kbnode (keyblock);
keydb_release (hd);
}
static void
list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret)
{
int rc = 0;
KBNODE keyblock = NULL;
GETKEY_CTX ctx;
const char *resname;
const char *keyring_str = _("Keyring");
int i;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (!secret && opt.check_sigs)
listctx.check_sigs = 1;
/* fixme: using the bynames function has the disadvantage that we
* don't know wether one of the names given was not found. OTOH,
* this function has the advantage to list the names in the
* sequence as defined by the keyDB and does not duplicate
* outputs. A solution could be do test whether all given have
* been listed (this needs a way to use the keyDB search
* functions) or to have the search function return indicators for
* found names. Yet another way is to use the keydb search
* facilities directly. */
rc = getkey_bynames (&ctx, NULL, names, secret, &keyblock);
if (rc)
{
log_error ("error reading key: %s\n", gpg_strerror (rc));
getkey_end (ctx);
return;
}
do
{
if ((opt.list_options & LIST_SHOW_KEYRING) && !opt.with_colons)
{
resname = keydb_get_resource_name (get_ctx_handle (ctx));
es_fprintf (es_stdout, "%s: %s\n", keyring_str, resname);
for (i = strlen (resname) + strlen (keyring_str) + 2; i; i--)
es_putc ('-', es_stdout);
es_putc ('\n', es_stdout);
}
list_keyblock (ctrl,
keyblock, secret, mark_secret, opt.fingerprint, &listctx);
release_kbnode (keyblock);
}
while (!getkey_next (ctx, NULL, &keyblock));
getkey_end (ctx);
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
keylist_context_release (&listctx);
}
static void
locate_one (ctrl_t ctrl, strlist_t names)
{
int rc = 0;
strlist_t sl;
GETKEY_CTX ctx = NULL;
KBNODE keyblock = NULL;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (opt.check_sigs)
listctx.check_sigs = 1;
for (sl = names; sl; sl = sl->next)
{
rc = get_best_pubkey_byname (ctrl, &ctx, NULL, sl->d, &keyblock, 1, 0);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY)
log_error ("error reading key: %s\n", gpg_strerror (rc));
else if (opt.verbose)
log_info (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (rc));
}
else
{
do
{
list_keyblock (ctrl, keyblock, 0, 0, opt.fingerprint, &listctx);
release_kbnode (keyblock);
}
while (ctx && !getkey_next (ctx, NULL, &keyblock));
getkey_end (ctx);
ctx = NULL;
}
}
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
keylist_context_release (&listctx);
}
static void
print_key_data (PKT_public_key * pk)
{
int n = pk ? pubkey_get_npkey (pk->pubkey_algo) : 0;
int i;
for (i = 0; i < n; i++)
{
es_fprintf (es_stdout, "pkd:%d:%u:", i, mpi_get_nbits (pk->pkey[i]));
mpi_print (es_stdout, pk->pkey[i], 1);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
}
static void
print_capabilities (PKT_public_key *pk, KBNODE keyblock)
{
unsigned int use = pk->pubkey_usage;
int c_printed = 0;
if (use & PUBKEY_USAGE_ENC)
es_putc ('e', es_stdout);
if (use & PUBKEY_USAGE_SIG)
{
es_putc ('s', es_stdout);
if (pk->flags.primary)
{
es_putc ('c', es_stdout);
/* The PUBKEY_USAGE_CERT flag was introduced later and we
used to always print 'c' for a primary key. To avoid any
regression here we better track whether we printed 'c'
already. */
c_printed = 1;
}
}
if ((use & PUBKEY_USAGE_CERT) && !c_printed)
es_putc ('c', es_stdout);
if ((use & PUBKEY_USAGE_AUTH))
es_putc ('a', es_stdout);
if ((use & PUBKEY_USAGE_UNKNOWN))
es_putc ('?', es_stdout);
if (keyblock)
{
/* Figure out the usable capabilities. */
KBNODE k;
int enc = 0, sign = 0, cert = 0, auth = 0, disabled = 0;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = k->pkt->pkt.public_key;
if (pk->flags.primary)
disabled = pk_is_disabled (pk);
if (pk->flags.valid && !pk->flags.revoked && !pk->has_expired)
{
if (pk->pubkey_usage & PUBKEY_USAGE_ENC)
enc = 1;
if (pk->pubkey_usage & PUBKEY_USAGE_SIG)
{
sign = 1;
if (pk->flags.primary)
cert = 1;
}
if (pk->pubkey_usage & PUBKEY_USAGE_CERT)
cert = 1;
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
auth = 1;
}
}
}
if (enc)
es_putc ('E', es_stdout);
if (sign)
es_putc ('S', es_stdout);
if (cert)
es_putc ('C', es_stdout);
if (auth)
es_putc ('A', es_stdout);
if (disabled)
es_putc ('D', es_stdout);
}
es_putc (':', es_stdout);
}
/* FLAGS: 0x01 hashed
0x02 critical */
static void
print_one_subpacket (sigsubpkttype_t type, size_t len, int flags,
const byte * buf)
{
size_t i;
es_fprintf (es_stdout, "spk:%d:%u:%u:", type, flags, (unsigned int) len);
for (i = 0; i < len; i++)
{
/* printable ascii other than : and % */
if (buf[i] >= 32 && buf[i] <= 126 && buf[i] != ':' && buf[i] != '%')
es_fprintf (es_stdout, "%c", buf[i]);
else
es_fprintf (es_stdout, "%%%02X", buf[i]);
}
es_fprintf (es_stdout, "\n");
}
void
print_subpackets_colon (PKT_signature * sig)
{
byte *i;
log_assert (opt.show_subpackets);
for (i = opt.show_subpackets; *i; i++)
{
const byte *p;
size_t len;
int seq, crit;
seq = 0;
while ((p = enum_sig_subpkt (sig->hashed, *i, &len, &seq, &crit)))
print_one_subpacket (*i, len, 0x01 | (crit ? 0x02 : 0), p);
seq = 0;
while ((p = enum_sig_subpkt (sig->unhashed, *i, &len, &seq, &crit)))
print_one_subpacket (*i, len, 0x00 | (crit ? 0x02 : 0), p);
}
}
void
dump_attribs (const PKT_user_id *uid, PKT_public_key *pk)
{
int i;
if (!attrib_fp)
return;
for (i = 0; i < uid->numattribs; i++)
{
if (is_status_enabled ())
{
byte array[MAX_FINGERPRINT_LEN], *p;
char buf[(MAX_FINGERPRINT_LEN * 2) + 90];
size_t j, n;
if (!pk)
BUG ();
fingerprint_from_pk (pk, array, &n);
p = array;
for (j = 0; j < n; j++, p++)
sprintf (buf + 2 * j, "%02X", *p);
sprintf (buf + strlen (buf), " %lu %u %u %u %lu %lu %u",
(ulong) uid->attribs[i].len, uid->attribs[i].type, i + 1,
uid->numattribs, (ulong) uid->created,
(ulong) uid->expiredate,
((uid->is_primary ? 0x01 : 0) | (uid->
is_revoked ? 0x02 : 0) |
(uid->is_expired ? 0x04 : 0)));
write_status_text (STATUS_ATTRIBUTE, buf);
}
es_fwrite (uid->attribs[i].data, uid->attribs[i].len, 1, attrib_fp);
es_fflush (attrib_fp);
}
}
static void
list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr,
struct keylist_context *listctx)
{
int rc;
KBNODE kbctx;
KBNODE node;
PKT_public_key *pk;
int skip_sigs = 0;
char *hexgrip = NULL;
char *serialno = NULL;
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; key lost!\n");
dump_kbnode (keyblock);
return;
}
pk = node->pkt->pkt.public_key;
if (secret || opt.with_keygrip)
{
rc = hexkeygrip_from_pk (pk, &hexgrip);
if (rc)
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
}
if (secret)
{
/* Encode some info about the secret key in SECRET. */
if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
secret = serialno? 3 : 1;
else
secret = 2; /* Key not found. */
}
if (!listctx->no_validity)
check_trustdb_stale (ctrl);
/* Print the "pub" line and in KF_NONE mode the fingerprint. */
print_key_line (es_stdout, pk, secret);
if (fpr)
print_fingerprint (NULL, pk, 0);
if (opt.with_keygrip && hexgrip)
es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip);
if (serialno)
print_card_serialno (serialno);
if (opt.with_key_data)
print_key_data (pk);
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
int indent;
int kl = opt.keyid_format == KF_NONE? 10 : keystrlen ();
if ((uid->is_expired || uid->is_revoked)
&& !(opt.list_options & LIST_SHOW_UNUSABLE_UIDS))
{
skip_sigs = 1;
continue;
}
else
skip_sigs = 0;
if (attrib_fp && uid->attrib_data != NULL)
dump_attribs (uid, pk);
if ((uid->is_revoked || uid->is_expired)
|| ((opt.list_options & LIST_SHOW_UID_VALIDITY)
&& !listctx->no_validity))
{
const char *validity;
validity = uid_trust_string_fixed (ctrl, pk, uid);
indent = ((kl + (opt.legacy_list_mode? 9:11))
- atoi (uid_trust_string_fixed (ctrl, NULL, NULL)));
if (indent < 0 || indent > 40)
indent = 0;
es_fprintf (es_stdout, "uid%*s%s ", indent, "", validity);
}
else
{
indent = kl + (opt.legacy_list_mode? 10:12);
es_fprintf (es_stdout, "uid%*s", indent, "");
}
print_utf8_buffer (es_stdout, uid->name, uid->len);
es_putc ('\n', es_stdout);
if (opt.with_wkd_hash)
{
char *mbox, *hash, *p;
char hashbuf[32];
mbox = mailbox_from_userid (uid->name);
if (mbox && (p = strchr (mbox, '@')))
{
*p++ = 0;
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf,
mbox, strlen (mbox));
hash = zb32_encode (hashbuf, 8*20);
if (hash)
{
es_fprintf (es_stdout, " %*s%s@%s\n",
indent, "", hash, p);
xfree (hash);
}
}
xfree (mbox);
}
if ((opt.list_options & LIST_SHOW_PHOTOS) && uid->attribs != NULL)
show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk2 = node->pkt->pkt.public_key;
if ((pk2->flags.revoked || pk2->has_expired)
&& !(opt.list_options & LIST_SHOW_UNUSABLE_SUBKEYS))
{
skip_sigs = 1;
continue;
}
else
skip_sigs = 0;
xfree (serialno); serialno = NULL;
xfree (hexgrip); hexgrip = NULL;
if (secret || opt.with_keygrip)
{
rc = hexkeygrip_from_pk (pk2, &hexgrip);
if (rc)
log_error ("error computing a keygrip: %s\n",
gpg_strerror (rc));
}
if (secret)
{
if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
secret = serialno? 3 : 1;
else
secret = '2'; /* Key not found. */
}
/* Print the "sub" line. */
print_key_line (es_stdout, pk2, secret);
if (fpr > 1 || opt.with_subkey_fingerprint)
{
print_fingerprint (NULL, pk2, 0);
if (serialno)
print_card_serialno (serialno);
}
if (opt.with_keygrip && hexgrip)
es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk2);
}
else if (opt.list_sigs
&& node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
char *sigstr;
if (listctx->check_sigs)
{
rc = check_key_signature (keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0:
listctx->good_sigs++;
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
listctx->inv_sigs++;
sigrc = '-';
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
listctx->no_key++;
continue;
default:
listctx->oth_err++;
sigrc = '%';
break;
}
/* TODO: Make sure a cached sig record here still has
the pk that issued it. See also
keyedit.c:print_and_check_one_sig */
}
else
{
rc = 0;
sigrc = ' ';
}
if (sig->sig_class == 0x20 || sig->sig_class == 0x28
|| sig->sig_class == 0x30)
sigstr = "rev";
else if ((sig->sig_class & ~3) == 0x10)
sigstr = "sig";
else if (sig->sig_class == 0x18)
sigstr = "sig";
else if (sig->sig_class == 0x1F)
sigstr = "sig";
else
{
es_fprintf (es_stdout, "sig "
"[unexpected signature class 0x%02x]\n",
sig->sig_class);
continue;
}
es_fputs (sigstr, es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (opt.list_options & LIST_SHOW_SIG_EXPIRE)
es_fprintf (es_stdout, " %s", expirestr_from_sig (sig));
es_fprintf (es_stdout, " ");
if (sigrc == '%')
es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (!opt.fast_list_mode)
{
size_t n;
char *p = get_user_id (sig->keyid, &n);
print_utf8_buffer (es_stdout, p, n);
xfree (p);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 :
0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
/* fixme: check or list other sigs here */
}
}
es_putc ('\n', es_stdout);
xfree (serialno);
xfree (hexgrip);
}
void
print_revokers (estream_t fp, PKT_public_key * pk)
{
/* print the revoker record */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i, j;
for (i = 0; i < pk->numrevkeys; i++)
{
byte *p;
es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid);
p = pk->revkey[i].fpr;
for (j = 0; j < 20; j++, p++)
es_fprintf (fp, "%02X", *p);
es_fprintf (fp, ":%02x%s:\n",
pk->revkey[i].class,
(pk->revkey[i].class & 0x40) ? "s" : "");
}
}
}
/* List a key in colon mode. If SECRET is true this is a secret key
record (i.e. requested via --list-secret-key). If HAS_SECRET a
secret key is available even if SECRET is not set. */
static void
list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock,
int secret, int has_secret)
{
int rc;
KBNODE kbctx;
KBNODE node;
PKT_public_key *pk;
u32 keyid[2];
int trustletter = 0;
int trustletter_print;
int ownertrust_print;
int ulti_hack = 0;
int i;
char *hexgrip_buffer = NULL;
const char *hexgrip = NULL;
char *serialno = NULL;
int stubkey;
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; key lost!\n");
dump_kbnode (keyblock);
return;
}
pk = node->pkt->pkt.public_key;
if (secret || has_secret || opt.with_keygrip || opt.with_key_data)
{
rc = hexkeygrip_from_pk (pk, &hexgrip_buffer);
if (rc)
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
/* In the error case we print an empty string so that we have a
* "grp" record for each and subkey - even if it is empty. This
* may help to prevent sync problems. */
hexgrip = hexgrip_buffer? hexgrip_buffer : "";
}
stubkey = 0;
if ((secret || has_secret)
&& agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
stubkey = 1; /* Key not found. */
keyid_from_pk (pk, keyid);
if (!pk->flags.valid)
trustletter_print = 'i';
else if (pk->flags.revoked)
trustletter_print = 'r';
else if (pk->has_expired)
trustletter_print = 'e';
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
trustletter_print = 0;
else
{
trustletter = get_validity_info (ctrl, keyblock, pk, NULL);
if (trustletter == 'u')
ulti_hack = 1;
trustletter_print = trustletter;
}
if (!opt.fast_list_mode && !opt.no_expensive_trust_checks)
ownertrust_print = get_ownertrust_info (pk, 0);
else
ownertrust_print = 0;
es_fputs (secret? "sec:":"pub:", es_stdout);
if (trustletter_print)
es_putc (trustletter_print, es_stdout);
es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s::",
nbits_from_pk (pk),
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
colon_datestr_from_pk (pk), colon_strtime (pk->expiredate));
if (ownertrust_print)
es_putc (ownertrust_print, es_stdout);
es_putc (':', es_stdout);
es_putc (':', es_stdout);
es_putc (':', es_stdout);
print_capabilities (pk, keyblock);
es_putc (':', es_stdout); /* End of field 13. */
es_putc (':', es_stdout); /* End of field 14. */
if (secret || has_secret)
{
if (stubkey)
es_putc ('#', es_stdout);
else if (serialno)
es_fputs (serialno, es_stdout);
else if (has_secret)
es_putc ('+', es_stdout);
}
es_putc (':', es_stdout); /* End of field 15. */
es_putc (':', es_stdout); /* End of field 16. */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
if (!name)
name = curve;
es_fputs (name, es_stdout);
xfree (curve);
}
es_putc (':', es_stdout); /* End of field 17. */
es_putc (':', es_stdout); /* End of field 18. */
es_putc ('\n', es_stdout);
print_revokers (es_stdout, pk);
print_fingerprint (NULL, pk, 0);
if (hexgrip)
es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk);
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
int uid_validity;
if (attrib_fp && uid->attrib_data != NULL)
dump_attribs (uid, pk);
if (uid->is_revoked)
uid_validity = 'r';
else if (uid->is_expired)
uid_validity = 'e';
else if (opt.no_expensive_trust_checks)
uid_validity = 0;
else if (ulti_hack)
uid_validity = 'u';
else
uid_validity = get_validity_info (ctrl, keyblock, pk, uid);
es_fputs (uid->attrib_data? "uat:":"uid:", es_stdout);
if (uid_validity)
es_putc (uid_validity, es_stdout);
es_fputs ("::::", es_stdout);
es_fprintf (es_stdout, "%s:", colon_strtime (uid->created));
es_fprintf (es_stdout, "%s:", colon_strtime (uid->expiredate));
namehash_from_uid (uid);
for (i = 0; i < 20; i++)
es_fprintf (es_stdout, "%02X", uid->namehash[i]);
es_fprintf (es_stdout, "::");
if (uid->attrib_data)
es_fprintf (es_stdout, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
#ifdef USE_TOFU
if (!uid->attrib_data && opt.with_tofu_info
&& (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP))
{
/* Print a "tfs" record. */
tofu_write_tfs_record (ctrl, es_stdout, pk, uid->name);
}
#endif /*USE_TOFU*/
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 keyid2[2];
PKT_public_key *pk2;
int need_hexgrip = !!hexgrip;
pk2 = node->pkt->pkt.public_key;
xfree (hexgrip_buffer); hexgrip_buffer = NULL; hexgrip = NULL;
xfree (serialno); serialno = NULL;
if (need_hexgrip
|| secret || has_secret || opt.with_keygrip || opt.with_key_data)
{
rc = hexkeygrip_from_pk (pk2, &hexgrip_buffer);
if (rc)
log_error ("error computing a keygrip: %s\n",
gpg_strerror (rc));
hexgrip = hexgrip_buffer? hexgrip_buffer : "";
}
stubkey = 0;
if ((secret||has_secret)
&& agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
stubkey = 1; /* Key not found. */
keyid_from_pk (pk2, keyid2);
es_fputs (secret? "ssb:":"sub:", es_stdout);
if (!pk2->flags.valid)
es_putc ('i', es_stdout);
else if (pk2->flags.revoked)
es_putc ('r', es_stdout);
else if (pk2->has_expired)
es_putc ('e', es_stdout);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
;
else
{
/* TRUSTLETTER should always be defined here. */
if (trustletter)
es_fprintf (es_stdout, "%c", trustletter);
}
es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s:::::",
nbits_from_pk (pk2),
pk2->pubkey_algo,
(ulong) keyid2[0], (ulong) keyid2[1],
colon_datestr_from_pk (pk2), colon_strtime (pk2->expiredate)
/* fixme: add LID and ownertrust here */
);
print_capabilities (pk2, NULL);
es_putc (':', es_stdout); /* End of field 13. */
es_putc (':', es_stdout); /* End of field 14. */
if (secret || has_secret)
{
if (stubkey)
es_putc ('#', es_stdout);
else if (serialno)
es_fputs (serialno, es_stdout);
else if (has_secret)
es_putc ('+', es_stdout);
}
es_putc (':', es_stdout); /* End of field 15. */
es_putc (':', es_stdout); /* End of field 16. */
if (pk2->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk2->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk2->pubkey_algo == PUBKEY_ALGO_ECDH)
{
char *curve = openpgp_oid_to_str (pk2->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
if (!name)
name = curve;
es_fputs (name, es_stdout);
xfree (curve);
}
es_putc (':', es_stdout); /* End of field 17. */
es_putc ('\n', es_stdout);
print_fingerprint (NULL, pk2, 0);
if (hexgrip)
es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk2);
}
else if (opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc, fprokay = 0;
char *sigstr;
size_t fplen;
byte fparray[MAX_FINGERPRINT_LEN];
char *siguid;
size_t siguidlen;
if (sig->sig_class == 0x20 || sig->sig_class == 0x28
|| sig->sig_class == 0x30)
sigstr = "rev";
else if ((sig->sig_class & ~3) == 0x10)
sigstr = "sig";
else if (sig->sig_class == 0x18)
sigstr = "sig";
else if (sig->sig_class == 0x1F)
sigstr = "sig";
else
{
es_fprintf (es_stdout, "sig::::::::::%02x%c:\n",
sig->sig_class, sig->flags.exportable ? 'x' : 'l');
continue;
}
if (opt.check_sigs)
{
PKT_public_key *signer_pk = NULL;
fflush (stdout);
if (opt.no_sig_cache)
signer_pk = xmalloc_clear (sizeof (PKT_public_key));
rc = check_key_signature2 (keyblock, node, NULL, signer_pk,
NULL, NULL, NULL);
switch (gpg_err_code (rc))
{
case 0:
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
sigrc = '-';
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
sigrc = '?';
break;
default:
sigrc = '%';
break;
}
if (opt.no_sig_cache)
{
if (!rc)
{
fingerprint_from_pk (signer_pk, fparray, &fplen);
fprokay = 1;
}
free_public_key (signer_pk);
}
}
else
{
rc = 0;
sigrc = ' ';
}
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
siguid = get_user_id (sig->keyid, &siguidlen);
else
{
siguid = NULL;
siguidlen = 0;
}
es_fputs (sigstr, es_stdout);
es_putc (':', es_stdout);
if (sigrc != ' ')
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (sig->trust_depth || sig->trust_value)
es_fprintf (es_stdout, "%d %d", sig->trust_depth, sig->trust_value);
es_fprintf (es_stdout, ":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout, sig->trust_regexp,
strlen (sig->trust_regexp), ":", NULL);
es_fprintf (es_stdout, ":");
if (sigrc == '%')
es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc));
else if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c::", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (opt.no_sig_cache && opt.check_sigs && fprokay)
{
for (i = 0; i < fplen; i++)
es_fprintf (es_stdout, "%02X", fparray[i]);
}
es_fprintf (es_stdout, ":::%d:\n", sig->digest_algo);
if (opt.show_subpackets)
print_subpackets_colon (sig);
/* fixme: check or list other sigs here */
xfree (siguid);
}
}
xfree (hexgrip_buffer);
xfree (serialno);
}
/*
* Reorder the keyblock so that the primary user ID (and not attribute
* packet) comes first. Fixme: Replace this by a generic sort
* function. */
static void
do_reorder_keyblock (KBNODE keyblock, int attr)
{
KBNODE primary = NULL, primary0 = NULL, primary2 = NULL;
KBNODE last, node;
for (node = keyblock; node; primary0 = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID &&
((attr && node->pkt->pkt.user_id->attrib_data) ||
(!attr && !node->pkt->pkt.user_id->attrib_data)) &&
node->pkt->pkt.user_id->is_primary)
{
primary = primary2 = node;
for (node = node->next; node; primary2 = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
break;
}
}
break;
}
}
if (!primary)
return; /* No primary key flag found (should not happen). */
for (last = NULL, node = keyblock; node; last = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
}
log_assert (node);
log_assert (last); /* The user ID is never the first packet. */
log_assert (primary0); /* Ditto (this is the node before primary). */
if (node == primary)
return; /* Already the first one. */
last->next = primary;
primary0->next = primary2->next;
primary2->next = node;
}
void
reorder_keyblock (KBNODE keyblock)
{
do_reorder_keyblock (keyblock, 1);
do_reorder_keyblock (keyblock, 0);
}
static void
list_keyblock (ctrl_t ctrl,
KBNODE keyblock, int secret, int has_secret, int fpr,
struct keylist_context *listctx)
{
reorder_keyblock (keyblock);
if (opt.with_colons)
list_keyblock_colon (ctrl, keyblock, secret, has_secret);
else
list_keyblock_print (ctrl, keyblock, secret, fpr, listctx);
if (secret)
es_fflush (es_stdout);
}
/* Public function used by keygen to list a keyblock. If NO_VALIDITY
* is set the validity of a key is never shown. */
void
list_keyblock_direct (ctrl_t ctrl,
kbnode_t keyblock, int secret, int has_secret, int fpr,
int no_validity)
{
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
listctx.no_validity = !!no_validity;
list_keyblock (ctrl, keyblock, secret, has_secret, fpr, &listctx);
keylist_context_release (&listctx);
}
/* Print an hex digit in ICAO spelling. */
static void
print_icao_hexdigit (estream_t fp, int c)
{
static const char *list[16] = {
"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Niner", "Alfa", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot"
};
tty_fprintf (fp, "%s", list[c&15]);
}
/*
* Function to print the finperprint.
* mode 0: as used in key listings, opt.with_colons is honored
* 1: print using log_info ()
* 2: direct use of tty
* 3: direct use of tty but only primary key.
* 4: direct use of tty but only subkey.
* 10: Same as 0 but with_colons etc is ignored.
* 20: Same as 0 but using a compact format.
*
* Modes 1 and 2 will try and print both subkey and primary key
* fingerprints. A MODE with bit 7 set is used internally. If
* OVERRIDE_FP is not NULL that stream will be used in 0 instead
* of es_stdout or instead of the TTY in modes 2 and 3.
*/
void
print_fingerprint (estream_t override_fp, PKT_public_key *pk, int mode)
{
char hexfpr[2*MAX_FINGERPRINT_LEN+1];
char *p;
size_t i;
estream_t fp;
const char *text;
int primary = 0;
int with_colons = opt.with_colons;
int with_icao = opt.with_icao_spelling;
int compact = 0;
if (mode == 10)
{
mode = 0;
with_colons = 0;
with_icao = 0;
}
else if (mode == 20)
{
mode = 0;
with_colons = 0;
compact = 1;
}
if (!opt.fingerprint && !opt.with_fingerprint
&& opt.with_subkey_fingerprint)
compact = 1;
if (pk->main_keyid[0] == pk->keyid[0]
&& pk->main_keyid[1] == pk->keyid[1])
primary = 1;
/* Just to be safe */
if ((mode & 0x80) && !primary)
{
log_error ("primary key is not really primary!\n");
return;
}
mode &= ~0x80;
if (!primary && (mode == 1 || mode == 2))
{
PKT_public_key *primary_pk = xmalloc_clear (sizeof (*primary_pk));
get_pubkey (primary_pk, pk->main_keyid);
print_fingerprint (override_fp, primary_pk, (mode | 0x80));
free_public_key (primary_pk);
}
if (mode == 1)
{
fp = log_get_stream ();
if (primary)
text = _("Primary key fingerprint:");
else
text = _(" Subkey fingerprint:");
}
else if (mode == 2)
{
fp = override_fp; /* Use tty or given stream. */
if (primary)
/* TRANSLATORS: this should fit into 24 bytes so that the
* fingerprint data is properly aligned with the user ID */
text = _(" Primary key fingerprint:");
else
text = _(" Subkey fingerprint:");
}
else if (mode == 3)
{
fp = override_fp; /* Use tty or given stream. */
text = _(" Key fingerprint =");
}
else if (mode == 4)
{
fp = override_fp; /* Use tty or given stream. */
text = _(" Subkey fingerprint:");
}
else
{
fp = override_fp? override_fp : es_stdout;
if (opt.keyid_format == KF_NONE)
{
text = " "; /* To indent ICAO spelling. */
compact = 1;
}
else
text = _(" Key fingerprint =");
}
hexfingerprint (pk, hexfpr, sizeof hexfpr);
if (with_colons && !mode)
{
es_fprintf (fp, "fpr:::::::::%s:", hexfpr);
}
else if (compact && !opt.fingerprint && !opt.with_fingerprint)
{
tty_fprintf (fp, "%*s%s", 6, "", hexfpr);
}
else
{
char fmtfpr[MAX_FORMATTED_FINGERPRINT_LEN + 1];
format_hexfingerprint (hexfpr, fmtfpr, sizeof fmtfpr);
if (compact)
tty_fprintf (fp, "%*s%s", 6, "", fmtfpr);
else
tty_fprintf (fp, "%s %s", text, fmtfpr);
}
tty_fprintf (fp, "\n");
if (!with_colons && with_icao)
{
;
tty_fprintf (fp, "%*s\"", (int)strlen(text)+1, "");
for (i = 0, p = hexfpr; *p; i++, p++)
{
if (!i)
;
else if (!(i%8))
tty_fprintf (fp, "\n%*s ", (int)strlen(text)+1, "");
else if (!(i%4))
tty_fprintf (fp, " ");
else
tty_fprintf (fp, " ");
print_icao_hexdigit (fp, xtoi_1 (p));
}
tty_fprintf (fp, "\"\n");
}
}
/* Print the serial number of an OpenPGP card if available. */
static void
print_card_serialno (const char *serialno)
{
if (!serialno)
return;
if (opt.with_colons)
return; /* Handled elsewhere. */
es_fputs (_(" Card serial no. ="), es_stdout);
es_putc (' ', es_stdout);
if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
es_fprintf (es_stdout, "%.*s %.*s", 4, serialno+16, 8, serialno+20);
}
else
es_fputs (serialno, es_stdout);
es_putc ('\n', es_stdout);
}
/* Print a public or secret (sub)key line. Example:
*
* pub dsa2048 2007-12-31 [SC] [expires: 2018-12-31]
* 80615870F5BAD690333686D0F2AD85AC1E42B367
*
* Some global options may result in a different output format. If
* SECRET is set, "sec" or "ssb" is used instead of "pub" or "sub" and
* depending on the value a flag character is shown:
*
* 1 := ' ' Regular secret key
* 2 := '#' Stub secret key
* 3 := '>' Secret key is on a token.
*/
void
print_key_line (estream_t fp, PKT_public_key *pk, int secret)
{
char pkstrbuf[PUBKEY_STRING_SIZE];
tty_fprintf (fp, "%s%c %s",
pk->flags.primary? (secret? "sec":"pub")
/**/ : (secret? "ssb":"sub"),
secret == 2? '#' : secret == 3? '>' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf));
if (opt.keyid_format != KF_NONE)
tty_fprintf (fp, "/%s", keystr_from_pk (pk));
tty_fprintf (fp, " %s", datestr_from_pk (pk));
if ((opt.list_options & LIST_SHOW_USAGE))
{
tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0));
}
if (pk->flags.revoked)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
tty_fprintf (fp, "]");
}
else if (pk->has_expired)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, "]");
}
else if (pk->expiredate)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, "]");
}
#if 0
/* I need to think about this some more. It's easy enough to
include, but it looks sort of confusing in the listing... */
if (opt.list_options & LIST_SHOW_VALIDITY)
{
int validity = get_validity (ctrl, pk, NULL, NULL, 0);
tty_fprintf (fp, " [%s]", trust_value_to_string (validity));
}
#endif
if (pk->pubkey_algo >= 100)
tty_fprintf (fp, " [experimental algorithm %d]", pk->pubkey_algo);
tty_fprintf (fp, "\n");
/* if the user hasn't explicitly asked for human-readable
fingerprints, show compact fpr of primary key: */
if (pk->flags.primary &&
!opt.fingerprint && !opt.with_fingerprint)
print_fingerprint (fp, pk, 20);
}
void
set_attrib_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
/* Fixme: Do we need to check for the log stream here? */
if (attrib_fp && attrib_fp != log_get_stream ())
es_fclose (attrib_fp);
attrib_fp = NULL;
if (fd == -1)
return;
if (! gnupg_fd_valid (fd))
log_fatal ("attribute-fd is invalid: %s\n", strerror (errno));
#ifdef HAVE_DOSISH_SYSTEM
setmode (fd, O_BINARY);
#endif
if (fd == 1)
attrib_fp = es_stdout;
else if (fd == 2)
attrib_fp = es_stderr;
else
attrib_fp = es_fdopen (fd, "wb");
if (!attrib_fp)
{
log_fatal ("can't open fd %d for attribute output: %s\n",
fd, strerror (errno));
}
last_fd = fd;
}
diff --git a/g10/keyring.c b/g10/keyring.c
index 9b7b5fd74..31f60f9fc 100644
--- a/g10/keyring.c
+++ b/g10/keyring.c
@@ -1,1785 +1,1785 @@
/* keyring.c - keyring file handling
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1997-2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "keyring.h"
#include "packet.h"
#include "keydb.h"
#include "options.h"
#include "main.h" /*for check_key_signature()*/
-#include "i18n.h"
+#include "../common/i18n.h"
#include "../kbx/keybox.h"
typedef struct keyring_resource *KR_RESOURCE;
struct keyring_resource
{
struct keyring_resource *next;
int read_only;
dotlock_t lockhd;
int is_locked;
int did_full_scan;
char fname[1];
};
typedef struct keyring_resource const * CONST_KR_RESOURCE;
static KR_RESOURCE kr_resources;
struct keyring_handle
{
CONST_KR_RESOURCE resource;
struct {
CONST_KR_RESOURCE kr;
IOBUF iobuf;
int eof;
int error;
} current;
struct {
CONST_KR_RESOURCE kr;
off_t offset;
size_t pk_no;
size_t uid_no;
unsigned int n_packets; /*used for delete and update*/
} found, saved_found;
struct {
char *name;
char *pattern;
} word_match;
};
/* The number of extant handles. */
static int active_handles;
static int do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets );
/* We keep a cache of entries that we have entered in the DB. This
includes not only public keys, but also subkeys.
Note: we'd like to keep the offset of the items that are present,
however, this doesn't work, because another concurrent GnuPG
process could modify the keyring. */
struct key_present {
struct key_present *next;
u32 kid[2];
};
/* For the hash table, we use separate chaining with linked lists.
This means that we have an array of N linked lists (buckets), which
is indexed by KEYID[1] mod N. Elements present in the keyring will
be on the list; elements not present in the keyring will not be on
the list.
Note: since the hash table stores both present and not present
information, it cannot be used until we complete a full scan of the
keyring. This is indicated by key_present_hash_ready. */
typedef struct key_present **key_present_hash_t;
static key_present_hash_t key_present_hash;
static int key_present_hash_ready;
#define KEY_PRESENT_HASH_BUCKETS 2048
/* Allocate a new value for a key present hash table. */
static struct key_present *
key_present_value_new (void)
{
struct key_present *k;
k = xmalloc_clear (sizeof *k);
return k;
}
/* Allocate a new key present hash table. */
static key_present_hash_t
key_present_hash_new (void)
{
struct key_present **tbl;
tbl = xmalloc_clear (KEY_PRESENT_HASH_BUCKETS * sizeof *tbl);
return tbl;
}
/* Return whether the value described by KID if it is in the hash
table. Otherwise, return NULL. */
static struct key_present *
key_present_hash_lookup (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return k;
return NULL;
}
/* Add the key to the hash table TBL if it is not already present. */
static void
key_present_hash_update (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
{
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return;
}
k = key_present_value_new ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))];
tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))] = k;
}
/* Add all the keys (public and subkeys) present in the keyblock to
the hash TBL. */
static void
key_present_hash_update_from_kb (key_present_hash_t tbl, KBNODE node)
{
for (; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
keyid_from_pk (node->pkt->pkt.public_key, aki);
key_present_hash_update (tbl, aki);
}
}
}
/*
* Register a filename for plain keyring files. ptr is set to a
* pointer to be used to create a handles etc, or the already-issued
* pointer if it has already been registered. The function returns 1
* if a new keyring was registered.
*/
int
keyring_register_filename (const char *fname, int read_only, void **ptr)
{
KR_RESOURCE kr;
if (active_handles)
/* There are open handles. */
BUG ();
for (kr=kr_resources; kr; kr = kr->next)
{
if (same_file_p (kr->fname, fname))
{
/* Already registered. */
if (read_only)
kr->read_only = 1;
*ptr=kr;
return 0;
}
}
kr = xmalloc (sizeof *kr + strlen (fname));
strcpy (kr->fname, fname);
kr->read_only = read_only;
kr->lockhd = NULL;
kr->is_locked = 0;
kr->did_full_scan = 0;
/* keep a list of all issued pointers */
kr->next = kr_resources;
kr_resources = kr;
/* create the offset table the first time a function here is used */
if (!key_present_hash)
key_present_hash = key_present_hash_new ();
*ptr=kr;
return 1;
}
int
keyring_is_writable (void *token)
{
KR_RESOURCE r = token;
return r? (r->read_only || !access (r->fname, W_OK)) : 0;
}
/* Create a new handle for the resource associated with TOKEN.
On error NULL is returned and ERRNO is set.
The returned handle must be released using keyring_release (). */
KEYRING_HANDLE
keyring_new (void *token)
{
KEYRING_HANDLE hd;
KR_RESOURCE resource = token;
log_assert (resource);
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return hd;
hd->resource = resource;
active_handles++;
return hd;
}
void
keyring_release (KEYRING_HANDLE hd)
{
if (!hd)
return;
log_assert (active_handles > 0);
active_handles--;
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
iobuf_close (hd->current.iobuf);
xfree (hd);
}
/* Save the current found state in HD for later retrieval by
keybox_pop_found_state. Only one state may be saved. */
void
keyring_push_found_state (KEYRING_HANDLE hd)
{
hd->saved_found = hd->found;
hd->found.kr = NULL;
}
/* Restore the saved found state in HD. */
void
keyring_pop_found_state (KEYRING_HANDLE hd)
{
hd->found = hd->saved_found;
hd->saved_found.kr = NULL;
}
const char *
keyring_get_resource_name (KEYRING_HANDLE hd)
{
if (!hd || !hd->resource)
return NULL;
return hd->resource->fname;
}
/*
* Lock the keyring with the given handle, or unlock if YES is false.
* We ignore the handle and lock all registered files.
*/
int
keyring_lock (KEYRING_HANDLE hd, int yes)
{
KR_RESOURCE kr;
int rc = 0;
(void)hd;
if (yes) {
/* first make sure the lock handles are created */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->lockhd) {
kr->lockhd = dotlock_create (kr->fname, 0);
if (!kr->lockhd) {
log_info ("can't allocate lock for '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
}
}
if (rc)
return rc;
/* and now set the locks */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (kr->is_locked)
continue;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to CloseHandle the file before we
* try to lock it. This is because another process might
* have taken the lock and is using keybox_file_rename to
* rename the base file. How if our dotlock_take below is
* waiting for the lock but we have the base file still
* open, keybox_file_rename will never succeed as we are
* in a deadlock. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0,
(char*)kr->fname);
#endif /*HAVE_W32_SYSTEM*/
if (dotlock_take (kr->lockhd, -1) ) {
log_info ("can't lock '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
else
kr->is_locked = 1;
}
}
if (rc || !yes) {
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->is_locked)
continue;
if (dotlock_release (kr->lockhd))
log_info ("can't unlock '%s'\n", kr->fname );
else
kr->is_locked = 0;
}
}
return rc;
}
/*
* Return the last found keyblock. Caller must free it.
* The returned keyblock has the kbode flag bit 0 set for the node with
* the public key used to locate the keyblock or flag bit 1 set for
* the user ID node.
*/
int
keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb)
{
PACKET *pkt;
int rc;
KBNODE keyblock = NULL, node, lastnode;
IOBUF a;
int in_cert = 0;
int pk_no = 0;
int uid_no = 0;
int save_mode;
if (ret_kb)
*ret_kb = NULL;
if (!hd->found.kr)
return -1; /* no successful search */
a = iobuf_open (hd->found.kr->fname);
if (!a)
{
log_error(_("can't open '%s'\n"), hd->found.kr->fname);
return GPG_ERR_KEYRING_OPEN;
}
if (iobuf_seek (a, hd->found.offset) ) {
log_error ("can't seek '%s'\n", hd->found.kr->fname);
iobuf_close(a);
return GPG_ERR_KEYRING_OPEN;
}
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
hd->found.n_packets = 0;;
lastnode = NULL;
save_mode = set_packet_list_mode(0);
while ((rc=parse_packet (a, pkt)) != -1) {
hd->found.n_packets++;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) {
free_packet (pkt);
init_packet (pkt);
continue;
}
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
if (in_cert)
/* It is not this key that is problematic, but the
following key. */
{
rc = 0;
hd->found.n_packets --;
}
else
/* Upper layer needs to handle this. */
{
}
break;
}
if (rc) {
log_error ("keyring_get_keyblock: read error: %s\n",
gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
break; /* Allowed per RFC. */
case PKT_RING_TRUST:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
break; /* Allowed by us. */
default:
log_error ("skipped packet of type %d in keyring\n",
(int)pkt->pkttype);
free_packet(pkt);
init_packet(pkt);
continue;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)) {
hd->found.n_packets--; /* fix counter */
break; /* ready */
}
in_cert = 1;
if (pkt->pkttype == PKT_RING_TRUST)
{
/*(this code is duplicated after the loop)*/
if ( lastnode
&& lastnode->pkt->pkttype == PKT_SIGNATURE
&& (pkt->pkt.ring_trust->sigcache & 1) ) {
/* This is a ring trust packet with a checked signature
* status cache following directly a signature paket.
* Set the cache status into that signature packet. */
PKT_signature *sig = lastnode->pkt->pkt.signature;
sig->flags.checked = 1;
sig->flags.valid = !!(pkt->pkt.ring_trust->sigcache & 2);
}
/* Reset LASTNODE, so that we set the cache status only from
* the ring trust packet immediately following a signature. */
lastnode = NULL;
free_packet(pkt);
init_packet(pkt);
continue;
}
node = lastnode = new_kbnode (pkt);
if (!keyblock)
keyblock = node;
else
add_kbnode (keyblock, node);
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_no == hd->found.pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_no == hd->found.uid_no)
node->flag |= 2;
break;
default:
break;
}
pkt = xmalloc (sizeof *pkt);
init_packet(pkt);
}
set_packet_list_mode(save_mode);
if (rc == -1 && keyblock)
rc = 0; /* got the entire keyblock */
if (rc || !ret_kb)
release_kbnode (keyblock);
else {
/*(duplicated from the loop body)*/
if ( pkt && pkt->pkttype == PKT_RING_TRUST
&& lastnode
&& lastnode->pkt->pkttype == PKT_SIGNATURE
&& (pkt->pkt.ring_trust->sigcache & 1) ) {
PKT_signature *sig = lastnode->pkt->pkt.signature;
sig->flags.checked = 1;
sig->flags.valid = !!(pkt->pkt.ring_trust->sigcache & 2);
}
*ret_kb = keyblock;
}
free_packet (pkt);
xfree (pkt);
iobuf_close(a);
/* Make sure that future search operations fail immediately when
* we know that we are working on a invalid keyring
*/
if (gpg_err_code (rc) == GPG_ERR_INV_KEYRING)
hd->current.error = rc;
return rc;
}
int
keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* The open iobuf isn't needed anymore and in fact is a problem when
it comes to renaming the keyring files on some operating systems,
so close it here */
iobuf_close(hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the update */
rc = do_copy (3, hd->found.kr->fname, kb,
hd->found.offset, hd->found.n_packets );
if (!rc) {
if (key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
}
return rc;
}
int
keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
const char *fname;
if (!hd)
fname = NULL;
else if (hd->found.kr)
{
fname = hd->found.kr->fname;
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else if (hd->current.kr)
{
fname = hd->current.kr->fname;
if (hd->current.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else
fname = hd->resource? hd->resource->fname:NULL;
if (!fname)
return GPG_ERR_GENERAL;
/* Close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
* after the write opertions.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the insert */
rc = do_copy (1, fname, kb, 0, 0 );
if (!rc && key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
return rc;
}
int
keyring_delete_keyblock (KEYRING_HANDLE hd)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
* after the write opertions.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the delete */
rc = do_copy (2, hd->found.kr->fname, NULL,
hd->found.offset, hd->found.n_packets );
if (!rc) {
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
/* Delete is a rare operations, so we don't remove the keys
* from the offset table */
}
return rc;
}
/*
* Start the next search on this handle right at the beginning
*/
int
keyring_search_reset (KEYRING_HANDLE hd)
{
log_assert (hd);
hd->current.kr = NULL;
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.eof = 0;
hd->current.error = 0;
hd->found.kr = NULL;
hd->found.offset = 0;
return 0;
}
static int
prepare_search (KEYRING_HANDLE hd)
{
if (hd->current.error) {
/* If the last key was a legacy key, we simply ignore the error so that
we can easily use search_next. */
if (gpg_err_code (hd->current.error) == GPG_ERR_LEGACY_KEY)
{
if (DBG_LOOKUP)
log_debug ("%s: last error was GPG_ERR_LEGACY_KEY, clearing\n",
__func__);
hd->current.error = 0;
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: returning last error: %s\n",
__func__, gpg_strerror (hd->current.error));
return hd->current.error; /* still in error state */
}
}
if (hd->current.kr && !hd->current.eof) {
if ( !hd->current.iobuf )
{
if (DBG_LOOKUP)
log_debug ("%s: missing iobuf!\n", __func__);
return GPG_ERR_GENERAL; /* Position invalid after a modify. */
}
return 0; /* okay */
}
if (!hd->current.kr && hd->current.eof)
{
if (DBG_LOOKUP)
log_debug ("%s: EOF!\n", __func__);
return -1; /* still EOF */
}
if (!hd->current.kr) { /* start search with first keyring */
hd->current.kr = hd->resource;
if (!hd->current.kr) {
if (DBG_LOOKUP)
log_debug ("%s: keyring not available!\n", __func__);
hd->current.eof = 1;
return -1; /* keyring not available */
}
log_assert (!hd->current.iobuf);
}
else { /* EOF */
if (DBG_LOOKUP)
log_debug ("%s: EOF\n", __func__);
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.kr = NULL;
hd->current.eof = 1;
return -1;
}
hd->current.eof = 0;
hd->current.iobuf = iobuf_open (hd->current.kr->fname);
if (!hd->current.iobuf)
{
hd->current.error = gpg_error_from_syserror ();
log_error(_("can't open '%s'\n"), hd->current.kr->fname );
return hd->current.error;
}
return 0;
}
/* A map of the all characters valid used for word_match()
* Valid characters are in this table converted to uppercase.
* because the upper 128 bytes have special meaning, we assume
* that they are all valid.
* Note: We must use numerical values here in case that this program
* will be converted to those little blue HAL9000s with their strange
* EBCDIC character set (user ids are UTF-8).
* wk 2000-04-13: Hmmm, does this really make sense, given the fact that
* we can run gpg now on a S/390 running GNU/Linux, where the code
* translation is done by the device drivers?
*/
static const byte word_match_chars[256] = {
/* 00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/* 38 */ 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 40 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 58 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 60 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 68 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 78 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
/* 88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
/* 90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
/* 98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
/* a0 */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
/* a8 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
/* b0 */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
/* b8 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
/* c0 */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
/* c8 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
/* d0 */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
/* d8 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
/* e0 */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
/* e8 */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
/* f0 */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
/* f8 */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/****************
* Do a word match (original user id starts with a '+').
* The pattern is already tokenized to a more suitable format:
* There are only the real words in it delimited by one space
* and all converted to uppercase.
*
* Returns: 0 if all words match.
*
* Note: This algorithm is a straightforward one and not very
* fast. It works for UTF-8 strings. The uidlen should
* be removed but due to the fact that old versions of
* pgp don't use UTF-8 we still use the length; this should
* be fixed in parse-packet (and replace \0 by some special
* UTF-8 encoding)
*/
static int
word_match( const byte *uid, size_t uidlen, const byte *pattern )
{
size_t wlen, n;
const byte *p;
const byte *s;
for( s=pattern; *s; ) {
do {
/* skip leading delimiters */
while( uidlen && !word_match_chars[*uid] )
uid++, uidlen--;
/* get length of the word */
n = uidlen; p = uid;
while( n && word_match_chars[*p] )
p++, n--;
wlen = p - uid;
/* and compare against the current word from pattern */
for(n=0, p=uid; n < wlen && s[n] != ' ' && s[n] ; n++, p++ ) {
if( word_match_chars[*p] != s[n] )
break;
}
if( n == wlen && (s[n] == ' ' || !s[n]) )
break; /* found */
uid += wlen;
uidlen -= wlen;
} while( uidlen );
if( !uidlen )
return -1; /* not found */
/* advance to next word in pattern */
for(; *s != ' ' && *s ; s++ )
;
if( *s )
s++ ;
}
return 0; /* found */
}
/****************
* prepare word word_match; that is parse the name and
* build the pattern.
* caller has to free the returned pattern
*/
static char*
prepare_word_match (const byte *name)
{
byte *pattern, *p;
int c;
/* the original length is always enough for the pattern */
p = pattern = xmalloc(strlen(name)+1);
do {
/* skip leading delimiters */
while( *name && !word_match_chars[*name] )
name++;
/* copy as long as we don't have a delimiter and convert
* to uppercase.
* fixme: how can we handle utf8 uppercasing */
for( ; *name && (c=word_match_chars[*name]); name++ )
*p++ = c;
*p++ = ' '; /* append pattern delimiter */
} while( *name );
p[-1] = 0; /* replace last pattern delimiter by EOS */
return pattern;
}
static int
compare_name (int mode, const char *name, const char *uid, size_t uidlen)
{
int i;
const char *s, *se;
if (mode == KEYDB_SEARCH_MODE_EXACT) {
for (i=0; name[i] && uidlen; i++, uidlen--)
if (uid[i] != name[i])
break;
if (!uidlen && !name[i])
return 0; /* found */
}
else if (mode == KEYDB_SEARCH_MODE_SUBSTR) {
if (ascii_memistr( uid, uidlen, name ))
return 0;
}
else if ( mode == KEYDB_SEARCH_MODE_MAIL
|| mode == KEYDB_SEARCH_MODE_MAILSUB
|| mode == KEYDB_SEARCH_MODE_MAILEND) {
int have_angles = 1;
for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++)
;
if (i == uidlen)
{
/* The UID is a plain addr-spec (cf. RFC2822 section 4.3). */
have_angles = 0;
s = uid;
i = 0;
}
if (i < uidlen) {
if (have_angles)
{
/* skip opening delim and one char and look for the closing one*/
s++; i++;
for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++)
;
}
else
se = s + uidlen;
if (i < uidlen) {
i = se - s;
if (mode == KEYDB_SEARCH_MODE_MAIL) {
if( strlen(name)-2 == i
&& !ascii_memcasecmp( s, name+1, i) )
return 0;
}
else if (mode == KEYDB_SEARCH_MODE_MAILSUB) {
if( ascii_memistr( s, i, name ) )
return 0;
}
else { /* email from end */
/* nyi */
}
}
}
}
else if (mode == KEYDB_SEARCH_MODE_WORDS)
return word_match (uid, uidlen, name);
else
BUG();
return -1; /* not found */
}
/*
* Search through the keyring(s), starting at the current position,
* for a keyblock which contains one of the keys described in the DESC array.
*/
int
keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex, int ignore_legacy)
{
int rc;
PACKET pkt;
int save_mode;
off_t offset, main_offset;
size_t n;
int need_uid, need_words, need_keyid, need_fpr, any_skip;
int pk_no, uid_no;
int initial_skip;
int scanned_from_start;
int use_key_present_hash;
PKT_user_id *uid = NULL;
PKT_public_key *pk = NULL;
u32 aki[2];
/* figure out what information we need */
need_uid = need_words = need_keyid = need_fpr = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
need_uid = 1;
break;
case KEYDB_SEARCH_MODE_WORDS:
need_uid = 1;
need_words = 1;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
need_keyid = 1;
break;
case KEYDB_SEARCH_MODE_FPR16:
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
need_fpr = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keyring_search_reset (hd);
break;
default: break;
}
if (desc[n].skipfnc)
{
any_skip = 1;
need_keyid = 1;
}
}
if (DBG_LOOKUP)
log_debug ("%s: need_uid = %d; need_words = %d; need_keyid = %d; need_fpr = %d; any_skip = %d\n",
__func__, need_uid, need_words, need_keyid, need_fpr, any_skip);
rc = prepare_search (hd);
if (rc)
{
if (DBG_LOOKUP)
log_debug ("%s: prepare_search failed: %s (%d)\n",
__func__, gpg_strerror (rc), gpg_err_code (rc));
return rc;
}
use_key_present_hash = !!key_present_hash;
if (!use_key_present_hash)
{
if (DBG_LOOKUP)
log_debug ("%s: no offset table.\n", __func__);
}
else if (!key_present_hash_ready)
{
if (DBG_LOOKUP)
log_debug ("%s: initializing offset table. (need_keyid: %d => 1)\n",
__func__, need_keyid);
need_keyid = 1;
}
else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
struct key_present *oi;
if (DBG_LOOKUP)
log_debug ("%s: look up by long key id, checking cache\n", __func__);
oi = key_present_hash_lookup (key_present_hash, desc[0].u.kid);
if (!oi)
{ /* We know that we don't have this key */
if (DBG_LOOKUP)
log_debug ("%s: cache says not present\n", __func__);
hd->found.kr = NULL;
hd->current.eof = 1;
return -1;
}
/* We could now create a positive search status and return.
* However the problem is that another instance of gpg may
* have changed the keyring so that the offsets are not valid
* anymore - therefore we don't do it
*/
}
if (need_words)
{
const char *name = NULL;
log_debug ("word search mode does not yet work\n");
/* FIXME: here is a long standing bug in our function and in addition we
just use the first search description */
for (n=0; n < ndesc && !name; n++)
{
if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS)
name = desc[n].u.name;
}
log_assert (name);
if ( !hd->word_match.name || strcmp (hd->word_match.name, name) )
{
/* name changed */
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
hd->word_match.name = xstrdup (name);
hd->word_match.pattern = prepare_word_match (name);
}
/* name = hd->word_match.pattern; */
}
init_packet(&pkt);
save_mode = set_packet_list_mode(0);
hd->found.kr = NULL;
main_offset = 0;
pk_no = uid_no = 0;
initial_skip = 1; /* skip until we see the start of a keyblock */
scanned_from_start = iobuf_tell (hd->current.iobuf) == 0;
if (DBG_LOOKUP)
log_debug ("%s: %ssearching from start of resource.\n",
__func__, scanned_from_start ? "" : "not ");
while (1)
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
rc = search_packet (hd->current.iobuf, &pkt, &offset, need_uid);
if (ignore_legacy && gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
free_packet (&pkt);
continue;
}
if (rc)
break;
if (pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_SECRET_KEY)
{
main_offset = offset;
pk_no = uid_no = 0;
initial_skip = 0;
}
if (initial_skip)
{
free_packet (&pkt);
continue;
}
pk = NULL;
uid = NULL;
if ( pkt.pkttype == PKT_PUBLIC_KEY
|| pkt.pkttype == PKT_PUBLIC_SUBKEY
|| pkt.pkttype == PKT_SECRET_KEY
|| pkt.pkttype == PKT_SECRET_SUBKEY)
{
pk = pkt.pkt.public_key;
++pk_no;
if (need_fpr) {
fingerprint_from_pk (pk, afp, &an);
while (an < 20) /* fill up to 20 bytes */
afp[an++] = 0;
}
if (need_keyid)
keyid_from_pk (pk, aki);
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
key_present_hash_update (key_present_hash, aki);
}
else if (pkt.pkttype == PKT_USER_ID)
{
uid = pkt.pkt.user_id;
++uid_no;
}
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode) {
case KEYDB_SEARCH_MODE_NONE:
BUG ();
break;
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
if ( uid && !compare_name (desc[n].mode,
desc[n].u.name,
uid->name, uid->len))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
if (pk && desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (pk && desc[n].u.kid[0] == aki[0]
&& desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_FPR16:
if (pk && !memcmp (desc[n].u.fpr, afp, 16))
goto found;
break;
case KEYDB_SEARCH_MODE_FPR20:
case KEYDB_SEARCH_MODE_FPR:
if (pk && !memcmp (desc[n].u.fpr, afp, 20))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
if (pk)
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
if (pk)
goto found;
break;
default:
rc = GPG_ERR_INV_ARG;
goto found;
}
}
free_packet (&pkt);
continue;
found:
if (rc)
goto real_found;
if (DBG_LOOKUP)
log_debug ("%s: packet starting at offset %lld matched descriptor %zu\n"
, __func__, (long long)offset, n);
/* Record which desc we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(descindex)
*descindex=n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
if (desc[n].skipfnc
&& desc[n].skipfnc (desc[n].skipfncvalue, aki, uid_no))
{
if (DBG_LOOKUP)
log_debug ("%s: skipping match: desc %zd's skip function returned TRUE\n",
__func__, n);
break;
}
}
if (n == ndesc)
goto real_found;
free_packet (&pkt);
}
real_found:
if (!rc)
{
if (DBG_LOOKUP)
log_debug ("%s: returning success\n", __func__);
hd->found.offset = main_offset;
hd->found.kr = hd->current.kr;
hd->found.pk_no = pk? pk_no : 0;
hd->found.uid_no = uid? uid_no : 0;
}
else if (rc == -1)
{
if (DBG_LOOKUP)
log_debug ("%s: no matches (EOF)\n", __func__);
hd->current.eof = 1;
/* if we scanned all keyrings, we are sure that
* all known key IDs are in our offtbl, mark that. */
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
{
KR_RESOURCE kr;
/* First set the did_full_scan flag for this keyring. */
for (kr=kr_resources; kr; kr = kr->next)
{
if (hd->resource == kr)
{
kr->did_full_scan = 1;
break;
}
}
/* Then check whether all flags are set and if so, mark the
offtbl ready */
for (kr=kr_resources; kr; kr = kr->next)
{
if (!kr->did_full_scan)
break;
}
if (!kr)
key_present_hash_ready = 1;
}
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: error encountered during search: %s (%d)\n",
__func__, gpg_strerror (rc), rc);
hd->current.error = rc;
}
free_packet(&pkt);
set_packet_list_mode(save_mode);
return rc;
}
static int
create_tmp_file (const char *template,
char **r_bakfname, char **r_tmpfname, IOBUF *r_fp)
{
gpg_error_t err;
mode_t oldmask;
err = keybox_tmp_names (template, 1, r_bakfname, r_tmpfname);
if (err)
return err;
/* Create the temp file with limited access. Note that the umask
call is not anymore needed because iobuf_create now takes care of
it. However, it does not harm and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (*r_tmpfname))
{
*r_fp = NULL;
gpg_err_set_errno (EPERM);
}
else
*r_fp = iobuf_create (*r_tmpfname, 1);
umask (oldmask);
if (!*r_fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), *r_tmpfname, gpg_strerror (err));
xfree (*r_tmpfname);
*r_tmpfname = NULL;
xfree (*r_bakfname);
*r_bakfname = NULL;
}
return err;
}
static int
rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname)
{
int rc = 0;
int block = 0;
/* Invalidate close caches. */
if (iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname ))
{
rc = gpg_error_from_syserror ();
goto fail;
}
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname );
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname );
/* First make a backup file. */
block = 1;
rc = gnupg_rename_file (fname, bakfname, &block);
if (rc)
goto fail;
/* then rename the file */
rc = gnupg_rename_file (tmpfname, fname, NULL);
if (block)
{
gnupg_unblock_all_signals ();
block = 0;
}
if (rc)
{
register_secured_file (fname);
goto fail;
}
/* Now make sure the file has the same permissions as the original */
#ifndef HAVE_DOSISH_SYSTEM
{
struct stat statbuf;
statbuf.st_mode=S_IRUSR | S_IWUSR;
if (!stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode))
;
else
log_error ("WARNING: unable to restore permissions to '%s': %s",
fname, strerror(errno));
}
#endif
return 0;
fail:
if (block)
gnupg_unblock_all_signals ();
return rc;
}
static int
write_keyblock (IOBUF fp, KBNODE keyblock)
{
KBNODE kbctx = NULL, node;
int rc;
while ( (node = walk_kbnode (keyblock, &kbctx, 0)) )
{
if (node->pkt->pkttype == PKT_RING_TRUST)
continue; /* we write it later on our own */
if ( (rc = build_packet (fp, node->pkt) ))
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
if (node->pkt->pkttype == PKT_SIGNATURE)
{ /* always write a signature cache packet */
PKT_signature *sig = node->pkt->pkt.signature;
unsigned int cacheval = 0;
if (sig->flags.checked)
{
cacheval |= 1;
if (sig->flags.valid)
cacheval |= 2;
}
iobuf_put (fp, 0xb0); /* old style packet 12, 1 byte len*/
iobuf_put (fp, 2); /* 2 bytes */
iobuf_put (fp, 0); /* unused */
if (iobuf_put (fp, cacheval))
{
rc = gpg_error_from_syserror ();
log_error ("writing sigcache packet failed\n");
return rc;
}
}
}
return 0;
}
/*
* Walk over all public keyrings, check the signatures and replace the
* keyring with a new one where the signature cache is then updated.
* This is only done for the public keyrings.
*/
int
keyring_rebuild_cache (void *token,int noisy)
{
KEYRING_HANDLE hd;
KEYDB_SEARCH_DESC desc;
KBNODE keyblock = NULL, node;
const char *lastresname = NULL, *resname;
IOBUF tmpfp = NULL;
char *tmpfilename = NULL;
char *bakfilename = NULL;
int rc;
ulong count = 0, sigcount = 0;
hd = keyring_new (token);
if (!hd)
return gpg_error_from_syserror ();
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
rc=keyring_lock (hd, 1);
if(rc)
goto leave;
for (;;)
{
rc = keyring_search (hd, &desc, 1, NULL, 1 /* ignore_legacy */);
if (rc)
break; /* ready. */
desc.mode = KEYDB_SEARCH_MODE_NEXT;
resname = keyring_get_resource_name (hd);
if (lastresname != resname )
{ /* we have switched to a new keyring - commit changes */
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
/* Static analyzer note: BAKFILENAME is never NULL here
because it is controlled by LASTRESNAME. */
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
if (rc)
goto leave;
lastresname = resname;
if (noisy && !opt.quiet)
log_info (_("caching keyring '%s'\n"), resname);
rc = create_tmp_file (resname, &bakfilename, &tmpfilename, &tmpfp);
if (rc)
goto leave;
}
release_kbnode (keyblock);
rc = keyring_get_keyblock (hd, &keyblock);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
continue; /* Skip legacy keys. */
log_error ("keyring_get_keyblock failed: %s\n", gpg_strerror (rc));
goto leave;
}
if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
/* We had a few reports about corrupted keyrings; if we have
been called directly from the command line we delete such
a keyblock instead of bailing out. */
log_error ("unexpected keyblock found (pkttype=%d)%s\n",
keyblock->pkt->pkttype, noisy? " - deleted":"");
if (noisy)
continue;
log_info ("Hint: backup your keys and try running '%s'\n",
"gpg --rebuild-keydb-caches");
rc = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (keyblock->pkt->pkt.public_key->version < 4)
{
/* We do not copy/cache v3 keys or any other unknown
packets. It is better to remove them from the keyring.
The code required to keep them in the keyring would be
too complicated. Given that we do not touch the old
secring.gpg a suitable backup for decryption of v3 stuff
using an older gpg version will always be available.
Note: This test is actually superfluous because we
already acted upon GPG_ERR_LEGACY_KEY. */
}
else
{
/* Check all signature to set the signature's cache flags. */
for (node=keyblock; node; node=node->next)
{
/* Note that this doesn't cache the result of a
revocation issued by a designated revoker. This is
because the pk in question does not carry the revkeys
as we haven't merged the key and selfsigs. It is
questionable whether this matters very much since
there are very very few designated revoker revocation
packets out there. */
if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig=node->pkt->pkt.signature;
if(!opt.no_sig_cache && sig->flags.checked && sig->flags.valid
&& (openpgp_md_test_algo(sig->digest_algo)
|| openpgp_pk_test_algo(sig->pubkey_algo)))
sig->flags.checked=sig->flags.valid=0;
else
check_key_signature (keyblock, node, NULL);
sigcount++;
}
}
/* Write the keyblock to the temporary file. */
rc = write_keyblock (tmpfp, keyblock);
if (rc)
goto leave;
if ( !(++count % 50) && noisy && !opt.quiet)
log_info (ngettext("%lu keys cached so far (%lu signature)\n",
"%lu keys cached so far (%lu signatures)\n",
sigcount),
count, sigcount);
}
} /* end main loop */
if (rc == -1)
rc = 0;
if (rc)
{
log_error ("keyring_search failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (noisy || opt.verbose)
{
log_info (ngettext("%lu key cached",
"%lu keys cached", count), count);
log_printf (ngettext(" (%lu signature)\n",
" (%lu signatures)\n", sigcount), sigcount);
}
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
leave:
if (tmpfp)
iobuf_cancel (tmpfp);
xfree (tmpfilename);
xfree (bakfilename);
release_kbnode (keyblock);
keyring_lock (hd, 0);
keyring_release (hd);
return rc;
}
/****************
* Perform insert/delete/update operation.
* mode 1 = insert
* 2 = delete
* 3 = update
*/
static int
do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets )
{
IOBUF fp, newfp;
int rc=0;
char *bakfname = NULL;
char *tmpfname = NULL;
/* Open the source file. Because we do a rename, we have to check the
permissions of the file */
if (access (fname, W_OK))
return gpg_error_from_syserror ();
fp = iobuf_open (fname);
if (mode == 1 && !fp && errno == ENOENT) {
/* insert mode but file does not exist: create a new file */
KBNODE kbctx, node;
mode_t oldmask;
oldmask=umask(077);
if (is_secured_filename (fname)) {
newfp = NULL;
gpg_err_set_errno (EPERM);
}
else
newfp = iobuf_create (fname, 1);
umask(oldmask);
if( !newfp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, strerror(errno));
return rc;
}
if( !opt.quiet )
log_info(_("%s: keyring created\n"), fname );
kbctx=NULL;
while ( (node = walk_kbnode( root, &kbctx, 0 )) ) {
if( (rc = build_packet( newfp, node->pkt )) ) {
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
iobuf_cancel(newfp);
return rc;
}
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error ("%s: close failed: %s\n", fname, strerror(errno));
return rc;
}
return 0; /* ready */
}
if( !fp )
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), fname, strerror(errno) );
goto leave;
}
/* Create the new file. */
rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
if (rc) {
iobuf_close(fp);
goto leave;
}
if( mode == 1 ) { /* insert */
/* copy everything to the new file */
rc = copy_all_packets (fp, newfp);
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy first part to the new file */
rc = copy_some_packets( fp, newfp, start_offset );
if( rc ) { /* should never get EOF here */
log_error ("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
/* skip this keyblock */
log_assert( n_packets );
rc = skip_some_packets( fp, n_packets );
if( rc ) {
log_error("%s: skipping %u packets failed: %s\n",
fname, n_packets, gpg_strerror (rc));
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 1 || mode == 3 ) { /* insert or update */
rc = write_keyblock (newfp, root);
if (rc) {
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy the rest */
rc = copy_all_packets( fp, newfp );
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
/* close both files */
if( iobuf_close(fp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", fname, strerror(errno) );
goto leave;
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", tmpfname, strerror(errno) );
goto leave;
}
rc = rename_tmp_file (bakfname, tmpfname, fname);
leave:
xfree(bakfname);
xfree(tmpfname);
return rc;
}
diff --git a/g10/keyserver-internal.h b/g10/keyserver-internal.h
index 77b362eef..02452e88a 100644
--- a/g10/keyserver-internal.h
+++ b/g10/keyserver-internal.h
@@ -1,56 +1,56 @@
/* keyserver-internal.h - Keyserver internals
* Copyright (C) 2001, 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef _KEYSERVER_INTERNAL_H_
#define _KEYSERVER_INTERNAL_H_
#include <time.h>
#include "../common/keyserver.h"
#include "../common/iobuf.h"
-#include "types.h"
+#include "../common/types.h"
int parse_keyserver_options(char *options);
void free_keyserver_spec(struct keyserver_spec *keyserver);
struct keyserver_spec *keyserver_match(struct keyserver_spec *spec);
struct keyserver_spec *parse_keyserver_uri (const char *string,
int require_scheme);
struct keyserver_spec *parse_preferred_keyserver(PKT_signature *sig);
int keyserver_any_configured (ctrl_t ctrl);
int keyserver_export (ctrl_t ctrl, strlist_t users);
int keyserver_import (ctrl_t ctrl, strlist_t users);
int keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len,
struct keyserver_spec *keyserver, int quick);
int keyserver_import_keyid (ctrl_t ctrl, u32 *keyid,
struct keyserver_spec *keyserver, int quick);
gpg_error_t keyserver_refresh (ctrl_t ctrl, strlist_t users);
gpg_error_t keyserver_search (ctrl_t ctrl, strlist_t tokens);
int keyserver_fetch (ctrl_t ctrl, strlist_t urilist);
int keyserver_import_cert (ctrl_t ctrl, const char *name, int dane_mode,
unsigned char **fpr,size_t *fpr_len);
gpg_error_t keyserver_import_pka (ctrl_t ctrl, const char *name,
unsigned char **fpr,size_t *fpr_len);
gpg_error_t keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick,
unsigned char **fpr, size_t *fpr_len);
int keyserver_import_name (ctrl_t ctrl,
const char *name,unsigned char **fpr,size_t *fpr_len,
struct keyserver_spec *keyserver);
int keyserver_import_ldap (ctrl_t ctrl, const char *name,
unsigned char **fpr,size_t *fpr_len);
#endif /* !_KEYSERVER_INTERNAL_H_ */
diff --git a/g10/keyserver.c b/g10/keyserver.c
index c7363c98f..0794527fd 100644
--- a/g10/keyserver.c
+++ b/g10/keyserver.c
@@ -1,2151 +1,2151 @@
/* keyserver.c - generic keyserver code
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
* 2009, 2011, 2012 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "gpg.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "filter.h"
#include "keydb.h"
-#include "status.h"
+#include "../common/status.h"
#include "exec.h"
#include "main.h"
-#include "i18n.h"
-#include "ttyio.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
#include "options.h"
#include "packet.h"
#include "trustdb.h"
#include "keyserver-internal.h"
-#include "util.h"
-#include "membuf.h"
-#include "mbox-util.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/mbox-util.h"
#include "call-dirmngr.h"
#ifdef HAVE_W32_SYSTEM
/* It seems Vista doesn't grok X_OK and so fails access() tests.
Previous versions interpreted X_OK as F_OK anyway, so we'll just
use F_OK directly. */
#undef X_OK
#define X_OK F_OK
#endif /* HAVE_W32_SYSTEM */
struct keyrec
{
KEYDB_SEARCH_DESC desc;
u32 createtime,expiretime;
int size,flags;
byte type;
IOBUF uidbuf;
unsigned int lines;
};
/* Parameters for the search line handler. */
struct search_line_handler_parm_s
{
ctrl_t ctrl; /* The session control structure. */
char *searchstr_disp; /* Native encoded search string or NULL. */
KEYDB_SEARCH_DESC *desc; /* Array with search descriptions. */
int count; /* Number of keys we are currently prepared to
handle. This is the size of the DESC array. If
it is too small, it will grow safely. */
int validcount; /* Enable the "Key x-y of z" messages. */
int nkeys; /* Number of processed records. */
int any_lines; /* At least one line has been processed. */
unsigned int numlines; /* Counter for displayed lines. */
int eof_seen; /* EOF encountered. */
int not_found; /* Set if no keys have been found. */
};
enum ks_action {KS_UNKNOWN=0,KS_GET,KS_GETNAME,KS_SEND,KS_SEARCH};
static struct parse_options keyserver_opts[]=
{
/* some of these options are not real - just for the help
message */
{"max-cert-size",0,NULL,NULL}, /* MUST be the first in this array! */
{"http-proxy", KEYSERVER_HTTP_PROXY, NULL, /* MUST be the second! */
N_("override proxy options set for dirmngr")},
{"include-revoked",0,NULL,N_("include revoked keys in search results")},
{"include-subkeys",0,NULL,N_("include subkeys when searching by key ID")},
{"timeout", KEYSERVER_TIMEOUT, NULL,
N_("override timeout options set for dirmngr")},
{"refresh-add-fake-v3-keyids",KEYSERVER_ADD_FAKE_V3,NULL,
NULL},
{"auto-key-retrieve",KEYSERVER_AUTO_KEY_RETRIEVE,NULL,
N_("automatically retrieve keys when verifying signatures")},
{"honor-keyserver-url",KEYSERVER_HONOR_KEYSERVER_URL,NULL,
N_("honor the preferred keyserver URL set on the key")},
{"honor-pka-record",KEYSERVER_HONOR_PKA_RECORD,NULL,
N_("honor the PKA record set on a key when retrieving keys")},
{NULL,0,NULL,NULL}
};
static gpg_error_t keyserver_get (ctrl_t ctrl,
KEYDB_SEARCH_DESC *desc, int ndesc,
struct keyserver_spec *override_keyserver,
int quick,
unsigned char **r_fpr, size_t *r_fprlen);
static gpg_error_t keyserver_put (ctrl_t ctrl, strlist_t keyspecs);
/* Reasonable guess. The commonly used test key simon.josefsson.org
is larger than 32k, thus we need at least this value. */
#define DEFAULT_MAX_CERT_SIZE 65536
static size_t max_cert_size=DEFAULT_MAX_CERT_SIZE;
static void
warn_kshelper_option(char *option, int noisy)
{
char *p;
if ((p=strchr (option, '=')))
*p = 0;
if (!strcmp (option, "ca-cert-file"))
log_info ("keyserver option '%s' is obsolete; please use "
"'%s' in dirmngr.conf\n",
"ca-cert-file", "hkp-cacert");
else if (!strcmp (option, "check-cert")
|| !strcmp (option, "broken-http-proxy"))
log_info ("keyserver option '%s' is obsolete\n", option);
else if (noisy || opt.verbose)
log_info ("keyserver option '%s' is unknown\n", option);
}
/* Called from main to parse the args for --keyserver-options. */
int
parse_keyserver_options(char *options)
{
int ret=1;
char *tok;
char *max_cert=NULL;
keyserver_opts[0].value=&max_cert;
keyserver_opts[1].value=&opt.keyserver_options.http_proxy;
while((tok=optsep(&options)))
{
if(tok[0]=='\0')
continue;
/* We accept quite a few possible options here - some options to
handle specially, the keyserver_options list, and import and
export options that pertain to keyserver operations. */
if (!parse_options (tok,&opt.keyserver_options.options, keyserver_opts,0)
&& !parse_import_options(tok,&opt.keyserver_options.import_options,0)
&& !parse_export_options(tok,&opt.keyserver_options.export_options,0))
{
/* All of the standard options have failed, so the option was
destined for a keyserver plugin as used by GnuPG < 2.1 */
warn_kshelper_option (tok, 1);
}
}
if(max_cert)
{
max_cert_size=strtoul(max_cert,(char **)NULL,10);
if(max_cert_size==0)
max_cert_size=DEFAULT_MAX_CERT_SIZE;
}
return ret;
}
void
free_keyserver_spec(struct keyserver_spec *keyserver)
{
xfree(keyserver->uri);
xfree(keyserver->scheme);
xfree(keyserver->auth);
xfree(keyserver->host);
xfree(keyserver->port);
xfree(keyserver->path);
xfree(keyserver->opaque);
free_strlist(keyserver->options);
xfree(keyserver);
}
/* Return 0 for match */
static int
cmp_keyserver_spec(struct keyserver_spec *one,struct keyserver_spec *two)
{
if(ascii_strcasecmp(one->scheme,two->scheme)==0)
{
if(one->host && two->host && ascii_strcasecmp(one->host,two->host)==0)
{
if((one->port && two->port
&& ascii_strcasecmp(one->port,two->port)==0)
|| (!one->port && !two->port))
return 0;
}
else if(one->opaque && two->opaque
&& ascii_strcasecmp(one->opaque,two->opaque)==0)
return 0;
}
return 1;
}
/* Try and match one of our keyservers. If we can, return that. If
we can't, return our input. */
struct keyserver_spec *
keyserver_match(struct keyserver_spec *spec)
{
struct keyserver_spec *ks;
for(ks=opt.keyserver;ks;ks=ks->next)
if(cmp_keyserver_spec(spec,ks)==0)
return ks;
return spec;
}
/* TODO: once we cut over to an all-curl world, we don't need this
parser any longer so it can be removed, or at least moved to
keyserver/ksutil.c for limited use in gpgkeys_ldap or the like. */
keyserver_spec_t
parse_keyserver_uri (const char *string,int require_scheme)
{
int assume_hkp=0;
struct keyserver_spec *keyserver;
const char *idx;
int count;
char *uri, *duped_uri, *options;
log_assert (string);
keyserver=xmalloc_clear(sizeof(struct keyserver_spec));
duped_uri = uri = xstrdup (string);
options=strchr(uri,' ');
if(options)
{
char *tok;
*options='\0';
options++;
while((tok=optsep(&options)))
warn_kshelper_option (tok, 0);
}
/* Get the scheme */
for(idx=uri,count=0;*idx && *idx!=':';idx++)
{
count++;
/* Do we see the start of an RFC-2732 ipv6 address here? If so,
there clearly isn't a scheme so get out early. */
if(*idx=='[')
{
/* Was the '[' the first thing in the string? If not, we
have a mangled scheme with a [ in it so fail. */
if(count==1)
break;
else
goto fail;
}
}
if(count==0)
goto fail;
if(*idx=='\0' || *idx=='[')
{
if(require_scheme)
return NULL;
/* Assume HKP if there is no scheme */
assume_hkp=1;
keyserver->scheme=xstrdup("hkp");
keyserver->uri=xmalloc(strlen(keyserver->scheme)+3+strlen(uri)+1);
strcpy(keyserver->uri,keyserver->scheme);
strcat(keyserver->uri,"://");
strcat(keyserver->uri,uri);
}
else
{
int i;
keyserver->uri=xstrdup(uri);
keyserver->scheme=xmalloc(count+1);
/* Force to lowercase */
for(i=0;i<count;i++)
keyserver->scheme[i]=ascii_tolower(uri[i]);
keyserver->scheme[i]='\0';
/* Skip past the scheme and colon */
uri+=count+1;
}
if(ascii_strcasecmp(keyserver->scheme,"x-broken-hkp")==0)
{
log_info ("keyserver option '%s' is obsolete\n",
"x-broken-hkp");
}
else if(ascii_strcasecmp(keyserver->scheme,"x-hkp")==0)
{
/* Canonicalize this to "hkp" so it works with both the internal
and external keyserver interface. */
xfree(keyserver->scheme);
keyserver->scheme=xstrdup("hkp");
}
if (uri[0]=='/' && uri[1]=='/' && uri[2] == '/')
{
/* Three slashes means network path with a default host name.
This is a hack because it does not crok all possible
combiantions. We should better repalce all code bythe parser
from http.c. */
keyserver->path = xstrdup (uri+2);
}
else if(assume_hkp || (uri[0]=='/' && uri[1]=='/'))
{
/* Two slashes means network path. */
/* Skip over the "//", if any */
if(!assume_hkp)
uri+=2;
/* Do we have userinfo auth data present? */
for(idx=uri,count=0;*idx && *idx!='@' && *idx!='/';idx++)
count++;
/* We found a @ before the slash, so that means everything
before the @ is auth data. */
if(*idx=='@')
{
if(count==0)
goto fail;
keyserver->auth=xmalloc(count+1);
strncpy(keyserver->auth,uri,count);
keyserver->auth[count]='\0';
uri+=count+1;
}
/* Is it an RFC-2732 ipv6 [literal address] ? */
if(*uri=='[')
{
for(idx=uri+1,count=1;*idx
&& ((isascii (*idx) && isxdigit(*idx))
|| *idx==':' || *idx=='.');idx++)
count++;
/* Is the ipv6 literal address terminated? */
if(*idx==']')
count++;
else
goto fail;
}
else
for(idx=uri,count=0;*idx && *idx!=':' && *idx!='/';idx++)
count++;
if(count==0)
goto fail;
keyserver->host=xmalloc(count+1);
strncpy(keyserver->host,uri,count);
keyserver->host[count]='\0';
/* Skip past the host */
uri+=count;
if(*uri==':')
{
/* It would seem to be reasonable to limit the range of the
ports to values between 1-65535, but RFC 1738 and 1808
imply there is no limit. Of course, the real world has
limits. */
for(idx=uri+1,count=0;*idx && *idx!='/';idx++)
{
count++;
/* Ports are digits only */
if(!digitp(idx))
goto fail;
}
keyserver->port=xmalloc(count+1);
strncpy(keyserver->port,uri+1,count);
keyserver->port[count]='\0';
/* Skip past the colon and port number */
uri+=1+count;
}
/* Everything else is the path */
if(*uri)
keyserver->path=xstrdup(uri);
else
keyserver->path=xstrdup("/");
if(keyserver->path[1])
keyserver->flags.direct_uri=1;
}
else if(uri[0]!='/')
{
/* No slash means opaque. Just record the opaque blob and get
out. */
keyserver->opaque=xstrdup(uri);
}
else
{
/* One slash means absolute path. We don't need to support that
yet. */
goto fail;
}
xfree (duped_uri);
return keyserver;
fail:
free_keyserver_spec(keyserver);
xfree (duped_uri);
return NULL;
}
struct keyserver_spec *
parse_preferred_keyserver(PKT_signature *sig)
{
struct keyserver_spec *spec=NULL;
const byte *p;
size_t plen;
p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&plen);
if(p && plen)
{
byte *dupe=xmalloc(plen+1);
memcpy(dupe,p,plen);
dupe[plen]='\0';
spec = parse_keyserver_uri (dupe, 1);
xfree(dupe);
}
return spec;
}
static void
print_keyrec(int number,struct keyrec *keyrec)
{
int i;
iobuf_writebyte(keyrec->uidbuf,0);
iobuf_flush_temp(keyrec->uidbuf);
es_printf ("(%d)\t%s ", number, iobuf_get_temp_buffer (keyrec->uidbuf));
if (keyrec->size>0)
es_printf ("%d bit ", keyrec->size);
if(keyrec->type)
{
const char *str;
str = openpgp_pk_algo_name (keyrec->type);
if (str && strcmp (str, "?"))
es_printf ("%s ",str);
else
es_printf ("unknown ");
}
switch(keyrec->desc.mode)
{
/* If the keyserver helper gave us a short keyid, we have no
choice but to use it. Do check --keyid-format to add a 0x if
needed. */
case KEYDB_SEARCH_MODE_SHORT_KID:
es_printf ("key %s%08lX",
(opt.keyid_format==KF_0xSHORT
|| opt.keyid_format==KF_0xLONG)?"0x":"",
(ulong)keyrec->desc.u.kid[1]);
break;
/* However, if it gave us a long keyid, we can honor
--keyid-format via keystr(). */
case KEYDB_SEARCH_MODE_LONG_KID:
es_printf ("key %s",keystr(keyrec->desc.u.kid));
break;
/* If it gave us a PGP 2.x fingerprint, not much we can do
beyond displaying it. */
case KEYDB_SEARCH_MODE_FPR16:
es_printf ("key ");
for(i=0;i<16;i++)
es_printf ("%02X",keyrec->desc.u.fpr[i]);
break;
/* If we get a modern fingerprint, we have the most
flexibility. */
case KEYDB_SEARCH_MODE_FPR20:
{
u32 kid[2];
keyid_from_fingerprint(keyrec->desc.u.fpr,20,kid);
es_printf("key %s",keystr(kid));
}
break;
default:
BUG();
break;
}
if(keyrec->createtime>0)
{
es_printf (", ");
es_printf (_("created: %s"), strtimestamp(keyrec->createtime));
}
if(keyrec->expiretime>0)
{
es_printf (", ");
es_printf (_("expires: %s"), strtimestamp(keyrec->expiretime));
}
if (keyrec->flags&1)
es_printf (" (%s)", _("revoked"));
if(keyrec->flags&2)
es_printf (" (%s)", _("disabled"));
if(keyrec->flags&4)
es_printf (" (%s)", _("expired"));
es_printf ("\n");
}
/* Returns a keyrec (which must be freed) once a key is complete, and
NULL otherwise. Call with a NULL keystring once key parsing is
complete to return any unfinished keys. */
static struct keyrec *
parse_keyrec(char *keystring)
{
/* FIXME: Remove the static and put the data into the parms we use
for the caller anyway. */
static struct keyrec *work=NULL;
struct keyrec *ret=NULL;
char *record;
int i;
if(keystring==NULL)
{
if(work==NULL)
return NULL;
else if(work->desc.mode==KEYDB_SEARCH_MODE_NONE)
{
xfree(work);
return NULL;
}
else
{
ret=work;
work=NULL;
return ret;
}
}
if(work==NULL)
{
work=xmalloc_clear(sizeof(struct keyrec));
work->uidbuf=iobuf_temp();
}
trim_trailing_ws (keystring, strlen (keystring));
if((record=strsep(&keystring,":"))==NULL)
return ret;
if(ascii_strcasecmp("pub",record)==0)
{
char *tok;
gpg_error_t err;
if(work->desc.mode)
{
ret=work;
work=xmalloc_clear(sizeof(struct keyrec));
work->uidbuf=iobuf_temp();
}
if((tok=strsep(&keystring,":"))==NULL)
return ret;
err = classify_user_id (tok, &work->desc, 1);
if (err || (work->desc.mode != KEYDB_SEARCH_MODE_SHORT_KID
&& work->desc.mode != KEYDB_SEARCH_MODE_LONG_KID
&& work->desc.mode != KEYDB_SEARCH_MODE_FPR16
&& work->desc.mode != KEYDB_SEARCH_MODE_FPR20))
{
work->desc.mode=KEYDB_SEARCH_MODE_NONE;
return ret;
}
/* Note all items after this are optional. This allows us to
have a pub line as simple as pub:keyid and nothing else. */
work->lines++;
if((tok=strsep(&keystring,":"))==NULL)
return ret;
work->type=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
work->size=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(atoi(tok)<=0)
work->createtime=0;
else
work->createtime=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(atoi(tok)<=0)
work->expiretime=0;
else
{
work->expiretime=atoi(tok);
/* Force the 'e' flag on if this key is expired. */
if(work->expiretime<=make_timestamp())
work->flags|=4;
}
if((tok=strsep(&keystring,":"))==NULL)
return ret;
while(*tok)
switch(*tok++)
{
case 'r':
case 'R':
work->flags|=1;
break;
case 'd':
case 'D':
work->flags|=2;
break;
case 'e':
case 'E':
work->flags|=4;
break;
}
}
else if(ascii_strcasecmp("uid",record)==0 && work->desc.mode)
{
char *userid,*tok,*decoded;
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(strlen(tok)==0)
return ret;
userid=tok;
/* By definition, de-%-encoding is always smaller than the
original string so we can decode in place. */
i=0;
while(*tok)
if(tok[0]=='%' && tok[1] && tok[2])
{
int c;
userid[i] = (c=hextobyte(&tok[1])) == -1 ? '?' : c;
i++;
tok+=3;
}
else
userid[i++]=*tok++;
/* We don't care about the other info provided in the uid: line
since no keyserver supports marking userids with timestamps
or revoked/expired/disabled yet. */
/* No need to check for control characters, as utf8_to_native
does this for us. */
decoded=utf8_to_native(userid,i,0);
if(strlen(decoded)>opt.screen_columns-10)
decoded[opt.screen_columns-10]='\0';
iobuf_writestr(work->uidbuf,decoded);
xfree(decoded);
iobuf_writestr(work->uidbuf,"\n\t");
work->lines++;
}
/* Ignore any records other than "pri" and "uid" for easy future
growth. */
return ret;
}
/* Show a prompt and allow the user to select keys for retrieval. */
static gpg_error_t
show_prompt (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int numdesc,
int count, const char *search)
{
gpg_error_t err;
char *answer = NULL;
es_fflush (es_stdout);
if (count && opt.command_fd == -1)
{
static int from = 1;
tty_printf ("Keys %d-%d of %d for \"%s\". ",
from, numdesc, count, search);
from = numdesc + 1;
}
again:
err = 0;
xfree (answer);
answer = cpr_get_no_help ("keysearch.prompt",
_("Enter number(s), N)ext, or Q)uit > "));
/* control-d */
if (answer[0]=='\x04')
{
tty_printf ("Q\n");
answer[0] = 'q';
}
if (answer[0]=='q' || answer[0]=='Q')
err = gpg_error (GPG_ERR_CANCELED);
else if (atoi (answer) >= 1 && atoi (answer) <= numdesc)
{
char *split = answer;
char *num;
int numarray[50];
int numidx = 0;
int idx;
while ((num = strsep (&split, " ,")))
if (atoi (num) >= 1 && atoi (num) <= numdesc)
{
if (numidx >= DIM (numarray))
{
tty_printf ("Too many keys selected\n");
goto again;
}
numarray[numidx++] = atoi (num);
}
if (!numidx)
goto again;
{
KEYDB_SEARCH_DESC *selarray;
selarray = xtrymalloc (numidx * sizeof *selarray);
if (!selarray)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (idx = 0; idx < numidx; idx++)
selarray[idx] = desc[numarray[idx]-1];
err = keyserver_get (ctrl, selarray, numidx, NULL, 0, NULL, NULL);
xfree (selarray);
}
}
leave:
xfree (answer);
return err;
}
/* This is a callback used by call-dirmngr.c to process the result of
KS_SEARCH command. If SPECIAL is 0, LINE is the actual data line
received with all escaping removed and guaranteed to be exactly one
line with stripped LF; an EOF is indicated by LINE passed as NULL.
If special is 1, the line contains the source of the information
(usually an URL). LINE may be modified after return. */
static gpg_error_t
search_line_handler (void *opaque, int special, char *line)
{
struct search_line_handler_parm_s *parm = opaque;
gpg_error_t err = 0;
struct keyrec *keyrec;
if (special == 1)
{
log_info ("data source: %s\n", line);
return 0;
}
else if (special)
{
log_debug ("unknown value %d for special search callback", special);
return 0;
}
if (parm->eof_seen && line)
{
log_debug ("ooops: unexpected data after EOF\n");
line = NULL;
}
/* Print the received line. */
if (opt.with_colons && line)
{
es_printf ("%s\n", line);
}
/* Look for an info: line. The only current info: values defined
are the version and key count. */
if (line && !parm->any_lines && !ascii_strncasecmp ("info:", line, 5))
{
char *str = line + 5;
char *tok;
if ((tok = strsep (&str, ":")))
{
int version;
if (sscanf (tok, "%d", &version) !=1 )
version = 1;
if (version !=1 )
{
log_error (_("invalid keyserver protocol "
"(us %d!=handler %d)\n"), 1, version);
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
}
}
if ((tok = strsep (&str, ":"))
&& sscanf (tok, "%d", &parm->count) == 1)
{
if (!parm->count)
parm->not_found = 1;/* Server indicated that no items follow. */
else if (parm->count < 0)
parm->count = 10; /* Bad value - assume something reasonable. */
else
parm->validcount = 1; /* COUNT seems to be okay. */
}
parm->any_lines = 1;
return 0; /* Line processing finished. */
}
again:
if (line)
keyrec = parse_keyrec (line);
else
{
/* Received EOF - flush data */
parm->eof_seen = 1;
keyrec = parse_keyrec (NULL);
if (!keyrec)
{
if (!parm->nkeys)
parm->not_found = 1; /* No keys at all. */
else
{
if (parm->nkeys != parm->count)
parm->validcount = 0;
if (!(opt.with_colons && opt.batch))
{
err = show_prompt (parm->ctrl, parm->desc, parm->nkeys,
parm->validcount? parm->count : 0,
parm->searchstr_disp);
return err;
}
}
}
}
/* Save the key in the key array. */
if (keyrec)
{
/* Allocate or enlarge the key array if needed. */
if (!parm->desc)
{
if (parm->count < 1)
{
parm->count = 10;
parm->validcount = 0;
}
parm->desc = xtrymalloc (parm->count * sizeof *parm->desc);
if (!parm->desc)
{
err = gpg_error_from_syserror ();
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
return err;
}
}
else if (parm->nkeys == parm->count)
{
/* Keyserver sent more keys than claimed in the info: line. */
KEYDB_SEARCH_DESC *tmp;
int newcount = parm->count + 10;
tmp = xtryrealloc (parm->desc, newcount * sizeof *parm->desc);
if (!tmp)
{
err = gpg_error_from_syserror ();
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
return err;
}
parm->count = newcount;
parm->desc = tmp;
parm->validcount = 0;
}
parm->desc[parm->nkeys] = keyrec->desc;
if (!opt.with_colons)
{
/* SCREEN_LINES - 1 for the prompt. */
if (parm->numlines + keyrec->lines > opt.screen_lines - 1)
{
err = show_prompt (parm->ctrl, parm->desc, parm->nkeys,
parm->validcount ? parm->count:0,
parm->searchstr_disp);
if (err)
return err;
parm->numlines = 0;
}
print_keyrec (parm->nkeys+1, keyrec);
}
parm->numlines += keyrec->lines;
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
parm->any_lines = 1;
parm->nkeys++;
/* If we are here due to a flush after the EOF, run again for
the last prompt. Fixme: Make this code better readable. */
if (parm->eof_seen)
goto again;
}
return 0;
}
int
keyserver_export (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
strlist_t sl=NULL;
KEYDB_SEARCH_DESC desc;
int rc=0;
/* Weed out descriptors that we don't support sending */
for(;users;users=users->next)
{
err = classify_user_id (users->d, &desc, 1);
if (err || (desc.mode != KEYDB_SEARCH_MODE_SHORT_KID
&& desc.mode != KEYDB_SEARCH_MODE_LONG_KID
&& desc.mode != KEYDB_SEARCH_MODE_FPR16
&& desc.mode != KEYDB_SEARCH_MODE_FPR20))
{
log_error(_("\"%s\" not a key ID: skipping\n"),users->d);
continue;
}
else
append_to_strlist(&sl,users->d);
}
if(sl)
{
rc = keyserver_put (ctrl, sl);
free_strlist(sl);
}
return rc;
}
/* Structure to convey the arg to keyserver_retrieval_screener. */
struct ks_retrieval_screener_arg_s
{
KEYDB_SEARCH_DESC *desc;
int ndesc;
};
/* Check whether a key matches the search description. The function
returns 0 if the key shall be imported. */
static gpg_error_t
keyserver_retrieval_screener (kbnode_t keyblock, void *opaque)
{
struct ks_retrieval_screener_arg_s *arg = opaque;
KEYDB_SEARCH_DESC *desc = arg->desc;
int ndesc = arg->ndesc;
kbnode_t node;
PKT_public_key *pk;
int n;
u32 keyid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fpr_len = 0;
/* Secret keys are not expected from a keyserver. We do not
care about secret subkeys because the import code takes care
of skipping them. Not allowing an import of a public key
with a secret subkey would make it too easy to inhibit the
downloading of a public key. Recall that keyservers do only
limited checks. */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (node)
return gpg_error (GPG_ERR_GENERAL); /* Do not import. */
if (!ndesc)
return 0; /* Okay if no description given. */
/* Loop over all key packets. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_KEY
&& node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fpr_len);
keyid_from_pk (pk, keyid);
/* Compare requested and returned fingerprints if available. */
for (n = 0; n < ndesc; n++)
{
if (desc[n].mode == KEYDB_SEARCH_MODE_FPR20)
{
if (fpr_len == 20 && !memcmp (fpr, desc[n].u.fpr, 20))
return 0;
}
else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR16)
{
if (fpr_len == 16 && !memcmp (fpr, desc[n].u.fpr, 16))
return 0;
}
else if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
if (keyid[0] == desc[n].u.kid[0] && keyid[1] == desc[n].u.kid[1])
return 0;
}
else if (desc[n].mode == KEYDB_SEARCH_MODE_SHORT_KID)
{
if (keyid[1] == desc[n].u.kid[1])
return 0;
}
else /* No keyid or fingerprint - can't check. */
return 0; /* allow import. */
}
}
return gpg_error (GPG_ERR_GENERAL);
}
int
keyserver_import (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
KEYDB_SEARCH_DESC *desc;
int num=100,count=0;
int rc=0;
/* Build a list of key ids */
desc=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num);
for(;users;users=users->next)
{
err = classify_user_id (users->d, &desc[count], 1);
if (err || (desc[count].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& desc[count].mode != KEYDB_SEARCH_MODE_LONG_KID
&& desc[count].mode != KEYDB_SEARCH_MODE_FPR16
&& desc[count].mode != KEYDB_SEARCH_MODE_FPR20))
{
log_error (_("\"%s\" not a key ID: skipping\n"), users->d);
continue;
}
count++;
if(count==num)
{
num+=100;
desc=xrealloc(desc,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
if(count>0)
rc = keyserver_get (ctrl, desc, count, NULL, 0, NULL, NULL);
xfree(desc);
return rc;
}
/* Return true if any keyserver has been configured. */
int
keyserver_any_configured (ctrl_t ctrl)
{
return !gpg_dirmngr_ks_list (ctrl, NULL);
}
/* Import all keys that exactly match NAME */
int
keyserver_import_name (ctrl_t ctrl, const char *name,
unsigned char **fpr, size_t *fprlen,
struct keyserver_spec *keyserver)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = name;
return keyserver_get (ctrl, &desc, 1, keyserver, 0, fpr, fprlen);
}
int
keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len,
struct keyserver_spec *keyserver, int quick)
{
KEYDB_SEARCH_DESC desc;
memset(&desc,0,sizeof(desc));
if(fprint_len==16)
desc.mode=KEYDB_SEARCH_MODE_FPR16;
else if(fprint_len==20)
desc.mode=KEYDB_SEARCH_MODE_FPR20;
else
return -1;
memcpy(desc.u.fpr,fprint,fprint_len);
/* TODO: Warn here if the fingerprint we got doesn't match the one
we asked for? */
return keyserver_get (ctrl, &desc, 1, keyserver, quick, NULL, NULL);
}
int
keyserver_import_keyid (ctrl_t ctrl,
u32 *keyid,struct keyserver_spec *keyserver, int quick)
{
KEYDB_SEARCH_DESC desc;
memset(&desc,0,sizeof(desc));
desc.mode=KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0]=keyid[0];
desc.u.kid[1]=keyid[1];
return keyserver_get (ctrl, &desc, 1, keyserver, quick, NULL, NULL);
}
/* code mostly stolen from do_export_stream */
static int
keyidlist(strlist_t users,KEYDB_SEARCH_DESC **klist,int *count,int fakev3)
{
int rc = 0;
int num = 100;
kbnode_t keyblock = NULL;
kbnode_t node;
KEYDB_HANDLE kdbhd;
int ndesc;
KEYDB_SEARCH_DESC *desc = NULL;
strlist_t sl;
*count=0;
*klist=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num);
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
if(!users)
{
ndesc = 1;
desc = xmalloc_clear ( ndesc * sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
gpg_error_t err;
if (!(err = classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
}
for (;;)
{
rc = keydb_search (kdbhd, desc, ndesc, NULL);
if (rc)
break; /* ready. */
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
/* read the keyblock */
rc = keydb_get_keyblock (kdbhd, &keyblock );
if( rc )
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
if((node=find_kbnode(keyblock,PKT_PUBLIC_KEY)))
{
/* This is to work around a bug in some keyservers (pksd and
OKS) that calculate v4 RSA keyids as if they were v3 RSA.
The answer is to refresh both the correct v4 keyid
(e.g. 99242560) and the fake v3 keyid (e.g. 68FDDBC7).
This only happens for key refresh using the HKP scheme
and if the refresh-add-fake-v3-keyids keyserver option is
set. */
if(fakev3 && is_RSA(node->pkt->pkt.public_key->pubkey_algo) &&
node->pkt->pkt.public_key->version>=4)
{
(*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID;
v3_keyid (node->pkt->pkt.public_key->pkey[0],
(*klist)[*count].u.kid);
(*count)++;
if(*count==num)
{
num+=100;
*klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
/* v4 keys get full fingerprints. v3 keys get long keyids.
This is because it's easy to calculate any sort of keyid
from a v4 fingerprint, but not a v3 fingerprint. */
if(node->pkt->pkt.public_key->version<4)
{
(*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID;
keyid_from_pk(node->pkt->pkt.public_key,
(*klist)[*count].u.kid);
}
else
{
size_t dummy;
(*klist)[*count].mode=KEYDB_SEARCH_MODE_FPR20;
fingerprint_from_pk(node->pkt->pkt.public_key,
(*klist)[*count].u.fpr,&dummy);
}
/* This is a little hackish, using the skipfncvalue as a
void* pointer to the keyserver spec, but we don't need
the skipfnc here, and it saves having an additional field
for this (which would be wasted space most of the
time). */
(*klist)[*count].skipfncvalue=NULL;
/* Are we honoring preferred keyservers? */
if(opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL)
{
PKT_user_id *uid=NULL;
PKT_signature *sig=NULL;
merge_keys_and_selfsig(keyblock);
for(node=node->next;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->is_primary)
uid=node->pkt->pkt.user_id;
else if(node->pkt->pkttype==PKT_SIGNATURE
&& node->pkt->pkt.signature->
flags.chosen_selfsig && uid)
{
sig=node->pkt->pkt.signature;
break;
}
}
/* Try and parse the keyserver URL. If it doesn't work,
then we end up writing NULL which indicates we are
the same as any other key. */
if(sig)
(*klist)[*count].skipfncvalue=parse_preferred_keyserver(sig);
}
(*count)++;
if(*count==num)
{
num+=100;
*klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0;
leave:
if(rc)
{
xfree(*klist);
*klist = NULL;
}
xfree(desc);
keydb_release(kdbhd);
release_kbnode(keyblock);
return rc;
}
/* Note this is different than the original HKP refresh. It allows
usernames to refresh only part of the keyring. */
gpg_error_t
keyserver_refresh (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
int count, numdesc;
int fakev3 = 0;
KEYDB_SEARCH_DESC *desc;
unsigned int options=opt.keyserver_options.import_options;
/* We switch merge-only on during a refresh, as 'refresh' should
never import new keys, even if their keyids match. */
opt.keyserver_options.import_options|=IMPORT_MERGE_ONLY;
/* Similarly, we switch on fast-import, since refresh may make
multiple import sets (due to preferred keyserver URLs). We don't
want each set to rebuild the trustdb. Instead we do it once at
the end here. */
opt.keyserver_options.import_options|=IMPORT_FAST;
/* If refresh_add_fake_v3_keyids is on and it's a HKP or MAILTO
scheme, then enable fake v3 keyid generation. Note that this
works only with a keyserver configured. gpg.conf
(i.e. opt.keyserver); however that method of configuring a
keyserver is deprecated and in any case it is questionable
whether we should keep on supporting these ancient and broken
keyservers. */
if((opt.keyserver_options.options&KEYSERVER_ADD_FAKE_V3) && opt.keyserver
&& (ascii_strcasecmp(opt.keyserver->scheme,"hkp")==0 ||
ascii_strcasecmp(opt.keyserver->scheme,"mailto")==0))
fakev3=1;
err = keyidlist (users, &desc, &numdesc, fakev3);
if (err)
return err;
count=numdesc;
if(count>0)
{
int i;
/* Try to handle preferred keyserver keys first */
for(i=0;i<numdesc;i++)
{
if(desc[i].skipfncvalue)
{
struct keyserver_spec *keyserver=desc[i].skipfncvalue;
if (!opt.quiet)
log_info (_("refreshing %d key from %s\n"), 1, keyserver->uri);
/* We use the keyserver structure we parsed out before.
Note that a preferred keyserver without a scheme://
will be interpreted as hkp:// */
err = keyserver_get (ctrl, &desc[i], 1, keyserver, 0, NULL, NULL);
if (err)
log_info(_("WARNING: unable to refresh key %s"
" via %s: %s\n"),keystr_from_desc(&desc[i]),
keyserver->uri,gpg_strerror (err));
else
{
/* We got it, so mark it as NONE so we don't try and
get it again from the regular keyserver. */
desc[i].mode=KEYDB_SEARCH_MODE_NONE;
count--;
}
free_keyserver_spec(keyserver);
}
}
}
if(count>0)
{
char *tmpuri;
err = gpg_dirmngr_ks_list (ctrl, &tmpuri);
if (!err)
{
if (!opt.quiet)
{
log_info (ngettext("refreshing %d key from %s\n",
"refreshing %d keys from %s\n",
count), count, tmpuri);
}
xfree (tmpuri);
err = keyserver_get (ctrl, desc, numdesc, NULL, 0, NULL, NULL);
}
}
xfree(desc);
opt.keyserver_options.import_options=options;
/* If the original options didn't have fast import, and the trustdb
is dirty, rebuild. */
if(!(opt.keyserver_options.import_options&IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
/* Search for keys on the keyservers. The patterns are given in the
string list TOKENS. */
gpg_error_t
keyserver_search (ctrl_t ctrl, strlist_t tokens)
{
gpg_error_t err;
char *searchstr;
struct search_line_handler_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!tokens)
return 0; /* Return success if no patterns are given. */
/* Write global options */
/* for(temp=opt.keyserver_options.other;temp;temp=temp->next) */
/* es_fprintf(spawn->tochild,"OPTION %s\n",temp->d); */
/* Write per-keyserver options */
/* for(temp=keyserver->options;temp;temp=temp->next) */
/* es_fprintf(spawn->tochild,"OPTION %s\n",temp->d); */
{
membuf_t mb;
strlist_t item;
init_membuf (&mb, 1024);
for (item = tokens; item; item = item->next)
{
if (item != tokens)
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, item->d);
}
put_membuf (&mb, "", 1); /* Append Nul. */
searchstr = get_membuf (&mb, NULL);
if (!searchstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* FIXME: Enable the next line */
/* log_info (_("searching for \"%s\" from %s\n"), searchstr, keyserver->uri); */
parm.ctrl = ctrl;
if (searchstr)
parm.searchstr_disp = utf8_to_native (searchstr, strlen (searchstr), 0);
err = gpg_dirmngr_ks_search (ctrl, searchstr, search_line_handler, &parm);
if (parm.not_found)
{
if (parm.searchstr_disp)
log_info (_("key \"%s\" not found on keyserver\n"),
parm.searchstr_disp);
else
log_info (_("key not found on keyserver\n"));
}
if (gpg_err_code (err) == GPG_ERR_NO_KEYSERVER)
log_error (_("no keyserver known (use option --keyserver)\n"));
else if (err)
log_error ("error searching keyserver: %s\n", gpg_strerror (err));
/* switch(ret) */
/* { */
/* case KEYSERVER_SCHEME_NOT_FOUND: */
/* log_error(_("no handler for keyserver scheme '%s'\n"), */
/* opt.keyserver->scheme); */
/* break; */
/* case KEYSERVER_NOT_SUPPORTED: */
/* log_error(_("action '%s' not supported with keyserver " */
/* "scheme '%s'\n"), "search", opt.keyserver->scheme); */
/* break; */
/* case KEYSERVER_TIMEOUT: */
/* log_error(_("keyserver timed out\n")); */
/* break; */
/* case KEYSERVER_INTERNAL_ERROR: */
/* default: */
/* log_error(_("keyserver internal error\n")); */
/* break; */
/* } */
/* return gpg_error (GPG_ERR_KEYSERVER); */
leave:
xfree (parm.desc);
xfree (parm.searchstr_disp);
xfree(searchstr);
return err;
}
/* Helper for keyserver_get. Here we only receive a chunk of the
description to be processed in one batch. This is required due to
the limited number of patterns the dirmngr interface (KS_GET) can
grok and to limit the amount of temporary required memory. */
static gpg_error_t
keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc,
int *r_ndesc_used,
import_stats_t stats_handle,
struct keyserver_spec *override_keyserver,
int quick,
unsigned char **r_fpr, size_t *r_fprlen)
{
gpg_error_t err = 0;
char **pattern;
int idx, npat;
estream_t datastream;
char *source = NULL;
size_t linelen; /* Estimated linelen for KS_GET. */
size_t n;
#define MAX_KS_GET_LINELEN 950 /* Somewhat lower than the real limit. */
*r_ndesc_used = 0;
/* Create an array filled with a search pattern for each key. The
array is delimited by a NULL entry. */
pattern = xtrycalloc (ndesc+1, sizeof *pattern);
if (!pattern)
return gpg_error_from_syserror ();
/* Note that we break the loop as soon as our estimation of the to
be used line length reaches the limit. But we do this only if we
have processed at least one search requests so that an overlong
single request will be rejected only later by gpg_dirmngr_ks_get
but we are sure that R_NDESC_USED has been updated. This avoids
a possible indefinite loop. */
linelen = 17; /* "KS_GET --quick --" */
for (npat=idx=0; idx < ndesc; idx++)
{
int quiet = 0;
if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[idx].mode == KEYDB_SEARCH_MODE_FPR16)
{
n = 1+2+2*20;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtrymalloc (n);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
{
strcpy (pattern[npat], "0x");
bin2hex (desc[idx].u.fpr,
desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16,
pattern[npat]+2);
npat++;
}
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
n = 1+2+16;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtryasprintf ("0x%08lX%08lX",
(ulong)desc[idx].u.kid[0],
(ulong)desc[idx].u.kid[1]);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
npat++;
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_SHORT_KID)
{
n = 1+2+8;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtryasprintf ("0x%08lX", (ulong)desc[idx].u.kid[1]);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
npat++;
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_EXACT)
{
/* The Dirmngr also uses classify_user_id to detect the type
of the search string. By adding the '=' prefix we force
Dirmngr's KS_GET to consider this an exact search string.
(In gpg 1.4 and gpg 2.0 the keyserver helpers used the
KS_GETNAME command to indicate this.) */
n = 1+1+strlen (desc[idx].u.name);
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = strconcat ("=", desc[idx].u.name, NULL);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
{
npat++;
quiet = 1;
}
}
else if (desc[idx].mode == KEYDB_SEARCH_MODE_NONE)
continue;
else
BUG();
if (err)
{
for (idx=0; idx < npat; idx++)
xfree (pattern[idx]);
xfree (pattern);
return err;
}
if (!quiet && override_keyserver)
{
if (override_keyserver->host)
log_info (_("requesting key %s from %s server %s\n"),
keystr_from_desc (&desc[idx]),
override_keyserver->scheme, override_keyserver->host);
else
log_info (_("requesting key %s from %s\n"),
keystr_from_desc (&desc[idx]), override_keyserver->uri);
}
}
/* Remember now many of search items were considered. Note that
this is different from NPAT. */
*r_ndesc_used = idx;
err = gpg_dirmngr_ks_get (ctrl, pattern, override_keyserver, quick,
&datastream, &source);
for (idx=0; idx < npat; idx++)
xfree (pattern[idx]);
xfree (pattern);
if (opt.verbose && source)
log_info ("data source: %s\n", source);
if (!err)
{
struct ks_retrieval_screener_arg_s screenerarg;
/* FIXME: Check whether this comment should be moved to dirmngr.
Slurp up all the key data. In the future, it might be nice
to look for KEY foo OUTOFBAND and FAILED indicators. It's
harmless to ignore them, but ignoring them does make gpg
complain about "no valid OpenPGP data found". One way to do
this could be to continue parsing this line-by-line and make
a temp iobuf for each key. Note that we don't allow the
import of secret keys from a keyserver. Keyservers should
never accept or send them but we better protect against rogue
keyservers. */
screenerarg.desc = desc;
screenerarg.ndesc = *r_ndesc_used;
import_keys_es_stream (ctrl, datastream, stats_handle,
r_fpr, r_fprlen,
(opt.keyserver_options.import_options
| IMPORT_NO_SECKEY),
keyserver_retrieval_screener, &screenerarg);
}
es_fclose (datastream);
xfree (source);
return err;
}
/* Retrieve a key from a keyserver. The search pattern are in
(DESC,NDESC). Allowed search modes are keyid, fingerprint, and
exact searches. OVERRIDE_KEYSERVER gives an optional override
keyserver. If (R_FPR,R_FPRLEN) are not NULL, they may return the
fingerprint of a single imported key. If QUICK is set, dirmngr is
advised to use a shorter timeout. */
static gpg_error_t
keyserver_get (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc,
struct keyserver_spec *override_keyserver, int quick,
unsigned char **r_fpr, size_t *r_fprlen)
{
gpg_error_t err;
import_stats_t stats_handle;
int ndesc_used;
int any_good = 0;
stats_handle = import_new_stats_handle();
for (;;)
{
err = keyserver_get_chunk (ctrl, desc, ndesc, &ndesc_used, stats_handle,
override_keyserver, quick, r_fpr, r_fprlen);
if (!err)
any_good = 1;
if (err || ndesc_used >= ndesc)
break; /* Error or all processed. */
/* Prepare for the next chunk. */
desc += ndesc_used;
ndesc -= ndesc_used;
}
if (any_good)
import_print_stats (stats_handle);
import_release_stats_handle (stats_handle);
return err;
}
/* Send all keys specified by KEYSPECS to the configured keyserver. */
static gpg_error_t
keyserver_put (ctrl_t ctrl, strlist_t keyspecs)
{
gpg_error_t err;
strlist_t kspec;
char *ksurl;
if (!keyspecs)
return 0; /* Return success if the list is empty. */
if (gpg_dirmngr_ks_list (ctrl, &ksurl))
{
log_error (_("no keyserver known\n"));
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
for (kspec = keyspecs; kspec; kspec = kspec->next)
{
void *data;
size_t datalen;
kbnode_t keyblock;
err = export_pubkey_buffer (ctrl, kspec->d,
opt.keyserver_options.export_options,
NULL,
&keyblock, &data, &datalen);
if (err)
log_error (_("skipped \"%s\": %s\n"), kspec->d, gpg_strerror (err));
else
{
log_info (_("sending key %s to %s\n"),
keystr (keyblock->pkt->pkt.public_key->keyid),
ksurl?ksurl:"[?]");
err = gpg_dirmngr_ks_put (ctrl, data, datalen, keyblock);
release_kbnode (keyblock);
xfree (data);
if (err)
{
write_status_error ("keyserver_send", err);
log_error (_("keyserver send failed: %s\n"), gpg_strerror (err));
}
}
}
xfree (ksurl);
return err;
}
/* Loop over all URLs in STRLIST and fetch the key at that URL. Note
that the fetch operation ignores the configured keyservers and
instead directly retrieves the keys. */
int
keyserver_fetch (ctrl_t ctrl, strlist_t urilist)
{
gpg_error_t err;
strlist_t sl;
estream_t datastream;
unsigned int save_options = opt.keyserver_options.import_options;
/* Switch on fast-import, since fetch can handle more than one
import and we don't want each set to rebuild the trustdb.
Instead we do it once at the end. */
opt.keyserver_options.import_options |= IMPORT_FAST;
for (sl=urilist; sl; sl=sl->next)
{
if (!opt.quiet)
log_info (_("requesting key from '%s'\n"), sl->d);
err = gpg_dirmngr_ks_fetch (ctrl, sl->d, &datastream);
if (!err)
{
import_stats_t stats_handle;
stats_handle = import_new_stats_handle();
import_keys_es_stream (ctrl, datastream, stats_handle, NULL, NULL,
opt.keyserver_options.import_options,
NULL, NULL);
import_print_stats (stats_handle);
import_release_stats_handle (stats_handle);
}
else
log_info (_("WARNING: unable to fetch URI %s: %s\n"),
sl->d, gpg_strerror (err));
es_fclose (datastream);
}
opt.keyserver_options.import_options = save_options;
/* If the original options didn't have fast import, and the trustdb
is dirty, rebuild. */
if (!(opt.keyserver_options.import_options&IMPORT_FAST))
check_or_update_trustdb (ctrl);
return 0;
}
/* Import key in a CERT or pointed to by a CERT. In DANE_MODE fetch
the certificate using the DANE method. */
int
keyserver_import_cert (ctrl_t ctrl, const char *name, int dane_mode,
unsigned char **fpr,size_t *fpr_len)
{
gpg_error_t err;
char *look,*url;
estream_t key;
look = xstrdup(name);
if (!dane_mode)
{
char *domain = strrchr (look,'@');
if (domain)
*domain='.';
}
err = gpg_dirmngr_dns_cert (ctrl, look, dane_mode? NULL : "*",
&key, fpr, fpr_len, &url);
if (err)
;
else if (key)
{
int armor_status=opt.no_armor;
/* CERTs and DANE records are always in binary format */
opt.no_armor=1;
err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len,
(opt.keyserver_options.import_options
| IMPORT_NO_SECKEY),
NULL, NULL);
opt.no_armor=armor_status;
es_fclose (key);
key = NULL;
}
else if (*fpr)
{
/* We only consider the IPGP type if a fingerprint was provided.
This lets us select the right key regardless of what a URL
points to, or get the key from a keyserver. */
if(url)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (url, 1);
if(spec)
{
err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0);
free_keyserver_spec(spec);
}
}
else if (keyserver_any_configured (ctrl))
{
/* If only a fingerprint is provided, try and fetch it from
the configured keyserver. */
err = keyserver_import_fprint (ctrl,
*fpr, *fpr_len, opt.keyserver, 0);
}
else
log_info(_("no keyserver known\n"));
/* Give a better string here? "CERT fingerprint for \"%s\"
found, but no keyserver" " known (use option
--keyserver)\n" ? */
}
xfree(url);
xfree(look);
return err;
}
/* Import key pointed to by a PKA record. Return the requested
fingerprint in fpr. */
gpg_error_t
keyserver_import_pka (ctrl_t ctrl, const char *name,
unsigned char **fpr, size_t *fpr_len)
{
gpg_error_t err;
char *url;
err = gpg_dirmngr_get_pka (ctrl, name, fpr, fpr_len, &url);
if (url && *url && fpr && fpr_len)
{
/* An URL is available. Lookup the key. */
struct keyserver_spec *spec;
spec = parse_keyserver_uri (url, 1);
if (spec)
{
err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0);
free_keyserver_spec (spec);
}
}
xfree (url);
if (err)
{
xfree(*fpr);
*fpr = NULL;
*fpr_len = 0;
}
return err;
}
/* Import a key using the Web Key Directory protocol. */
gpg_error_t
keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick,
unsigned char **fpr, size_t *fpr_len)
{
gpg_error_t err;
char *mbox;
estream_t key;
/* We want to work on the mbox. That is what dirmngr will do anyway
* and we need the mbox for the import filter anyway. */
mbox = mailbox_from_userid (name);
if (!mbox)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EINVAL)
err = gpg_error (GPG_ERR_INV_USER_ID);
return err;
}
err = gpg_dirmngr_wkd_get (ctrl, mbox, quick, &key);
if (err)
;
else if (key)
{
int armor_status = opt.no_armor;
import_filter_t save_filt;
/* Keys returned via WKD are in binary format. */
opt.no_armor = 1;
save_filt = save_and_clear_import_filter ();
if (!save_filt)
err = gpg_error_from_syserror ();
else
{
char *filtstr = es_bsprintf ("keep-uid=mbox = %s", mbox);
err = filtstr? 0 : gpg_error_from_syserror ();
if (!err)
err = parse_and_set_import_filter (filtstr);
xfree (filtstr);
if (!err)
err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len,
IMPORT_NO_SECKEY,
NULL, NULL);
}
restore_import_filter (save_filt);
opt.no_armor = armor_status;
es_fclose (key);
key = NULL;
}
xfree (mbox);
return err;
}
/* Import a key by name using LDAP */
int
keyserver_import_ldap (ctrl_t ctrl,
const char *name, unsigned char **fpr, size_t *fprlen)
{
(void)ctrl;
(void)name;
(void)fpr;
(void)fprlen;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/
#if 0
char *domain;
struct keyserver_spec *keyserver;
strlist_t list=NULL;
int rc,hostlen=1;
struct srventry *srvlist=NULL;
int srvcount,i;
char srvname[MAXDNAME];
/* Parse out the domain */
domain=strrchr(name,'@');
if(!domain)
return GPG_ERR_GENERAL;
domain++;
keyserver=xmalloc_clear(sizeof(struct keyserver_spec));
keyserver->scheme=xstrdup("ldap");
keyserver->host=xmalloc(1);
keyserver->host[0]='\0';
snprintf(srvname,MAXDNAME,"_pgpkey-ldap._tcp.%s",domain);
FIXME("network related - move to dirmngr or drop the code");
srvcount=getsrv(srvname,&srvlist);
for(i=0;i<srvcount;i++)
{
hostlen+=strlen(srvlist[i].target)+1;
keyserver->host=xrealloc(keyserver->host,hostlen);
strcat(keyserver->host,srvlist[i].target);
if(srvlist[i].port!=389)
{
char port[7];
hostlen+=6; /* a colon, plus 5 digits (unsigned 16-bit value) */
keyserver->host=xrealloc(keyserver->host,hostlen);
snprintf(port,7,":%u",srvlist[i].port);
strcat(keyserver->host,port);
}
strcat(keyserver->host," ");
}
free(srvlist);
/* If all else fails, do the PGP Universal trick of
ldap://keys.(domain) */
hostlen+=5+strlen(domain);
keyserver->host=xrealloc(keyserver->host,hostlen);
strcat(keyserver->host,"keys.");
strcat(keyserver->host,domain);
append_to_strlist(&list,name);
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/
/* keyserver_work (ctrl, KS_GETNAME, list, NULL, */
/* 0, fpr, fpr_len, keyserver); */
free_strlist(list);
free_keyserver_spec(keyserver);
return rc;
#endif
}
diff --git a/g10/main.h b/g10/main.h
index 6837e989e..f58f04162 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -1,495 +1,495 @@
/* main.h
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_MAIN_H
#define G10_MAIN_H
-#include "types.h"
-#include "iobuf.h"
+#include "../common/types.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
/* It could be argued that the default cipher should be 3DES rather
than AES128, and the default compression should be 0
(i.e. uncompressed) rather than 1 (zip). However, the real world
issues of speed and size come into play here. */
#if GPG_USE_AES128
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_AES
#elif GPG_USE_CAST5
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_CAST5
#else
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_3DES
#endif
#define DEFAULT_DIGEST_ALGO ((GNUPG)? DIGEST_ALGO_SHA256:DIGEST_ALGO_SHA1)
#define DEFAULT_S2K_DIGEST_ALGO DIGEST_ALGO_SHA1
#ifdef HAVE_ZIP
# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_ZIP
#else
# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_NONE
#endif
#define S2K_DIGEST_ALGO (opt.s2k_digest_algo?opt.s2k_digest_algo:DEFAULT_S2K_DIGEST_ALGO)
/* Various data objects. */
typedef struct
{
int header_okay;
PK_LIST pk_list;
DEK *symkey_dek;
STRING2KEY *symkey_s2k;
cipher_filter_context_t cfx;
} encrypt_filter_context_t;
struct groupitem
{
char *name;
strlist_t values;
struct groupitem *next;
};
struct weakhash
{
enum gcry_md_algos algo;
int rejection_shown;
struct weakhash *next;
};
/*-- gpg.c --*/
extern int g10_errors_seen;
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
void g10_exit(int rc) __attribute__ ((noreturn));
#else
void g10_exit(int rc);
#endif
void print_pubkey_algo_note (pubkey_algo_t algo);
void print_cipher_algo_note (cipher_algo_t algo);
void print_digest_algo_note (digest_algo_t algo);
void print_digest_rejected_note (enum gcry_md_algos algo);
void print_reported_error (gpg_error_t err, gpg_err_code_t skip_if_ec);
void print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2);
void additional_weak_digest (const char* digestname);
/*-- armor.c --*/
char *make_radix64_string( const byte *data, size_t len );
/*-- misc.c --*/
void trap_unaligned(void);
void register_secured_file (const char *fname);
void unregister_secured_file (const char *fname);
int is_secured_file (int fd);
int is_secured_filename (const char *fname);
u16 checksum_u16( unsigned n );
u16 checksum( byte *p, unsigned n );
u16 checksum_mpi( gcry_mpi_t a );
u32 buffer_to_u32( const byte *buffer );
const byte *get_session_marker( size_t *rlen );
enum gcry_cipher_algos map_cipher_openpgp_to_gcry (cipher_algo_t algo);
#define openpgp_cipher_open(_a,_b,_c,_d) \
gcry_cipher_open((_a),map_cipher_openpgp_to_gcry((_b)),(_c),(_d))
#define openpgp_cipher_get_algo_keylen(_a) \
gcry_cipher_get_algo_keylen(map_cipher_openpgp_to_gcry((_a)))
#define openpgp_cipher_get_algo_blklen(_a) \
gcry_cipher_get_algo_blklen(map_cipher_openpgp_to_gcry((_a)))
int openpgp_cipher_blocklen (cipher_algo_t algo);
int openpgp_cipher_test_algo(cipher_algo_t algo);
const char *openpgp_cipher_algo_name (cipher_algo_t algo);
pubkey_algo_t map_pk_gcry_to_openpgp (enum gcry_pk_algos algo);
int openpgp_pk_test_algo (pubkey_algo_t algo);
int openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use);
int openpgp_pk_algo_usage ( int algo );
const char *openpgp_pk_algo_name (pubkey_algo_t algo);
enum gcry_md_algos map_md_openpgp_to_gcry (digest_algo_t algo);
int openpgp_md_test_algo (digest_algo_t algo);
const char *openpgp_md_algo_name (int algo);
struct expando_args
{
PKT_public_key *pk;
PKT_public_key *pksk;
byte imagetype;
int validity_info;
const char *validity_string;
const byte *namehash;
};
char *pct_expando(const char *string,struct expando_args *args);
void deprecated_warning(const char *configname,unsigned int configlineno,
const char *option,const char *repl1,const char *repl2);
void deprecated_command (const char *name);
void obsolete_scdaemon_option (const char *configname,
unsigned int configlineno, const char *name);
int string_to_cipher_algo (const char *string);
int string_to_digest_algo (const char *string);
const char *compress_algo_to_string(int algo);
int string_to_compress_algo(const char *string);
int check_compress_algo(int algo);
int default_cipher_algo(void);
int default_compress_algo(void);
const char *compliance_option_string(void);
void compliance_failure(void);
struct parse_options
{
char *name;
unsigned int bit;
char **value;
char *help;
};
char *optsep(char **stringp);
char *argsplit(char *string);
int parse_options(char *str,unsigned int *options,
struct parse_options *opts,int noisy);
const char *get_libexecdir (void);
int path_access(const char *file,int mode);
int pubkey_get_npkey (pubkey_algo_t algo);
int pubkey_get_nskey (pubkey_algo_t algo);
int pubkey_get_nsig (pubkey_algo_t algo);
int pubkey_get_nenc (pubkey_algo_t algo);
/* Temporary helpers. */
unsigned int pubkey_nbits( int algo, gcry_mpi_t *pkey );
int mpi_print (estream_t stream, gcry_mpi_t a, int mode);
unsigned int ecdsa_qbits_from_Q (unsigned int qbits);
/*-- cpr.c --*/
void set_status_fd ( int fd );
int is_status_enabled ( void );
void write_status ( int no );
void write_status_error (const char *where, gpg_error_t err);
void write_status_errcode (const char *where, int errcode);
void write_status_failure (const char *where, gpg_error_t err);
void write_status_text ( int no, const char *text );
void write_status_printf (int no, const char *format,
...) GPGRT_ATTR_PRINTF(2,3);
void write_status_strings (int no, const char *text,
...) GPGRT_ATTR_SENTINEL(0);
void write_status_buffer ( int no,
const char *buffer, size_t len, int wrap );
void write_status_text_and_buffer ( int no, const char *text,
const char *buffer, size_t len, int wrap );
void write_status_begin_signing (gcry_md_hd_t md);
int cpr_enabled(void);
char *cpr_get( const char *keyword, const char *prompt );
char *cpr_get_no_help( const char *keyword, const char *prompt );
char *cpr_get_utf8( const char *keyword, const char *prompt );
char *cpr_get_hidden( const char *keyword, const char *prompt );
void cpr_kill_prompt(void);
int cpr_get_answer_is_yes_def (const char *keyword, const char *prompt,
int def_yes);
int cpr_get_answer_is_yes( const char *keyword, const char *prompt );
int cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt );
int cpr_get_answer_okay_cancel (const char *keyword,
const char *prompt,
int def_answer);
/*-- helptext.c --*/
void display_online_help( const char *keyword );
/*-- encode.c --*/
int setup_symkey (STRING2KEY **symkey_s2k,DEK **symkey_dek);
void encrypt_seskey (DEK *dek, DEK **seskey, byte *enckey);
int use_mdc (pk_list_t pk_list,int algo);
int encrypt_symmetric (const char *filename );
int encrypt_store (const char *filename );
int encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
strlist_t remusr, int use_symkey, pk_list_t provided_keys,
int outputfd);
void encrypt_crypt_files (ctrl_t ctrl,
int nfiles, char **files, strlist_t remusr);
int encrypt_filter (void *opaque, int control,
iobuf_t a, byte *buf, size_t *ret_len);
int write_pubkey_enc (PKT_public_key *pk, int throw_keyid,
DEK *dek, iobuf_t out);
/*-- sign.c --*/
int complete_sig (PKT_signature *sig, PKT_public_key *pksk, gcry_md_hd_t md,
const char *cache_nonce);
int sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int do_encrypt, strlist_t remusr, const char *outfile );
int clearsign_file (ctrl_t ctrl,
const char *fname, strlist_t locusr, const char *outfile);
int sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr);
/*-- sig-check.c --*/
/* SIG is a revocation signature. Check if any of PK's designated
revokers generated it. If so, return 0. Note: this function
(correctly) doesn't care if the designated revoker is revoked. */
int check_revocation_keys (PKT_public_key *pk, PKT_signature *sig);
/* Check that the backsig BACKSIG from the subkey SUB_PK to its
primary key MAIN_PK is valid. */
int check_backsig(PKT_public_key *main_pk,PKT_public_key *sub_pk,
PKT_signature *backsig);
/* Check that the signature SIG over a key (e.g., a key binding or a
key revocation) is valid. (To check signatures over data, use
check_signature.) */
int check_key_signature( KBNODE root, KBNODE sig, int *is_selfsig );
/* Like check_key_signature, but with the ability to specify some
additional parameters and get back additional information. See the
documentation for the implementation for details. */
int check_key_signature2( KBNODE root, KBNODE node, PKT_public_key *check_pk,
PKT_public_key *ret_pk, int *is_selfsig,
u32 *r_expiredate, int *r_expired );
/* Returns whether SIGNER generated the signature SIG over the packet
PACKET, which is a key, subkey or uid, and comes from the key block
KB. If SIGNER is NULL, it is looked up based on the information in
SIG. If not NULL, sets *IS_SELFSIG to indicate whether the
signature is a self-signature and *RET_PK to a copy of the signer's
key. */
gpg_error_t check_signature_over_key_or_uid (PKT_public_key *signer,
PKT_signature *sig,
KBNODE kb, PACKET *packet,
int *is_selfsig,
PKT_public_key *ret_pk);
/*-- delkey.c --*/
gpg_error_t delete_keys (strlist_t names, int secret, int allow_both);
/*-- keyedit.c --*/
void keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check );
void keyedit_passwd (ctrl_t ctrl, const char *username);
void keyedit_quick_adduid (ctrl_t ctrl, const char *username,
const char *newuid);
void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr);
void keyedit_quick_revuid (ctrl_t ctrl, const char *username,
const char *uidtorev);
void keyedit_quick_sign (ctrl_t ctrl, const char *fpr,
strlist_t uids, strlist_t locusr, int local);
void keyedit_quick_set_expire (ctrl_t ctrl,
const char *fpr, const char *expirestr);
void show_basic_key_info (KBNODE keyblock);
/*-- keygen.c --*/
const char *get_default_pubkey_algo (void);
u32 parse_expire_string(const char *string);
u32 ask_expire_interval(int object,const char *def_expire);
u32 ask_expiredate(void);
unsigned int ask_key_flags (int algo, int subkey, unsigned int current);
void quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr);
void generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key);
int keygen_set_std_prefs (const char *string,int personal);
PKT_user_id *keygen_get_std_prefs (void);
int keygen_add_key_expire( PKT_signature *sig, void *opaque );
int keygen_add_key_flags (PKT_signature *sig, void *opaque);
int keygen_add_std_prefs( PKT_signature *sig, void *opaque );
int keygen_upd_std_prefs( PKT_signature *sig, void *opaque );
int keygen_add_keyserver_url(PKT_signature *sig, void *opaque);
int keygen_add_notations(PKT_signature *sig,void *opaque);
int keygen_add_revkey(PKT_signature *sig, void *opaque);
gpg_error_t make_backsig (PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce);
gpg_error_t generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock,
const char *algostr,
const char *usagestr,
const char *expirestr);
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t generate_card_subkeypair (kbnode_t pub_keyblock,
int keyno, const char *serialno);
#endif
/*-- openfile.c --*/
int overwrite_filep( const char *fname );
char *make_outfile_name( const char *iname );
char *ask_outfile_name( const char *name, size_t namelen );
int open_outfile (int inp_fd, const char *iname, int mode,
int restrictedperm, iobuf_t *a);
char *get_matching_datafile (const char *sigfilename);
iobuf_t open_sigfile (const char *sigfilename, progress_filter_context_t *pfx);
void try_make_homedir( const char *fname );
char *get_openpgp_revocdir (const char *home);
/*-- seskey.c --*/
void make_session_key( DEK *dek );
gcry_mpi_t encode_session_key( int openpgp_pk_algo, DEK *dek, unsigned nbits );
gcry_mpi_t encode_md_value (PKT_public_key *pk,
gcry_md_hd_t md, int hash_algo );
/*-- import.c --*/
struct import_stats_s;
typedef struct import_stats_s *import_stats_t;
struct import_filter_s;
typedef struct import_filter_s *import_filter_t;
typedef gpg_error_t (*import_screener_t)(kbnode_t keyblock, void *arg);
int parse_import_options(char *str,unsigned int *options,int noisy);
gpg_error_t parse_and_set_import_filter (const char *string);
import_filter_t save_and_clear_import_filter (void);
void restore_import_filter (import_filter_t filt);
gpg_error_t read_key_from_file (ctrl_t ctrl, const char *fname,
kbnode_t *r_keyblock);
void import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_hd, unsigned int options);
int import_keys_stream (ctrl_t ctrl, iobuf_t inp, import_stats_t stats_hd,
unsigned char **fpr,
size_t *fpr_len, unsigned int options);
int import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg);
gpg_error_t import_old_secring (ctrl_t ctrl, const char *fname);
import_stats_t import_new_stats_handle (void);
void import_release_stats_handle (import_stats_t hd);
void import_print_stats (import_stats_t hd);
const char *impex_filter_getval (void *cookie, const char *propname);
gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force);
int collapse_uids( KBNODE *keyblock );
/*-- export.c --*/
struct export_stats_s;
typedef struct export_stats_s *export_stats_t;
export_stats_t export_new_stats (void);
void export_release_stats (export_stats_t stats);
void export_print_stats (export_stats_t stats);
int parse_export_options(char *str,unsigned int *options,int noisy);
gpg_error_t parse_and_set_export_filter (const char *string);
int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
int export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
int export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec,
unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock,
void **r_data, size_t *r_datalen);
gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext,
char **cache_nonce_addr,
const char *hexgrip,
PKT_public_key *pk);
gpg_error_t write_keyblock_to_output (kbnode_t keyblock,
int with_armor, unsigned int options);
gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid);
/*-- dearmor.c --*/
int dearmor_file( const char *fname );
int enarmor_file( const char *fname );
/*-- revoke.c --*/
struct revocation_reason_info;
int gen_standard_revoke (PKT_public_key *psk, const char *cache_nonce);
int gen_revoke( const char *uname );
int gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr);
int revocation_reason_build_cb( PKT_signature *sig, void *opaque );
struct revocation_reason_info *
ask_revocation_reason( int key_rev, int cert_rev, int hint );
struct revocation_reason_info * get_default_uid_revocation_reason(void);
void release_revocation_reason_info( struct revocation_reason_info *reason );
/*-- keylist.c --*/
void public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode );
void secret_key_list (ctrl_t ctrl, strlist_t list );
void print_subpackets_colon(PKT_signature *sig);
void reorder_keyblock (KBNODE keyblock);
void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret,
int has_secret, int fpr, int no_validity);
void print_fingerprint (estream_t fp, PKT_public_key *pk, int mode);
void print_revokers (estream_t fp, PKT_public_key *pk);
void show_policy_url(PKT_signature *sig,int indent,int mode);
void show_keyserver_url(PKT_signature *sig,int indent,int mode);
void show_notation(PKT_signature *sig,int indent,int mode,int which);
void dump_attribs (const PKT_user_id *uid, PKT_public_key *pk);
void set_attrib_fd(int fd);
char *format_seckey_info (PKT_public_key *pk);
void print_seckey_info (PKT_public_key *pk);
void print_pubkey_info (estream_t fp, PKT_public_key *pk);
void print_card_key_info (estream_t fp, KBNODE keyblock);
void print_key_line (estream_t fp, PKT_public_key *pk, int secret);
/*-- verify.c --*/
void print_file_status( int status, const char *name, int what );
int verify_signatures (ctrl_t ctrl, int nfiles, char **files );
int verify_files (ctrl_t ctrl, int nfiles, char **files );
int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp);
/*-- decrypt.c --*/
int decrypt_message (ctrl_t ctrl, const char *filename );
gpg_error_t decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd);
void decrypt_messages (ctrl_t ctrl, int nfiles, char *files[]);
/*-- plaintext.c --*/
int hash_datafiles( gcry_md_hd_t md, gcry_md_hd_t md2,
strlist_t files, const char *sigfilename, int textmode);
int hash_datafile_by_fd ( gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd,
int textmode );
PKT_plaintext *setup_plaintext_name(const char *filename,IOBUF iobuf);
/*-- server.c --*/
int gpg_server (ctrl_t);
gpg_error_t gpg_proxy_pinentry_notify (ctrl_t ctrl,
const unsigned char *line);
#ifdef ENABLE_CARD_SUPPORT
/*-- card-util.c --*/
void change_pin (int no, int allow_admin);
void card_status (estream_t fp, char *serialno, size_t serialnobuflen);
void card_edit (ctrl_t ctrl, strlist_t commands);
gpg_error_t card_generate_subkey (KBNODE pub_keyblock);
int card_store_subkey (KBNODE node, int use);
#endif
#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6))
/*-- migrate.c --*/
void migrate_secring (ctrl_t ctrl);
#endif /*G10_MAIN_H*/
diff --git a/g10/mainproc.c b/g10/mainproc.c
index 4f8d0be3f..2acd51e83 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -1,2491 +1,2491 @@
/* mainproc.c - handle packets
* Copyright (C) 1998-2009 Free Software Foundation, Inc.
* Copyright (C) 2013-2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "options.h"
#include "keydb.h"
#include "filter.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#include "trustdb.h"
#include "keyserver-internal.h"
#include "photoid.h"
-#include "mbox-util.h"
+#include "../common/mbox-util.h"
#include "call-dirmngr.h"
/* Put an upper limit on nested packets. The 32 is an arbitrary
value, a much lower should actually be sufficient. */
#define MAX_NESTING_DEPTH 32
/* An object to build a list of keyid related info. */
struct kidlist_item
{
struct kidlist_item *next;
u32 kid[2];
int pubkey_algo;
int reason;
};
/*
* Object to hold the processing context.
*/
typedef struct mainproc_context *CTX;
struct mainproc_context
{
ctrl_t ctrl;
struct mainproc_context *anchor; /* May be useful in the future. */
PKT_public_key *last_pubkey;
PKT_user_id *last_user_id;
md_filter_context_t mfx;
int sigs_only; /* Process only signatures and reject all other stuff. */
int encrypt_only; /* Process only encryption messages. */
/* Name of the file with the complete signature or the file with the
detached signature. This is currently only used to deduce the
file name of the data file if that has not been given. */
const char *sigfilename;
/* A structure to describe the signed data in case of a detached
signature. */
struct
{
/* A file descriptor of the signed data. Only used if not -1. */
int data_fd;
/* A list of filenames with the data files or NULL. This is only
used if DATA_FD is -1. */
strlist_t data_names;
/* Flag to indicated that either one of the next previous fields
is used. This is only needed for better readability. */
int used;
} signed_data;
DEK *dek;
int last_was_session_key;
kbnode_t list; /* The current list of packets. */
iobuf_t iobuf; /* Used to get the filename etc. */
int trustletter; /* Temporary usage in list_node. */
ulong symkeys;
struct kidlist_item *pkenc_list; /* List of encryption packets. */
struct {
unsigned int sig_seen:1; /* Set to true if a signature packet
has been seen. */
unsigned int data:1; /* Any data packet seen */
unsigned int uncompress_failed:1;
} any;
};
/*** Local prototypes. ***/
static int do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a);
static void list_node (CTX c, kbnode_t node);
static void proc_tree (CTX c, kbnode_t node);
static int literals_seen;
/*** Functions. ***/
void
reset_literals_seen(void)
{
literals_seen = 0;
}
static void
release_list( CTX c )
{
proc_tree (c, c->list);
release_kbnode (c->list);
while (c->pkenc_list)
{
struct kidlist_item *tmp = c->pkenc_list->next;
xfree (c->pkenc_list);
c->pkenc_list = tmp;
}
c->pkenc_list = NULL;
c->list = NULL;
c->any.data = 0;
c->any.uncompress_failed = 0;
c->last_was_session_key = 0;
xfree (c->dek);
c->dek = NULL;
}
static int
add_onepass_sig (CTX c, PACKET *pkt)
{
kbnode_t node;
if (c->list) /* Add another packet. */
add_kbnode (c->list, new_kbnode (pkt));
else /* Insert the first one. */
c->list = node = new_kbnode (pkt);
return 1;
}
static int
add_gpg_control (CTX c, PACKET *pkt)
{
if ( pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START )
{
/* New clear text signature.
* Process the last one and reset everything */
release_list(c);
}
if (c->list) /* Add another packet. */
add_kbnode (c->list, new_kbnode (pkt));
else /* Insert the first one. */
c->list = new_kbnode (pkt);
return 1;
}
static int
add_user_id (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("orphaned user ID\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_subkey (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("subkey w/o mainkey\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_ring_trust (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("ring trust w/o key\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_signature (CTX c, PACKET *pkt)
{
kbnode_t node;
c->any.sig_seen = 1;
if (pkt->pkttype == PKT_SIGNATURE && !c->list)
{
/* This is the first signature for the following datafile.
* GPG does not write such packets; instead it always uses
* onepass-sig packets. The drawback of PGP's method
* of prepending the signature to the data is
* that it is not possible to make a signature from data read
* from stdin. (GPG is able to read PGP stuff anyway.) */
node = new_kbnode (pkt);
c->list = node;
return 1;
}
else if (!c->list)
return 0; /* oops (invalid packet sequence)*/
else if (!c->list->pkt)
BUG(); /* so nicht */
/* Add a new signature node item at the end. */
node = new_kbnode (pkt);
add_kbnode (c->list, node);
return 1;
}
static int
symkey_decrypt_seskey (DEK *dek, byte *seskey, size_t slen)
{
gcry_cipher_hd_t hd;
if(slen < 17 || slen > 33)
{
log_error ( _("weird size for an encrypted session key (%d)\n"),
(int)slen);
return GPG_ERR_BAD_KEY;
}
if (openpgp_cipher_open (&hd, dek->algo, GCRY_CIPHER_MODE_CFB, 1))
BUG ();
if (gcry_cipher_setkey ( hd, dek->key, dek->keylen ))
BUG ();
gcry_cipher_setiv ( hd, NULL, 0 );
gcry_cipher_decrypt ( hd, seskey, slen, NULL, 0 );
gcry_cipher_close ( hd );
/* Now we replace the dek components with the real session key to
decrypt the contents of the sequencing packet. */
dek->keylen=slen-1;
dek->algo=seskey[0];
if(dek->keylen > DIM(dek->key))
BUG ();
memcpy(dek->key, seskey + 1, dek->keylen);
/*log_hexdump( "thekey", dek->key, dek->keylen );*/
return 0;
}
static void
proc_symkey_enc (CTX c, PACKET *pkt)
{
PKT_symkey_enc *enc;
enc = pkt->pkt.symkey_enc;
if (!enc)
log_error ("invalid symkey encrypted packet\n");
else if(!c->dek)
{
int algo = enc->cipher_algo;
const char *s = openpgp_cipher_algo_name (algo);
if (!openpgp_cipher_test_algo (algo))
{
if (!opt.quiet)
{
if (enc->seskeylen)
log_info (_("%s encrypted session key\n"), s );
else
log_info (_("%s encrypted data\n"), s );
}
}
else
log_error (_("encrypted with unknown algorithm %d\n"), algo);
if (openpgp_md_test_algo (enc->s2k.hash_algo))
{
log_error(_("passphrase generated with unknown digest"
" algorithm %d\n"),enc->s2k.hash_algo);
s = NULL;
}
c->last_was_session_key = 2;
if (!s || opt.list_only)
goto leave;
if (opt.override_session_key)
{
c->dek = xmalloc_clear (sizeof *c->dek);
if (get_override_session_key (c->dek, opt.override_session_key))
{
xfree (c->dek);
c->dek = NULL;
}
}
else
{
c->dek = passphrase_to_dek (algo, &enc->s2k, 0, 0, NULL, NULL);
if (c->dek)
{
c->dek->symmetric = 1;
/* FIXME: This doesn't work perfectly if a symmetric key
comes before a public key in the message - if the
user doesn't know the passphrase, then there is a
chance that the "decrypted" algorithm will happen to
be a valid one, which will make the returned dek
appear valid, so we won't try any public keys that
come later. */
if (enc->seskeylen)
{
if (symkey_decrypt_seskey (c->dek,
enc->seskey, enc->seskeylen))
{
xfree (c->dek);
c->dek = NULL;
}
}
else
c->dek->algo_info_printed = 1;
}
}
}
leave:
c->symkeys++;
free_packet (pkt);
}
static void
proc_pubkey_enc (ctrl_t ctrl, CTX c, PACKET *pkt)
{
PKT_pubkey_enc *enc;
int result = 0;
/* Check whether the secret key is available and store in this case. */
c->last_was_session_key = 1;
enc = pkt->pkt.pubkey_enc;
/*printf("enc: encrypted by a pubkey with keyid %08lX\n", enc->keyid[1] );*/
/* Hmmm: why do I have this algo check here - anyway there is
* function to check it. */
if (opt.verbose)
log_info (_("public key is %s\n"), keystr (enc->keyid));
if (is_status_enabled())
{
char buf[50];
/* FIXME: For ECC support we need to map the OpenPGP algo number
to the Libgcrypt defined one. This is due a chicken-egg
problem: We need to have code in Libgcrypt for a new
algorithm so to implement a proposed new algorithm before the
IANA will finally assign an OpenPGP identifier. */
snprintf (buf, sizeof buf, "%08lX%08lX %d 0",
(ulong)enc->keyid[0], (ulong)enc->keyid[1], enc->pubkey_algo);
write_status_text (STATUS_ENC_TO, buf);
}
if (!opt.list_only && opt.override_session_key)
{
/* It does not make much sense to store the session key in
* secure memory because it has already been passed on the
* command line and the GCHQ knows about it. */
c->dek = xmalloc_clear (sizeof *c->dek);
result = get_override_session_key (c->dek, opt.override_session_key);
if (result)
{
xfree (c->dek);
c->dek = NULL;
}
}
else if (enc->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E
|| enc->pubkey_algo == PUBKEY_ALGO_ECDH
|| enc->pubkey_algo == PUBKEY_ALGO_RSA
|| enc->pubkey_algo == PUBKEY_ALGO_RSA_E
|| enc->pubkey_algo == PUBKEY_ALGO_ELGAMAL)
{
/* Note that we also allow type 20 Elgamal keys for decryption.
There are still a couple of those keys in active use as a
subkey. */
/* FIXME: Store this all in a list and process it later so that
we can prioritize what key to use. This gives a better user
experience if wildcard keyids are used. */
if (!c->dek && ((!enc->keyid[0] && !enc->keyid[1])
|| opt.try_all_secrets
|| have_secret_key_with_kid (enc->keyid)))
{
if(opt.list_only)
result = -1;
else
{
c->dek = xmalloc_secure_clear (sizeof *c->dek);
if ((result = get_session_key (ctrl, enc, c->dek)))
{
/* Error: Delete the DEK. */
xfree (c->dek);
c->dek = NULL;
}
}
}
else
result = GPG_ERR_NO_SECKEY;
}
else
result = GPG_ERR_PUBKEY_ALGO;
if (result == -1)
;
else
{
/* Store it for later display. */
struct kidlist_item *x = xmalloc (sizeof *x);
x->kid[0] = enc->keyid[0];
x->kid[1] = enc->keyid[1];
x->pubkey_algo = enc->pubkey_algo;
x->reason = result;
x->next = c->pkenc_list;
c->pkenc_list = x;
if (!result && opt.verbose > 1)
log_info (_("public key encrypted data: good DEK\n"));
}
free_packet(pkt);
}
/*
* Print the list of public key encrypted packets which we could
* not decrypt.
*/
static void
print_pkenc_list (struct kidlist_item *list, int failed)
{
for (; list; list = list->next)
{
PKT_public_key *pk;
const char *algstr;
if (failed && !list->reason)
continue;
if (!failed && list->reason)
continue;
algstr = openpgp_pk_algo_name (list->pubkey_algo);
pk = xmalloc_clear (sizeof *pk);
if (!algstr)
algstr = "[?]";
pk->pubkey_algo = list->pubkey_algo;
if (!get_pubkey (pk, list->kid))
{
char *p;
log_info (_("encrypted with %u-bit %s key, ID %s, created %s\n"),
nbits_from_pk (pk), algstr, keystr_from_pk(pk),
strtimestamp (pk->timestamp));
p = get_user_id_native (list->kid);
log_printf (_(" \"%s\"\n"), p);
xfree (p);
}
else
log_info (_("encrypted with %s key, ID %s\n"),
algstr, keystr(list->kid));
free_public_key (pk);
if (gpg_err_code (list->reason) == GPG_ERR_NO_SECKEY)
{
if (is_status_enabled())
{
char buf[20];
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong)list->kid[0], (ulong)list->kid[1]);
write_status_text (STATUS_NO_SECKEY, buf);
}
}
else if (list->reason)
{
log_info (_("public key decryption failed: %s\n"),
gpg_strerror (list->reason));
write_status_error ("pkdecrypt_failed", list->reason);
}
}
}
static void
proc_encrypted (CTX c, PACKET *pkt)
{
int result = 0;
if (!opt.quiet)
{
if (c->symkeys>1)
log_info (_("encrypted with %lu passphrases\n"), c->symkeys);
else if (c->symkeys == 1)
log_info (_("encrypted with 1 passphrase\n"));
print_pkenc_list ( c->pkenc_list, 1 );
print_pkenc_list ( c->pkenc_list, 0 );
}
/* FIXME: Figure out the session key by looking at all pkenc packets. */
write_status (STATUS_BEGIN_DECRYPTION);
/*log_debug("dat: %sencrypted data\n", c->dek?"":"conventional ");*/
if (opt.list_only)
result = -1;
else if (!c->dek && !c->last_was_session_key)
{
int algo;
STRING2KEY s2kbuf;
STRING2KEY *s2k = NULL;
int canceled;
if (opt.override_session_key)
{
c->dek = xmalloc_clear (sizeof *c->dek);
result = get_override_session_key (c->dek, opt.override_session_key);
if (result)
{
xfree (c->dek);
c->dek = NULL;
}
}
else
{
/* Assume this is old style conventional encrypted data. */
algo = opt.def_cipher_algo;
if (algo)
log_info (_("assuming %s encrypted data\n"),
openpgp_cipher_algo_name (algo));
else if (openpgp_cipher_test_algo (CIPHER_ALGO_IDEA))
{
algo = opt.def_cipher_algo;
if (!algo)
algo = opt.s2k_cipher_algo;
log_info (_("IDEA cipher unavailable, "
"optimistically attempting to use %s instead\n"),
openpgp_cipher_algo_name (algo));
}
else
{
algo = CIPHER_ALGO_IDEA;
if (!opt.s2k_digest_algo)
{
/* If no digest is given we assume SHA-1. */
s2kbuf.mode = 0;
s2kbuf.hash_algo = DIGEST_ALGO_SHA1;
s2k = &s2kbuf;
}
log_info (_("assuming %s encrypted data\n"), "IDEA");
}
c->dek = passphrase_to_dek (algo, s2k, 0, 0, NULL, &canceled);
if (c->dek)
c->dek->algo_info_printed = 1;
else if (canceled)
result = gpg_error (GPG_ERR_CANCELED);
else
result = gpg_error (GPG_ERR_INV_PASSPHRASE);
}
}
else if (!c->dek)
result = GPG_ERR_NO_SECKEY;
if (!result)
result = decrypt_data (c->ctrl, c, pkt->pkt.encrypted, c->dek );
if (result == -1)
;
else if (!result
&& !opt.ignore_mdc_error
&& !pkt->pkt.encrypted->mdc_method
&& openpgp_cipher_get_algo_blklen (c->dek->algo) != 8
&& c->dek->algo != CIPHER_ALGO_TWOFISH)
{
/* The message has been decrypted but has no MDC despite that a
modern cipher (blocklength != 64 bit, except for Twofish) is
used and the option to ignore MDC errors is not used: To
avoid attacks changing an MDC message to a non-MDC message,
we fail here. */
log_error (_("WARNING: message was not integrity protected\n"));
if (opt.verbose > 1)
log_info ("decryption forced to fail\n");
write_status (STATUS_DECRYPTION_FAILED);
}
else if (!result || (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE
&& opt.ignore_mdc_error))
{
write_status (STATUS_DECRYPTION_OKAY);
if (opt.verbose > 1)
log_info(_("decryption okay\n"));
if (pkt->pkt.encrypted->mdc_method && !result)
write_status (STATUS_GOODMDC);
else if (!opt.no_mdc_warn)
log_info (_("WARNING: message was not integrity protected\n"));
}
else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE)
{
glo_ctrl.lasterr = result;
log_error (_("WARNING: encrypted message has been manipulated!\n"));
write_status (STATUS_BADMDC);
write_status (STATUS_DECRYPTION_FAILED);
}
else
{
if (gpg_err_code (result) == GPG_ERR_BAD_KEY
&& *c->dek->s2k_cacheid != '\0')
{
if (opt.debug)
log_debug ("cleared passphrase cached with ID: %s\n",
c->dek->s2k_cacheid);
passphrase_clear_cache (c->dek->s2k_cacheid);
}
glo_ctrl.lasterr = result;
write_status (STATUS_DECRYPTION_FAILED);
log_error (_("decryption failed: %s\n"), gpg_strerror (result));
/* Hmmm: does this work when we have encrypted using multiple
* ways to specify the session key (symmmetric and PK). */
}
xfree (c->dek);
c->dek = NULL;
free_packet (pkt);
c->last_was_session_key = 0;
write_status (STATUS_END_DECRYPTION);
}
static void
proc_plaintext( CTX c, PACKET *pkt )
{
PKT_plaintext *pt = pkt->pkt.plaintext;
int any, clearsig, rc;
kbnode_t n;
literals_seen++;
if (pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8))
log_info (_("Note: sender requested \"for-your-eyes-only\"\n"));
else if (opt.verbose)
log_info (_("original file name='%.*s'\n"), pt->namelen, pt->name);
free_md_filter_context (&c->mfx);
if (gcry_md_open (&c->mfx.md, 0, 0))
BUG ();
/* fixme: we may need to push the textfilter if we have sigclass 1
* and no armoring - Not yet tested
* Hmmm, why don't we need it at all if we have sigclass 1
* Should we assume that plaintext in mode 't' has always sigclass 1??
* See: Russ Allbery's mail 1999-02-09
*/
any = clearsig = 0;
for (n=c->list; n; n = n->next )
{
if (n->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* The onepass signature case. */
if (n->pkt->pkt.onepass_sig->digest_algo)
{
gcry_md_enable (c->mfx.md, n->pkt->pkt.onepass_sig->digest_algo);
any = 1;
}
}
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START)
{
/* The clearsigned message case. */
size_t datalen = n->pkt->pkt.gpg_control->datalen;
const byte *data = n->pkt->pkt.gpg_control->data;
/* Check that we have at least the sigclass and one hash. */
if (datalen < 2)
log_fatal ("invalid control packet CTRLPKT_CLEARSIGN_START\n");
/* Note that we don't set the clearsig flag for not-dash-escaped
* documents. */
clearsig = (*data == 0x01);
for (data++, datalen--; datalen; datalen--, data++)
gcry_md_enable (c->mfx.md, *data);
any = 1;
break; /* Stop here as one-pass signature packets are not
expected. */
}
else if (n->pkt->pkttype == PKT_SIGNATURE)
{
/* The SIG+LITERAL case that PGP used to use. */
gcry_md_enable ( c->mfx.md, n->pkt->pkt.signature->digest_algo );
any = 1;
}
}
if (!any && !opt.skip_verify)
{
/* This is for the old GPG LITERAL+SIG case. It's not legal
according to 2440, so hopefully it won't come up that often.
There is no good way to specify what algorithms to use in
that case, so these there are the historical answer. */
gcry_md_enable (c->mfx.md, DIGEST_ALGO_RMD160);
gcry_md_enable (c->mfx.md, DIGEST_ALGO_SHA1);
}
if (DBG_HASHING)
{
gcry_md_debug (c->mfx.md, "verify");
if (c->mfx.md2)
gcry_md_debug (c->mfx.md2, "verify2");
}
rc=0;
if (literals_seen > 1)
{
log_info (_("WARNING: multiple plaintexts seen\n"));
if (!opt.flags.allow_multiple_messages)
{
write_status_text (STATUS_ERROR, "proc_pkt.plaintext 89_BAD_DATA");
log_inc_errorcount ();
rc = gpg_error (GPG_ERR_UNEXPECTED);
}
}
if (!rc)
{
/* It we are in --verify mode, we do not want to output the
* signed text. However, if --output is also used we do what
* has been requested and write out the signed data. */
rc = handle_plaintext (pt, &c->mfx,
(opt.outfp || opt.outfile)? 0 : c->sigs_only,
clearsig);
if (gpg_err_code (rc) == GPG_ERR_EACCES && !c->sigs_only)
{
/* Can't write output but we hash it anyway to check the
signature. */
rc = handle_plaintext( pt, &c->mfx, 1, clearsig );
}
}
if (rc)
log_error ("handle plaintext failed: %s\n", gpg_strerror (rc));
free_packet(pkt);
c->last_was_session_key = 0;
/* We add a marker control packet instead of the plaintext packet.
* This is so that we can later detect invalid packet sequences. */
n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, NULL, 0));
if (c->list)
add_kbnode (c->list, n);
else
c->list = n;
}
static int
proc_compressed_cb (iobuf_t a, void *info)
{
if ( ((CTX)info)->signed_data.used
&& ((CTX)info)->signed_data.data_fd != -1)
return proc_signature_packets_by_fd (((CTX)info)->ctrl, info, a,
((CTX)info)->signed_data.data_fd);
else
return proc_signature_packets (((CTX)info)->ctrl, info, a,
((CTX)info)->signed_data.data_names,
((CTX)info)->sigfilename );
}
static int
proc_encrypt_cb (iobuf_t a, void *info )
{
CTX c = info;
return proc_encryption_packets (c->ctrl, info, a );
}
static int
proc_compressed (CTX c, PACKET *pkt)
{
PKT_compressed *zd = pkt->pkt.compressed;
int rc;
/*printf("zip: compressed data packet\n");*/
if (c->sigs_only)
rc = handle_compressed (c->ctrl, c, zd, proc_compressed_cb, c);
else if( c->encrypt_only )
rc = handle_compressed (c->ctrl, c, zd, proc_encrypt_cb, c);
else
rc = handle_compressed (c->ctrl, c, zd, NULL, NULL);
if (gpg_err_code (rc) == GPG_ERR_BAD_DATA)
{
if (!c->any.uncompress_failed)
{
CTX cc;
for (cc=c; cc; cc = cc->anchor)
cc->any.uncompress_failed = 1;
log_error ("uncompressing failed: %s\n", gpg_strerror (rc));
}
}
else if (rc)
log_error ("uncompressing failed: %s\n", gpg_strerror (rc));
free_packet(pkt);
c->last_was_session_key = 0;
return rc;
}
/*
* Check the signature. If R_PK is not NULL a copy of the public key
* used to verify the signature will be stored tehre, or NULL if not
* found. Returns: 0 = valid signature or an error code
*/
static int
do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
int *is_expkey, int *is_revkey, PKT_public_key **r_pk)
{
PKT_signature *sig;
gcry_md_hd_t md = NULL;
gcry_md_hd_t md2 = NULL;
gcry_md_hd_t md_good = NULL;
int algo, rc;
if (r_pk)
*r_pk = NULL;
log_assert (node->pkt->pkttype == PKT_SIGNATURE);
if (is_selfsig)
*is_selfsig = 0;
sig = node->pkt->pkt.signature;
algo = sig->digest_algo;
rc = openpgp_md_test_algo (algo);
if (rc)
return rc;
if (sig->sig_class == 0x00)
{
if (c->mfx.md)
{
if (gcry_md_copy (&md, c->mfx.md ))
BUG ();
}
else /* detached signature */
{
/* check_signature() will enable the md. */
if (gcry_md_open (&md, 0, 0 ))
BUG ();
}
}
else if (sig->sig_class == 0x01)
{
/* How do we know that we have to hash the (already hashed) text
in canonical mode ??? (calculating both modes???) */
if (c->mfx.md)
{
if (gcry_md_copy (&md, c->mfx.md ))
BUG ();
if (c->mfx.md2 && gcry_md_copy (&md2, c->mfx.md2))
BUG ();
}
else /* detached signature */
{
log_debug ("Do we really need this here?");
/* check_signature() will enable the md*/
if (gcry_md_open (&md, 0, 0 ))
BUG ();
if (gcry_md_open (&md2, 0, 0 ))
BUG ();
}
}
else if ((sig->sig_class&~3) == 0x10
|| sig->sig_class == 0x18
|| sig->sig_class == 0x1f
|| sig->sig_class == 0x20
|| sig->sig_class == 0x28
|| sig->sig_class == 0x30)
{
if (c->list->pkt->pkttype == PKT_PUBLIC_KEY
|| c->list->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
return check_key_signature( c->list, node, is_selfsig );
}
else if (sig->sig_class == 0x20)
{
log_error (_("standalone revocation - "
"use \"gpg --import\" to apply\n"));
return GPG_ERR_NOT_PROCESSED;
}
else
{
log_error ("invalid root packet for sigclass %02x\n", sig->sig_class);
return GPG_ERR_SIG_CLASS;
}
}
else
return GPG_ERR_SIG_CLASS;
/* We only get here if we are checking the signature of a binary
(0x00) or text document (0x01). */
rc = check_signature2 (sig, md, NULL, is_expkey, is_revkey, r_pk);
if (! rc)
md_good = md;
else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
{
PKT_public_key *pk2;
rc = check_signature2 (sig, md2, NULL, is_expkey, is_revkey,
r_pk? &pk2 : NULL);
if (!rc)
{
md_good = md2;
if (r_pk)
{
free_public_key (*r_pk);
*r_pk = pk2;
}
}
}
if (md_good)
{
unsigned char *buffer = gcry_md_read (md_good, sig->digest_algo);
sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo));
memcpy (sig->digest, buffer, sig->digest_len);
}
gcry_md_close (md);
gcry_md_close (md2);
return rc;
}
static void
print_userid (PACKET *pkt)
{
if (!pkt)
BUG();
if (pkt->pkttype != PKT_USER_ID)
{
es_printf ("ERROR: unexpected packet type %d", pkt->pkttype );
return;
}
if (opt.with_colons)
{
if (pkt->pkt.user_id->attrib_data)
es_printf("%u %lu",
pkt->pkt.user_id->numattribs,
pkt->pkt.user_id->attrib_len);
else
es_write_sanitized (es_stdout, pkt->pkt.user_id->name,
pkt->pkt.user_id->len, ":", NULL);
}
else
print_utf8_buffer (es_stdout, pkt->pkt.user_id->name,
pkt->pkt.user_id->len );
}
/*
* List the keyblock in a user friendly way
*/
static void
list_node (CTX c, kbnode_t node)
{
if (!node)
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (opt.with_colons)
{
u32 keyid[2];
keyid_from_pk( pk, keyid );
if (pk->flags.primary)
c->trustletter = (opt.fast_list_mode
? 0
: get_validity_info
(c->ctrl,
node->pkt->pkttype == PKT_PUBLIC_KEY
? node : NULL,
pk, NULL));
es_printf ("%s:", pk->flags.primary? "pub":"sub" );
if (c->trustletter)
es_putc (c->trustletter, es_stdout);
es_printf (":%u:%d:%08lX%08lX:%s:%s::",
nbits_from_pk( pk ),
pk->pubkey_algo,
(ulong)keyid[0],(ulong)keyid[1],
colon_datestr_from_pk( pk ),
colon_strtime (pk->expiredate) );
if (pk->flags.primary && !opt.fast_list_mode)
es_putc (get_ownertrust_info (pk, 1), es_stdout);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
else
{
print_key_line (es_stdout, pk, 0);
}
if (opt.keyid_format == KF_NONE && !opt.with_colons)
; /* Already printed. */
else if ((pk->flags.primary && opt.fingerprint) || opt.fingerprint > 1)
print_fingerprint (NULL, pk, 0);
if (opt.with_colons)
{
if (node->next && node->next->pkt->pkttype == PKT_RING_TRUST)
es_printf ("rtv:1:%u:\n",
node->next->pkt->pkt.ring_trust->trustval);
}
if (pk->flags.primary)
{
int kl = opt.keyid_format == KF_NONE? 0 : keystrlen ();
/* Now list all userids with their signatures. */
for (node = node->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_SIGNATURE)
{
list_node (c, node );
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
if (opt.with_colons)
es_printf ("%s:::::::::",
node->pkt->pkt.user_id->attrib_data?"uat":"uid");
else
es_printf ("uid%*s",
kl + (opt.legacy_list_mode? 9:11),
"" );
print_userid (node->pkt);
if (opt.with_colons)
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
if (opt.with_colons
&& node->next
&& node->next->pkt->pkttype == PKT_RING_TRUST)
{
es_printf ("rtv:2:%u:\n",
node->next->pkt->pkt.ring_trust?
node->next->pkt->pkt.ring_trust->trustval : 0);
}
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
list_node(c, node );
}
}
}
}
else if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
log_debug ("FIXME: No way to print secret key packets here\n");
/* fixme: We may use a function to turn a secret key packet into
a public key one and use that here. */
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int is_selfsig = 0;
int rc2 = 0;
size_t n;
char *p;
int sigrc = ' ';
if (!opt.verbose)
return;
if (sig->sig_class == 0x20 || sig->sig_class == 0x30)
es_fputs ("rev", es_stdout);
else
es_fputs ("sig", es_stdout);
if (opt.check_sigs)
{
fflush (stdout);
rc2 = do_check_sig (c, node, &is_selfsig, NULL, NULL, NULL);
switch (gpg_err_code (rc2))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
}
else /* Check whether this is a self signature. */
{
u32 keyid[2];
if (c->list->pkt->pkttype == PKT_PUBLIC_KEY
|| c->list->pkt->pkttype == PKT_SECRET_KEY )
{
keyid_from_pk (c->list->pkt->pkt.public_key, keyid);
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
is_selfsig = 1;
}
}
if (opt.with_colons)
{
es_putc (':', es_stdout);
if (sigrc != ' ')
es_putc (sigrc, es_stdout);
es_printf ("::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo,
(ulong)sig->keyid[0], (ulong)sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d",sig->trust_depth,sig->trust_value);
es_putc (':', es_stdout);
if (sig->trust_regexp)
es_write_sanitized (es_stdout, sig->trust_regexp,
strlen (sig->trust_regexp), ":", NULL);
es_putc (':', es_stdout);
}
else
es_printf ("%c %s %s ",
sigrc, keystr (sig->keyid), datestr_from_sig(sig));
if (sigrc == '%')
es_printf ("[%s] ", gpg_strerror (rc2) );
else if (sigrc == '?')
;
else if (is_selfsig)
{
if (opt.with_colons)
es_putc (':', es_stdout);
es_fputs (sig->sig_class == 0x18? "[keybind]":"[selfsig]", es_stdout);
if (opt.with_colons)
es_putc (':', es_stdout);
}
else if (!opt.fast_list_mode)
{
p = get_user_id (sig->keyid, &n);
es_write_sanitized (es_stdout, p, n,
opt.with_colons?":":NULL, NULL );
xfree (p);
}
if (opt.with_colons)
es_printf (":%02x%c:", sig->sig_class, sig->flags.exportable?'x':'l');
es_putc ('\n', es_stdout);
}
else
log_error ("invalid node with packet of type %d\n", node->pkt->pkttype);
}
int
proc_packets (ctrl_t ctrl, void *anchor, iobuf_t a )
{
int rc;
CTX c = xmalloc_clear (sizeof *c);
c->ctrl = ctrl;
c->anchor = anchor;
rc = do_proc_packets (ctrl, c, a);
xfree (c);
return rc;
}
int
proc_signature_packets (ctrl_t ctrl, void *anchor, iobuf_t a,
strlist_t signedfiles, const char *sigfilename )
{
CTX c = xmalloc_clear (sizeof *c);
int rc;
c->ctrl = ctrl;
c->anchor = anchor;
c->sigs_only = 1;
c->signed_data.data_fd = -1;
c->signed_data.data_names = signedfiles;
c->signed_data.used = !!signedfiles;
c->sigfilename = sigfilename;
rc = do_proc_packets (ctrl, c, a);
/* If we have not encountered any signature we print an error
messages, send a NODATA status back and return an error code.
Using log_error is required because verify_files does not check
error codes for each file but we want to terminate the process
with an error. */
if (!rc && !c->any.sig_seen)
{
write_status_text (STATUS_NODATA, "4");
log_error (_("no signature found\n"));
rc = GPG_ERR_NO_DATA;
}
/* Propagate the signature seen flag upward. Do this only on success
so that we won't issue the nodata status several times. */
if (!rc && c->anchor && c->any.sig_seen)
c->anchor->any.sig_seen = 1;
xfree (c);
return rc;
}
int
proc_signature_packets_by_fd (ctrl_t ctrl,
void *anchor, iobuf_t a, int signed_data_fd )
{
int rc;
CTX c;
c = xtrycalloc (1, sizeof *c);
if (!c)
return gpg_error_from_syserror ();
c->ctrl = ctrl;
c->anchor = anchor;
c->sigs_only = 1;
c->signed_data.data_fd = signed_data_fd;
c->signed_data.data_names = NULL;
c->signed_data.used = (signed_data_fd != -1);
rc = do_proc_packets (ctrl, c, a);
/* If we have not encountered any signature we print an error
messages, send a NODATA status back and return an error code.
Using log_error is required because verify_files does not check
error codes for each file but we want to terminate the process
with an error. */
if (!rc && !c->any.sig_seen)
{
write_status_text (STATUS_NODATA, "4");
log_error (_("no signature found\n"));
rc = gpg_error (GPG_ERR_NO_DATA);
}
/* Propagate the signature seen flag upward. Do this only on success
so that we won't issue the nodata status several times. */
if (!rc && c->anchor && c->any.sig_seen)
c->anchor->any.sig_seen = 1;
xfree ( c );
return rc;
}
int
proc_encryption_packets (ctrl_t ctrl, void *anchor, iobuf_t a )
{
CTX c = xmalloc_clear (sizeof *c);
int rc;
c->ctrl = ctrl;
c->anchor = anchor;
c->encrypt_only = 1;
rc = do_proc_packets (ctrl, c, a);
xfree (c);
return rc;
}
static int
check_nesting (CTX c)
{
int level;
for (level=0; c; c = c->anchor)
level++;
if (level > MAX_NESTING_DEPTH)
{
log_error ("input data with too deeply nested packets\n");
write_status_text (STATUS_UNEXPECTED, "1");
return GPG_ERR_BAD_DATA;
}
return 0;
}
static int
do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a)
{
PACKET *pkt;
int rc = 0;
int any_data = 0;
int newpkt;
rc = check_nesting (c);
if (rc)
return rc;
pkt = xmalloc( sizeof *pkt );
c->iobuf = a;
init_packet(pkt);
while ((rc=parse_packet(a, pkt)) != -1)
{
any_data = 1;
if (rc)
{
free_packet (pkt);
/* Stop processing when an invalid packet has been encountered
* but don't do so when we are doing a --list-packets. */
if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& opt.list_packets == 0)
break;
continue;
}
newpkt = -1;
if (opt.list_packets)
{
switch (pkt->pkttype)
{
case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
default: newpkt = 0; break;
}
}
else if (c->sigs_only)
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_USER_ID:
case PKT_SYMKEY_ENC:
case PKT_PUBKEY_ENC:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
write_status_text( STATUS_UNEXPECTED, "0" );
rc = GPG_ERR_UNEXPECTED;
goto leave;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control (c, pkt); break;
default: newpkt = 0; break;
}
}
else if (c->encrypt_only)
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_USER_ID:
write_status_text (STATUS_UNEXPECTED, "0");
rc = GPG_ERR_UNEXPECTED;
goto leave;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control (c, pkt); break;
default: newpkt = 0; break;
}
}
else
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
release_list (c);
c->list = new_kbnode (pkt);
newpkt = 1;
break;
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_SUBKEY:
newpkt = add_subkey (c, pkt);
break;
case PKT_USER_ID: newpkt = add_user_id (c, pkt); break;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control(c, pkt); break;
case PKT_RING_TRUST: newpkt = add_ring_trust (c, pkt); break;
default: newpkt = 0; break;
}
}
if (rc)
goto leave;
/* This is a very ugly construct and frankly, I don't remember why
* I used it. Adding the MDC check here is a hack.
* The right solution is to initiate another context for encrypted
* packet and not to reuse the current one ... It works right
* when there is a compression packet between which adds just
* an extra layer.
* Hmmm: Rewrite this whole module here??
*/
if (pkt->pkttype != PKT_SIGNATURE && pkt->pkttype != PKT_MDC)
c->any.data = (pkt->pkttype == PKT_PLAINTEXT);
if (newpkt == -1)
;
else if (newpkt)
{
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
}
else
free_packet(pkt);
}
if (rc == GPG_ERR_INV_PACKET)
write_status_text (STATUS_NODATA, "3");
if (any_data)
rc = 0;
else if (rc == -1)
write_status_text (STATUS_NODATA, "2");
leave:
release_list (c);
xfree(c->dek);
free_packet (pkt);
xfree (pkt);
free_md_filter_context (&c->mfx);
return rc;
}
/* Helper for pka_uri_from_sig to parse the to-be-verified address out
of the notation data. */
static pka_info_t *
get_pka_address (PKT_signature *sig)
{
pka_info_t *pka = NULL;
struct notation *nd,*notation;
notation=sig_to_notation(sig);
for(nd=notation;nd;nd=nd->next)
{
if(strcmp(nd->name,"pka-address@gnupg.org")!=0)
continue; /* Not the notation we want. */
/* For now we only use the first valid PKA notation. In future
we might want to keep additional PKA notations in a linked
list. */
if (is_valid_mailbox (nd->value))
{
pka = xmalloc (sizeof *pka + strlen(nd->value));
pka->valid = 0;
pka->checked = 0;
pka->uri = NULL;
strcpy (pka->email, nd->value);
break;
}
}
free_notation(notation);
return pka;
}
/* Return the URI from a DNS PKA record. If this record has already
be retrieved for the signature we merely return it; if not we go
out and try to get that DNS record. */
static const char *
pka_uri_from_sig (CTX c, PKT_signature *sig)
{
if (!sig->flags.pka_tried)
{
log_assert (!sig->pka_info);
sig->flags.pka_tried = 1;
sig->pka_info = get_pka_address (sig);
if (sig->pka_info)
{
char *url;
unsigned char *fpr;
size_t fprlen;
if (!gpg_dirmngr_get_pka (c->ctrl, sig->pka_info->email,
&fpr, &fprlen, &url))
{
if (fpr && fprlen == sizeof sig->pka_info->fpr)
{
memcpy (sig->pka_info->fpr, fpr, fprlen);
if (url)
{
sig->pka_info->valid = 1;
if (!*url)
xfree (url);
else
sig->pka_info->uri = url;
url = NULL;
}
}
xfree (fpr);
xfree (url);
}
}
}
return sig->pka_info? sig->pka_info->uri : NULL;
}
/* Return true if the AKL has the WKD method specified. */
static int
akl_has_wkd_method (void)
{
struct akl *akl;
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_WKD)
return 1;
return 0;
}
/* Return the ISSUER fingerprint string in human readbale format if
* available. Caller must release the string. */
static char *
issuer_fpr_string (PKT_signature *sig)
{
const byte *p;
size_t n;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n);
if (p && n == 21 && p[0] == 4)
return bin2hex (p+1, n-1, NULL);
return NULL;
}
static void
print_good_bad_signature (int statno, const char *keyid_str, kbnode_t un,
PKT_signature *sig, int rc)
{
char *p;
write_status_text_and_buffer (statno, keyid_str,
un? un->pkt->pkt.user_id->name:"[?]",
un? un->pkt->pkt.user_id->len:3,
-1);
if (un)
p = utf8_to_native (un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len, 0);
else
p = xstrdup ("[?]");
if (rc)
log_info (_("BAD signature from \"%s\""), p);
else if (sig->flags.expired)
log_info (_("Expired signature from \"%s\""), p);
else
log_info (_("Good signature from \"%s\""), p);
xfree (p);
}
static int
check_sig_and_print (CTX c, kbnode_t node)
{
PKT_signature *sig = node->pkt->pkt.signature;
const char *astr;
int rc;
int is_expkey = 0;
int is_revkey = 0;
char *issuer_fpr;
PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */
if (opt.skip_verify)
{
log_info(_("signature verification suppressed\n"));
return 0;
}
/* Check that the message composition is valid.
*
* Per RFC-2440bis (-15) allowed:
*
* S{1,n} -- detached signature.
* S{1,n} P -- old style PGP2 signature
* O{1,n} P S{1,n} -- standard OpenPGP signature.
* C P S{1,n} -- cleartext signature.
*
*
* O = One-Pass Signature packet.
* S = Signature packet.
* P = OpenPGP Message packet (Encrypted | Compressed | Literal)
* (Note that the current rfc2440bis draft also allows
* for a signed message but that does not work as it
* introduces ambiguities.)
* We keep track of these packages using the marker packet
* CTRLPKT_PLAINTEXT_MARK.
* C = Marker packet for cleartext signatures.
*
* We reject all other messages.
*
* Actually we are calling this too often, i.e. for verification of
* each message but better have some duplicate work than to silently
* introduce a bug here.
*/
{
kbnode_t n;
int n_onepass, n_sig;
/* log_debug ("checking signature packet composition\n"); */
/* dump_kbnode (c->list); */
n = c->list;
log_assert (n);
if ( n->pkt->pkttype == PKT_SIGNATURE )
{
/* This is either "S{1,n}" case (detached signature) or
"S{1,n} P" (old style PGP2 signature). */
for (n = n->next; n; n = n->next)
if (n->pkt->pkttype != PKT_SIGNATURE)
break;
if (!n)
; /* Okay, this is a detached signature. */
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK) )
{
if (n->next)
goto ambiguous; /* We only allow one P packet. */
}
else
goto ambiguous;
}
else if (n->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* This is the "O{1,n} P S{1,n}" case (standard signature). */
for (n_onepass=1, n = n->next;
n && n->pkt->pkttype == PKT_ONEPASS_SIG; n = n->next)
n_onepass++;
if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK)))
goto ambiguous;
for (n_sig=0, n = n->next;
n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
n_sig++;
if (!n_sig)
goto ambiguous;
/* If we wanted to disallow multiple sig verification, we'd do
something like this:
if (n && !opt.allow_multisig_verification)
goto ambiguous;
However, now that we have --allow-multiple-messages, this
can stay allowable as we can't get here unless multiple
messages (i.e. multiple literals) are allowed. */
if (n_onepass != n_sig)
{
log_info ("number of one-pass packets does not match "
"number of signature packets\n");
goto ambiguous;
}
}
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START )
{
/* This is the "C P S{1,n}" case (clear text signature). */
n = n->next;
if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK)))
goto ambiguous;
for (n_sig=0, n = n->next;
n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
n_sig++;
if (n || !n_sig)
goto ambiguous;
}
else
{
ambiguous:
log_error(_("can't handle this ambiguous signature data\n"));
return 0;
}
}
if (sig->signers_uid)
write_status_buffer (STATUS_NEWSIG,
sig->signers_uid, strlen (sig->signers_uid), 0);
else
write_status_text (STATUS_NEWSIG, NULL);
astr = openpgp_pk_algo_name ( sig->pubkey_algo );
if ((issuer_fpr = issuer_fpr_string (sig)))
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", issuer_fpr);
xfree (issuer_fpr);
}
else if (!keystrlen () || keystrlen () > 8)
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", keystr(sig->keyid));
}
else /* Legacy format. */
log_info (_("Signature made %s using %s key ID %s\n"),
asctimestamp(sig->timestamp), astr? astr: "?",
keystr(sig->keyid));
/* In verbose mode print the signers UID. */
if (sig->signers_uid)
log_info (_(" issuer \"%s\"\n"), sig->signers_uid);
rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk);
/* If the key isn't found, check for a preferred keyserver. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && sig->flags.pref_ks)
{
const byte *p;
int seq = 0;
size_t n;
while ((p=enum_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS,&n,&seq,NULL)))
{
/* According to my favorite copy editor, in English grammar,
you say "at" if the key is located on a web page, but
"from" if it is located on a keyserver. I'm not going to
even try to make two strings here :) */
log_info(_("Key available at: ") );
print_utf8_buffer (log_get_stream(), p, n);
log_printf ("\n");
if (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE
&& opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL)
{
struct keyserver_spec *spec;
spec = parse_preferred_keyserver (sig);
if (spec)
{
int res;
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_keyid (c->ctrl, sig->keyid,spec, 1);
glo_ctrl.in_auto_key_retrieve--;
if (!res)
rc = do_check_sig (c, node, NULL,
&is_expkey, &is_revkey, &pk);
free_keyserver_spec (spec);
if (!rc)
break;
}
}
}
}
/* If the avove methods didn't work, our next try is to use the URI
* from a DNS PKA record. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)
&& (opt.keyserver_options.options & KEYSERVER_HONOR_PKA_RECORD))
{
const char *uri = pka_uri_from_sig (c, sig);
if (uri)
{
/* FIXME: We might want to locate the key using the
fingerprint instead of the keyid. */
int res;
struct keyserver_spec *spec;
spec = parse_keyserver_uri (uri, 1);
if (spec)
{
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_keyid (c->ctrl, sig->keyid, spec, 1);
glo_ctrl.in_auto_key_retrieve--;
free_keyserver_spec (spec);
if (!res)
rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk);
}
}
}
/* If the above methods didn't work, our next try is to locate
* the key via its fingerprint from a keyserver. This requires
* that the signers fingerprint is encoded in the signature. We
* favor this over the WKD method (to be tried next), because an
* arbitrary keyserver is less subject to web bug like monitoring. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (c->ctrl))
{
int res;
const byte *p;
size_t n;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n);
if (p && n == 21 && p[0] == 4)
{
/* v4 packet with a SHA-1 fingerprint. */
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_fprint (c->ctrl, p+1, n-1, opt.keyserver, 1);
glo_ctrl.in_auto_key_retrieve--;
if (!res)
rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk);
}
}
/* If the above methods didn't work, our next try is to retrieve the
* key from the WKD. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)
&& !opt.flags.disable_signer_uid
&& akl_has_wkd_method ()
&& sig->signers_uid)
{
int res;
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_wkd (c->ctrl, sig->signers_uid, 1, NULL, NULL);
glo_ctrl.in_auto_key_retrieve--;
/* Fixme: If the fingerprint is embedded in the signature,
* compare it to the fingerprint of the returned key. */
if (!res)
rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk);
}
/* If the above methods did't work, our next try is to use a
* keyserver. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (c->ctrl))
{
int res;
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_keyid (c->ctrl, sig->keyid, opt.keyserver, 1);
glo_ctrl.in_auto_key_retrieve--;
if (!res)
rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk);
}
if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
{
kbnode_t un, keyblock;
int count = 0;
int statno;
char keyid_str[50];
PKT_public_key *mainpk = NULL;
if (rc)
statno = STATUS_BADSIG;
else if (sig->flags.expired)
statno = STATUS_EXPSIG;
else if (is_expkey)
statno = STATUS_EXPKEYSIG;
else if(is_revkey)
statno = STATUS_REVKEYSIG;
else
statno = STATUS_GOODSIG;
/* FIXME: We should have the public key in PK and thus the
* keyboock has already been fetched. Thus we could use the
* fingerprint or PK itself to lookup the entire keyblock. That
* would best be done with a cache. */
keyblock = get_pubkeyblock (sig->keyid);
snprintf (keyid_str, sizeof keyid_str, "%08lX%08lX [uncertain] ",
(ulong)sig->keyid[0], (ulong)sig->keyid[1]);
/* Find and print the primary user ID along with the
"Good|Expired|Bad signature" line. */
for (un=keyblock; un; un = un->next)
{
int valid;
if (un->pkt->pkttype==PKT_PUBLIC_KEY)
{
mainpk = un->pkt->pkt.public_key;
continue;
}
if (un->pkt->pkttype != PKT_USER_ID)
continue;
if (!un->pkt->pkt.user_id->created)
continue;
if (un->pkt->pkt.user_id->is_revoked)
continue;
if (un->pkt->pkt.user_id->is_expired)
continue;
if (!un->pkt->pkt.user_id->is_primary)
continue;
/* We want the textual primary user ID here */
if (un->pkt->pkt.user_id->attrib_data)
continue;
log_assert (mainpk);
/* Since this is just informational, don't actually ask the
user to update any trust information. (Note: we register
the signature later.) Because print_good_bad_signature
does not print a LF we need to compute the validity
before calling that function. */
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
valid = get_validity (c->ctrl, keyblock, mainpk,
un->pkt->pkt.user_id, NULL, 0);
else
valid = 0; /* Not used. */
keyid_str[17] = 0; /* cut off the "[uncertain]" part */
print_good_bad_signature (statno, keyid_str, un, sig, rc);
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
log_printf (" [%s]\n",trust_value_to_string(valid));
else
log_printf ("\n");
count++;
}
log_assert (mainpk);
/* In case we did not found a valid textual userid above
we print the first user id packet or a "[?]" instead along
with the "Good|Expired|Bad signature" line. */
if (!count)
{
/* Try for an invalid textual userid */
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype == PKT_USER_ID
&& !un->pkt->pkt.user_id->attrib_data)
break;
}
/* Try for any userid at all */
if (!un)
{
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype == PKT_USER_ID)
break;
}
}
if (opt.trust_model==TM_ALWAYS || !un)
keyid_str[17] = 0; /* cut off the "[uncertain]" part */
print_good_bad_signature (statno, keyid_str, un, sig, rc);
if (opt.trust_model != TM_ALWAYS && un)
log_printf (" %s",_("[uncertain]") );
log_printf ("\n");
}
/* If we have a good signature and already printed
* the primary user ID, print all the other user IDs */
if (count
&& !rc
&& !(opt.verify_options & VERIFY_SHOW_PRIMARY_UID_ONLY))
{
char *p;
for( un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype != PKT_USER_ID)
continue;
if ((un->pkt->pkt.user_id->is_revoked
|| un->pkt->pkt.user_id->is_expired)
&& !(opt.verify_options & VERIFY_SHOW_UNUSABLE_UIDS))
continue;
/* Skip textual primary user ids which we printed above. */
if (un->pkt->pkt.user_id->is_primary
&& !un->pkt->pkt.user_id->attrib_data )
continue;
/* If this user id has attribute data, print that. */
if (un->pkt->pkt.user_id->attrib_data)
{
dump_attribs (un->pkt->pkt.user_id, mainpk);
if (opt.verify_options&VERIFY_SHOW_PHOTOS)
show_photos (c->ctrl,
un->pkt->pkt.user_id->attribs,
un->pkt->pkt.user_id->numattribs,
mainpk ,un->pkt->pkt.user_id);
}
p = utf8_to_native (un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len, 0);
log_info (_(" aka \"%s\""), p);
xfree (p);
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
{
const char *valid;
if (un->pkt->pkt.user_id->is_revoked)
valid = _("revoked");
else if (un->pkt->pkt.user_id->is_expired)
valid = _("expired");
else
/* Since this is just informational, don't
actually ask the user to update any trust
information. */
valid = (trust_value_to_string
(get_validity (c->ctrl, keyblock, mainpk,
un->pkt->pkt.user_id, NULL, 0)));
log_printf (" [%s]\n",valid);
}
else
log_printf ("\n");
}
}
/* For good signatures print notation data. */
if (!rc)
{
if ((opt.verify_options & VERIFY_SHOW_POLICY_URLS))
show_policy_url (sig, 0, 1);
else
show_policy_url (sig, 0, 2);
if ((opt.verify_options & VERIFY_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 0, 1);
else
show_keyserver_url (sig, 0, 2);
if ((opt.verify_options & VERIFY_SHOW_NOTATIONS))
show_notation
(sig, 0, 1,
(((opt.verify_options&VERIFY_SHOW_STD_NOTATIONS)?1:0)
+ ((opt.verify_options&VERIFY_SHOW_USER_NOTATIONS)?2:0)));
else
show_notation (sig, 0, 2, 0);
}
/* For good signatures print the VALIDSIG status line. */
if (!rc && is_status_enabled () && pk)
{
char pkhex[MAX_FINGERPRINT_LEN*2+1];
char mainpkhex[MAX_FINGERPRINT_LEN*2+1];
hexfingerprint (pk, pkhex, sizeof pkhex);
hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex);
/* TODO: Replace the reserved '0' in the field below with
bits for status flags (policy url, notation, etc.). */
write_status_printf (STATUS_VALIDSIG,
"%s %s %lu %lu %d 0 %d %d %02X %s",
pkhex,
strtimestamp (sig->timestamp),
(ulong)sig->timestamp,
(ulong)sig->expiredate,
sig->version, sig->pubkey_algo,
sig->digest_algo,
sig->sig_class,
mainpkhex);
}
/* For good signatures compute and print the trust information.
Note that in the Tofu trust model this may ask the user on
how to resolve a conflict. */
if (!rc)
{
if ((opt.verify_options & VERIFY_PKA_LOOKUPS))
pka_uri_from_sig (c, sig); /* Make sure PKA info is available. */
rc = check_signatures_trust (c->ctrl, sig);
}
/* Print extra information about the signature. */
if (sig->flags.expired)
{
log_info (_("Signature expired %s\n"), asctimestamp(sig->expiredate));
rc = GPG_ERR_GENERAL; /* Need a better error here? */
}
else if (sig->expiredate)
log_info (_("Signature expires %s\n"), asctimestamp(sig->expiredate));
if (opt.verbose)
{
char pkstrbuf[PUBKEY_STRING_SIZE];
if (pk)
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf);
else
*pkstrbuf = 0;
log_info (_("%s signature, digest algorithm %s%s%s\n"),
sig->sig_class==0x00?_("binary"):
sig->sig_class==0x01?_("textmode"):_("unknown"),
gcry_md_algo_name (sig->digest_algo),
*pkstrbuf?_(", key algorithm "):"", pkstrbuf);
}
/* Print final warnings. */
if (!rc && !c->signed_data.used)
{
/* Signature is basically good but we test whether the
deprecated command
gpg --verify FILE.sig
was used instead of
gpg --verify FILE.sig FILE
to verify a detached signature. If we figure out that a
data file with a matching name exists, we print a warning.
The problem is that the first form would also verify a
standard signature. This behavior could be used to
create a made up .sig file for a tarball by creating a
standard signature from a valid detached signature packet
(for example from a signed git tag). Then replace the
sig file on the FTP server along with a changed tarball.
Using the first form the verify command would correctly
verify the signature but don't even consider the tarball. */
kbnode_t n;
char *dfile;
dfile = get_matching_datafile (c->sigfilename);
if (dfile)
{
for (n = c->list; n; n = n->next)
if (n->pkt->pkttype != PKT_SIGNATURE)
break;
if (n)
{
/* Not only signature packets in the tree thus this
is not a detached signature. */
log_info (_("WARNING: not a detached signature; "
"file '%s' was NOT verified!\n"), dfile);
}
xfree (dfile);
}
}
free_public_key (pk);
pk = NULL;
release_kbnode( keyblock );
if (rc)
g10_errors_seen = 1;
if (opt.batch && rc)
g10_exit (1);
}
else
{
char buf[50];
snprintf (buf, sizeof buf, "%08lX%08lX %d %d %02x %lu %d",
(ulong)sig->keyid[0], (ulong)sig->keyid[1],
sig->pubkey_algo, sig->digest_algo,
sig->sig_class, (ulong)sig->timestamp, gpg_err_code (rc));
write_status_text (STATUS_ERRSIG, buf);
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
{
buf[16] = 0;
write_status_text (STATUS_NO_PUBKEY, buf);
}
if (gpg_err_code (rc) != GPG_ERR_NOT_PROCESSED)
log_error (_("Can't check signature: %s\n"), gpg_strerror (rc));
}
return rc;
}
/*
* Process the tree which starts at node
*/
static void
proc_tree (CTX c, kbnode_t node)
{
kbnode_t n1;
int rc;
if (opt.list_packets || opt.list_only)
return;
/* We must skip our special plaintext marker packets here because
they may be the root packet. These packets are only used in
additional checks and skipping them here doesn't matter. */
while (node
&& node->pkt->pkttype == PKT_GPG_CONTROL
&& node->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK)
{
node = node->next;
}
if (!node)
return;
c->trustletter = ' ';
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_keys_and_selfsig (node);
list_node (c, node);
}
else if (node->pkt->pkttype == PKT_SECRET_KEY)
{
merge_keys_and_selfsig (node);
list_node (c, node);
}
else if (node->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* Check all signatures. */
if (!c->any.data)
{
int use_textmode = 0;
free_md_filter_context (&c->mfx);
/* Prepare to create all requested message digests. */
rc = gcry_md_open (&c->mfx.md, 0, 0);
if (rc)
goto hash_err;
/* Fixme: why looking for the signature packet and not the
one-pass packet? */
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
gcry_md_enable (c->mfx.md, n1->pkt->pkt.signature->digest_algo);
if (n1 && n1->pkt->pkt.onepass_sig->sig_class == 0x01)
use_textmode = 1;
/* Ask for file and hash it. */
if (c->sigs_only)
{
if (c->signed_data.used && c->signed_data.data_fd != -1)
rc = hash_datafile_by_fd (c->mfx.md, NULL,
c->signed_data.data_fd,
use_textmode);
else
rc = hash_datafiles (c->mfx.md, NULL,
c->signed_data.data_names,
c->sigfilename,
use_textmode);
}
else
{
rc = ask_for_detached_datafile (c->mfx.md, c->mfx.md2,
iobuf_get_real_fname (c->iobuf),
use_textmode);
}
hash_err:
if (rc)
{
log_error ("can't hash datafile: %s\n", gpg_strerror (rc));
return;
}
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
check_sig_and_print (c, n1);
}
else if (node->pkt->pkttype == PKT_GPG_CONTROL
&& node->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START)
{
/* Clear text signed message. */
if (!c->any.data)
{
log_error ("cleartext signature without data\n");
return;
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
check_sig_and_print (c, n1);
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int multiple_ok = 1;
n1 = find_next_kbnode (node, PKT_SIGNATURE);
if (n1)
{
byte class = sig->sig_class;
byte hash = sig->digest_algo;
for (; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE)))
{
/* We can't currently handle multiple signatures of
* different classes (we'd pretty much have to run a
* different hash context for each), but if they are all
* the same and it is detached signature, we make an
* exception. Note that the old code also disallowed
* multiple signatures if the digest algorithms are
* different. We softened this restriction only for
* detached signatures, to be on the safe side. */
if (n1->pkt->pkt.signature->sig_class != class
|| (c->any.data
&& n1->pkt->pkt.signature->digest_algo != hash))
{
multiple_ok = 0;
log_info (_("WARNING: multiple signatures detected. "
"Only the first will be checked.\n"));
break;
}
}
}
if (sig->sig_class != 0x00 && sig->sig_class != 0x01)
{
log_info(_("standalone signature of class 0x%02x\n"), sig->sig_class);
}
else if (!c->any.data)
{
/* Detached signature */
free_md_filter_context (&c->mfx);
rc = gcry_md_open (&c->mfx.md, sig->digest_algo, 0);
if (rc)
goto detached_hash_err;
if (multiple_ok)
{
/* If we have and want to handle multiple signatures we
* need to enable all hash algorithms for the context. */
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE)); )
if (!openpgp_md_test_algo (n1->pkt->pkt.signature->digest_algo))
gcry_md_enable (c->mfx.md,
map_md_openpgp_to_gcry
(n1->pkt->pkt.signature->digest_algo));
}
if (RFC2440 || RFC4880)
; /* Strict RFC mode. */
else if (sig->digest_algo == DIGEST_ALGO_SHA1
&& sig->pubkey_algo == PUBKEY_ALGO_DSA
&& sig->sig_class == 0x01)
{
/* Enable a workaround for a pgp5 bug when the detached
* signature has been created in textmode. Note that we
* do not implement this for multiple signatures with
* different hash algorithms. */
rc = gcry_md_open (&c->mfx.md2, sig->digest_algo, 0);
if (rc)
goto detached_hash_err;
}
/* Here we used to have another hack to work around a pgp
* 2 bug: It worked by not using the textmode for detached
* signatures; this would let the first signature check
* (on md) fail but the second one (on md2), which adds an
* extra CR would then have produced the "correct" hash.
* This is very, very ugly hack but it may haved help in
* some cases (and break others).
* c->mfx.md2? 0 :(sig->sig_class == 0x01)
*/
if (DBG_HASHING)
{
gcry_md_debug (c->mfx.md, "verify");
if (c->mfx.md2)
gcry_md_debug (c->mfx.md2, "verify2");
}
if (c->sigs_only)
{
if (c->signed_data.used && c->signed_data.data_fd != -1)
rc = hash_datafile_by_fd (c->mfx.md, c->mfx.md2,
c->signed_data.data_fd,
(sig->sig_class == 0x01));
else
rc = hash_datafiles (c->mfx.md, c->mfx.md2,
c->signed_data.data_names,
c->sigfilename,
(sig->sig_class == 0x01));
}
else
{
rc = ask_for_detached_datafile (c->mfx.md, c->mfx.md2,
iobuf_get_real_fname(c->iobuf),
(sig->sig_class == 0x01));
}
detached_hash_err:
if (rc)
{
log_error ("can't hash datafile: %s\n", gpg_strerror (rc));
return;
}
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
else if (!opt.quiet)
log_info (_("old style (PGP 2.x) signature\n"));
if (multiple_ok)
{
for (n1 = node; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE)))
check_sig_and_print (c, n1);
}
else
check_sig_and_print (c, node);
}
else
{
dump_kbnode (c->list);
log_error ("invalid root packet detected in proc_tree()\n");
dump_kbnode (node);
}
}
diff --git a/g10/mdfilter.c b/g10/mdfilter.c
index 69b226c3b..f3318f15c 100644
--- a/g10/mdfilter.c
+++ b/g10/mdfilter.c
@@ -1,73 +1,73 @@
/* mdfilter.c - filter data and calculate a message digest
* Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "filter.h"
/****************
* This filter is used to collect a message digest
*/
int
md_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
md_filter_context_t *mfx = opaque;
int i, rc=0;
if( control == IOBUFCTRL_UNDERFLOW ) {
if( mfx->maxbuf_size && size > mfx->maxbuf_size )
size = mfx->maxbuf_size;
i = iobuf_read( a, buf, size );
if( i == -1 ) i = 0;
if( i ) {
gcry_md_write(mfx->md, buf, i );
if( mfx->md2 )
gcry_md_write(mfx->md2, buf, i );
}
else
rc = -1; /* eof */
*ret_len = i;
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "md_filter", *ret_len);
return rc;
}
void
free_md_filter_context( md_filter_context_t *mfx )
{
gcry_md_close(mfx->md);
gcry_md_close(mfx->md2);
mfx->md = NULL;
mfx->md2 = NULL;
mfx->maxbuf_size = 0;
}
diff --git a/g10/migrate.c b/g10/migrate.c
index 6ff1014e6..c52c67a77 100644
--- a/g10/migrate.c
+++ b/g10/migrate.c
@@ -1,118 +1,118 @@
/* migrate.c - Migrate from earlier GnupG versions.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "gpg.h"
#include "options.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "call-agent.h"
#ifdef HAVE_DOSISH_SYSTEM
# define V21_MIGRATION_FNAME "gpg-v21-migrated"
#else
# define V21_MIGRATION_FNAME ".gpg-v21-migrated"
#endif
/* Check whether a default secring.gpg from GnuPG < 2.1 exists and
import it if not yet done. */
void
migrate_secring (ctrl_t ctrl)
{
dotlock_t lockhd = NULL;
char *secring = NULL;
char *flagfile = NULL;
char *agent_version = NULL;
secring = make_filename (gnupg_homedir (), "secring" EXTSEP_S "gpg", NULL);
if (access (secring, F_OK))
goto leave; /* Does not exist or is not readable. */
flagfile = make_filename (gnupg_homedir (), V21_MIGRATION_FNAME, NULL);
if (!access (flagfile, F_OK))
goto leave; /* Does exist - fine. */
log_info ("starting migration from earlier GnuPG versions\n");
lockhd = dotlock_create (flagfile, 0);
if (!lockhd)
{
log_error ("can't allocate lock for '%s': %s\n",
flagfile, gpg_strerror (gpg_error_from_syserror ()));
goto leave;
}
if (dotlock_take (lockhd, -1))
{
log_error ("can't lock '%s': %s\n",
flagfile, gpg_strerror (gpg_error_from_syserror ()));
dotlock_destroy (lockhd);
lockhd = NULL;
goto leave;
}
if (!agent_get_version (ctrl, &agent_version))
{
if (!gnupg_compare_version (agent_version, "2.1.0"))
{
log_error ("error: GnuPG agent version \"%s\" is too old. ",
agent_version);
log_info ("Please make sure that a recent gpg-agent is running.\n");
log_info ("(restarting the user session may achieve this.)\n");
log_info ("migration aborted\n");
xfree (agent_version);
goto leave;
}
xfree (agent_version);
}
else
{
log_error ("error: GnuPG agent unusable. "
"Please check that a GnuPG agent can be started.\n");
log_error ("migration aborted\n");
goto leave;
}
log_info ("porting secret keys from '%s' to gpg-agent\n", secring);
if (!import_old_secring (ctrl, secring))
{
FILE *fp = fopen (flagfile, "w");
if (!fp || fclose (fp))
log_error ("error creating flag file '%s': %s\n",
flagfile, gpg_strerror (gpg_error_from_syserror ()));
else
log_info ("migration succeeded\n");
}
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
xfree (flagfile);
xfree (secring);
}
diff --git a/g10/misc.c b/g10/misc.c
index ac0000943..c69f9948b 100644
--- a/g10/misc.c
+++ b/g10/misc.c
@@ -1,1772 +1,1772 @@
/* misc.c - miscellaneous functions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2
#include <asm/sysinfo.h>
#include <asm/unistd.h>
#endif
#ifdef HAVE_SETRLIMIT
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#endif
#ifdef ENABLE_SELINUX_HACKS
#include <sys/stat.h>
#endif
#ifdef HAVE_W32_SYSTEM
#include <time.h>
#include <process.h>
#ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
#endif
#include <windows.h>
#include <shlobj.h>
#ifndef CSIDL_APPDATA
#define CSIDL_APPDATA 0x001a
#endif
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
#ifndef CSIDL_FLAG_CREATE
#define CSIDL_FLAG_CREATE 0x8000
#endif
#endif /*HAVE_W32_SYSTEM*/
#include "gpg.h"
#ifdef HAVE_W32_SYSTEM
-# include "status.h"
+# include "../common/status.h"
#endif /*HAVE_W32_SYSTEM*/
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "photoid.h"
#include "options.h"
#include "call-agent.h"
-#include "i18n.h"
-#include "zb32.h"
+#include "../common/i18n.h"
+#include "../common/zb32.h"
#ifdef ENABLE_SELINUX_HACKS
/* A object and a global variable to keep track of files marked as
secured. */
struct secured_file_item
{
struct secured_file_item *next;
ino_t ino;
dev_t dev;
};
static struct secured_file_item *secured_files;
#endif /*ENABLE_SELINUX_HACKS*/
/* For the sake of SELinux we want to restrict access through gpg to
certain files we keep under our own control. This function
registers such a file and is_secured_file may then be used to
check whether a file has ben registered as secured. */
void
register_secured_file (const char *fname)
{
#ifdef ENABLE_SELINUX_HACKS
struct stat buf;
struct secured_file_item *sf;
/* Note that we stop immediately if something goes wrong here. */
if (stat (fname, &buf))
log_fatal (_("fstat of '%s' failed in %s: %s\n"), fname,
"register_secured_file", strerror (errno));
/* log_debug ("registering '%s' i=%lu.%lu\n", fname, */
/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */
for (sf=secured_files; sf; sf = sf->next)
{
if (sf->ino == buf.st_ino && sf->dev == buf.st_dev)
return; /* Already registered. */
}
sf = xmalloc (sizeof *sf);
sf->ino = buf.st_ino;
sf->dev = buf.st_dev;
sf->next = secured_files;
secured_files = sf;
#else /*!ENABLE_SELINUX_HACKS*/
(void)fname;
#endif /*!ENABLE_SELINUX_HACKS*/
}
/* Remove a file registered as secure. */
void
unregister_secured_file (const char *fname)
{
#ifdef ENABLE_SELINUX_HACKS
struct stat buf;
struct secured_file_item *sf, *sfprev;
if (stat (fname, &buf))
{
log_error (_("fstat of '%s' failed in %s: %s\n"), fname,
"unregister_secured_file", strerror (errno));
return;
}
/* log_debug ("unregistering '%s' i=%lu.%lu\n", fname, */
/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */
for (sfprev=NULL,sf=secured_files; sf; sfprev=sf, sf = sf->next)
{
if (sf->ino == buf.st_ino && sf->dev == buf.st_dev)
{
if (sfprev)
sfprev->next = sf->next;
else
secured_files = sf->next;
xfree (sf);
return;
}
}
#else /*!ENABLE_SELINUX_HACKS*/
(void)fname;
#endif /*!ENABLE_SELINUX_HACKS*/
}
/* Return true if FD is corresponds to a secured file. Using -1 for
FS is allowed and will return false. */
int
is_secured_file (int fd)
{
#ifdef ENABLE_SELINUX_HACKS
struct stat buf;
struct secured_file_item *sf;
if (fd == -1)
return 0; /* No file descriptor so it can't be secured either. */
/* Note that we print out a error here and claim that a file is
secure if something went wrong. */
if (fstat (fd, &buf))
{
log_error (_("fstat(%d) failed in %s: %s\n"), fd,
"is_secured_file", strerror (errno));
return 1;
}
/* log_debug ("is_secured_file (%d) i=%lu.%lu\n", fd, */
/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */
for (sf=secured_files; sf; sf = sf->next)
{
if (sf->ino == buf.st_ino && sf->dev == buf.st_dev)
return 1; /* Yes. */
}
#else /*!ENABLE_SELINUX_HACKS*/
(void)fd;
#endif /*!ENABLE_SELINUX_HACKS*/
return 0; /* No. */
}
/* Return true if FNAME is corresponds to a secured file. Using NULL,
"" or "-" for FS is allowed and will return false. This function is
used before creating a file, thus it won't fail if the file does
not exist. */
int
is_secured_filename (const char *fname)
{
#ifdef ENABLE_SELINUX_HACKS
struct stat buf;
struct secured_file_item *sf;
if (iobuf_is_pipe_filename (fname) || !*fname)
return 0;
/* Note that we print out a error here and claim that a file is
secure if something went wrong. */
if (stat (fname, &buf))
{
if (errno == ENOENT || errno == EPERM || errno == EACCES)
return 0;
log_error (_("fstat of '%s' failed in %s: %s\n"), fname,
"is_secured_filename", strerror (errno));
return 1;
}
/* log_debug ("is_secured_filename (%s) i=%lu.%lu\n", fname, */
/* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */
for (sf=secured_files; sf; sf = sf->next)
{
if (sf->ino == buf.st_ino && sf->dev == buf.st_dev)
return 1; /* Yes. */
}
#else /*!ENABLE_SELINUX_HACKS*/
(void)fname;
#endif /*!ENABLE_SELINUX_HACKS*/
return 0; /* No. */
}
u16
checksum_u16( unsigned n )
{
u16 a;
a = (n >> 8) & 0xff;
a += n & 0xff;
return a;
}
u16
checksum( byte *p, unsigned n )
{
u16 a;
for(a=0; n; n-- )
a += *p++;
return a;
}
u16
checksum_mpi (gcry_mpi_t a)
{
u16 csum;
byte *buffer;
size_t nbytes;
if ( gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &nbytes, a) )
BUG ();
/* Fixme: For numbers not in secure memory we should use a stack
* based buffer and only allocate a larger one if mpi_print returns
* an error. */
buffer = (gcry_is_secure(a)?
gcry_xmalloc_secure (nbytes) : gcry_xmalloc (nbytes));
if ( gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, NULL, a) )
BUG ();
csum = checksum (buffer, nbytes);
xfree (buffer);
return csum;
}
void
print_pubkey_algo_note (pubkey_algo_t algo)
{
if(algo >= 100 && algo <= 110)
{
static int warn=0;
if(!warn)
{
warn=1;
es_fflush (es_stdout);
log_info (_("WARNING: using experimental public key algorithm %s\n"),
openpgp_pk_algo_name (algo));
}
}
else if (algo == PUBKEY_ALGO_ELGAMAL)
{
es_fflush (es_stdout);
log_info (_("WARNING: Elgamal sign+encrypt keys are deprecated\n"));
}
}
void
print_cipher_algo_note (cipher_algo_t algo)
{
if(algo >= 100 && algo <= 110)
{
static int warn=0;
if(!warn)
{
warn=1;
es_fflush (es_stdout);
log_info (_("WARNING: using experimental cipher algorithm %s\n"),
openpgp_cipher_algo_name (algo));
}
}
}
void
print_digest_algo_note (digest_algo_t algo)
{
const enum gcry_md_algos galgo = map_md_openpgp_to_gcry (algo);
const struct weakhash *weak;
if(algo >= 100 && algo <= 110)
{
static int warn=0;
if(!warn)
{
warn=1;
es_fflush (es_stdout);
log_info (_("WARNING: using experimental digest algorithm %s\n"),
gcry_md_algo_name (galgo));
}
}
else
for (weak = opt.weak_digests; weak != NULL; weak = weak->next)
if (weak->algo == galgo)
{
es_fflush (es_stdout);
log_info (_("WARNING: digest algorithm %s is deprecated\n"),
gcry_md_algo_name (galgo));
}
}
void
print_digest_rejected_note (enum gcry_md_algos algo)
{
struct weakhash* weak;
int show = 1;
for (weak = opt.weak_digests; weak; weak = weak->next)
if (weak->algo == algo)
{
if (weak->rejection_shown)
show = 0;
else
weak->rejection_shown = 1;
break;
}
if (show)
{
es_fflush (es_stdout);
log_info
(_("Note: signatures using the %s algorithm are rejected\n"),
gcry_md_algo_name(algo));
}
}
/* Print a message
* "(reported error: %s)\n
* in verbose mode to further explain an error. If the error code has
* the value IGNORE_EC no message is printed. A message is also not
* printed if ERR is 0. */
void
print_reported_error (gpg_error_t err, gpg_err_code_t ignore_ec)
{
if (!opt.verbose)
return;
if (!gpg_err_code (err))
;
else if (gpg_err_code (err) == ignore_ec)
;
else if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_info (_("(reported error: %s)\n"),
gpg_strerror (err));
else
log_info (_("(reported error: %s <%s>)\n"),
gpg_strerror (err), gpg_strsource (err));
}
/* Print a message
* "(further info: %s)\n
* in verbose mode to further explain an error. That message is
* intended to help debug a problem and should not be translated.
*/
void
print_further_info (const char *format, ...)
{
va_list arg_ptr;
if (!opt.verbose)
return;
log_info (_("(further info: "));
va_start (arg_ptr, format);
log_logv (GPGRT_LOG_CONT, format, arg_ptr);
va_end (arg_ptr);
log_printf (")\n");
}
/* Map OpenPGP algo numbers to those used by Libgcrypt. We need to do
this for algorithms we implemented in Libgcrypt after they become
part of OpenPGP. */
enum gcry_cipher_algos
map_cipher_openpgp_to_gcry (cipher_algo_t algo)
{
switch (algo)
{
case CIPHER_ALGO_NONE: return GCRY_CIPHER_NONE;
#ifdef GPG_USE_IDEA
case CIPHER_ALGO_IDEA: return GCRY_CIPHER_IDEA;
#else
case CIPHER_ALGO_IDEA: return 0;
#endif
case CIPHER_ALGO_3DES: return GCRY_CIPHER_3DES;
#ifdef GPG_USE_CAST5
case CIPHER_ALGO_CAST5: return GCRY_CIPHER_CAST5;
#else
case CIPHER_ALGO_CAST5: return 0;
#endif
#ifdef GPG_USE_BLOWFISH
case CIPHER_ALGO_BLOWFISH: return GCRY_CIPHER_BLOWFISH;
#else
case CIPHER_ALGO_BLOWFISH: return 0;
#endif
#ifdef GPG_USE_AES128
case CIPHER_ALGO_AES: return GCRY_CIPHER_AES;
#else
case CIPHER_ALGO_AES: return 0;
#endif
#ifdef GPG_USE_AES192
case CIPHER_ALGO_AES192: return GCRY_CIPHER_AES192;
#else
case CIPHER_ALGO_AES192: return 0;
#endif
#ifdef GPG_USE_AES256
case CIPHER_ALGO_AES256: return GCRY_CIPHER_AES256;
#else
case CIPHER_ALGO_AES256: return 0;
#endif
#ifdef GPG_USE_TWOFISH
case CIPHER_ALGO_TWOFISH: return GCRY_CIPHER_TWOFISH;
#else
case CIPHER_ALGO_TWOFISH: return 0;
#endif
#ifdef GPG_USE_CAMELLIA128
case CIPHER_ALGO_CAMELLIA128: return GCRY_CIPHER_CAMELLIA128;
#else
case CIPHER_ALGO_CAMELLIA128: return 0;
#endif
#ifdef GPG_USE_CAMELLIA192
case CIPHER_ALGO_CAMELLIA192: return GCRY_CIPHER_CAMELLIA192;
#else
case CIPHER_ALGO_CAMELLIA192: return 0;
#endif
#ifdef GPG_USE_CAMELLIA256
case CIPHER_ALGO_CAMELLIA256: return GCRY_CIPHER_CAMELLIA256;
#else
case CIPHER_ALGO_CAMELLIA256: return 0;
#endif
}
return 0;
}
/* The inverse function of above. */
static cipher_algo_t
map_cipher_gcry_to_openpgp (enum gcry_cipher_algos algo)
{
switch (algo)
{
case GCRY_CIPHER_NONE: return CIPHER_ALGO_NONE;
case GCRY_CIPHER_IDEA: return CIPHER_ALGO_IDEA;
case GCRY_CIPHER_3DES: return CIPHER_ALGO_3DES;
case GCRY_CIPHER_CAST5: return CIPHER_ALGO_CAST5;
case GCRY_CIPHER_BLOWFISH: return CIPHER_ALGO_BLOWFISH;
case GCRY_CIPHER_AES: return CIPHER_ALGO_AES;
case GCRY_CIPHER_AES192: return CIPHER_ALGO_AES192;
case GCRY_CIPHER_AES256: return CIPHER_ALGO_AES256;
case GCRY_CIPHER_TWOFISH: return CIPHER_ALGO_TWOFISH;
case GCRY_CIPHER_CAMELLIA128: return CIPHER_ALGO_CAMELLIA128;
case GCRY_CIPHER_CAMELLIA192: return CIPHER_ALGO_CAMELLIA192;
case GCRY_CIPHER_CAMELLIA256: return CIPHER_ALGO_CAMELLIA256;
default: return 0;
}
}
/* Map Gcrypt public key algorithm numbers to those used by OpenPGP.
FIXME: This mapping is used at only two places - we should get rid
of it. */
pubkey_algo_t
map_pk_gcry_to_openpgp (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA;
case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH;
default: return algo < 110 ? algo : 0;
}
}
/* Return the block length of an OpenPGP cipher algorithm. */
int
openpgp_cipher_blocklen (cipher_algo_t algo)
{
/* We use the numbers from OpenPGP to be sure that we get the right
block length. This is so that the packet parsing code works even
for unknown algorithms (for which we assume 8 due to tradition).
NOTE: If you change the returned blocklen above 16, check
the callers because they may use a fixed size buffer of that
size. */
switch (algo)
{
case CIPHER_ALGO_AES:
case CIPHER_ALGO_AES192:
case CIPHER_ALGO_AES256:
case CIPHER_ALGO_TWOFISH:
case CIPHER_ALGO_CAMELLIA128:
case CIPHER_ALGO_CAMELLIA192:
case CIPHER_ALGO_CAMELLIA256:
return 16;
default:
return 8;
}
}
/****************
* Wrapper around the libgcrypt function with additional checks on
* the OpenPGP contraints for the algo ID.
*/
int
openpgp_cipher_test_algo (cipher_algo_t algo)
{
enum gcry_cipher_algos ga;
ga = map_cipher_openpgp_to_gcry (algo);
if (!ga)
return gpg_error (GPG_ERR_CIPHER_ALGO);
return gcry_cipher_test_algo (ga);
}
/* Map the OpenPGP cipher algorithm whose ID is contained in ALGORITHM to a
string representation of the algorithm name. For unknown algorithm
IDs this function returns "?". */
const char *
openpgp_cipher_algo_name (cipher_algo_t algo)
{
switch (algo)
{
case CIPHER_ALGO_NONE: break;
case CIPHER_ALGO_IDEA: return "IDEA";
case CIPHER_ALGO_3DES: return "3DES";
case CIPHER_ALGO_CAST5: return "CAST5";
case CIPHER_ALGO_BLOWFISH: return "BLOWFISH";
case CIPHER_ALGO_AES: return "AES";
case CIPHER_ALGO_AES192: return "AES192";
case CIPHER_ALGO_AES256: return "AES256";
case CIPHER_ALGO_TWOFISH: return "TWOFISH";
case CIPHER_ALGO_CAMELLIA128: return "CAMELLIA128";
case CIPHER_ALGO_CAMELLIA192: return "CAMELLIA192";
case CIPHER_ALGO_CAMELLIA256: return "CAMELLIA256";
}
return "?";
}
/* Return 0 if ALGO is a supported OpenPGP public key algorithm. */
int
openpgp_pk_test_algo (pubkey_algo_t algo)
{
return openpgp_pk_test_algo2 (algo, 0);
}
/* Return 0 if ALGO is a supported OpenPGP public key algorithm and
allows the usage USE. */
int
openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use)
{
enum gcry_pk_algos ga = 0;
size_t use_buf = use;
switch (algo)
{
#ifdef GPG_USE_RSA
case PUBKEY_ALGO_RSA: ga = GCRY_PK_RSA; break;
case PUBKEY_ALGO_RSA_E: ga = GCRY_PK_RSA_E; break;
case PUBKEY_ALGO_RSA_S: ga = GCRY_PK_RSA_S; break;
#else
case PUBKEY_ALGO_RSA: break;
case PUBKEY_ALGO_RSA_E: break;
case PUBKEY_ALGO_RSA_S: break;
#endif
case PUBKEY_ALGO_ELGAMAL_E: ga = GCRY_PK_ELG; break;
case PUBKEY_ALGO_DSA: ga = GCRY_PK_DSA; break;
#ifdef GPG_USE_ECDH
case PUBKEY_ALGO_ECDH: ga = GCRY_PK_ECC; break;
#else
case PUBKEY_ALGO_ECDH: break;
#endif
#ifdef GPG_USE_ECDSA
case PUBKEY_ALGO_ECDSA: ga = GCRY_PK_ECC; break;
#else
case PUBKEY_ALGO_ECDSA: break;
#endif
#ifdef GPG_USE_EDDSA
case PUBKEY_ALGO_EDDSA: ga = GCRY_PK_ECC; break;
#else
case PUBKEY_ALGO_EDDSA: break;
#endif
case PUBKEY_ALGO_ELGAMAL:
/* Dont't allow type 20 keys unless in rfc2440 mode. */
if (RFC2440)
ga = GCRY_PK_ELG;
break;
}
if (!ga)
return gpg_error (GPG_ERR_PUBKEY_ALGO);
/* No check whether Libgcrypt has support for the algorithm. */
return gcry_pk_algo_info (ga, GCRYCTL_TEST_ALGO, NULL, &use_buf);
}
int
openpgp_pk_algo_usage ( int algo )
{
int use = 0;
/* They are hardwired in gpg 1.0. */
switch ( algo ) {
case PUBKEY_ALGO_RSA:
use = (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG
| PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH);
break;
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_ECDH:
use = PUBKEY_USAGE_ENC;
break;
case PUBKEY_ALGO_RSA_S:
use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG;
break;
case PUBKEY_ALGO_ELGAMAL:
if (RFC2440)
use = PUBKEY_USAGE_ENC;
break;
case PUBKEY_ALGO_ELGAMAL_E:
use = PUBKEY_USAGE_ENC;
break;
case PUBKEY_ALGO_DSA:
use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA:
use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH;
default:
break;
}
return use;
}
/* Map the OpenPGP pubkey algorithm whose ID is contained in ALGO to a
string representation of the algorithm name. For unknown algorithm
IDs this function returns "?". */
const char *
openpgp_pk_algo_name (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: return "RSA";
case PUBKEY_ALGO_ELGAMAL:
case PUBKEY_ALGO_ELGAMAL_E: return "ELG";
case PUBKEY_ALGO_DSA: return "DSA";
case PUBKEY_ALGO_ECDH: return "ECDH";
case PUBKEY_ALGO_ECDSA: return "ECDSA";
case PUBKEY_ALGO_EDDSA: return "EDDSA";
}
return "?";
}
/* Explicit mapping of OpenPGP digest algos to Libgcrypt. */
/* FIXME: We do not yes use it everywhere. */
enum gcry_md_algos
map_md_openpgp_to_gcry (digest_algo_t algo)
{
switch (algo)
{
#ifdef GPG_USE_MD5
case DIGEST_ALGO_MD5: return GCRY_MD_MD5;
#else
case DIGEST_ALGO_MD5: return 0;
#endif
case DIGEST_ALGO_SHA1: return GCRY_MD_SHA1;
#ifdef GPG_USE_RMD160
case DIGEST_ALGO_RMD160: return GCRY_MD_RMD160;
#else
case DIGEST_ALGO_RMD160: return 0;
#endif
#ifdef GPG_USE_SHA224
case DIGEST_ALGO_SHA224: return GCRY_MD_SHA224;
#else
case DIGEST_ALGO_SHA224: return 0;
#endif
case DIGEST_ALGO_SHA256: return GCRY_MD_SHA256;
#ifdef GPG_USE_SHA384
case DIGEST_ALGO_SHA384: return GCRY_MD_SHA384;
#else
case DIGEST_ALGO_SHA384: return 0;
#endif
#ifdef GPG_USE_SHA512
case DIGEST_ALGO_SHA512: return GCRY_MD_SHA512;
#else
case DIGEST_ALGO_SHA512: return 0;
#endif
}
return 0;
}
/* Return 0 if ALGO is suitable and implemented OpenPGP hash
algorithm. */
int
openpgp_md_test_algo (digest_algo_t algo)
{
enum gcry_md_algos ga;
ga = map_md_openpgp_to_gcry (algo);
if (!ga)
return gpg_error (GPG_ERR_DIGEST_ALGO);
return gcry_md_test_algo (ga);
}
/* Map the OpenPGP digest algorithm whose ID is contained in ALGO to a
string representation of the algorithm name. For unknown algorithm
IDs this function returns "?". */
const char *
openpgp_md_algo_name (int algo)
{
switch (algo)
{
case DIGEST_ALGO_MD5: return "MD5";
case DIGEST_ALGO_SHA1: return "SHA1";
case DIGEST_ALGO_RMD160: return "RIPEMD160";
case DIGEST_ALGO_SHA256: return "SHA256";
case DIGEST_ALGO_SHA384: return "SHA384";
case DIGEST_ALGO_SHA512: return "SHA512";
case DIGEST_ALGO_SHA224: return "SHA224";
}
return "?";
}
static unsigned long
get_signature_count (PKT_public_key *pk)
{
#ifdef ENABLE_CARD_SUPPORT
struct agent_card_info_s info;
(void)pk;
if (!agent_scd_getattr ("SIG-COUNTER",&info))
return info.sig_counter;
else
return 0;
#else
(void)pk;
return 0;
#endif
}
/* Expand %-strings. Returns a string which must be xfreed. Returns
NULL if the string cannot be expanded (too large). */
char *
pct_expando(const char *string,struct expando_args *args)
{
const char *ch=string;
int idx=0,maxlen=0,done=0;
u32 pk_keyid[2]={0,0},sk_keyid[2]={0,0};
char *ret=NULL;
if(args->pk)
keyid_from_pk(args->pk,pk_keyid);
if(args->pksk)
keyid_from_pk (args->pksk, sk_keyid);
/* This is used so that %k works in photoid command strings in
--list-secret-keys (which of course has a sk, but no pk). */
if(!args->pk && args->pksk)
keyid_from_pk (args->pksk, pk_keyid);
while(*ch!='\0')
{
if(!done)
{
/* 8192 is way bigger than we'll need here */
if(maxlen>=8192)
goto fail;
maxlen+=1024;
ret=xrealloc(ret,maxlen);
}
done=0;
if(*ch=='%')
{
switch(*(ch+1))
{
case 's': /* short key id */
if(idx+8<maxlen)
{
sprintf(&ret[idx],"%08lX",(ulong)sk_keyid[1]);
idx+=8;
done=1;
}
break;
case 'S': /* long key id */
if(idx+16<maxlen)
{
sprintf(&ret[idx],"%08lX%08lX",
(ulong)sk_keyid[0],(ulong)sk_keyid[1]);
idx+=16;
done=1;
}
break;
case 'k': /* short key id */
if(idx+8<maxlen)
{
sprintf(&ret[idx],"%08lX",(ulong)pk_keyid[1]);
idx+=8;
done=1;
}
break;
case 'K': /* long key id */
if(idx+16<maxlen)
{
sprintf(&ret[idx],"%08lX%08lX",
(ulong)pk_keyid[0],(ulong)pk_keyid[1]);
idx+=16;
done=1;
}
break;
case 'U': /* z-base-32 encoded user id hash. */
if (args->namehash)
{
char *tmp = zb32_encode (args->namehash, 8*20);
if (tmp)
{
if (idx + strlen (tmp) < maxlen)
{
strcpy (ret+idx, tmp);
idx += strlen (tmp);
}
xfree (tmp);
done = 1;
}
}
break;
case 'c': /* signature count from card, if any. */
if(idx+10<maxlen)
{
sprintf (&ret[idx],"%lu", get_signature_count (args->pksk));
idx+=strlen(&ret[idx]);
done=1;
}
break;
case 'f': /* Fingerprint of key being signed */
case 'p': /* Fingerprint of the primary key making the signature. */
case 'g': /* Fingerprint of the key making the signature. */
{
byte array[MAX_FINGERPRINT_LEN];
size_t len;
int i;
if ((*(ch+1))=='f' && args->pk)
fingerprint_from_pk (args->pk, array, &len);
else if ((*(ch+1))=='p' && args->pksk)
{
if(args->pksk->flags.primary)
fingerprint_from_pk (args->pksk, array, &len);
else if (args->pksk->main_keyid[0]
|| args->pksk->main_keyid[1])
{
/* Not the primary key: Find the fingerprint
of the primary key. */
PKT_public_key *pk=
xmalloc_clear(sizeof(PKT_public_key));
if (!get_pubkey_fast (pk,args->pksk->main_keyid))
fingerprint_from_pk (pk, array, &len);
else
memset (array, 0, (len=MAX_FINGERPRINT_LEN));
free_public_key (pk);
}
else /* Oops: info about the primary key missing. */
memset(array,0,(len=MAX_FINGERPRINT_LEN));
}
else if((*(ch+1))=='g' && args->pksk)
fingerprint_from_pk (args->pksk, array, &len);
else
memset(array,0,(len=MAX_FINGERPRINT_LEN));
if(idx+(len*2)<maxlen)
{
for(i=0;i<len;i++)
{
sprintf(&ret[idx],"%02X",array[i]);
idx+=2;
}
done=1;
}
}
break;
case 'v': /* validity letters */
if(args->validity_info && idx+1<maxlen)
{
ret[idx++]=args->validity_info;
ret[idx]='\0';
done=1;
}
break;
/* The text string types */
case 't':
case 'T':
case 'V':
{
const char *str=NULL;
switch(*(ch+1))
{
case 't': /* e.g. "jpg" */
str=image_type_to_string(args->imagetype,0);
break;
case 'T': /* e.g. "image/jpeg" */
str=image_type_to_string(args->imagetype,2);
break;
case 'V': /* e.g. "full", "expired", etc. */
str=args->validity_string;
break;
}
if(str && idx+strlen(str)<maxlen)
{
strcpy(&ret[idx],str);
idx+=strlen(str);
done=1;
}
}
break;
case '%':
if(idx+1<maxlen)
{
ret[idx++]='%';
ret[idx]='\0';
done=1;
}
break;
/* Any unknown %-keys (like %i, %o, %I, and %O) are
passed through for later expansion. Note this also
handles the case where the last character in the
string is a '%' - the terminating \0 will end up here
and properly terminate the string. */
default:
if(idx+2<maxlen)
{
ret[idx++]='%';
ret[idx++]=*(ch+1);
ret[idx]='\0';
done=1;
}
break;
}
if(done)
ch++;
}
else
{
if(idx+1<maxlen)
{
ret[idx++]=*ch;
ret[idx]='\0';
done=1;
}
}
if(done)
ch++;
}
return ret;
fail:
xfree(ret);
return NULL;
}
void
deprecated_warning(const char *configname,unsigned int configlineno,
const char *option,const char *repl1,const char *repl2)
{
if(configname)
{
if(strncmp("--",option,2)==0)
option+=2;
if(strncmp("--",repl1,2)==0)
repl1+=2;
log_info(_("%s:%d: deprecated option \"%s\"\n"),
configname,configlineno,option);
}
else
log_info(_("WARNING: \"%s\" is a deprecated option\n"),option);
log_info(_("please use \"%s%s\" instead\n"),repl1,repl2);
}
void
deprecated_command (const char *name)
{
log_info(_("WARNING: \"%s\" is a deprecated command - do not use it\n"),
name);
}
void
obsolete_scdaemon_option (const char *configname, unsigned int configlineno,
const char *name)
{
if (configname)
log_info (_("%s:%u: \"%s\" is obsolete in this file"
" - it only has effect in %s\n"),
configname, configlineno, name, SCDAEMON_NAME EXTSEP_S "conf");
else
log_info (_("WARNING: \"%s%s\" is an obsolete option"
" - it has no effect except on %s\n"),
"--", name, SCDAEMON_NAME);
}
/*
* Wrapper around gcry_cipher_map_name to provide a fallback using the
* "Sn" syntax as used by the preference strings.
*/
int
string_to_cipher_algo (const char *string)
{
int val;
val = map_cipher_gcry_to_openpgp (gcry_cipher_map_name (string));
if (!val && string && (string[0]=='S' || string[0]=='s'))
{
char *endptr;
string++;
val = strtol (string, &endptr, 10);
if (!*string || *endptr || openpgp_cipher_test_algo (val))
val = 0;
}
return val;
}
/*
* Wrapper around gcry_md_map_name to provide a fallback using the
* "Hn" syntax as used by the preference strings.
*/
int
string_to_digest_algo (const char *string)
{
int val;
/* FIXME: We should make use of our wrapper function and not assume
that there is a 1 to 1 mapping between OpenPGP and Libgcrypt. */
val = gcry_md_map_name (string);
if (!val && string && (string[0]=='H' || string[0]=='h'))
{
char *endptr;
string++;
val = strtol (string, &endptr, 10);
if (!*string || *endptr || openpgp_md_test_algo (val))
val = 0;
}
return val;
}
const char *
compress_algo_to_string(int algo)
{
const char *s=NULL;
switch(algo)
{
case COMPRESS_ALGO_NONE:
s=_("Uncompressed");
break;
case COMPRESS_ALGO_ZIP:
s="ZIP";
break;
case COMPRESS_ALGO_ZLIB:
s="ZLIB";
break;
#ifdef HAVE_BZIP2
case COMPRESS_ALGO_BZIP2:
s="BZIP2";
break;
#endif
}
return s;
}
int
string_to_compress_algo(const char *string)
{
/* TRANSLATORS: See doc/TRANSLATE about this string. */
if(match_multistr(_("uncompressed|none"),string))
return 0;
else if(ascii_strcasecmp(string,"uncompressed")==0)
return 0;
else if(ascii_strcasecmp(string,"none")==0)
return 0;
else if(ascii_strcasecmp(string,"zip")==0)
return 1;
else if(ascii_strcasecmp(string,"zlib")==0)
return 2;
#ifdef HAVE_BZIP2
else if(ascii_strcasecmp(string,"bzip2")==0)
return 3;
#endif
else if(ascii_strcasecmp(string,"z0")==0)
return 0;
else if(ascii_strcasecmp(string,"z1")==0)
return 1;
else if(ascii_strcasecmp(string,"z2")==0)
return 2;
#ifdef HAVE_BZIP2
else if(ascii_strcasecmp(string,"z3")==0)
return 3;
#endif
else
return -1;
}
int
check_compress_algo(int algo)
{
switch (algo)
{
case 0: return 0;
#ifdef HAVE_ZIP
case 1:
case 2: return 0;
#endif
#ifdef HAVE_BZIP2
case 3: return 0;
#endif
default: return GPG_ERR_COMPR_ALGO;
}
}
int
default_cipher_algo(void)
{
if(opt.def_cipher_algo)
return opt.def_cipher_algo;
else if(opt.personal_cipher_prefs)
return opt.personal_cipher_prefs[0].value;
else
return opt.s2k_cipher_algo;
}
/* There is no default_digest_algo function, but see
sign.c:hash_for() */
int
default_compress_algo(void)
{
if(opt.compress_algo!=-1)
return opt.compress_algo;
else if(opt.personal_compress_prefs)
return opt.personal_compress_prefs[0].value;
else
return DEFAULT_COMPRESS_ALGO;
}
const char *
compliance_option_string(void)
{
char *ver="???";
switch(opt.compliance)
{
case CO_GNUPG: return "--gnupg";
case CO_RFC4880: return "--openpgp";
case CO_RFC2440: return "--rfc2440";
case CO_PGP6: return "--pgp6";
case CO_PGP7: return "--pgp7";
case CO_PGP8: return "--pgp8";
case CO_DE_VS: return "--compliance=de-vs";
}
return ver;
}
void
compliance_failure(void)
{
char *ver="???";
switch(opt.compliance)
{
case CO_GNUPG:
ver="GnuPG";
break;
case CO_RFC4880:
ver="OpenPGP";
break;
case CO_RFC2440:
ver="OpenPGP (older)";
break;
case CO_PGP6:
ver="PGP 6.x";
break;
case CO_PGP7:
ver="PGP 7.x";
break;
case CO_PGP8:
ver="PGP 8.x";
break;
case CO_DE_VS:
ver="DE-VS applications";
break;
}
log_info(_("this message may not be usable by %s\n"),ver);
opt.compliance=CO_GNUPG;
}
/* Break a string into successive option pieces. Accepts single word
options and key=value argument options. */
char *
optsep(char **stringp)
{
char *tok,*end;
tok=*stringp;
if(tok)
{
end=strpbrk(tok," ,=");
if(end)
{
int sawequals=0;
char *ptr=end;
/* what we need to do now is scan along starting with *end,
If the next character we see (ignoring spaces) is an =
sign, then there is an argument. */
while(*ptr)
{
if(*ptr=='=')
sawequals=1;
else if(*ptr!=' ')
break;
ptr++;
}
/* There is an argument, so grab that too. At this point,
ptr points to the first character of the argument. */
if(sawequals)
{
/* Is it a quoted argument? */
if(*ptr=='"')
{
ptr++;
end=strchr(ptr,'"');
if(end)
end++;
}
else
end=strpbrk(ptr," ,");
}
if(end && *end)
{
*end='\0';
*stringp=end+1;
}
else
*stringp=NULL;
}
else
*stringp=NULL;
}
return tok;
}
/* Breaks an option value into key and value. Returns NULL if there
is no value. Note that "string" is modified to remove the =value
part. */
char *
argsplit(char *string)
{
char *equals,*arg=NULL;
equals=strchr(string,'=');
if(equals)
{
char *quote,*space;
*equals='\0';
arg=equals+1;
/* Quoted arg? */
quote=strchr(arg,'"');
if(quote)
{
arg=quote+1;
quote=strchr(arg,'"');
if(quote)
*quote='\0';
}
else
{
size_t spaces;
/* Trim leading spaces off of the arg */
spaces=strspn(arg," ");
arg+=spaces;
}
/* Trim tailing spaces off of the tag */
space=strchr(string,' ');
if(space)
*space='\0';
}
return arg;
}
/* Return the length of the initial token, leaving off any
argument. */
static size_t
optlen(const char *s)
{
char *end=strpbrk(s," =");
if(end)
return end-s;
else
return strlen(s);
}
int
parse_options(char *str,unsigned int *options,
struct parse_options *opts,int noisy)
{
char *tok;
if (str && !strcmp (str, "help"))
{
int i,maxlen=0;
/* Figure out the longest option name so we can line these up
neatly. */
for(i=0;opts[i].name;i++)
if(opts[i].help && maxlen<strlen(opts[i].name))
maxlen=strlen(opts[i].name);
for(i=0;opts[i].name;i++)
if(opts[i].help)
es_printf("%s%*s%s\n",opts[i].name,
maxlen+2-(int)strlen(opts[i].name),"",_(opts[i].help));
g10_exit(0);
}
while((tok=optsep(&str)))
{
int i,rev=0;
char *otok=tok;
if(tok[0]=='\0')
continue;
if(ascii_strncasecmp("no-",tok,3)==0)
{
rev=1;
tok+=3;
}
for(i=0;opts[i].name;i++)
{
size_t toklen=optlen(tok);
if(ascii_strncasecmp(opts[i].name,tok,toklen)==0)
{
/* We have a match, but it might be incomplete */
if(toklen!=strlen(opts[i].name))
{
int j;
for(j=i+1;opts[j].name;j++)
{
if(ascii_strncasecmp(opts[j].name,tok,toklen)==0)
{
if(noisy)
log_info(_("ambiguous option '%s'\n"),otok);
return 0;
}
}
}
if(rev)
{
*options&=~opts[i].bit;
if(opts[i].value)
*opts[i].value=NULL;
}
else
{
*options|=opts[i].bit;
if(opts[i].value)
*opts[i].value=argsplit(tok);
}
break;
}
}
if(!opts[i].name)
{
if(noisy)
log_info(_("unknown option '%s'\n"),otok);
return 0;
}
}
return 1;
}
/* Similar to access(2), but uses PATH to find the file. */
int
path_access(const char *file,int mode)
{
char *envpath;
int ret=-1;
envpath=getenv("PATH");
if(!envpath
#ifdef HAVE_DRIVE_LETTERS
|| (((file[0]>='A' && file[0]<='Z')
|| (file[0]>='a' && file[0]<='z'))
&& file[1]==':')
#else
|| file[0]=='/'
#endif
)
return access(file,mode);
else
{
/* At least as large as, but most often larger than we need. */
char *buffer=xmalloc(strlen(envpath)+1+strlen(file)+1);
char *split,*item,*path=xstrdup(envpath);
split=path;
while((item=strsep(&split,PATHSEP_S)))
{
strcpy(buffer,item);
strcat(buffer,"/");
strcat(buffer,file);
ret=access(buffer,mode);
if(ret==0)
break;
}
xfree(path);
xfree(buffer);
}
return ret;
}
/* Return the number of public key parameters as used by OpenPGP. */
int
pubkey_get_npkey (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: return 2;
case PUBKEY_ALGO_ELGAMAL_E: return 3;
case PUBKEY_ALGO_DSA: return 4;
case PUBKEY_ALGO_ECDH: return 3;
case PUBKEY_ALGO_ECDSA: return 2;
case PUBKEY_ALGO_ELGAMAL: return 3;
case PUBKEY_ALGO_EDDSA: return 2;
}
return 0;
}
/* Return the number of secret key parameters as used by OpenPGP. */
int
pubkey_get_nskey (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: return 6;
case PUBKEY_ALGO_ELGAMAL_E: return 4;
case PUBKEY_ALGO_DSA: return 5;
case PUBKEY_ALGO_ECDH: return 4;
case PUBKEY_ALGO_ECDSA: return 3;
case PUBKEY_ALGO_ELGAMAL: return 4;
case PUBKEY_ALGO_EDDSA: return 3;
}
return 0;
}
/* Temporary helper. */
int
pubkey_get_nsig (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: return 1;
case PUBKEY_ALGO_ELGAMAL_E: return 0;
case PUBKEY_ALGO_DSA: return 2;
case PUBKEY_ALGO_ECDH: return 0;
case PUBKEY_ALGO_ECDSA: return 2;
case PUBKEY_ALGO_ELGAMAL: return 2;
case PUBKEY_ALGO_EDDSA: return 2;
}
return 0;
}
/* Temporary helper. */
int
pubkey_get_nenc (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: return 1;
case PUBKEY_ALGO_ELGAMAL_E: return 2;
case PUBKEY_ALGO_DSA: return 0;
case PUBKEY_ALGO_ECDH: return 2;
case PUBKEY_ALGO_ECDSA: return 0;
case PUBKEY_ALGO_ELGAMAL: return 2;
case PUBKEY_ALGO_EDDSA: return 0;
}
return 0;
}
/* Temporary helper. */
unsigned int
pubkey_nbits( int algo, gcry_mpi_t *key )
{
int rc, nbits;
gcry_sexp_t sexp;
if (algo == PUBKEY_ALGO_DSA
&& key[0] && key[1] && key[2] && key[3])
{
rc = gcry_sexp_build (&sexp, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
key[0], key[1], key[2], key[3] );
}
else if ((algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
&& key[0] && key[1] && key[2])
{
rc = gcry_sexp_build (&sexp, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
key[0], key[1], key[2] );
}
else if (is_RSA (algo)
&& key[0] && key[1])
{
rc = gcry_sexp_build (&sexp, NULL,
"(public-key(rsa(n%m)(e%m)))",
key[0], key[1] );
}
else if ((algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_ECDH
|| algo == PUBKEY_ALGO_EDDSA)
&& key[0] && key[1])
{
char *curve = openpgp_oid_to_str (key[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&sexp, NULL,
"(public-key(ecc(curve%s)(q%m)))",
curve, key[1]);
xfree (curve);
}
}
else
return 0;
if (rc)
BUG ();
nbits = gcry_pk_get_nbits (sexp);
gcry_sexp_release (sexp);
return nbits;
}
int
mpi_print (estream_t fp, gcry_mpi_t a, int mode)
{
int n = 0;
size_t nwritten;
if (!a)
return es_fprintf (fp, "[MPI_NULL]");
if (!mode)
{
unsigned int n1;
n1 = gcry_mpi_get_nbits(a);
n += es_fprintf (fp, "[%u bits]", n1);
}
else if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
unsigned char *p = gcry_mpi_get_opaque (a, &nbits);
if (!p)
n += es_fprintf (fp, "[invalid opaque value]");
else
{
if (!es_write_hexstring (fp, p, (nbits + 7)/8, 0, &nwritten))
n += nwritten;
}
}
else
{
unsigned char *buffer;
size_t buflen;
if (gcry_mpi_aprint (GCRYMPI_FMT_USG, &buffer, &buflen, a))
BUG ();
if (!es_write_hexstring (fp, buffer, buflen, 0, &nwritten))
n += nwritten;
gcry_free (buffer);
}
return n;
}
/* pkey[1] or skey[1] is Q for ECDSA, which is an uncompressed point,
i.e. 04 <x> <y> */
unsigned int
ecdsa_qbits_from_Q (unsigned int qbits)
{
if ((qbits%8) > 3)
{
log_error (_("ECDSA public key is expected to be in SEC encoding "
"multiple of 8 bits\n"));
return 0;
}
qbits -= qbits%8;
qbits /= 2;
return qbits;
}
/* Ignore signatures and certifications made over certain digest
* algorithms by default, MD5 is considered weak. This allows users
* to deprecate support for other algorithms as well.
*/
void
additional_weak_digest (const char* digestname)
{
struct weakhash *weak = NULL;
const enum gcry_md_algos algo = string_to_digest_algo(digestname);
if (algo == GCRY_MD_NONE)
{
log_error (_("unknown weak digest '%s'\n"), digestname);
return;
}
/* Check to ensure it's not already present. */
for (weak = opt.weak_digests; weak; weak = weak->next)
if (algo == weak->algo)
return;
/* Add it to the head of the list. */
weak = xmalloc(sizeof(*weak));
weak->algo = algo;
weak->rejection_shown = 0;
weak->next = opt.weak_digests;
opt.weak_digests = weak;
}
diff --git a/g10/openfile.c b/g10/openfile.c
index f62deec54..2257107ad 100644
--- a/g10/openfile.c
+++ b/g10/openfile.c
@@ -1,523 +1,523 @@
/* openfile.c
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gpg.h"
-#include "util.h"
-#include "ttyio.h"
+#include "../common/util.h"
+#include "../common/ttyio.h"
#include "options.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#ifdef USE_ONLY_8DOT3
#define SKELEXT ".skl"
#else
#define SKELEXT EXTSEP_S "skel"
#endif
#ifdef HAVE_W32_SYSTEM
#define NAME_OF_DEV_NULL "nul"
#else
#define NAME_OF_DEV_NULL "/dev/null"
#endif
#if defined (HAVE_DRIVE_LETTERS) || defined (__riscos__)
#define CMP_FILENAME(a,b) ascii_strcasecmp( (a), (b) )
#else
#define CMP_FILENAME(a,b) strcmp( (a), (b) )
#endif
/* FIXME: Implement opt.interactive. */
/*
* Check whether FNAME exists and ask if it's okay to overwrite an
* existing one.
* Returns: True: it's okay to overwrite or the file does not exist
* False: Do not overwrite
*/
int
overwrite_filep( const char *fname )
{
if ( iobuf_is_pipe_filename (fname) )
return 1; /* Writing to stdout is always okay. */
if ( access( fname, F_OK ) )
return 1; /* Does not exist. */
if ( !compare_filenames (fname, NAME_OF_DEV_NULL) )
return 1; /* Does not do any harm. */
if (opt.answer_yes)
return 1;
if (opt.answer_no || opt.batch)
return 0; /* Do not overwrite. */
tty_printf (_("File '%s' exists. "), fname);
if (cpr_enabled ())
tty_printf ("\n");
if (cpr_get_answer_is_yes ("openfile.overwrite.okay",
_("Overwrite? (y/N) ")) )
return 1;
return 0;
}
/*
* Strip known extensions from iname and return a newly allocated
* filename. Return NULL if we can't do that.
*/
char *
make_outfile_name (const char *iname)
{
size_t n;
if (iobuf_is_pipe_filename (iname))
return xstrdup ("-");
n = strlen (iname);
if (n > 4 && (!CMP_FILENAME(iname+n-4, EXTSEP_S GPGEXT_GPG)
|| !CMP_FILENAME(iname+n-4, EXTSEP_S "pgp")
|| !CMP_FILENAME(iname+n-4, EXTSEP_S "sig")
|| !CMP_FILENAME(iname+n-4, EXTSEP_S "asc")))
{
char *buf = xstrdup (iname);
buf[n-4] = 0;
return buf;
}
else if (n > 5 && !CMP_FILENAME(iname+n-5, EXTSEP_S "sign"))
{
char *buf = xstrdup (iname);
buf[n-5] = 0;
return buf;
}
log_info (_("%s: unknown suffix\n"), iname);
return NULL;
}
/* Ask for an output filename; use the given one as default. Return
NULL if no file has been given or if it is not possible to ask the
user. NAME is the template len which might conatin enbedded Nuls.
NAMELEN is its actual length.
*/
char *
ask_outfile_name( const char *name, size_t namelen )
{
size_t n;
const char *s;
char *prompt;
char *fname;
char *defname;
if ( opt.batch )
return NULL;
defname = name && namelen? make_printable_string (name, namelen, 0) : NULL;
s = _("Enter new filename");
n = strlen(s) + (defname?strlen (defname):0) + 10;
prompt = xmalloc (n);
if (defname)
snprintf (prompt, n, "%s [%s]: ", s, defname );
else
snprintf (prompt, n, "%s: ", s );
tty_enable_completion(NULL);
fname = cpr_get ("openfile.askoutname", prompt );
cpr_kill_prompt ();
tty_disable_completion ();
xfree (prompt);
if ( !*fname )
{
xfree (fname);
fname = defname;
defname = NULL;
}
xfree (defname);
if (fname)
trim_spaces (fname);
return fname;
}
/*
* Make an output filename for the inputfile INAME.
* Returns an IOBUF and an errorcode
* Mode 0 = use ".gpg"
* 1 = use ".asc"
* 2 = use ".sig"
* 3 = use ".rev"
*
* If INP_FD is not -1 the function simply creates an IOBUF for that
* file descriptor and ignore INAME and MODE. Note that INP_FD won't
* be closed if the returned IOBUF is closed. With RESTRICTEDPERM a
* file will be created with mode 700 if possible.
*/
int
open_outfile (int inp_fd, const char *iname, int mode, int restrictedperm,
iobuf_t *a)
{
int rc = 0;
*a = NULL;
if (inp_fd != -1)
{
char xname[64];
*a = iobuf_fdopen_nc (inp_fd, "wb");
if (!*a)
{
rc = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", inp_fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (rc));
}
else if (opt.verbose)
{
snprintf (xname, sizeof xname, "[fd %d]", inp_fd);
log_info (_("writing to '%s'\n"), xname);
}
}
else if (iobuf_is_pipe_filename (iname) && !opt.outfile)
{
*a = iobuf_create (NULL, 0);
if ( !*a )
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), "[stdout]", strerror(errno) );
}
else if ( opt.verbose )
log_info (_("writing to stdout\n"));
}
else
{
char *buf = NULL;
const char *name;
if (opt.dry_run)
name = NAME_OF_DEV_NULL;
else if (opt.outfile)
name = opt.outfile;
else
{
#ifdef USE_ONLY_8DOT3
if (opt.mangle_dos_filenames)
{
/* It is quite common for DOS systems to have only one
dot in a filename. If we have something like this,
we simple replace the suffix except in cases where
the suffix is larger than 3 characters and not the
same as the new one. We don't map the filenames to
8.3 because this is a duty of the file system. */
char *dot;
const char *newsfx;
newsfx = (mode==1 ? ".asc" :
mode==2 ? ".sig" :
mode==3 ? ".rev" : ".gpg");
buf = xmalloc (strlen(iname)+4+1);
strcpy (buf, iname);
dot = strchr (buf, '.' );
if ( dot && dot > buf && dot[1] && strlen(dot) <= 4
&& CMP_FILENAME (newsfx, dot) )
strcpy (dot, newsfx);
else if (dot && !dot[1]) /* Do not duplicate a dot. */
strcpy (dot, newsfx+1);
else
strcat (buf, newsfx);
}
if (!buf)
#endif /* USE_ONLY_8DOT3 */
{
buf = xstrconcat (iname,
(mode==1 ? EXTSEP_S "asc" :
mode==2 ? EXTSEP_S "sig" :
mode==3 ? EXTSEP_S "rev" :
/* */ EXTSEP_S GPGEXT_GPG),
NULL);
}
name = buf;
}
rc = 0;
while ( !overwrite_filep (name) )
{
char *tmp = ask_outfile_name (NULL, 0);
if ( !tmp || !*tmp )
{
xfree (tmp);
rc = gpg_error (GPG_ERR_EEXIST);
break;
}
xfree (buf);
name = buf = tmp;
}
if ( !rc )
{
if (is_secured_filename (name) )
{
*a = NULL;
gpg_err_set_errno (EPERM);
}
else
*a = iobuf_create (name, restrictedperm);
if (!*a)
{
rc = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), name, strerror(errno) );
}
else if( opt.verbose )
log_info (_("writing to '%s'\n"), name );
}
xfree(buf);
}
if (*a)
iobuf_ioctl (*a, IOBUF_IOCTL_NO_CACHE, 1, NULL);
return rc;
}
/* Find a matching data file for the signature file SIGFILENAME and
return it as a malloced string. If no matching data file is found,
return NULL. */
char *
get_matching_datafile (const char *sigfilename)
{
char *fname = NULL;
size_t len;
if (iobuf_is_pipe_filename (sigfilename))
return NULL;
len = strlen (sigfilename);
if (len > 4
&& (!strcmp (sigfilename + len - 4, EXTSEP_S "sig")
|| (len > 5 && !strcmp(sigfilename + len - 5, EXTSEP_S "sign"))
|| !strcmp(sigfilename + len - 4, EXTSEP_S "asc")))
{
fname = xstrdup (sigfilename);
fname[len-(fname[len-1]=='n'?5:4)] = 0 ;
if (access (fname, R_OK ))
{
/* Not found or other error. */
xfree (fname);
fname = NULL;
}
}
return fname;
}
/*
* Try to open a file without the extension ".sig" or ".asc"
* Return NULL if such a file is not available.
*/
iobuf_t
open_sigfile (const char *sigfilename, progress_filter_context_t *pfx)
{
iobuf_t a = NULL;
char *buf;
buf = get_matching_datafile (sigfilename);
if (buf)
{
a = iobuf_open (buf);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if (a)
log_info (_("assuming signed data in '%s'\n"), buf);
if (a && pfx)
handle_progress (pfx, a, buf);
xfree (buf);
}
return a;
}
/****************
* Copy the option file skeleton for NAME to the given directory.
* Returns true if the new option file has any option.
*/
static int
copy_options_file (const char *destdir, const char *name)
{
const char *datadir = gnupg_datadir ();
char *fname;
FILE *src, *dst;
int linefeeds=0;
int c;
mode_t oldmask;
int esc = 0;
int any_option = 0;
if (opt.dry_run)
return 0;
fname = xstrconcat (datadir, DIRSEP_S, name, "-conf", SKELEXT, NULL);
src = fopen (fname, "r");
if (src && is_secured_file (fileno (src)))
{
fclose (src);
src = NULL;
gpg_err_set_errno (EPERM);
}
if (!src)
{
log_info (_("can't open '%s': %s\n"), fname, strerror(errno));
xfree(fname);
return 0;
}
xfree (fname);
fname = xstrconcat (destdir, DIRSEP_S, name, EXTSEP_S, "conf", NULL);
oldmask = umask (077);
if (is_secured_filename (fname))
{
dst = NULL;
gpg_err_set_errno (EPERM);
}
else
dst = fopen( fname, "w" );
umask (oldmask);
if (!dst)
{
log_info (_("can't create '%s': %s\n"), fname, strerror(errno) );
fclose (src);
xfree (fname);
return 0;
}
while ((c = getc (src)) != EOF)
{
if (linefeeds < 3)
{
if (c == '\n')
linefeeds++;
}
else
{
putc (c, dst);
if (c== '\n')
esc = 1;
else if (esc == 1)
{
if (c == ' ' || c == '\t')
;
else if (c == '#')
esc = 2;
else
any_option = 1;
}
}
}
fclose (dst);
fclose (src);
log_info (_("new configuration file '%s' created\n"), fname);
xfree (fname);
return any_option;
}
void
try_make_homedir (const char *fname)
{
const char *defhome = standard_homedir ();
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we do compare only the suffix if we see that the
default homedir does start with a tilde. */
if ( opt.dry_run || opt.no_homedir_creation )
return;
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (fname, defhome) )
#else
( *defhome == '~'
&& (strlen(fname) >= strlen (defhome+1)
&& !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) ))
|| (*defhome != '~' && !compare_filenames( fname, defhome ) )
#endif
)
{
if (gnupg_mkdir (fname, "-rwx"))
log_fatal ( _("can't create directory '%s': %s\n"),
fname, strerror(errno) );
else if (!opt.quiet )
log_info ( _("directory '%s' created\n"), fname );
/* Note that we also copy a dirmngr.conf file here. This is
because gpg is likely the first invoked tool and thus creates
the directory. */
copy_options_file (fname, DIRMNGR_NAME);
if (copy_options_file (fname, GPG_NAME))
log_info (_("WARNING: options in '%s'"
" are not yet active during this run\n"),
fname);
}
}
/* Get and if needed create a string with the directory used to store
openpgp revocations. */
char *
get_openpgp_revocdir (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_OPENPGP_REVOC_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
return fname;
}
diff --git a/g10/options.h b/g10/options.h
index 88a8f32bd..def638591 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -1,398 +1,398 @@
/* options.h
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_OPTIONS_H
#define G10_OPTIONS_H
#include <sys/types.h>
-#include <types.h>
+#include "../common/types.h"
#include <stdint.h>
#include "main.h"
#include "packet.h"
#include "tofu.h"
#include "../common/session-env.h"
#ifndef EXTERN_UNLESS_MAIN_MODULE
/* Norcraft can't cope with common symbols */
#if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE)
#define EXTERN_UNLESS_MAIN_MODULE extern
#else
#define EXTERN_UNLESS_MAIN_MODULE
#endif
#endif
/* Declaration of a keyserver spec type. The definition is found in
../common/keyserver.h. */
struct keyserver_spec;
typedef struct keyserver_spec *keyserver_spec_t;
/* Global options for GPG. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
int verbose;
int quiet;
unsigned debug;
int armor;
char *outfile;
estream_t outfp; /* Hack, sometimes used in place of outfile. */
off_t max_output;
/* If > 0 a hint with the expected number of input data bytes. This
* is not necessary an exact number but intended to be used for
* progress info and to decide on how to allocate buffers. */
uint64_t input_size_hint;
int dry_run;
int autostart;
int list_only;
int mimemode;
int textmode;
int expert;
const char *def_sig_expire;
int ask_sig_expire;
const char *def_cert_expire;
int ask_cert_expire;
int batch; /* run in batch mode */
int answer_yes; /* answer yes on most questions */
int answer_no; /* answer no on most questions */
int check_sigs; /* check key signatures */
int with_colons;
int with_key_data;
int with_icao_spelling; /* Print ICAO spelling with fingerprints. */
int with_fingerprint; /* Option --with-fingerprint active. */
int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active. */
int with_keygrip; /* Option --with-keygrip active. */
int with_tofu_info; /* Option --with-tofu_info active. */
int with_secret; /* Option --with-secret active. */
int with_wkd_hash; /* Option --with-wkd-hash. */
int fingerprint; /* list fingerprints */
int list_sigs; /* list signatures */
int no_armor;
int list_packets; /* Option --list-packets active. */
int def_cipher_algo;
int force_mdc;
int disable_mdc;
int def_digest_algo;
int cert_digest_algo;
int compress_algo;
int compress_level;
int bz2_compress_level;
int bz2_decompress_lowmem;
strlist_t def_secret_key;
char *def_recipient;
int def_recipient_self;
strlist_t secret_keys_to_try;
/* A list of mail addresses (addr-spec) provided by the user with
* the option --sender. */
strlist_t sender_list;
int def_cert_level;
int min_cert_level;
int ask_cert_level;
int emit_version; /* 0 = none,
1 = major only,
2 = major and minor,
3 = full version,
4 = full version plus OS string. */
int marginals_needed;
int completes_needed;
int max_cert_depth;
const char *agent_program;
const char *dirmngr_program;
const char *def_new_key_algo;
/* Options to be passed to the gpg-agent */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
int skip_verify;
int skip_hidden_recipients;
/* TM_CLASSIC must be zero to accommodate trustdbsg generated before
we started storing the trust model inside the trustdb. */
enum
{
TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
} trust_model;
enum tofu_policy tofu_default_policy;
int force_ownertrust;
enum
{
CO_GNUPG, CO_RFC4880, CO_RFC2440,
CO_PGP6, CO_PGP7, CO_PGP8, CO_DE_VS
} compliance;
enum
{
KF_DEFAULT, KF_NONE, KF_SHORT, KF_LONG, KF_0xSHORT, KF_0xLONG
} keyid_format;
int shm_coprocess;
const char *set_filename;
strlist_t comments;
int throw_keyids;
const char *photo_viewer;
int s2k_mode;
int s2k_digest_algo;
int s2k_cipher_algo;
unsigned char s2k_count; /* This is the encoded form, not the raw
count */
int not_dash_escaped;
int escape_from;
int lock_once;
keyserver_spec_t keyserver; /* The list of configured keyservers. */
struct
{
unsigned int options;
unsigned int import_options;
unsigned int export_options;
char *http_proxy;
} keyserver_options;
int exec_disable;
int exec_path_set;
unsigned int import_options;
unsigned int export_options;
unsigned int list_options;
unsigned int verify_options;
const char *def_preference_list;
const char *def_keyserver_url;
prefitem_t *personal_cipher_prefs;
prefitem_t *personal_digest_prefs;
prefitem_t *personal_compress_prefs;
struct weakhash *weak_digests;
int no_perm_warn;
int no_mdc_warn;
char *temp_dir;
int no_encrypt_to;
int encrypt_to_default_key;
int interactive;
struct notation *sig_notations;
struct notation *cert_notations;
strlist_t sig_policy_url;
strlist_t cert_policy_url;
strlist_t sig_keyserver_url;
strlist_t cert_subpackets;
strlist_t sig_subpackets;
int allow_non_selfsigned_uid;
int allow_freeform_uid;
int no_literal;
ulong set_filesize;
int fast_list_mode;
int legacy_list_mode;
int ignore_time_conflict;
int ignore_valid_from;
int ignore_crc_error;
int ignore_mdc_error;
int command_fd;
const char *override_session_key;
int show_session_key;
const char *gpg_agent_info;
int try_all_secrets;
int no_expensive_trust_checks;
int no_sig_cache;
int no_auto_check_trustdb;
int preserve_permissions;
int no_homedir_creation;
struct groupitem *grouplist;
int mangle_dos_filenames;
int enable_progress_filter;
unsigned int screen_columns;
unsigned int screen_lines;
byte *show_subpackets;
int rfc2440_text;
/* If true, let write failures on the status-fd exit the process. */
int exit_on_status_write_error;
/* If > 0, limit the number of card insertion prompts to this
value. */
int limit_card_insert_tries;
struct
{
/* If set, require an 0x19 backsig to be present on signatures
made by signing subkeys. If not set, a missing backsig is not
an error (but an invalid backsig still is). */
unsigned int require_cross_cert:1;
unsigned int use_embedded_filename:1;
unsigned int utf8_filename:1;
unsigned int dsa2:1;
unsigned int allow_multiple_messages:1;
unsigned int allow_weak_digest_algos:1;
unsigned int large_rsa:1;
unsigned int disable_signer_uid:1;
/* Flag to enbale experimental features from RFC4880bis. */
unsigned int rfc4880bis:1;
} flags;
/* Linked list of ways to find a key if the key isn't on the local
keyring. */
struct akl
{
enum {
AKL_NODEFAULT,
AKL_LOCAL,
AKL_CERT,
AKL_PKA,
AKL_DANE,
AKL_WKD,
AKL_LDAP,
AKL_KEYSERVER,
AKL_SPEC
} type;
keyserver_spec_t spec;
struct akl *next;
} *auto_key_locate;
int passphrase_repeat;
int pinentry_mode;
int unwrap_encryption;
int only_sign_text_ids;
} opt;
/* CTRL is used to keep some global variables we currently can't
avoid. Future concurrent versions of gpg will put it into a per
request structure CTRL. */
EXTERN_UNLESS_MAIN_MODULE
struct {
int in_auto_key_retrieve; /* True if we are doing an
auto_key_retrieve. */
/* Hack to store the last error. We currently need it because the
proc_packet machinery is not able to reliabale return error
codes. Thus for the --server purposes we store some of the error
codes here. FIXME! */
gpg_error_t lasterr;
} glo_ctrl;
#define DBG_PACKET_VALUE 1 /* debug packet reading/writing */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug crypto handling */
/* (may reveal sensitive data) */
#define DBG_FILTER_VALUE 8 /* debug internal filter handling */
#define DBG_IOBUF_VALUE 16 /* debug iobuf stuff */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_TRUST_VALUE 256 /* debug the trustdb */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* debug assuan communication */
#define DBG_CLOCK_VALUE 4096
#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
/* Tests for the debugging flags. */
#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE)
#define DBG_MPI (opt.debug & DBG_MPI_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_FILTER (opt.debug & DBG_FILTER_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_TRUST (opt.debug & DBG_TRUST_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* FIXME: We need to check why we did not put this into opt. */
#define DBG_MEMORY memory_debug_mode
#define DBG_MEMSTAT memory_stat_debug_mode
EXTERN_UNLESS_MAIN_MODULE int memory_debug_mode;
EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode;
/* Compatibility flags. */
#define GNUPG (opt.compliance==CO_GNUPG || opt.compliance==CO_DE_VS)
#define RFC2440 (opt.compliance==CO_RFC2440)
#define RFC4880 (opt.compliance==CO_RFC4880)
#define PGP6 (opt.compliance==CO_PGP6)
#define PGP7 (opt.compliance==CO_PGP7)
#define PGP8 (opt.compliance==CO_PGP8)
#define PGPX (PGP6 || PGP7 || PGP8)
/* Various option flags. Note that there should be no common string
names between the IMPORT_ and EXPORT_ flags as they can be mixed in
the keyserver-options option. */
#define IMPORT_LOCAL_SIGS (1<<0)
#define IMPORT_REPAIR_PKS_SUBKEY_BUG (1<<1)
#define IMPORT_FAST (1<<2)
#define IMPORT_SHOW (1<<3)
#define IMPORT_MERGE_ONLY (1<<4)
#define IMPORT_MINIMAL (1<<5)
#define IMPORT_CLEAN (1<<6)
#define IMPORT_NO_SECKEY (1<<7)
#define IMPORT_KEEP_OWNERTTRUST (1<<8)
#define IMPORT_EXPORT (1<<9)
#define IMPORT_RESTORE (1<<10)
#define EXPORT_LOCAL_SIGS (1<<0)
#define EXPORT_ATTRIBUTES (1<<1)
#define EXPORT_SENSITIVE_REVKEYS (1<<2)
#define EXPORT_RESET_SUBKEY_PASSWD (1<<3)
#define EXPORT_MINIMAL (1<<4)
#define EXPORT_CLEAN (1<<5)
#define EXPORT_PKA_FORMAT (1<<6)
#define EXPORT_DANE_FORMAT (1<<7)
#define EXPORT_BACKUP (1<<10)
#define LIST_SHOW_PHOTOS (1<<0)
#define LIST_SHOW_POLICY_URLS (1<<1)
#define LIST_SHOW_STD_NOTATIONS (1<<2)
#define LIST_SHOW_USER_NOTATIONS (1<<3)
#define LIST_SHOW_NOTATIONS (LIST_SHOW_STD_NOTATIONS|LIST_SHOW_USER_NOTATIONS)
#define LIST_SHOW_KEYSERVER_URLS (1<<4)
#define LIST_SHOW_UID_VALIDITY (1<<5)
#define LIST_SHOW_UNUSABLE_UIDS (1<<6)
#define LIST_SHOW_UNUSABLE_SUBKEYS (1<<7)
#define LIST_SHOW_KEYRING (1<<8)
#define LIST_SHOW_SIG_EXPIRE (1<<9)
#define LIST_SHOW_SIG_SUBPACKETS (1<<10)
#define LIST_SHOW_USAGE (1<<11)
#define VERIFY_SHOW_PHOTOS (1<<0)
#define VERIFY_SHOW_POLICY_URLS (1<<1)
#define VERIFY_SHOW_STD_NOTATIONS (1<<2)
#define VERIFY_SHOW_USER_NOTATIONS (1<<3)
#define VERIFY_SHOW_NOTATIONS (VERIFY_SHOW_STD_NOTATIONS|VERIFY_SHOW_USER_NOTATIONS)
#define VERIFY_SHOW_KEYSERVER_URLS (1<<4)
#define VERIFY_SHOW_UID_VALIDITY (1<<5)
#define VERIFY_SHOW_UNUSABLE_UIDS (1<<6)
#define VERIFY_PKA_LOOKUPS (1<<7)
#define VERIFY_PKA_TRUST_INCREASE (1<<8)
#define VERIFY_SHOW_PRIMARY_UID_ONLY (1<<9)
#define KEYSERVER_HTTP_PROXY (1<<0)
#define KEYSERVER_TIMEOUT (1<<1)
#define KEYSERVER_ADD_FAKE_V3 (1<<2)
#define KEYSERVER_AUTO_KEY_RETRIEVE (1<<3)
#define KEYSERVER_HONOR_KEYSERVER_URL (1<<4)
#define KEYSERVER_HONOR_PKA_RECORD (1<<5)
#endif /*G10_OPTIONS_H*/
diff --git a/g10/packet.h b/g10/packet.h
index 9780d939e..3457f5398 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -1,855 +1,855 @@
/* packet.h - OpenPGP packet definitions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_PACKET_H
#define G10_PACKET_H
-#include "types.h"
+#include "../common/types.h"
#include "../common/iobuf.h"
#include "../common/strlist.h"
#include "dek.h"
#include "filter.h"
#include "../common/openpgpdefs.h"
#include "../common/userids.h"
-#include "util.h"
+#include "../common/util.h"
#define DEBUG_PARSE_PACKET 1
/* Constants to allocate static MPI arrays. */
#define PUBKEY_MAX_NPKEY 5
#define PUBKEY_MAX_NSKEY 7
#define PUBKEY_MAX_NSIG 2
#define PUBKEY_MAX_NENC 2
/* Usage flags */
#define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */
#define PUBKEY_USAGE_ENC GCRY_PK_USAGE_ENCR /* Good for encryption. */
#define PUBKEY_USAGE_CERT GCRY_PK_USAGE_CERT /* Also good to certify keys.*/
#define PUBKEY_USAGE_AUTH GCRY_PK_USAGE_AUTH /* Good for authentication. */
#define PUBKEY_USAGE_UNKNOWN GCRY_PK_USAGE_UNKN /* Unknown usage flag. */
#define PUBKEY_USAGE_NONE 256 /* No usage given. */
#if (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR | GCRY_PK_USAGE_CERT \
| GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256
# error Please choose another value for PUBKEY_USAGE_NONE
#endif
/* Helper macros. */
#define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \
|| (a)==PUBKEY_ALGO_RSA_S )
#define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL_E)
#define is_DSA(a) ((a)==PUBKEY_ALGO_DSA)
/* A pointer to the packet object. */
typedef struct packet_struct PACKET;
/* PKT_GPG_CONTROL types */
typedef enum {
CTRLPKT_CLEARSIGN_START = 1,
CTRLPKT_PIPEMODE = 2,
CTRLPKT_PLAINTEXT_MARK =3
} ctrlpkttype_t;
typedef enum {
PREFTYPE_NONE = 0,
PREFTYPE_SYM = 1,
PREFTYPE_HASH = 2,
PREFTYPE_ZIP = 3
} preftype_t;
typedef struct {
byte type;
byte value;
} prefitem_t;
/* A string-to-key specifier as defined in RFC 4880, Section 3.7. */
typedef struct
{
int mode; /* Must be an integer due to the GNU modes 1001 et al. */
byte hash_algo;
byte salt[8];
/* The *coded* (i.e., the serialized version) iteration count. */
u32 count;
} STRING2KEY;
/* A symmetric-key encrypted session key packet as defined in RFC
4880, Section 5.3. All fields are serialized. */
typedef struct {
/* RFC 4880: this must be 4. */
byte version;
/* The cipher algorithm used to encrypt the session key. (This may
be different from the algorithm that is used to encrypt the SED
packet.) */
byte cipher_algo;
/* The string-to-key specifier. */
STRING2KEY s2k;
/* The length of SESKEY in bytes or 0 if this packet does not
encrypt a session key. (In the latter case, the results of the
S2K function on the password is the session key. See RFC 4880,
Section 5.3.) */
byte seskeylen;
/* The session key as encrypted by the S2K specifier. */
byte seskey[1];
} PKT_symkey_enc;
/* A public-key encrypted session key packet as defined in RFC 4880,
Section 5.1. All fields are serialized. */
typedef struct {
/* The 64-bit keyid. */
u32 keyid[2];
/* The packet's version. Currently, only version 3 is defined. */
byte version;
/* The algorithm used for the public key encryption scheme. */
byte pubkey_algo;
/* Whether to hide the key id. This value is not directly
serialized. */
byte throw_keyid;
/* The session key. */
gcry_mpi_t data[PUBKEY_MAX_NENC];
} PKT_pubkey_enc;
/* A one-pass signature packet as defined in RFC 4880, Section
5.4. All fields are serialized. */
typedef struct {
u32 keyid[2]; /* The 64-bit keyid */
/* The signature's classification (RFC 4880, Section 5.2.1). */
byte sig_class;
byte digest_algo; /* algorithm used for digest */
byte pubkey_algo; /* algorithm used for public key scheme */
/* A message can be signed by multiple keys. In this case, there
are n one-pass signature packets before the message to sign and
n signatures packets after the message. It is conceivable that
someone wants to not only sign the message, but all of the
signatures. Now we need to distinguish between signing the
message and signing the message plus the surrounding
signatures. This is the point of this flag. If set, it means:
I sign all of the data starting at the next packet. */
byte last;
} PKT_onepass_sig;
/* A v4 OpenPGP signature has a hashed and unhashed area containing
co-called signature subpackets (RFC 4880, Section 5.2.3). These
areas are described by this data structure. Use enum_sig_subpkt to
parse this area. */
typedef struct {
size_t size; /* allocated */
size_t len; /* used (serialized) */
byte data[1]; /* the serialized subpackes (serialized) */
} subpktarea_t;
/* The in-memory representation of a designated revoker signature
subpacket (RFC 4880, Section 5.2.3.15). */
struct revocation_key {
/* A bit field. 0x80 must be set. 0x40 means this information is
sensitive (and should not be uploaded to a keyserver by
default). */
byte class;
/* The public-key algorithm ID. */
byte algid;
/* The fingerprint of the authorized key. */
byte fpr[MAX_FINGERPRINT_LEN];
};
/* Object to keep information about a PKA DNS record. */
typedef struct
{
int valid; /* An actual PKA record exists for EMAIL. */
int checked; /* Set to true if the FPR has been checked against the
actual key. */
char *uri; /* Malloced string with the URI. NULL if the URI is
not available.*/
unsigned char fpr[20]; /* The fingerprint as stored in the PKA RR. */
char email[1];/* The email address from the notation data. */
} pka_info_t;
/* A signature packet (RFC 4880, Section 5.2). Only a subset of these
fields are directly serialized (these are marked as such); the rest
are read from the subpackets, which are not synthesized when
serializing this data structure (i.e., when using build_packet()).
Instead, the subpackets must be created by hand. */
typedef struct
{
struct
{
unsigned checked:1; /* Signature has been checked. */
unsigned valid:1; /* Signature is good (if checked is set). */
unsigned chosen_selfsig:1; /* A selfsig that is the chosen one. */
unsigned unknown_critical:1;
unsigned exportable:1;
unsigned revocable:1;
unsigned policy_url:1; /* At least one policy URL is present */
unsigned notation:1; /* At least one notation is present */
unsigned pref_ks:1; /* At least one preferred keyserver is present */
unsigned expired:1;
unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */
} flags;
/* The key that allegedly generated this signature. (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as an issuer subpacket (5.2.3.5.) */
u32 keyid[2];
/* When the signature was made (seconds since the Epoch). (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as a signature creation time subpacket (5.2.3.4).) */
u32 timestamp;
u32 expiredate; /* Expires at this date or 0 if not at all. */
/* The serialization format used / to use. If 0, then defaults to
version 3. (Serialized.) */
byte version;
/* The signature type. (See RFC 4880, Section 5.2.1.) */
byte sig_class;
/* Algorithm used for public key scheme (e.g., PUBKEY_ALGO_RSA).
(Serialized.) */
byte pubkey_algo;
/* Algorithm used for digest (e.g., DIGEST_ALGO_SHA1).
(Serialized.) */
byte digest_algo;
byte trust_depth;
byte trust_value;
const byte *trust_regexp;
struct revocation_key *revkey;
int numrevkeys;
pka_info_t *pka_info; /* Malloced PKA data or NULL if not
available. See also flags.pka_tried. */
char *signers_uid; /* Malloced value of the SIGNERS_UID
* subpacket or NULL. This string has
* already been sanitized. */
subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */
subpktarea_t *unhashed; /* Ditto for unhashed data. */
/* First 2 bytes of the digest. (Serialized. Note: this is not
automatically filled in when serializing a signature!) */
byte digest_start[2];
/* The signature. (Serialized.) */
gcry_mpi_t data[PUBKEY_MAX_NSIG];
/* The message digest and its length (in bytes). Note the maximum
digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then
the digest's value has not been saved here. */
byte digest[512 / 8];
int digest_len;
} PKT_signature;
#define ATTRIB_IMAGE 1
/* This is the cooked form of attributes. */
struct user_attribute {
byte type;
const byte *data;
u32 len;
};
/* A user id (RFC 4880, Section 5.11) or a user attribute packet (RFC
4880, Section 5.12). Only a subset of these fields are directly
serialized (these are marked as such); the rest are read from the
self-signatures in merge_keys_and_selfsig()). */
typedef struct
{
int ref; /* reference counter */
/* The length of NAME. */
int len;
struct user_attribute *attribs;
int numattribs;
/* If this is not NULL, the packet is a user attribute rather than a
user id (See RFC 4880 5.12). (Serialized.) */
byte *attrib_data;
/* The length of ATTRIB_DATA. */
unsigned long attrib_len;
byte *namehash;
int help_key_usage;
u32 help_key_expire;
int help_full_count;
int help_marginal_count;
int is_primary; /* 2 if set via the primary flag, 1 if calculated */
int is_revoked;
int is_expired;
u32 expiredate; /* expires at this date or 0 if not at all */
prefitem_t *prefs; /* list of preferences (may be NULL)*/
u32 created; /* according to the self-signature */
byte selfsigversion;
struct
{
/* TODO: Move more flags here */
unsigned int mdc:1;
unsigned int ks_modify:1;
unsigned int compacted:1;
} flags;
char *mbox; /* NULL or the result of mailbox_from_userid. */
/* The text contained in the user id packet, which is normally the
name and email address of the key holder (See RFC 4880 5.11).
(Serialized.). For convenience an extra Nul is always appended. */
char name[1];
} PKT_user_id;
struct revoke_info
{
/* revoked at this date */
u32 date;
/* the keyid of the revoking key (selfsig or designated revoker) */
u32 keyid[2];
/* the algo of the revoking key */
byte algo;
};
/* Information pertaining to secret keys. */
struct seckey_info
{
int is_protected:1; /* The secret info is protected and must */
/* be decrypted before use, the protected */
/* MPIs are simply (void*) pointers to memory */
/* and should never be passed to a mpi_xxx() */
int sha1chk:1; /* SHA1 is used instead of a 16 bit checksum */
u16 csum; /* Checksum for old protection modes. */
byte algo; /* Cipher used to protect the secret information. */
STRING2KEY s2k; /* S2K parameter. */
byte ivlen; /* Used length of the IV. */
byte iv[16]; /* Initialization vector for CFB mode. */
};
/****************
* The in-memory representation of a public key (RFC 4880, Section
* 5.5). Note: this structure contains significantly more information
* than is contained in an OpenPGP public key packet. This
* information is derived from the self-signed signatures (by
* merge_keys_and_selfsig()) and is ignored when serializing the
* packet. The fields that are actually written out when serializing
* this packet are marked as accordingly.
*
* We assume that secret keys have the same number of parameters as
* the public key and that the public parameters are the first items
* in the PKEY array. Thus NPKEY is always less than NSKEY and it is
* possible to compare the secret and public keys by comparing the
* first NPKEY elements of the PKEY array. Note that since GnuPG 2.1
* we don't use secret keys anymore directly because they are managed
* by gpg-agent. However for parsing OpenPGP key files we need a way
* to temporary store those secret keys. We do this by putting them
* into the public key structure and extending the PKEY field to NSKEY
* elements; the extra secret key information are stored in the
* SECKEY_INFO field.
*/
typedef struct
{
/* When the key was created. (Serialized.) */
u32 timestamp;
u32 expiredate; /* expires at this date or 0 if not at all */
u32 max_expiredate; /* must not expire past this date */
struct revoke_info revoked;
/* An OpenPGP packet consists of a header and a body. This is the
size of the header. If this is 0, an appropriate size is
automatically chosen based on the size of the body.
(Serialized.) */
byte hdrbytes;
/* The serialization format. If 0, the default version (4) is used
when serializing. (Serialized.) */
byte version;
byte selfsigversion; /* highest version of all of the self-sigs */
/* The public key algorithm. (Serialized.) */
byte pubkey_algo;
byte pubkey_usage; /* for now only used to pass it to getkey() */
byte req_usage; /* hack to pass a request to getkey() */
u32 has_expired; /* set to the expiration date if expired */
/* keyid of the primary key. Never access this value directly.
Instead, use pk_main_keyid(). */
u32 main_keyid[2];
/* keyid of this key. Never access this value directly! Instead,
use pk_keyid(). */
u32 keyid[2];
prefitem_t *prefs; /* list of preferences (may be NULL) */
struct
{
unsigned int mdc:1; /* MDC feature set. */
unsigned int disabled_valid:1;/* The next flag is valid. */
unsigned int disabled:1; /* The key has been disabled. */
unsigned int primary:1; /* This is a primary key. */
unsigned int revoked:2; /* Key has been revoked.
1 = revoked by the owner
2 = revoked by designated revoker. */
unsigned int maybe_revoked:1; /* A designated revocation is
present, but without the key to
check it. */
unsigned int valid:1; /* Key (especially subkey) is valid. */
unsigned int dont_cache:1; /* Do not cache this key. */
unsigned int backsig:2; /* 0=none, 1=bad, 2=good. */
unsigned int serialno_valid:1;/* SERIALNO below is valid. */
unsigned int exact:1; /* Found via exact (!) search. */
} flags;
PKT_user_id *user_id; /* If != NULL: found by that uid. */
struct revocation_key *revkey;
int numrevkeys;
u32 trust_timestamp;
byte trust_depth;
byte trust_value;
const byte *trust_regexp;
char *serialno; /* Malloced hex string or NULL if it is
likely not on a card. See also
flags.serialno_valid. */
/* If not NULL this malloced structure describes a secret key.
(Serialized.) */
struct seckey_info *seckey_info;
/* The public key. Contains pubkey_get_npkey (pubkey_algo) +
pubkey_get_nskey (pubkey_algo) MPIs. (If pubkey_get_npkey
returns 0, then the algorithm is not understood and the PKEY
contains a single opaque MPI.) (Serialized.) */
gcry_mpi_t pkey[PUBKEY_MAX_NSKEY]; /* Right, NSKEY elements. */
} PKT_public_key;
/* Evaluates as true if the pk is disabled, and false if it isn't. If
there is no disable value cached, fill one in. */
#define pk_is_disabled(a) \
(((a)->flags.disabled_valid)? \
((a)->flags.disabled):(cache_disabled_value((a))))
typedef struct {
int len; /* length of data */
char data[1];
} PKT_comment;
/* A compression packet (RFC 4880, Section 5.6). */
typedef struct {
/* Not used. */
u32 len;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* The compression algorithm. */
byte algorithm;
/* An iobuf holding the data to be decompressed. (This is not used
for compression!) */
iobuf_t buf;
} PKT_compressed;
/* A symmetrically encrypted data packet (RFC 4880, Section 5.7) or a
symmetrically encrypted integrity protected data packet (Section
5.13) */
typedef struct {
/* Remaining length of encrypted data. */
u32 len;
/* When encrypting, the first block size bytes of data are random
data and the following 2 bytes are copies of the last two bytes
of the random data (RFC 4880, Section 5.7). This provides a
simple check that the key is correct. extralen is the size of
this extra data. This is used by build_packet when writing out
the packet's header. */
int extralen;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* Whether the packet has an indeterminate length (old format) or
was encoded using partial body length headers (new format).
Note: this is ignored when encrypting. */
byte is_partial;
/* If 0, MDC is disabled. Otherwise, the MDC method that was used
(currently, only DIGEST_ALGO_SHA1 is supported). */
byte mdc_method;
/* An iobuf holding the data to be decrypted. (This is not used for
encryption!) */
iobuf_t buf;
} PKT_encrypted;
typedef struct {
byte hash[20];
} PKT_mdc;
typedef struct {
unsigned int trustval;
unsigned int sigcache;
} PKT_ring_trust;
/* A plaintext packet (see RFC 4880, 5.9). */
typedef struct {
/* The length of data in BUF or 0 if unknown. */
u32 len;
/* A buffer containing the data stored in the packet's body. */
iobuf_t buf;
byte new_ctb;
byte is_partial; /* partial length encoded */
/* The data's formatting. This is either 'b', 't', 'u', 'l' or '1'
(however, the last two are deprecated). */
int mode;
u32 timestamp;
/* The name of the file. This can be at most 255 characters long,
since namelen is just a byte in the serialized format. */
int namelen;
char name[1];
} PKT_plaintext;
typedef struct {
int control;
size_t datalen;
char data[1];
} PKT_gpg_control;
/* combine all packets into a union */
struct packet_struct {
pkttype_t pkttype;
union {
void *generic;
PKT_symkey_enc *symkey_enc; /* PKT_SYMKEY_ENC */
PKT_pubkey_enc *pubkey_enc; /* PKT_PUBKEY_ENC */
PKT_onepass_sig *onepass_sig; /* PKT_ONEPASS_SIG */
PKT_signature *signature; /* PKT_SIGNATURE */
PKT_public_key *public_key; /* PKT_PUBLIC_[SUB]KEY */
PKT_public_key *secret_key; /* PKT_SECRET_[SUB]KEY */
PKT_comment *comment; /* PKT_COMMENT */
PKT_user_id *user_id; /* PKT_USER_ID */
PKT_compressed *compressed; /* PKT_COMPRESSED */
PKT_encrypted *encrypted; /* PKT_ENCRYPTED[_MDC] */
PKT_mdc *mdc; /* PKT_MDC */
PKT_ring_trust *ring_trust; /* PKT_RING_TRUST */
PKT_plaintext *plaintext; /* PKT_PLAINTEXT */
PKT_gpg_control *gpg_control; /* PKT_GPG_CONTROL */
} pkt;
};
#define init_packet(a) do { (a)->pkttype = 0; \
(a)->pkt.generic = NULL; \
} while(0)
/* A notation. See RFC 4880, Section 5.2.3.16. */
struct notation
{
/* The notation's name. */
char *name;
/* If the notation is human readable, then the value is stored here
as a NUL-terminated string. If it is not human readable a human
readable approximation of the binary value _may_ be stored
here. */
char *value;
/* Sometimes we want to %-expand the value. In these cases, we save
that transformed value here. */
char *altvalue;
/* If the notation is not human readable, then the value is stored
here. */
unsigned char *bdat;
/* The amount of data stored in BDAT.
Note: if this is 0 and BDAT is NULL, this does not necessarily
mean that the value is human readable. It could be that we have
a 0-length value. To determine whether the notation is human
readable, always check if VALUE is not NULL. This works, because
if a human-readable value has a length of 0, we will still
allocate space for the NUL byte. */
size_t blen;
struct
{
/* The notation is critical. */
unsigned int critical:1;
/* The notation is human readable. */
unsigned int human:1;
/* The notation should be deleted. */
unsigned int ignore:1;
} flags;
/* A field to facilitate creating a list of notations. */
struct notation *next;
};
typedef struct notation *notation_t;
/*-- mainproc.c --*/
void reset_literals_seen(void);
int proc_packets (ctrl_t ctrl, void *ctx, iobuf_t a );
int proc_signature_packets (ctrl_t ctrl, void *ctx, iobuf_t a,
strlist_t signedfiles, const char *sigfile );
int proc_signature_packets_by_fd (ctrl_t ctrl,
void *anchor, IOBUF a, int signed_data_fd );
int proc_encryption_packets (ctrl_t ctrl, void *ctx, iobuf_t a);
int list_packets( iobuf_t a );
/*-- parse-packet.c --*/
/* Sets the packet list mode to MODE (i.e., whether we are dumping a
packet or not). Returns the current mode. This allows for
temporarily suspending dumping by doing the following:
int saved_mode = set_packet_list_mode (0);
...
set_packet_list_mode (saved_mode);
*/
int set_packet_list_mode( int mode );
#if DEBUG_PARSE_PACKET
/* There are debug functions and should not be used directly. */
int dbg_search_packet( iobuf_t inp, PACKET *pkt, off_t *retpos, int with_uid,
const char* file, int lineno );
int dbg_parse_packet( iobuf_t inp, PACKET *ret_pkt,
const char* file, int lineno );
int dbg_copy_all_packets( iobuf_t inp, iobuf_t out,
const char* file, int lineno );
int dbg_copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff,
const char* file, int lineno );
int dbg_skip_some_packets( iobuf_t inp, unsigned n,
const char* file, int lineno );
#define search_packet( a,b,c,d ) \
dbg_search_packet( (a), (b), (c), (d), __FILE__, __LINE__ )
#define parse_packet( a, b ) \
dbg_parse_packet( (a), (b), __FILE__, __LINE__ )
#define copy_all_packets( a,b ) \
dbg_copy_all_packets((a),(b), __FILE__, __LINE__ )
#define copy_some_packets( a,b,c ) \
dbg_copy_some_packets((a),(b),(c), __FILE__, __LINE__ )
#define skip_some_packets( a,b ) \
dbg_skip_some_packets((a),(b), __FILE__, __LINE__ )
#else
/* Return the next valid OpenPGP packet in *PKT. (This function will
skip any packets whose type is 0.)
Returns 0 on success, -1 if EOF is reached, and an error code
otherwise. In the case of an error, the packet in *PKT may be
partially constructed. As such, even if there is an error, it is
necessary to free *PKT to avoid a resource leak. To detect what
has been allocated, clear *PKT before calling this function. */
int parse_packet( iobuf_t inp, PACKET *pkt);
/* Return the first OpenPGP packet in *PKT that contains a key (either
a public subkey, a public key, a secret subkey or a secret key) or,
if WITH_UID is set, a user id.
Saves the position in the pipeline of the start of the returned
packet (according to iobuf_tell) in RETPOS, if it is not NULL.
The return semantics are the same as parse_packet. */
int search_packet( iobuf_t inp, PACKET *pkt, off_t *retpos, int with_uid );
/* Copy all packets (except invalid packets, i.e., those with a type
of 0) from INP to OUT until either an error occurs or EOF is
reached.
Returns -1 when end of file is reached or an error code, if an
error occurred. (Note: this function never returns 0, because it
effectively keeps going until it gets an EOF.) */
int copy_all_packets( iobuf_t inp, iobuf_t out );
/* Like copy_all_packets, but stops at the first packet that starts at
or after STOPOFF (as indicated by iobuf_tell).
Example: if STOPOFF is 100, the first packet in INP goes from 0 to
110 and the next packet starts at offset 111, then the packet
starting at offset 0 will be completely processed (even though it
extends beyond STOPOFF) and the packet starting at offset 111 will
not be processed at all. */
int copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff );
/* Skips the next N packets from INP.
If parsing a packet returns an error code, then the function stops
immediately and returns the error code. Note: in the case of an
error, this function does not indicate how many packets were
successfully processed. */
int skip_some_packets( iobuf_t inp, unsigned n );
#endif
/* Parse a signature packet and store it in *SIG.
The signature packet is read from INP. The OpenPGP header (the tag
and the packet's length) have already been read; the next byte read
from INP should be the first byte of the packet's contents. The
packet's type (as extract from the tag) must be passed as PKTTYPE
and the packet's length must be passed as PKTLEN. This is used as
the upper bound on the amount of data read from INP. If the packet
is shorter than PKTLEN, the data at the end will be silently
skipped. If an error occurs, an error code will be returned. -1
means the EOF was encountered. 0 means parsing was successful. */
int parse_signature( iobuf_t inp, int pkttype, unsigned long pktlen,
PKT_signature *sig );
/* Given a subpacket area (typically either PKT_signature.hashed or
PKT_signature.unhashed), either:
- test whether there are any subpackets with the critical bit set
that we don't understand,
- list the subpackets, or,
- find a subpacket with a specific type.
REQTYPE indicates the type of operation.
If REQTYPE is SIGSUBPKT_TEST_CRITICAL, then this function checks
whether there are any subpackets that have the critical bit and
which GnuPG cannot handle. If GnuPG understands all subpackets
whose critical bit is set, then this function returns simply
returns SUBPKTS. If there is a subpacket whose critical bit is set
and which GnuPG does not understand, then this function returns
NULL and, if START is not NULL, sets *START to the 1-based index of
the subpacket that violates the constraint.
If REQTYPE is SIGSUBPKT_LIST_HASHED or SIGSUBPKT_LIST_UNHASHED, the
packets are dumped. Note: if REQTYPE is SIGSUBPKT_LIST_HASHED,
this function does not check whether the hash is correct; this is
merely an indication of the section that the subpackets came from.
If REQTYPE is anything else, then this function interprets the
values as a subpacket type and looks for the first subpacket with
that type. If such a packet is found, *CRITICAL (if not NULL) is
set if the critical bit was set, *RET_N is set to the offset of the
subpacket's content within the SUBPKTS buffer, *START is set to the
1-based index of the subpacket within the buffer, and returns
&SUBPKTS[*RET_N].
*START is the number of initial subpackets to not consider. Thus,
if *START is 2, then the first 2 subpackets are ignored. */
const byte *enum_sig_subpkt ( const subpktarea_t *subpkts,
sigsubpkttype_t reqtype,
size_t *ret_n, int *start, int *critical );
/* Shorthand for:
enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL); */
const byte *parse_sig_subpkt ( const subpktarea_t *buffer,
sigsubpkttype_t reqtype,
size_t *ret_n );
/* This calls parse_sig_subpkt first on the hashed signature area in
SIG and then, if that returns NULL, calls parse_sig_subpkt on the
unhashed subpacket area in SIG. */
const byte *parse_sig_subpkt2 ( PKT_signature *sig,
sigsubpkttype_t reqtype);
/* Returns whether the N byte large buffer BUFFER is sufficient to
hold a subpacket of type TYPE. Note: the buffer refers to the
contents of the subpacket (not the header) and it must already be
initialized: for some subpackets, it checks some internal
constraints.
Returns 0 if the size is acceptable. Returns -2 if the buffer is
definitely too short. To check for an error, check whether the
return value is less than 0. */
int parse_one_sig_subpkt( const byte *buffer, size_t n, int type );
/* Looks for revocation key subpackets (see RFC 4880 5.2.3.15) in the
hashed area of the signature packet. Any that are found are added
to SIG->REVKEY and SIG->NUMREVKEYS is updated appropriately. */
void parse_revkeys(PKT_signature *sig);
/* Extract the attributes from the buffer at UID->ATTRIB_DATA and
update UID->ATTRIBS and UID->NUMATTRIBS accordingly. */
int parse_attribute_subpkts(PKT_user_id *uid);
/* Set the UID->NAME field according to the attributes. MAX_NAMELEN
must be at least 71. */
void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen);
/* Allocate and initialize a new GPG control packet. DATA is the data
to save in the packet. */
PACKET *create_gpg_control ( ctrlpkttype_t type,
const byte *data,
size_t datalen );
/*-- build-packet.c --*/
int build_packet( iobuf_t inp, PACKET *pkt );
gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a);
gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a);
u32 calc_packet_length( PACKET *pkt );
void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type,
const byte *buffer, size_t buflen );
void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk);
int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type );
void build_attribute_subpkt(PKT_user_id *uid,byte type,
const void *buf,u32 buflen,
const void *header,u32 headerlen);
struct notation *string_to_notation(const char *string,int is_utf8);
struct notation *blob_to_notation(const char *name,
const char *data, size_t len);
struct notation *sig_to_notation(PKT_signature *sig);
void free_notation(struct notation *notation);
/*-- free-packet.c --*/
void free_symkey_enc( PKT_symkey_enc *enc );
void free_pubkey_enc( PKT_pubkey_enc *enc );
void free_seckey_enc( PKT_signature *enc );
void release_public_key_parts( PKT_public_key *pk );
void free_public_key( PKT_public_key *key );
void free_attributes(PKT_user_id *uid);
void free_user_id( PKT_user_id *uid );
void free_comment( PKT_comment *rem );
void free_packet( PACKET *pkt );
prefitem_t *copy_prefs (const prefitem_t *prefs);
PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s );
PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s );
PKT_user_id *scopy_user_id (PKT_user_id *sd );
int cmp_public_keys( PKT_public_key *a, PKT_public_key *b );
int cmp_signatures( PKT_signature *a, PKT_signature *b );
int cmp_user_ids( PKT_user_id *a, PKT_user_id *b );
/*-- sig-check.c --*/
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int check_signature (PKT_signature *sig, gcry_md_hd_t digest);
/* Check a signature. Looks up the public key from the key db. (If
* R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a
* valid hash context that already includes the signed data. This
* function adds the relevant meta-data to the hash before finalizing
* it and verifying the signature. */
gpg_error_t check_signature2 (PKT_signature *sig, gcry_md_hd_t digest,
u32 *r_expiredate, int *r_expired, int *r_revoked,
PKT_public_key **r_pk);
/*-- pubkey-enc.c --*/
gpg_error_t get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek);
gpg_error_t get_override_session_key (DEK *dek, const char *string);
/*-- compress.c --*/
int handle_compressed (ctrl_t ctrl, void *ctx, PKT_compressed *cd,
int (*callback)(iobuf_t, void *), void *passthru );
/*-- encr-data.c --*/
int decrypt_data (ctrl_t ctrl, void *ctx, PKT_encrypted *ed, DEK *dek );
/*-- plaintext.c --*/
gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen,
iobuf_t data, char **fnamep, estream_t *fpp);
int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx,
int nooutput, int clearsig );
int ask_for_detached_datafile( gcry_md_hd_t md, gcry_md_hd_t md2,
const char *inname, int textmode );
/*-- sign.c --*/
int make_keysig_packet( PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
PKT_public_key *pksk, int sigclass, int digest_algo,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque,
const char *cache_nonce);
gpg_error_t update_keysig_packet (PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque );
/*-- keygen.c --*/
PKT_user_id *generate_user_id (kbnode_t keyblock, const char *uidstr);
#endif /*G10_PACKET_H*/
diff --git a/g10/parse-packet.c b/g10/parse-packet.c
index 7f44ce532..06b286bd7 100644
--- a/g10/parse-packet.c
+++ b/g10/parse-packet.c
@@ -1,3149 +1,3149 @@
/* parse-packet.c - read packets
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "filter.h"
#include "photoid.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
-#include "host2net.h"
+#include "../common/i18n.h"
+#include "../common/host2net.h"
/* Maximum length of packets to avoid excessive memory allocation. */
#define MAX_KEY_PACKET_LENGTH (256 * 1024)
#define MAX_UID_PACKET_LENGTH ( 2 * 1024)
#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
static int mpi_print_mode;
static int list_mode;
static estream_t listfp;
static int parse (IOBUF inp, PACKET * pkt, int onlykeypkts,
off_t * retpos, int *skip, IOBUF out, int do_skip
#ifdef DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
);
static int copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial);
static void skip_packet (IOBUF inp, int pkttype,
unsigned long pktlen, int partial);
static void *read_rest (IOBUF inp, size_t pktlen);
static int parse_marker (IOBUF inp, int pkttype, unsigned long pktlen);
static int parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops);
static int parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * packet);
static int parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_comment (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static void parse_trust (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static int parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static int parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial);
/* Read a 16-bit value in MSB order (big endian) from an iobuf. */
static unsigned short
read_16 (IOBUF inp)
{
unsigned short a;
a = (unsigned short)iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read a 32-bit value in MSB order (big endian) from an iobuf. */
static unsigned long
read_32 (IOBUF inp)
{
unsigned long a;
a = (unsigned long)iobuf_get_noeof (inp) << 24;
a |= iobuf_get_noeof (inp) << 16;
a |= iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read an external representation of an MPI and return the MPI. The
external format is a 16-bit unsigned value stored in network byte
order giving the number of bits for the following integer. The
integer is stored MSB first and is left padded with zero bits to
align on a byte boundary.
The caller must set *RET_NREAD to the maximum number of bytes to
read from the pipeline INP. This function sets *RET_NREAD to be
the number of bytes actually read from the pipeline.
If SECURE is true, the integer is stored in secure memory
(allocated using gcry_xmalloc_secure). */
static gcry_mpi_t
mpi_read (iobuf_t inp, unsigned int *ret_nread, int secure)
{
int c, c1, c2, i;
unsigned int nmax = *ret_nread;
unsigned int nbits, nbytes;
size_t nread = 0;
gcry_mpi_t a = NULL;
byte *buf = NULL;
byte *p;
if (!nmax)
goto overflow;
if ((c = c1 = iobuf_get (inp)) == -1)
goto leave;
if (++nread == nmax)
goto overflow;
nbits = c << 8;
if ((c = c2 = iobuf_get (inp)) == -1)
goto leave;
++nread;
nbits |= c;
if (nbits > MAX_EXTERN_MPI_BITS)
{
log_error ("mpi too large (%u bits)\n", nbits);
goto leave;
}
nbytes = (nbits + 7) / 8;
buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2);
p = buf;
p[0] = c1;
p[1] = c2;
for (i = 0; i < nbytes; i++)
{
if (nread == nmax)
goto overflow;
c = iobuf_get (inp);
if (c == -1)
goto leave;
p[i + 2] = c;
nread ++;
}
if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread))
a = NULL;
*ret_nread = nread;
gcry_free(buf);
return a;
overflow:
log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax);
leave:
*ret_nread = nread;
gcry_free(buf);
return a;
}
int
set_packet_list_mode (int mode)
{
int old = list_mode;
list_mode = mode;
/* We use stdout only if invoked by the --list-packets command
but switch to stderr in all other cases. This breaks the
previous behaviour but that seems to be more of a bug than
intentional. I don't believe that any application makes use of
this long standing annoying way of printing to stdout except when
doing a --list-packets. If this assumption fails, it will be easy
to add an option for the listing stream. Note that we initialize
it only once; mainly because there is code which switches
opt.list_mode back to 1 and we want to have all output to the
same stream. The MPI_PRINT_MODE will be enabled if the
corresponding debug flag is set or if we are in --list-packets
and --verbose is given.
Using stderr is not actually very clean because it bypasses the
logging code but it is a special thing anyway. I am not sure
whether using log_stream() would be better. Perhaps we should
enable the list mode only with a special option. */
if (!listfp)
{
if (opt.list_packets)
{
listfp = es_stdout;
if (opt.verbose)
mpi_print_mode = 1;
}
else
listfp = es_stderr;
if (DBG_MPI)
mpi_print_mode = 1;
}
return old;
}
/* If OPT.VERBOSE is set, print a warning that the algorithm ALGO is
not suitable for signing and encryption. */
static void
unknown_pubkey_warning (int algo)
{
static byte unknown_pubkey_algos[256];
/* First check whether the algorithm is usable but not suitable for
encryption/signing. */
if (pubkey_get_npkey (algo))
{
if (opt.verbose)
{
if (!pubkey_get_nsig (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "signing");
if (!pubkey_get_nenc (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "encryption");
}
}
else
{
algo &= 0xff;
if (!unknown_pubkey_algos[algo])
{
if (opt.verbose)
log_info (_("can't handle public key algorithm %d\n"), algo);
unknown_pubkey_algos[algo] = 1;
}
}
}
#ifdef DEBUG_PARSE_PACKET
int
dbg_parse_packet (IOBUF inp, PACKET *pkt, const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc = parse (inp, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
parse_packet (IOBUF inp, PACKET * pkt)
{
int skip, rc;
do
{
rc = parse (inp, pkt, 0, NULL, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Like parse packet, but only return secret or public (sub)key
* packets.
*/
#ifdef DEBUG_PARSE_PACKET
int
dbg_search_packet (IOBUF inp, PACKET * pkt, off_t * retpos, int with_uid,
const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc =
parse (inp, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0, "search",
dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
search_packet (IOBUF inp, PACKET * pkt, off_t * retpos, int with_uid)
{
int skip, rc;
do
{
rc = parse (inp, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy all packets from INP to OUT, thereby removing unused spaces.
*/
#ifdef DEBUG_PARSE_PACKET
int
dbg_copy_all_packets (IOBUF inp, IOBUF out, const char *dbg_f, int dbg_l)
{
PACKET pkt;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
do
{
init_packet (&pkt);
}
while (!
(rc =
parse (inp, &pkt, 0, NULL, &skip, out, 0, "copy", dbg_f, dbg_l)));
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_all_packets (IOBUF inp, IOBUF out)
{
PACKET pkt;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
do
{
init_packet (&pkt);
}
while (!(rc = parse (inp, &pkt, 0, NULL, &skip, out, 0)));
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy some packets from INP to OUT, thereby removing unused spaces.
* Stop at offset STOPoff (i.e. don't copy packets at this or later
* offsets)
*/
#ifdef DEBUG_PARSE_PACKET
int
dbg_copy_some_packets (IOBUF inp, IOBUF out, off_t stopoff,
const char *dbg_f, int dbg_l)
{
PACKET pkt;
int skip, rc = 0;
do
{
if (iobuf_tell (inp) >= stopoff)
return 0;
init_packet (&pkt);
}
while (!(rc = parse (inp, &pkt, 0, NULL, &skip, out, 0,
"some", dbg_f, dbg_l)));
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_some_packets (IOBUF inp, IOBUF out, off_t stopoff)
{
PACKET pkt;
int skip, rc = 0;
do
{
if (iobuf_tell (inp) >= stopoff)
return 0;
init_packet (&pkt);
}
while (!(rc = parse (inp, &pkt, 0, NULL, &skip, out, 0)));
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Skip over N packets
*/
#ifdef DEBUG_PARSE_PACKET
int
dbg_skip_some_packets (IOBUF inp, unsigned n, const char *dbg_f, int dbg_l)
{
int skip, rc = 0;
PACKET pkt;
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (inp, &pkt, 0, NULL, &skip, NULL, 1, "skip", dbg_f, dbg_l);
}
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
skip_some_packets (IOBUF inp, unsigned n)
{
int skip, rc = 0;
PACKET pkt;
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (inp, &pkt, 0, NULL, &skip, NULL, 1);
}
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/* Parse a packet and save it in *PKT.
If OUT is not NULL and the packet is valid (its type is not 0),
then the header, the initial length field and the packet's contents
are written to OUT. In this case, the packet is not saved in *PKT.
ONLYKEYPKTS is a simple packet filter. If ONLYKEYPKTS is set to 1,
then only public subkey packets, public key packets, private subkey
packets and private key packets are parsed. The rest are skipped
(i.e., the header and the contents are read from the pipeline and
discarded). If ONLYKEYPKTS is set to 2, then in addition to the
above 4 types of packets, user id packets are also accepted.
DO_SKIP is a more coarse grained filter. Unless ONLYKEYPKTS is set
to 2 and the packet is a user id packet, all packets are skipped.
Finally, if a packet is invalid (it's type is 0), it is skipped.
If a packet is skipped and SKIP is not NULL, then *SKIP is set to
1.
Note: ONLYKEYPKTS and DO_SKIP are only respected if OUT is NULL,
i.e., the packets are not simply being copied.
If RETPOS is not NULL, then the position of INP (as returned by
iobuf_tell) is saved there before any data is read from INP.
*/
static int
parse (IOBUF inp, PACKET * pkt, int onlykeypkts, off_t * retpos,
int *skip, IOBUF out, int do_skip
#ifdef DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
)
{
int rc = 0, c, ctb, pkttype, lenbytes;
unsigned long pktlen;
byte hdr[8];
int hdrlen;
int new_ctb = 0, partial = 0;
int with_uid = (onlykeypkts == 2);
off_t pos;
*skip = 0;
log_assert (!pkt->pkt.generic);
if (retpos || list_mode)
{
pos = iobuf_tell (inp);
if (retpos)
*retpos = pos;
}
else
pos = 0; /* (silence compiler warning) */
/* The first byte of a packet is the so-called tag. The highest bit
must be set. */
if ((ctb = iobuf_get (inp)) == -1)
{
rc = -1;
goto leave;
}
hdrlen = 0;
hdr[hdrlen++] = ctb;
if (!(ctb & 0x80))
{
log_error ("%s: invalid packet (ctb=%02x)\n", iobuf_where (inp), ctb);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Immediately following the header is the length. There are two
formats: the old format and the new format. If bit 6 (where the
least significant bit is bit 0) is set in the tag, then we are
dealing with a new format packet. Otherwise, it is an old format
packet. */
pktlen = 0;
new_ctb = !!(ctb & 0x40);
if (new_ctb)
{
/* Get the packet's type. This is encoded in the 6 least
significant bits of the tag. */
pkttype = ctb & 0x3f;
/* Extract the packet's length. New format packets have 4 ways
to encode the packet length. The value of the first byte
determines the encoding and partially determines the length.
See section 4.2.2 of RFC 4880 for details. */
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 1st length byte missing\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
if (c < 192)
pktlen = c;
else if (c < 224)
{
pktlen = (c - 192) * 256;
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 2nd length byte missing\n",
iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
pktlen += c + 192;
}
else if (c == 255)
{
int i;
char value[4];
for (i = 0; i < 4; i ++)
{
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 4 byte length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
value[i] = hdr[hdrlen++] = c;
}
pktlen = buf32_to_ulong (value);
}
else /* Partial body length. */
{
switch (pkttype)
{
case PKT_PLAINTEXT:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_COMPRESSED:
iobuf_set_partial_body_length_mode (inp, c & 0xff);
pktlen = 0; /* To indicate partial length. */
partial = 1;
break;
default:
log_error ("%s: partial length invalid for"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else
/* This is an old format packet. */
{
/* Extract the packet's type. This is encoded in bits 2-5. */
pkttype = (ctb >> 2) & 0xf;
/* The type of length encoding is encoded in bits 0-1 of the
tag. */
lenbytes = ((ctb & 3) == 3) ? 0 : (1 << (ctb & 3));
if (!lenbytes)
{
pktlen = 0; /* Don't know the value. */
/* This isn't really partial, but we can treat it the same
in a "read until the end" sort of way. */
partial = 1;
if (pkttype != PKT_ENCRYPTED && pkttype != PKT_PLAINTEXT
&& pkttype != PKT_COMPRESSED)
{
log_error ("%s: indeterminate length for invalid"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
else
{
for (; lenbytes; lenbytes--)
{
pktlen <<= 8;
c = iobuf_get (inp);
if (c == -1)
{
log_error ("%s: length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pktlen |= hdr[hdrlen++] = c;
}
}
}
/* Sometimes the decompressing layer enters an error state in which
it simply outputs 0xff for every byte read. If we have a stream
of 0xff bytes, then it will be detected as a new format packet
with type 63 and a 4-byte encoded length that is 4G-1. Since
packets with type 63 are private and we use them as a control
packet, which won't be 4 GB, we reject such packets as
invalid. */
if (pkttype == 63 && pktlen == 0xFFFFFFFF)
{
/* With some probability this is caused by a problem in the
* the uncompressing layer - in some error cases it just loops
* and spits out 0xff bytes. */
log_error ("%s: garbled packet detected\n", iobuf_where (inp));
g10_exit (2);
}
if (out && pkttype)
{
/* This type of copying won't work if the packet uses a partial
body length. (In other words, this only works if HDR is
actually the length.) Currently, no callers require this
functionality so we just log this as an error. */
if (partial)
{
log_error ("parse: Can't copy partial packet. Aborting.\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
rc = iobuf_write (out, hdr, hdrlen);
if (!rc)
rc = copy_packet (inp, out, pkttype, pktlen, partial);
goto leave;
}
if (with_uid && pkttype == PKT_USER_ID)
/* If ONLYKEYPKTS is set to 2, then we never skip user id packets,
even if DO_SKIP is set. */
;
else if (do_skip
/* type==0 is not allowed. This is an invalid packet. */
|| !pkttype
/* When ONLYKEYPKTS is set, we don't skip keys. */
|| (onlykeypkts && pkttype != PKT_PUBLIC_SUBKEY
&& pkttype != PKT_PUBLIC_KEY
&& pkttype != PKT_SECRET_SUBKEY && pkttype != PKT_SECRET_KEY))
{
iobuf_skip_rest (inp, pktlen, partial);
*skip = 1;
rc = 0;
goto leave;
}
if (DBG_PACKET)
{
#ifdef DEBUG_PARSE_PACKET
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s (%s.%s.%d)\n",
iobuf_id (inp), pkttype, pktlen, new_ctb ? " (new_ctb)" : "",
dbg_w, dbg_f, dbg_l);
#else
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s\n",
iobuf_id (inp), pkttype, pktlen,
new_ctb ? " (new_ctb)" : "");
#endif
}
if (list_mode)
es_fprintf (listfp, "# off=%lu ctb=%02x tag=%d hlen=%d plen=%lu%s%s\n",
(unsigned long)pos, ctb, pkttype, hdrlen, pktlen,
partial? (new_ctb ? " partial" : " indeterminate") :"",
new_ctb? " new-ctb":"");
pkt->pkttype = pkttype;
rc = GPG_ERR_UNKNOWN_PACKET; /* default error */
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
pkt->pkt.public_key = xmalloc_clear (sizeof *pkt->pkt.public_key);
rc = parse_key (inp, pkttype, pktlen, hdr, hdrlen, pkt);
break;
case PKT_SYMKEY_ENC:
rc = parse_symkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_PUBKEY_ENC:
rc = parse_pubkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_SIGNATURE:
pkt->pkt.signature = xmalloc_clear (sizeof *pkt->pkt.signature);
rc = parse_signature (inp, pkttype, pktlen, pkt->pkt.signature);
break;
case PKT_ONEPASS_SIG:
pkt->pkt.onepass_sig = xmalloc_clear (sizeof *pkt->pkt.onepass_sig);
rc = parse_onepass_sig (inp, pkttype, pktlen, pkt->pkt.onepass_sig);
break;
case PKT_USER_ID:
rc = parse_user_id (inp, pkttype, pktlen, pkt);
break;
case PKT_ATTRIBUTE:
pkt->pkttype = pkttype = PKT_USER_ID; /* we store it in the userID */
rc = parse_attribute (inp, pkttype, pktlen, pkt);
break;
case PKT_OLD_COMMENT:
case PKT_COMMENT:
rc = parse_comment (inp, pkttype, pktlen, pkt);
break;
case PKT_RING_TRUST:
parse_trust (inp, pkttype, pktlen, pkt);
rc = 0;
break;
case PKT_PLAINTEXT:
rc = parse_plaintext (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_COMPRESSED:
rc = parse_compressed (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
rc = parse_encrypted (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_MDC:
rc = parse_mdc (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_GPG_CONTROL:
rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial);
break;
case PKT_MARKER:
rc = parse_marker (inp, pkttype, pktlen);
break;
default:
/* Unknown packet. Skip it. */
skip_packet (inp, pkttype, pktlen, partial);
break;
}
leave:
/* FIXME: We leak in case of an error (see the xmalloc's above). */
if (!rc && iobuf_error (inp))
rc = GPG_ERR_INV_KEYRING;
/* FIXME: We use only the error code for now to avoid problems with
callers which have not been checked to always use gpg_err_code()
when comparing error codes. */
return rc == -1? -1 : gpg_err_code (rc);
}
static void
dump_hex_line (int c, int *i)
{
if (*i && !(*i % 8))
{
if (*i && !(*i % 24))
es_fprintf (listfp, "\n%4d:", *i);
else
es_putc (' ', listfp);
}
if (c == -1)
es_fprintf (listfp, " EOF");
else
es_fprintf (listfp, " %02x", c);
++*i;
}
/* Copy the contents of a packet from the pipeline IN to the pipeline
OUT.
The header and length have already been read from INP and the
decoded values are given as PKGTYPE and PKTLEN.
If the packet is a partial body length packet (RFC 4880, Section
4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode
should already have been called on INP and PARTIAL should be set.
If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED,
copy until the first EOF is encountered on INP.
Returns 0 on success and an error code if an error occurs. */
static int
copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial)
{
int rc;
int n;
char buf[100];
if (partial)
{
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else if (!pktlen && pkttype == PKT_COMPRESSED)
{
log_debug ("copy_packet: compressed!\n");
/* compressed packet, copy till EOF */
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else
{
for (; pktlen; pktlen -= n)
{
n = pktlen > sizeof (buf) ? sizeof (buf) : pktlen;
n = iobuf_read (inp, buf, n);
if (n == -1)
return gpg_error (GPG_ERR_EOF);
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
}
return 0;
}
/* Skip an unknown packet. PKTTYPE is the packet's type, PKTLEN is
the length of the packet's content and PARTIAL is whether partial
body length encoding in used (in this case PKTLEN is ignored). */
static void
skip_packet (IOBUF inp, int pkttype, unsigned long pktlen, int partial)
{
if (list_mode)
{
es_fprintf (listfp, ":unknown packet: type %2d, length %lu\n",
pkttype, pktlen);
if (pkttype)
{
int c, i = 0;
es_fputs ("dump:", listfp);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
return;
}
}
iobuf_skip_rest (inp, pktlen, partial);
}
/* Read PKTLEN bytes form INP and return them in a newly allocated
buffer. In case of an error (including reading fewer than PKTLEN
bytes from INP before EOF is returned), NULL is returned and an
error message is logged. */
static void *
read_rest (IOBUF inp, size_t pktlen)
{
int c;
byte *buf, *p;
buf = xtrymalloc (pktlen);
if (!buf)
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("error reading rest of packet: %s\n", gpg_strerror (err));
return NULL;
}
for (p = buf; pktlen; pktlen--)
{
c = iobuf_get (inp);
if (c == -1)
{
log_error ("premature eof while reading rest of packet\n");
xfree (buf);
return NULL;
}
*p++ = c;
}
return buf;
}
/* Read a special size+body from INP. On success store an opaque MPI
with it at R_DATA. On error return an error code and store NULL at
R_DATA. Even in the error case store the number of read bytes at
R_NREAD. The caller shall pass the remaining size of the packet in
PKTLEN. */
static gpg_error_t
read_size_body (iobuf_t inp, int pktlen, size_t *r_nread,
gcry_mpi_t *r_data)
{
char buffer[256];
char *tmpbuf;
int i, c, nbytes;
*r_nread = 0;
*r_data = NULL;
if (!pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
c = iobuf_readbyte (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
pktlen--;
++*r_nread;
nbytes = c;
if (nbytes < 2 || nbytes > 254)
return gpg_error (GPG_ERR_INV_PACKET);
if (nbytes > pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
buffer[0] = nbytes;
for (i = 0; i < nbytes; i++)
{
c = iobuf_get (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
++*r_nread;
buffer[1+i] = c;
}
tmpbuf = xtrymalloc (1 + nbytes);
if (!tmpbuf)
return gpg_error_from_syserror ();
memcpy (tmpbuf, buffer, 1 + nbytes);
*r_data = gcry_mpi_set_opaque (NULL, tmpbuf, 8 * (1 + nbytes));
if (!*r_data)
{
xfree (tmpbuf);
return gpg_error_from_syserror ();
}
return 0;
}
/* Parse a marker packet. */
static int
parse_marker (IOBUF inp, int pkttype, unsigned long pktlen)
{
(void) pkttype;
if (pktlen != 3)
goto fail;
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'G')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (list_mode)
es_fputs (":marker packet: PGP\n", listfp);
return 0;
fail:
log_error ("invalid marker packet\n");
if (list_mode)
es_fputs (":marker packet: [invalid]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
PKT_symkey_enc *k;
int rc = 0;
int i, version, s2kmode, cipher_algo, hash_algo, seskeylen, minlen;
if (pktlen < 4)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
version = iobuf_get_noeof (inp);
pktlen--;
if (version != 4)
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown version]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen > 200)
{ /* (we encode the seskeylen in a byte) */
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too large]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
cipher_algo = iobuf_get_noeof (inp);
pktlen--;
s2kmode = iobuf_get_noeof (inp);
pktlen--;
hash_algo = iobuf_get_noeof (inp);
pktlen--;
switch (s2kmode)
{
case 0: /* Simple S2K. */
minlen = 0;
break;
case 1: /* Salted S2K. */
minlen = 8;
break;
case 3: /* Iterated+salted S2K. */
minlen = 9;
break;
default:
log_error ("unknown S2K mode %d\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown S2K mode]\n");
goto leave;
}
if (minlen > pktlen)
{
log_error ("packet with S2K %d too short\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
seskeylen = pktlen - minlen;
k = packet->pkt.symkey_enc = xmalloc_clear (sizeof *packet->pkt.symkey_enc
+ seskeylen - 1);
k->version = version;
k->cipher_algo = cipher_algo;
k->s2k.mode = s2kmode;
k->s2k.hash_algo = hash_algo;
if (s2kmode == 1 || s2kmode == 3)
{
for (i = 0; i < 8 && pktlen; i++, pktlen--)
k->s2k.salt[i] = iobuf_get_noeof (inp);
}
if (s2kmode == 3)
{
k->s2k.count = iobuf_get (inp);
pktlen--;
}
k->seskeylen = seskeylen;
if (k->seskeylen)
{
for (i = 0; i < seskeylen && pktlen; i++, pktlen--)
k->seskey[i] = iobuf_get_noeof (inp);
/* What we're watching out for here is a session key decryptor
with no salt. The RFC says that using salt for this is a
MUST. */
if (s2kmode != 1 && s2kmode != 3)
log_info (_("WARNING: potentially insecure symmetrically"
" encrypted session key\n"));
}
log_assert (!pktlen);
if (list_mode)
{
es_fprintf (listfp,
":symkey enc packet: version %d, cipher %d, s2k %d, hash %d",
version, cipher_algo, s2kmode, hash_algo);
if (seskeylen)
es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8);
es_fprintf (listfp, "\n");
if (s2kmode == 1 || s2kmode == 3)
{
es_fprintf (listfp, "\tsalt ");
es_write_hexstring (listfp, k->s2k.salt, 8, 0, NULL);
if (s2kmode == 3)
es_fprintf (listfp, ", count %lu (%lu)",
S2K_DECODE_COUNT ((ulong) k->s2k.count),
(ulong) k->s2k.count);
es_fprintf (listfp, "\n");
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
static int
parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
int rc = 0;
int i, ndata;
PKT_pubkey_enc *k;
k = packet->pkt.pubkey_enc = xmalloc_clear (sizeof *packet->pkt.pubkey_enc);
if (pktlen < 12)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":pubkey enc packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->version = iobuf_get_noeof (inp);
pktlen--;
if (k->version != 2 && k->version != 3)
{
log_error ("packet(%d) with unknown version %d\n", pkttype, k->version);
if (list_mode)
es_fputs (":pubkey enc packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->keyid[0] = read_32 (inp);
pktlen -= 4;
k->keyid[1] = read_32 (inp);
pktlen -= 4;
k->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
k->throw_keyid = 0; /* Only used as flag for build_packet. */
if (list_mode)
es_fprintf (listfp,
":pubkey enc packet: version %d, algo %d, keyid %08lX%08lX\n",
k->version, k->pubkey_algo, (ulong) k->keyid[0],
(ulong) k->keyid[1]);
ndata = pubkey_get_nenc (k->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunsupported algorithm %d\n", k->pubkey_algo);
unknown_pubkey_warning (k->pubkey_algo);
k->data[0] = NULL; /* No need to store the encrypted data. */
}
else
{
for (i = 0; i < ndata; i++)
{
if (k->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1)
{
size_t n;
rc = read_size_body (inp, pktlen, &n, k->data+i);
pktlen -= n;
}
else
{
int n = pktlen;
k->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!k->data[i])
rc = gpg_error (GPG_ERR_INV_PACKET);
}
if (rc)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, k->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
/* Dump a subpacket to LISTFP. BUFFER contains the subpacket in
question and points to the type field in the subpacket header (not
the start of the header). TYPE is the subpacket's type with the
critical bit cleared. CRITICAL is the value of the CRITICAL bit.
BUFLEN is the length of the buffer and LENGTH is the length of the
subpacket according to the subpacket's header. */
static void
dump_sig_subpkt (int hashed, int type, int critical,
const byte * buffer, size_t buflen, size_t length)
{
const char *p = NULL;
int i;
/* The CERT has warning out with explains how to use GNUPG to detect
* the ARRs - we print our old message here when it is a faked ARR
* and add an additional notice. */
if (type == SIGSUBPKT_ARR && !hashed)
{
es_fprintf (listfp,
"\tsubpkt %d len %u (additional recipient request)\n"
"WARNING: PGP versions > 5.0 and < 6.5.8 will automagically "
"encrypt to this key and thereby reveal the plaintext to "
"the owner of this ARR key. Detailed info follows:\n",
type, (unsigned) length);
}
buffer++;
length--;
es_fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*) */
critical ? "critical " : "",
hashed ? "hashed " : "", type, (unsigned) length);
if (length > buflen)
{
es_fprintf (listfp, "too short: buffer is only %u)\n", (unsigned) buflen);
return;
}
switch (type)
{
case SIGSUBPKT_SIG_CREATED:
if (length >= 4)
es_fprintf (listfp, "sig created %s",
strtimestamp (buf32_to_u32 (buffer)));
break;
case SIGSUBPKT_SIG_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "sig expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "sig does not expire");
}
break;
case SIGSUBPKT_EXPORTABLE:
if (length)
es_fprintf (listfp, "%sexportable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_TRUST:
if (length != 2)
p = "[invalid trust subpacket]";
else
es_fprintf (listfp, "trust signature of depth %d, value %d", buffer[0],
buffer[1]);
break;
case SIGSUBPKT_REGEXP:
if (!length)
p = "[invalid regexp subpacket]";
else
{
es_fprintf (listfp, "regular expression: \"");
es_write_sanitized (listfp, buffer, length, "\"", NULL);
p = "\"";
}
break;
case SIGSUBPKT_REVOCABLE:
if (length)
es_fprintf (listfp, "%srevocable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_KEY_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "key expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "key does not expire");
}
break;
case SIGSUBPKT_PREF_SYM:
es_fputs ("pref-sym-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_REV_KEY:
es_fputs ("revocation key: ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
for (i = 2; i < length; i++)
es_fprintf (listfp, "%02X", buffer[i]);
}
break;
case SIGSUBPKT_ISSUER:
if (length >= 8)
es_fprintf (listfp, "issuer key ID %08lX%08lX",
(ulong) buf32_to_u32 (buffer),
(ulong) buf32_to_u32 (buffer + 4));
break;
case SIGSUBPKT_ISSUER_FPR:
if (length >= 21)
{
char *tmp;
es_fprintf (listfp, "issuer fpr v%d ", buffer[0]);
tmp = bin2hex (buffer+1, length-1, NULL);
if (tmp)
{
es_fputs (tmp, listfp);
xfree (tmp);
}
}
break;
case SIGSUBPKT_NOTATION:
{
es_fputs ("notation: ", listfp);
if (length < 8)
p = "[too short]";
else
{
const byte *s = buffer;
size_t n1, n2;
n1 = (s[4] << 8) | s[5];
n2 = (s[6] << 8) | s[7];
s += 8;
if (8 + n1 + n2 != length)
p = "[error]";
else
{
es_write_sanitized (listfp, s, n1, ")", NULL);
es_putc ('=', listfp);
if (*buffer & 0x80)
es_write_sanitized (listfp, s + n1, n2, ")", NULL);
else
p = "[not human readable]";
}
}
}
break;
case SIGSUBPKT_PREF_HASH:
es_fputs ("pref-hash-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_PREF_COMPR:
es_fputs ("pref-zip-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_KS_FLAGS:
es_fputs ("keyserver preferences:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_PREF_KS:
es_fputs ("preferred keyserver: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_PRIMARY_UID:
p = "primary user ID";
break;
case SIGSUBPKT_POLICY:
es_fputs ("policy: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_KEY_FLAGS:
es_fputs ("key flags:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_SIGNERS_UID:
p = "signer's user ID";
break;
case SIGSUBPKT_REVOC_REASON:
if (length)
{
es_fprintf (listfp, "revocation reason 0x%02x (", *buffer);
es_write_sanitized (listfp, buffer + 1, length - 1, ")", NULL);
p = ")";
}
break;
case SIGSUBPKT_ARR:
es_fputs ("Big Brother's key (ignored): ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
if (length > 2)
es_write_hexstring (listfp, buffer+2, length-2, 0, NULL);
}
break;
case SIGSUBPKT_FEATURES:
es_fputs ("features:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02x", buffer[i]);
break;
case SIGSUBPKT_SIGNATURE:
es_fputs ("signature: ", listfp);
if (length < 17)
p = "[too short]";
else
es_fprintf (listfp, "v%d, class 0x%02X, algo %d, digest algo %d",
buffer[0],
buffer[0] == 3 ? buffer[2] : buffer[1],
buffer[0] == 3 ? buffer[15] : buffer[2],
buffer[0] == 3 ? buffer[16] : buffer[3]);
break;
default:
if (type >= 100 && type <= 110)
p = "experimental / private subpacket";
else
p = "?";
break;
}
es_fprintf (listfp, "%s)\n", p ? p : "");
}
/*
* Returns: >= 0 use this offset into buffer
* -1 explicitly reject returning this type
* -2 subpacket too short
*/
int
parse_one_sig_subpkt (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_REV_KEY:
if (n < 22)
break;
return 0;
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
if (n < 4)
break;
return 0;
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_KS_FLAGS:
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_REGEXP:
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REVOC_REASON:
if (!n)
break;
return 0;
case SIGSUBPKT_ISSUER: /* issuer key ID */
if (n < 8)
break;
return 0;
case SIGSUBPKT_ISSUER_FPR: /* issuer key ID */
if (n < 21)
break;
return 0;
case SIGSUBPKT_NOTATION:
/* minimum length needed, and the subpacket must be well-formed
where the name length and value length all fit inside the
packet. */
if (n < 8
|| 8 + ((buffer[4] << 8) | buffer[5]) +
((buffer[6] << 8) | buffer[7]) != n)
break;
return 0;
case SIGSUBPKT_PRIMARY_UID:
if (n != 1)
break;
return 0;
case SIGSUBPKT_TRUST:
if (n != 2)
break;
return 0;
default:
return 0;
}
return -2;
}
/* Return true if we understand the critical notation. */
static int
can_handle_critical_notation (const byte * name, size_t len)
{
if (len == 32 && memcmp (name, "preferred-email-encoding@pgp.com", 32) == 0)
return 1;
if (len == 21 && memcmp (name, "pka-address@gnupg.org", 21) == 0)
return 1;
return 0;
}
static int
can_handle_critical (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_NOTATION:
if (n >= 8)
{
size_t notation_len = ((buffer[4] << 8) | buffer[5]);
if (n - 8 >= notation_len)
return can_handle_critical_notation (buffer + 8, notation_len);
}
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REV_KEY:
case SIGSUBPKT_ISSUER: /* issuer key ID */
case SIGSUBPKT_ISSUER_FPR: /* issuer fingerprint */
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_PRIMARY_UID:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_TRUST:
case SIGSUBPKT_REGEXP:
/* Is it enough to show the policy or keyserver? */
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
return 1;
default:
return 0;
}
}
const byte *
enum_sig_subpkt (const subpktarea_t * pktbuf, sigsubpkttype_t reqtype,
size_t * ret_n, int *start, int *critical)
{
const byte *buffer;
int buflen;
int type;
int critical_dummy;
int offset;
size_t n;
int seq = 0;
int reqseq = start ? *start : 0;
if (!critical)
critical = &critical_dummy;
if (!pktbuf || reqseq == -1)
{
static char dummy[] = "x";
/* Return a value different from NULL to indicate that
* there is no critical bit we do not understand. */
return reqtype == SIGSUBPKT_TEST_CRITICAL ? dummy : NULL;
}
buffer = pktbuf->data;
buflen = pktbuf->len;
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 4 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
type = *buffer;
if (type & 0x80)
{
type &= 0x7f;
*critical = 1;
}
else
*critical = 0;
if (!(++seq > reqseq))
;
else if (reqtype == SIGSUBPKT_TEST_CRITICAL)
{
if (*critical)
{
if (n - 1 > buflen + 1)
goto too_short;
if (!can_handle_critical (buffer + 1, n - 1, type))
{
if (opt.verbose)
log_info (_("subpacket of type %d has "
"critical bit set\n"), type);
if (start)
*start = seq;
return NULL; /* This is an error. */
}
}
}
else if (reqtype < 0) /* List packets. */
dump_sig_subpkt (reqtype == SIGSUBPKT_LIST_HASHED,
type, *critical, buffer, buflen, n);
else if (type == reqtype) /* Found. */
{
buffer++;
n--;
if (n > buflen)
goto too_short;
if (ret_n)
*ret_n = n;
offset = parse_one_sig_subpkt (buffer, n, type);
switch (offset)
{
case -2:
log_error ("subpacket of type %d too short\n", type);
return NULL;
case -1:
return NULL;
default:
break;
}
if (start)
*start = seq;
return buffer + offset;
}
buffer += n;
buflen -= n;
}
if (reqtype == SIGSUBPKT_TEST_CRITICAL)
/* Returning NULL means we found a subpacket with the critical bit
set that we don't grok. We've iterated over all the subpackets
and haven't found such a packet so we need to return a non-NULL
value. */
return buffer;
/* Critical bit we don't understand. */
if (start)
*start = -1;
return NULL; /* End of packets; not found. */
too_short:
if (opt.verbose)
log_info ("buffer shorter than subpacket\n");
if (start)
*start = -1;
return NULL;
}
const byte *
parse_sig_subpkt (const subpktarea_t * buffer, sigsubpkttype_t reqtype,
size_t * ret_n)
{
return enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL);
}
const byte *
parse_sig_subpkt2 (PKT_signature * sig, sigsubpkttype_t reqtype)
{
const byte *p;
p = parse_sig_subpkt (sig->hashed, reqtype, NULL);
if (!p)
p = parse_sig_subpkt (sig->unhashed, reqtype, NULL);
return p;
}
/* Find all revocation keys. Look in hashed area only. */
void
parse_revkeys (PKT_signature * sig)
{
const byte *revkey;
int seq = 0;
size_t len;
if (sig->sig_class != 0x1F)
return;
while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY,
&len, &seq, NULL)))
{
if (/* The only valid length is 22 bytes. See RFC 4880
5.2.3.15. */
len == 22
/* 0x80 bit must be set on the class. */
&& (revkey[0] & 0x80))
{
sig->revkey = xrealloc (sig->revkey,
sizeof (struct revocation_key) *
(sig->numrevkeys + 1));
/* Copy the individual fields. */
sig->revkey[sig->numrevkeys].class = revkey[0];
sig->revkey[sig->numrevkeys].algid = revkey[1];
memcpy (sig->revkey[sig->numrevkeys].fpr, &revkey[2], 20);
sig->numrevkeys++;
}
}
}
int
parse_signature (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_signature * sig)
{
int md5_len = 0;
unsigned n;
int is_v4 = 0;
int rc = 0;
int i, ndata;
if (pktlen < 16)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
goto leave;
}
sig->version = iobuf_get_noeof (inp);
pktlen--;
if (sig->version == 4)
is_v4 = 1;
else if (sig->version != 2 && sig->version != 3)
{
log_error ("packet(%d) with unknown version %d\n",
pkttype, sig->version);
if (list_mode)
es_fputs (":signature packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (!is_v4)
{
if (pktlen == 0)
goto underflow;
md5_len = iobuf_get_noeof (inp);
pktlen--;
}
if (pktlen == 0)
goto underflow;
sig->sig_class = iobuf_get_noeof (inp);
pktlen--;
if (!is_v4)
{
if (pktlen < 12)
goto underflow;
sig->timestamp = read_32 (inp);
pktlen -= 4;
sig->keyid[0] = read_32 (inp);
pktlen -= 4;
sig->keyid[1] = read_32 (inp);
pktlen -= 4;
}
if (pktlen < 2)
goto underflow;
sig->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
sig->digest_algo = iobuf_get_noeof (inp);
pktlen--;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
if (is_v4) /* Read subpackets. */
{
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of hashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: hashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [hashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->hashed = xmalloc (sizeof (*sig->hashed) + n - 1);
sig->hashed->size = n;
sig->hashed->len = n;
if (iobuf_read (inp, sig->hashed->data, n) != n)
{
log_error ("premature eof while reading "
"hashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of unhashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: unhashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [unhashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->unhashed = xmalloc (sizeof (*sig->unhashed) + n - 1);
sig->unhashed->size = n;
sig->unhashed->len = n;
if (iobuf_read (inp, sig->unhashed->data, n) != n)
{
log_error ("premature eof while reading "
"unhashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
}
if (pktlen < 2)
goto underflow;
sig->digest_start[0] = iobuf_get_noeof (inp);
pktlen--;
sig->digest_start[1] = iobuf_get_noeof (inp);
pktlen--;
if (is_v4 && sig->pubkey_algo) /* Extract required information. */
{
const byte *p;
size_t len;
/* Set sig->flags.unknown_critical if there is a critical bit
* set for packets which we do not understand. */
if (!parse_sig_subpkt (sig->hashed, SIGSUBPKT_TEST_CRITICAL, NULL)
|| !parse_sig_subpkt (sig->unhashed, SIGSUBPKT_TEST_CRITICAL, NULL))
sig->flags.unknown_critical = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_CREATED, NULL);
if (p)
sig->timestamp = buf32_to_u32 (p);
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without timestamp\n");
p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER);
if (p)
{
sig->keyid[0] = buf32_to_u32 (p);
sig->keyid[1] = buf32_to_u32 (p + 4);
}
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without keyid\n");
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
sig->expiredate = sig->timestamp + buf32_to_u32 (p);
if (sig->expiredate && sig->expiredate <= make_timestamp ())
sig->flags.expired = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, NULL);
if (p)
sig->flags.policy_url = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, NULL);
if (p)
sig->flags.pref_ks = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIGNERS_UID, &len);
if (p && len)
{
sig->signers_uid = try_make_printable_string (p, len, 0);
if (!sig->signers_uid)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_NOTATION, NULL);
if (p)
sig->flags.notation = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_REVOCABLE, NULL);
if (p && *p == 0)
sig->flags.revocable = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_TRUST, &len);
if (p && len == 2)
{
sig->trust_depth = p[0];
sig->trust_value = p[1];
/* Only look for a regexp if there is also a trust
subpacket. */
sig->trust_regexp =
parse_sig_subpkt (sig->hashed, SIGSUBPKT_REGEXP, &len);
/* If the regular expression is of 0 length, there is no
regular expression. */
if (len == 0)
sig->trust_regexp = NULL;
}
/* We accept the exportable subpacket from either the hashed or
unhashed areas as older versions of gpg put it in the
unhashed area. In theory, anyway, we should never see this
packet off of a local keyring. */
p = parse_sig_subpkt2 (sig, SIGSUBPKT_EXPORTABLE);
if (p && *p == 0)
sig->flags.exportable = 0;
/* Find all revocation keys. */
if (sig->sig_class == 0x1F)
parse_revkeys (sig);
}
if (list_mode)
{
es_fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n"
"\tversion %d, created %lu, md5len %d, sigclass 0x%02x\n"
"\tdigest algo %d, begin of digest %02x %02x\n",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
sig->version, (ulong) sig->timestamp, md5_len, sig->sig_class,
sig->digest_algo, sig->digest_start[0], sig->digest_start[1]);
if (is_v4)
{
parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL);
parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL);
}
}
ndata = pubkey_get_nsig (sig->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", sig->pubkey_algo);
unknown_pubkey_warning (sig->pubkey_algo);
/* We store the plain material in data[0], so that we are able
* to write it back with build_packet(). */
if (pktlen > (5 * MAX_EXTERN_MPI_BITS / 8))
{
/* We include a limit to avoid too trivial DoS attacks by
having gpg allocate too much memory. */
log_error ("signature packet: too much data\n");
rc = GPG_ERR_INV_PACKET;
}
else
{
sig->data[0] =
gcry_mpi_set_opaque (NULL, read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
}
}
else
{
for (i = 0; i < ndata; i++)
{
n = pktlen;
sig->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, sig->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!sig->data[i])
rc = GPG_ERR_INV_PACKET;
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
underflow:
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops)
{
int version;
int rc = 0;
if (pktlen < 13)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":onepass_sig packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
version = iobuf_get_noeof (inp);
pktlen--;
if (version != 3)
{
log_error ("onepass_sig with unknown version %d\n", version);
if (list_mode)
es_fputs (":onepass_sig packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ops->sig_class = iobuf_get_noeof (inp);
pktlen--;
ops->digest_algo = iobuf_get_noeof (inp);
pktlen--;
ops->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
ops->keyid[0] = read_32 (inp);
pktlen -= 4;
ops->keyid[1] = read_32 (inp);
pktlen -= 4;
ops->last = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp,
":onepass_sig packet: keyid %08lX%08lX\n"
"\tversion %d, sigclass 0x%02x, digest %d, pubkey %d, "
"last=%d\n",
(ulong) ops->keyid[0], (ulong) ops->keyid[1],
version, ops->sig_class,
ops->digest_algo, ops->pubkey_algo, ops->last);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
static int
parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * pkt)
{
gpg_error_t err = 0;
int i, version, algorithm;
unsigned long timestamp, expiredate, max_expiredate;
int npkey, nskey;
u32 keyid[2];
PKT_public_key *pk;
(void) hdr;
pk = pkt->pkt.public_key; /* PK has been cleared. */
version = iobuf_get_noeof (inp);
pktlen--;
if (pkttype == PKT_PUBLIC_SUBKEY && version == '#')
{
/* Early versions of G10 used the old PGP comments packets;
* luckily all those comments are started by a hash. */
if (list_mode)
{
es_fprintf (listfp, ":rfc1991 comment packet: \"");
for (; pktlen; pktlen--)
{
int c;
c = iobuf_get (inp);
if (c == -1)
break; /* Ooops: shorter than indicated. */
if (c >= ' ' && c <= 'z')
es_putc (c, listfp);
else
es_fprintf (listfp, "\\x%02x", c);
}
es_fprintf (listfp, "\"\n");
}
iobuf_skip_rest (inp, pktlen, 0);
return 0;
}
else if (version == 4)
{
/* The only supported version. Use an older gpg
version (i.e. gpg 1.4) to parse v3 packets. */
}
else if (version == 2 || version == 3)
{
if (opt.verbose > 1)
log_info ("packet(%d) with obsolete version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version);
pk->version = version;
err = gpg_error (GPG_ERR_LEGACY_KEY);
goto leave;
}
else
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fputs (":key packet: [unknown version]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen < 11)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too short]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
else if (pktlen > MAX_KEY_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too larget]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
timestamp = read_32 (inp);
pktlen -= 4;
expiredate = 0; /* have to get it from the selfsignature */
max_expiredate = 0;
algorithm = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, ":%s key packet:\n"
"\tversion %d, algo %d, created %lu, expires %lu\n",
pkttype == PKT_PUBLIC_KEY ? "public" :
pkttype == PKT_SECRET_KEY ? "secret" :
pkttype == PKT_PUBLIC_SUBKEY ? "public sub" :
pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??",
version, algorithm, timestamp, expiredate);
pk->timestamp = timestamp;
pk->expiredate = expiredate;
pk->max_expiredate = max_expiredate;
pk->hdrbytes = hdrlen;
pk->version = version;
pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY);
pk->pubkey_algo = algorithm;
nskey = pubkey_get_nskey (algorithm);
npkey = pubkey_get_npkey (algorithm);
if (!npkey)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm);
unknown_pubkey_warning (algorithm);
}
if (!npkey)
{
/* Unknown algorithm - put data into an opaque MPI. */
pk->pkey[0] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
goto leave;
}
else
{
for (i = 0; i < npkey; i++)
{
if ( (algorithm == PUBKEY_ALGO_ECDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_EDDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_ECDH && (i == 0 || i == 2)))
{
/* Read the OID (i==1) or the KDF params (i==2). */
size_t n;
err = read_size_body (inp, pktlen, &n, pk->pkey+i);
pktlen -= n;
}
else
{
unsigned int n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tpkey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
if ((algorithm == PUBKEY_ALGO_ECDSA
|| algorithm == PUBKEY_ALGO_EDDSA
|| algorithm == PUBKEY_ALGO_ECDH) && i==0)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
es_fprintf (listfp, " %s (%s)", name?name:"", curve);
xfree (curve);
}
es_putc ('\n', listfp);
}
}
}
if (list_mode)
keyid_from_pk (pk, keyid);
if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)
{
struct seckey_info *ski;
byte temp[16];
size_t snlen = 0;
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!pk->seckey_info)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->algo = iobuf_get_noeof (inp);
pktlen--;
if (ski->algo)
{
ski->is_protected = 1;
ski->s2k.count = 0;
if (ski->algo == 254 || ski->algo == 255)
{
if (pktlen < 3)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->sha1chk = (ski->algo == 254);
ski->algo = iobuf_get_noeof (inp);
pktlen--;
/* Note that a ski->algo > 110 is illegal, but I'm not
erroring on it here as otherwise there would be no
way to delete such a key. */
ski->s2k.mode = iobuf_get_noeof (inp);
pktlen--;
ski->s2k.hash_algo = iobuf_get_noeof (inp);
pktlen--;
/* Check for the special GNU extension. */
if (ski->s2k.mode == 101)
{
for (i = 0; i < 4 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 4 || memcmp (temp, "GNU", 3))
{
if (list_mode)
es_fprintf (listfp, "\tunknown S2K %d\n",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Here we know that it is a GNU extension. What
* follows is the GNU protection mode: All values
* have special meanings and they are mapped to MODE
* with a base of 1000. */
ski->s2k.mode = 1000 + temp[3];
}
/* Read the salt. */
switch (ski->s2k.mode)
{
case 1:
case 3:
for (i = 0; i < 8 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 8)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
memcpy (ski->s2k.salt, temp, 8);
break;
}
/* Check the mode. */
switch (ski->s2k.mode)
{
case 0:
if (list_mode)
es_fprintf (listfp, "\tsimple S2K");
break;
case 1:
if (list_mode)
es_fprintf (listfp, "\tsalted S2K");
break;
case 3:
if (list_mode)
es_fprintf (listfp, "\titer+salt S2K");
break;
case 1001:
if (list_mode)
es_fprintf (listfp, "\tgnu-dummy S2K");
break;
case 1002:
if (list_mode)
es_fprintf (listfp, "\tgnu-divert-to-card S2K");
break;
default:
if (list_mode)
es_fprintf (listfp, "\tunknown %sS2K %d\n",
ski->s2k.mode < 1000 ? "" : "GNU ",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Print some info. */
if (list_mode)
{
es_fprintf (listfp, ", algo: %d,%s hash: %d",
ski->algo,
ski->sha1chk ? " SHA1 protection,"
: " simple checksum,", ski->s2k.hash_algo);
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
{
es_fprintf (listfp, ", salt: ");
es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL);
}
es_putc ('\n', listfp);
}
/* Read remaining protection parameters. */
if (ski->s2k.mode == 3)
{
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->s2k.count = iobuf_get (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
(ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count),
(ulong) ski->s2k.count);
}
else if (ski->s2k.mode == 1002)
{
/* Read the serial number. */
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
snlen = iobuf_get (inp);
pktlen--;
if (pktlen < snlen || snlen == (size_t)(-1))
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else /* Old version; no S2K, so we set mode to 0, hash MD5. */
{
/* Note that a ski->algo > 110 is illegal, but I'm not
erroring on it here as otherwise there would be no
way to delete such a key. */
ski->s2k.mode = 0;
ski->s2k.hash_algo = DIGEST_ALGO_MD5;
if (list_mode)
es_fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n",
ski->algo, ski->s2k.hash_algo);
}
/* It is really ugly that we don't know the size
* of the IV here in cases we are not aware of the algorithm.
* so a
* ski->ivlen = cipher_get_blocksize (ski->algo);
* won't work. The only solution I see is to hardwire it.
* NOTE: if you change the ivlen above 16, don't forget to
* enlarge temp. */
ski->ivlen = openpgp_cipher_blocklen (ski->algo);
log_assert (ski->ivlen <= sizeof (temp));
if (ski->s2k.mode == 1001)
ski->ivlen = 0;
else if (ski->s2k.mode == 1002)
ski->ivlen = snlen < 16 ? snlen : 16;
if (pktlen < ski->ivlen)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
for (i = 0; i < ski->ivlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (list_mode)
{
es_fprintf (listfp,
ski->s2k.mode == 1002 ? "\tserial-number: "
: "\tprotect IV: ");
for (i = 0; i < ski->ivlen; i++)
es_fprintf (listfp, " %02x", temp[i]);
es_putc ('\n', listfp);
}
memcpy (ski->iv, temp, ski->ivlen);
}
/* It does not make sense to read it into secure memory.
* If the user is so careless, not to protect his secret key,
* we can assume, that he operates an open system :=(.
* So we put the key into secure memory when we unprotect it. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
/* Better set some dummy stuff here. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
xstrdup ("dummydata"),
10 * 8);
pktlen = 0;
}
else if (ski->is_protected)
{
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Ugly: The length is encrypted too, so we read all stuff
* up to the end of the packet into the first SKEY
* element. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen),
pktlen * 8);
/* Mark that MPI as protected - we need this information for
importing a key. The OPAQUE flag can't be used because
we also store public EdDSA values in opaque MPIs. */
if (pk->pkey[npkey])
gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1);
pktlen = 0;
if (list_mode)
es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey);
}
else
{
/* Not encrypted. */
for (i = npkey; i < nskey; i++)
{
unsigned int n;
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tskey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (pktlen < 2)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->csum = read_16 (inp);
pktlen -= 2;
if (list_mode)
es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum);
}
}
/* Note that KEYID below has been initialized above in list_mode. */
if (list_mode)
es_fprintf (listfp, "\tkeyid: %08lX%08lX\n",
(ulong) keyid[0], (ulong) keyid[1]);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return err;
}
/* Attribute subpackets have the same format as v4 signature
subpackets. This is not part of OpenPGP, but is done in several
versions of PGP nevertheless. */
int
parse_attribute_subpkts (PKT_user_id * uid)
{
size_t n;
int count = 0;
struct user_attribute *attribs = NULL;
const byte *buffer = uid->attrib_data;
int buflen = uid->attrib_len;
byte type;
xfree (uid->attribs);
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 2 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
if (!n)
{
/* Too short to encode the subpacket type. */
if (opt.verbose)
log_info ("attribute subpacket too short\n");
break;
}
attribs = xrealloc (attribs,
(count + 1) * sizeof (struct user_attribute));
memset (&attribs[count], 0, sizeof (struct user_attribute));
type = *buffer;
buffer++;
buflen--;
n--;
attribs[count].type = type;
attribs[count].data = buffer;
attribs[count].len = n;
buffer += n;
buflen -= n;
count++;
}
uid->attribs = attribs;
uid->numattribs = count;
return count;
too_short:
if (opt.verbose)
log_info ("buffer shorter than attribute subpacket\n");
uid->attribs = attribs;
uid->numattribs = count;
return count;
}
static int
parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap the size of a user ID at 2k: a value absurdly large enough
that there is no sane user ID string (which is printable text
as of RFC2440bis) that won't fit in it, but yet small enough to
avoid allocation problems. A large pktlen may not be
allocatable, and a very large pktlen could actually cause our
allocation to wrap around in xmalloc to a small number. */
if (pktlen > MAX_UID_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":user ID packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id + pktlen);
packet->pkt.user_id->len = pktlen;
packet->pkt.user_id->ref = 1;
p = packet->pkt.user_id->name;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
*p = 0;
if (list_mode)
{
int n = packet->pkt.user_id->len;
es_fprintf (listfp, ":user ID packet: \"");
/* fixme: Hey why don't we replace this with es_write_sanitized?? */
for (p = packet->pkt.user_id->name; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
void
make_attribute_uidname (PKT_user_id * uid, size_t max_namelen)
{
log_assert (max_namelen > 70);
if (uid->numattribs <= 0)
sprintf (uid->name, "[bad attribute packet of size %lu]",
uid->attrib_len);
else if (uid->numattribs > 1)
sprintf (uid->name, "[%d attributes of size %lu]",
uid->numattribs, uid->attrib_len);
else
{
/* Only one attribute, so list it as the "user id" */
if (uid->attribs->type == ATTRIB_IMAGE)
{
u32 len;
byte type;
if (parse_image_header (uid->attribs, &type, &len))
sprintf (uid->name, "[%.20s image of size %lu]",
image_type_to_string (type, 1), (ulong) len);
else
sprintf (uid->name, "[invalid image]");
}
else
sprintf (uid->name, "[unknown attribute of size %lu]",
(ulong) uid->attribs->len);
}
uid->len = strlen (uid->name);
}
static int
parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
byte *p;
(void) pkttype;
/* We better cap the size of an attribute packet to make DoS not too
easy. 16MB should be more then enough for one attribute packet
(ie. a photo). */
if (pktlen > MAX_ATTR_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":attribute packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
#define EXTRA_UID_NAME_SPACE 71
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id
+ EXTRA_UID_NAME_SPACE);
packet->pkt.user_id->ref = 1;
packet->pkt.user_id->attrib_data = xmalloc (pktlen? pktlen:1);
packet->pkt.user_id->attrib_len = pktlen;
p = packet->pkt.user_id->attrib_data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
/* Now parse out the individual attribute subpackets. This is
somewhat pointless since there is only one currently defined
attribute type (jpeg), but it is correct by the spec. */
parse_attribute_subpkts (packet->pkt.user_id);
make_attribute_uidname (packet->pkt.user_id, EXTRA_UID_NAME_SPACE);
if (list_mode)
{
es_fprintf (listfp, ":attribute packet: %s\n", packet->pkt.user_id->name);
}
return 0;
}
static int
parse_comment (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap comment packet at a reasonable value to avoid an integer
overflow in the malloc below. Comment packets are actually not
anymore define my OpenPGP and we even stopped to use our
private comment packet. */
if (pktlen > MAX_COMMENT_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":%scomment packet: [too large]\n",
pkttype == PKT_OLD_COMMENT ? "OpenPGP draft " : "");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.comment = xmalloc (sizeof *packet->pkt.comment + pktlen - 1);
packet->pkt.comment->len = pktlen;
p = packet->pkt.comment->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
if (list_mode)
{
int n = packet->pkt.comment->len;
es_fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT ?
"OpenPGP draft " : "");
for (p = packet->pkt.comment->data; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
static void
parse_trust (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * pkt)
{
int c;
(void) pkttype;
pkt->pkt.ring_trust = xmalloc (sizeof *pkt->pkt.ring_trust);
if (pktlen)
{
c = iobuf_get_noeof (inp);
pktlen--;
pkt->pkt.ring_trust->trustval = c;
pkt->pkt.ring_trust->sigcache = 0;
if (!c && pktlen == 1)
{
c = iobuf_get_noeof (inp);
pktlen--;
/* We require that bit 7 of the sigcache is 0 (easier eof
handling). */
if (!(c & 0x80))
pkt->pkt.ring_trust->sigcache = c;
}
if (list_mode)
es_fprintf (listfp, ":trust packet: flag=%02x sigcache=%02x\n",
pkt->pkt.ring_trust->trustval,
pkt->pkt.ring_trust->sigcache);
}
else
{
pkt->pkt.ring_trust->trustval = 0;
pkt->pkt.ring_trust->sigcache = 0;
if (list_mode)
es_fprintf (listfp, ":trust packet: empty\n");
}
iobuf_skip_rest (inp, pktlen, 0);
}
static int
parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
int mode, namelen;
PKT_plaintext *pt;
byte *p;
int c, i;
if (!partial && pktlen < 6)
{
log_error ("packet(%d) too short (%lu)\n", pkttype, (ulong) pktlen);
if (list_mode)
es_fputs (":literal data packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
mode = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
namelen = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
/* Note that namelen will never exceed 255 bytes. */
pt = pkt->pkt.plaintext =
xmalloc (sizeof *pkt->pkt.plaintext + namelen - 1);
pt->new_ctb = new_ctb;
pt->mode = mode;
pt->namelen = namelen;
pt->is_partial = partial;
if (pktlen)
{
for (i = 0; pktlen > 4 && i < namelen; pktlen--, i++)
pt->name[i] = iobuf_get_noeof (inp);
}
else
{
for (i = 0; i < namelen; i++)
if ((c = iobuf_get (inp)) == -1)
break;
else
pt->name[i] = c;
}
pt->timestamp = read_32 (inp);
if (pktlen)
pktlen -= 4;
pt->len = pktlen;
pt->buf = inp;
if (list_mode)
{
es_fprintf (listfp, ":literal data packet:\n"
"\tmode %c (%X), created %lu, name=\"",
mode >= ' ' && mode < 'z' ? mode : '?', mode,
(ulong) pt->timestamp);
for (p = pt->name, i = 0; i < namelen; p++, i++)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\",\n\traw data: ");
if (partial)
es_fprintf (listfp, "unknown length\n");
else
es_fprintf (listfp, "%lu bytes\n", (ulong) pt->len);
}
leave:
return rc;
}
static int
parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
PKT_compressed *zd;
/* PKTLEN is here 0, but data follows (this should be the last
object in a file or the compress algorithm should know the
length). */
(void) pkttype;
(void) pktlen;
zd = pkt->pkt.compressed = xmalloc (sizeof *pkt->pkt.compressed);
zd->algorithm = iobuf_get_noeof (inp);
zd->len = 0; /* not used */
zd->new_ctb = new_ctb;
zd->buf = inp;
if (list_mode)
es_fprintf (listfp, ":compressed packet: algo=%d\n", zd->algorithm);
return 0;
}
static int
parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
PKT_encrypted *ed;
unsigned long orig_pktlen = pktlen;
ed = pkt->pkt.encrypted = xmalloc (sizeof *pkt->pkt.encrypted);
/* ed->len is set below. */
ed->extralen = 0; /* Unknown here; only used in build_packet. */
ed->buf = NULL;
ed->new_ctb = new_ctb;
ed->is_partial = partial;
if (pkttype == PKT_ENCRYPTED_MDC)
{
/* Fixme: add some pktlen sanity checks. */
int version;
version = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
if (version != 1)
{
log_error ("encrypted_mdc packet with unknown version %d\n",
version);
if (list_mode)
es_fputs (":encrypted data packet: [unknown version]\n", listfp);
/*skip_rest(inp, pktlen); should we really do this? */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ed->mdc_method = DIGEST_ALGO_SHA1;
}
else
ed->mdc_method = 0;
/* A basic sanity check. We need at least an 8 byte IV plus the 2
detection bytes. Note that we don't known the algorithm and thus
we may only check against the minimum blocksize. */
if (orig_pktlen && pktlen < 10)
{
/* Actually this is blocksize+2. */
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":encrypted data packet: [too short]\n", listfp);
rc = GPG_ERR_INV_PACKET;
iobuf_skip_rest (inp, pktlen, partial);
goto leave;
}
/* Store the remaining length of the encrypted data (i.e. without
the MDC version number but with the IV etc.). This value is
required during decryption. */
ed->len = pktlen;
if (list_mode)
{
if (orig_pktlen)
es_fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n",
orig_pktlen);
else
es_fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n");
if (ed->mdc_method)
es_fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method);
}
ed->buf = inp;
leave:
return rc;
}
/* Note, that this code is not anymore used in real life because the
MDC checking is now done right after the decryption in
decrypt_data. */
static int
parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
int rc = 0;
PKT_mdc *mdc;
byte *p;
(void) pkttype;
mdc = pkt->pkt.mdc = xmalloc (sizeof *pkt->pkt.mdc);
if (list_mode)
es_fprintf (listfp, ":mdc packet: length=%lu\n", pktlen);
if (!new_ctb || pktlen != 20)
{
log_error ("mdc_packet with invalid encoding\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
p = mdc->hash;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
leave:
return rc;
}
/*
* This packet is internally generated by us (in armor.c) to transfer
* some information to the lower layer. To make sure that this packet
* is really a GPG faked one and not one coming from outside, we
* first check that there is a unique tag in it.
*
* The format of such a control packet is:
* n byte session marker
* 1 byte control type CTRLPKT_xxxxx
* m byte control data
*/
static int
parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial)
{
byte *p;
const byte *sesmark;
size_t sesmarklen;
int i;
(void) pkttype;
if (list_mode)
es_fprintf (listfp, ":packet 63: length %lu ", pktlen);
sesmark = get_session_marker (&sesmarklen);
if (pktlen < sesmarklen + 1) /* 1 is for the control bytes */
goto skipit;
for (i = 0; i < sesmarklen; i++, pktlen--)
{
if (sesmark[i] != iobuf_get_noeof (inp))
goto skipit;
}
if (pktlen > 4096)
goto skipit; /* Definitely too large. We skip it to avoid an
overflow in the malloc. */
if (list_mode)
es_fputs ("- gpg control packet", listfp);
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
+ pktlen - 1);
packet->pkt.gpg_control->control = iobuf_get_noeof (inp);
pktlen--;
packet->pkt.gpg_control->datalen = pktlen;
p = packet->pkt.gpg_control->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
return 0;
skipit:
if (list_mode)
{
int c;
i = 0;
es_fprintf (listfp, "- private (rest length %lu)\n", pktlen);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
}
iobuf_skip_rest (inp, pktlen, 0);
return gpg_error (GPG_ERR_INV_PACKET);
}
/* Create a GPG control packet to be used internally as a placeholder. */
PACKET *
create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen)
{
PACKET *packet;
byte *p;
packet = xmalloc (sizeof *packet);
init_packet (packet);
packet->pkttype = PKT_GPG_CONTROL;
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
+ datalen - 1);
packet->pkt.gpg_control->control = type;
packet->pkt.gpg_control->datalen = datalen;
p = packet->pkt.gpg_control->data;
for (; datalen; datalen--, p++)
*p = *data++;
return packet;
}
diff --git a/g10/passphrase.c b/g10/passphrase.c
index 37abc0f1c..02371fe9f 100644
--- a/g10/passphrase.c
+++ b/g10/passphrase.c
@@ -1,550 +1,550 @@
/* passphrase.c - Get a passphrase
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2006, 2007, 2009, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
-#include "ttyio.h"
+#include "../common/ttyio.h"
#include "keydb.h"
#include "main.h"
-#include "i18n.h"
-#include "status.h"
+#include "../common/i18n.h"
+#include "../common/status.h"
#include "call-agent.h"
#include "../common/shareddefs.h"
static char *fd_passwd = NULL;
static char *next_pw = NULL;
static char *last_pw = NULL;
/* Pack an s2k iteration count into the form specified in 2440. If
we're in between valid values, round up. With value 0 return the
old default. */
unsigned char
encode_s2k_iterations (int iterations)
{
gpg_error_t err;
unsigned char c=0;
unsigned char result;
unsigned int count;
if (!iterations)
{
unsigned long mycnt;
/* Ask the gpg-agent for a useful iteration count. */
err = agent_get_s2k_count (&mycnt);
if (err || mycnt < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which we used up to 2.0.13. */
return 96;
}
else if (mycnt >= 65011712)
return 255; /* Largest possible value. */
else
return encode_s2k_iterations ((int)mycnt);
}
if (iterations <= 1024)
return 0; /* Command line arg compatibility. */
if (iterations >= 65011712)
return 255;
/* Need count to be in the range 16-31 */
for (count=iterations>>6; count>=32; count>>=1)
c++;
result = (c<<4)|(count-16);
if (S2K_DECODE_COUNT(result) < iterations)
result++;
return result;
}
int
have_static_passphrase()
{
return (!!fd_passwd
&& (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK));
}
/* Return a static passphrase. The returned value is only valid as
long as no other passphrase related function is called. NULL may
be returned if no passphrase has been set; better use
have_static_passphrase first. */
const char *
get_static_passphrase (void)
{
return fd_passwd;
}
/****************
* Set the passphrase to be used for the next query and only for the next
* one.
*/
void
set_next_passphrase( const char *s )
{
xfree(next_pw);
next_pw = NULL;
if ( s )
{
next_pw = xmalloc_secure( strlen(s)+1 );
strcpy (next_pw, s );
}
}
/****************
* Get the last passphrase used in passphrase_to_dek.
* Note: This removes the passphrase from this modules and
* the caller must free the result. May return NULL:
*/
char *
get_last_passphrase()
{
char *p = last_pw;
last_pw = NULL;
return p;
}
/* Here's an interesting question: since this passphrase was passed in
on the command line, is there really any point in using secure
memory for it? I'm going with 'yes', since it doesn't hurt, and
might help in some small way (swapping). */
void
set_passphrase_from_string(const char *pass)
{
xfree (fd_passwd);
fd_passwd = xmalloc_secure(strlen(pass)+1);
strcpy (fd_passwd, pass);
}
void
read_passphrase_from_fd( int fd )
{
int i, len;
char *pw;
if (! gnupg_fd_valid (fd))
log_fatal ("passphrase-fd is invalid: %s\n", strerror (errno));
if ( !opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK)
{ /* Not used but we have to do a dummy read, so that it won't end
up at the begin of the message if the quite usual trick to
prepend the passphtrase to the message is used. */
char buf[1];
while (!(read (fd, buf, 1) != 1 || *buf == '\n' ))
;
*buf = 0;
return;
}
for (pw = NULL, i = len = 100; ; i++ )
{
if (i >= len-1 )
{
char *pw2 = pw;
len += 100;
pw = xmalloc_secure( len );
if( pw2 )
{
memcpy(pw, pw2, i );
xfree (pw2);
}
else
i=0;
}
if (read( fd, pw+i, 1) != 1 || pw[i] == '\n' )
break;
}
pw[i] = 0;
if (!opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK)
tty_printf("\b\b\b \n" );
xfree ( fd_passwd );
fd_passwd = pw;
}
/*
* Ask the GPG Agent for the passphrase.
* If NOCACHE is set the symmetric passpharse caching will not be used.
*
* Note that TRYAGAIN_TEXT must not be translated. If CANCELED is not
* NULL, the function does set it to 1 if the user canceled the
* operation. If CACHEID is not NULL, it will be used as the cacheID
* for the gpg-agent; if is NULL and a key fingerprint can be
* computed, this will be used as the cacheid.
*/
static char *
passphrase_get (int nocache, const char *cacheid, int repeat,
const char *tryagain_text, int *canceled)
{
int rc;
char *pw = NULL;
char *orig_codeset;
const char *my_cacheid;
if (canceled)
*canceled = 0;
orig_codeset = i18n_switchto_utf8 ();
if (!nocache && cacheid)
my_cacheid = cacheid;
else
my_cacheid = NULL;
if (tryagain_text)
tryagain_text = _(tryagain_text);
rc = agent_get_passphrase (my_cacheid, tryagain_text, NULL,
_("Enter passphrase\n"),
repeat, nocache, &pw);
i18n_switchback (orig_codeset);
if (!rc)
;
else if (gpg_err_code (rc) == GPG_ERR_CANCELED
|| gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
{
log_info (_("cancelled by user\n") );
if (canceled)
*canceled = 1;
}
else
{
log_error (_("problem with the agent: %s\n"), gpg_strerror (rc));
/* Due to limitations in the API of the upper layers they
consider an error as no passphrase entered. This works in
most cases but not during key creation where this should
definitely not happen and let it continue without requiring a
passphrase. Given that now all the upper layers handle a
cancel correctly, we simply set the cancel flag now for all
errors from the agent. */
if (canceled)
*canceled = 1;
write_status_errcode ("get_passphrase", rc);
}
if (rc)
{
xfree (pw);
pw = NULL;
}
return pw;
}
/*
* Clear the cached passphrase with CACHEID.
*/
void
passphrase_clear_cache (const char *cacheid)
{
int rc;
rc = agent_clear_passphrase (cacheid);
if (rc)
log_error (_("problem with the agent: %s\n"), gpg_strerror (rc));
}
/* Return a new DEK object using the string-to-key specifier S2K.
* Returns NULL if the user canceled the passphrase entry and if
* CANCELED is not NULL, sets it to true.
*
* If CREATE is true a new passphrase sll be created. If NOCACHE is
* true the symmetric key caching will not be used. */
DEK *
passphrase_to_dek (int cipher_algo, STRING2KEY *s2k,
int create, int nocache,
const char *tryagain_text, int *canceled)
{
char *pw = NULL;
DEK *dek;
STRING2KEY help_s2k;
int dummy_canceled;
char s2k_cacheidbuf[1+16+1];
char *s2k_cacheid = NULL;
if (!canceled)
canceled = &dummy_canceled;
*canceled = 0;
if ( !s2k )
{
log_assert (create && !nocache);
/* This is used for the old rfc1991 mode
* Note: This must match the code in encode.c with opt.rfc1991 set */
memset (&help_s2k, 0, sizeof (help_s2k));
s2k = &help_s2k;
s2k->hash_algo = S2K_DIGEST_ALGO;
}
/* Create a new salt or what else to be filled into the s2k for a
new key. */
if (create && (s2k->mode == 1 || s2k->mode == 3))
{
gcry_randomize (s2k->salt, 8, GCRY_STRONG_RANDOM);
if ( s2k->mode == 3 )
{
/* We delay the encoding until it is really needed. This is
if we are going to dynamically calibrate it, we need to
call out to gpg-agent and that should not be done during
option processing in main(). */
if (!opt.s2k_count)
opt.s2k_count = encode_s2k_iterations (0);
s2k->count = opt.s2k_count;
}
}
/* If we do not have a passphrase available in NEXT_PW and status
information are request, we print them now. */
if ( !next_pw && is_status_enabled() )
{
char buf[50];
snprintf (buf, sizeof buf, "%d %d %d",
cipher_algo, s2k->mode, s2k->hash_algo );
write_status_text ( STATUS_NEED_PASSPHRASE_SYM, buf );
}
if ( next_pw )
{
/* Simply return the passphrase we already have in NEXT_PW. */
pw = next_pw;
next_pw = NULL;
}
else if ( have_static_passphrase () )
{
/* Return the passphrase we have stored in FD_PASSWD. */
pw = xmalloc_secure ( strlen(fd_passwd)+1 );
strcpy ( pw, fd_passwd );
}
else
{
if (!nocache && (s2k->mode == 1 || s2k->mode == 3))
{
memset (s2k_cacheidbuf, 0, sizeof s2k_cacheidbuf);
*s2k_cacheidbuf = 'S';
bin2hex (s2k->salt, 8, s2k_cacheidbuf + 1);
s2k_cacheid = s2k_cacheidbuf;
}
if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
char buf[32];
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
}
/* Divert to the gpg-agent. */
pw = passphrase_get (create && nocache, s2k_cacheid,
create? opt.passphrase_repeat : 0,
tryagain_text, canceled);
if (*canceled)
{
xfree (pw);
write_status( STATUS_MISSING_PASSPHRASE );
return NULL;
}
}
if ( !pw || !*pw )
write_status( STATUS_MISSING_PASSPHRASE );
/* Hash the passphrase and store it in a newly allocated DEK object.
Keep a copy of the passphrase in LAST_PW for use by
get_last_passphrase(). */
dek = xmalloc_secure_clear ( sizeof *dek );
dek->algo = cipher_algo;
if ( (!pw || !*pw) && create)
dek->keylen = 0;
else
{
gpg_error_t err;
dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo);
if (!(dek->keylen > 0 && dek->keylen <= DIM(dek->key)))
BUG ();
err = gcry_kdf_derive (pw, strlen (pw),
s2k->mode == 3? GCRY_KDF_ITERSALTED_S2K :
s2k->mode == 1? GCRY_KDF_SALTED_S2K :
/* */ GCRY_KDF_SIMPLE_S2K,
s2k->hash_algo, s2k->salt, 8,
S2K_DECODE_COUNT(s2k->count),
dek->keylen, dek->key);
if (err)
{
log_error ("gcry_kdf_derive failed: %s", gpg_strerror (err));
xfree (pw);
xfree (dek);
write_status( STATUS_MISSING_PASSPHRASE );
return NULL;
}
}
if (s2k_cacheid)
memcpy (dek->s2k_cacheid, s2k_cacheid, sizeof dek->s2k_cacheid);
xfree(last_pw);
last_pw = pw;
return dek;
}
/* Emit the USERID_HINT and the NEED_PASSPHRASE status messages.
MAINKEYID may be NULL. */
void
emit_status_need_passphrase (u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
char buf[50];
char *us;
us = get_long_user_id_string (keyid);
write_status_text (STATUS_USERID_HINT, us);
xfree (us);
snprintf (buf, sizeof buf, "%08lX%08lX %08lX%08lX %d 0",
(ulong)keyid[0],
(ulong)keyid[1],
(ulong)(mainkeyid? mainkeyid[0]:keyid[0]),
(ulong)(mainkeyid? mainkeyid[1]:keyid[1]),
pubkey_algo);
write_status_text (STATUS_NEED_PASSPHRASE, buf);
}
/* Return an allocated utf-8 string describing the key PK. If ESCAPED
is true spaces and control characters are percent or plus escaped.
MODE describes the use of the key description; use one of the
FORMAT_KEYDESC_ macros. */
char *
gpg_format_keydesc (PKT_public_key *pk, int mode, int escaped)
{
char *uid;
size_t uidlen;
const char *algo_name;
const char *timestr;
char *orig_codeset;
char *maink;
char *desc;
const char *prompt;
const char *trailer = "";
int is_subkey;
is_subkey = (pk->main_keyid[0] && pk->main_keyid[1]
&& pk->keyid[0] != pk->main_keyid[0]
&& pk->keyid[1] != pk->main_keyid[1]);
algo_name = openpgp_pk_algo_name (pk->pubkey_algo);
timestr = strtimestamp (pk->timestamp);
uid = get_user_id (is_subkey? pk->main_keyid:pk->keyid, &uidlen);
orig_codeset = i18n_switchto_utf8 ();
if (is_subkey)
maink = xtryasprintf (_(" (main key ID %s)"), keystr (pk->main_keyid));
else
maink = NULL;
switch (mode)
{
case FORMAT_KEYDESC_NORMAL:
prompt = _("Please enter the passphrase to unlock the"
" OpenPGP secret key:");
break;
case FORMAT_KEYDESC_IMPORT:
prompt = _("Please enter the passphrase to import the"
" OpenPGP secret key:");
break;
case FORMAT_KEYDESC_EXPORT:
if (is_subkey)
prompt = _("Please enter the passphrase to export the"
" OpenPGP secret subkey:");
else
prompt = _("Please enter the passphrase to export the"
" OpenPGP secret key:");
break;
case FORMAT_KEYDESC_DELKEY:
if (is_subkey)
prompt = _("Do you really want to permanently delete the"
" OpenPGP secret subkey key:");
else
prompt = _("Do you really want to permanently delete the"
" OpenPGP secret key:");
trailer = "?";
break;
default:
prompt = "?";
break;
}
desc = xtryasprintf (_("%s\n"
"\"%.*s\"\n"
"%u-bit %s key, ID %s,\n"
"created %s%s.\n%s"),
prompt,
(int)uidlen, uid,
nbits_from_pk (pk), algo_name,
keystr (pk->keyid), timestr,
maink?maink:"", trailer);
xfree (maink);
xfree (uid);
i18n_switchback (orig_codeset);
if (escaped)
{
char *tmp = percent_plus_escape (desc);
xfree (desc);
desc = tmp;
}
return desc;
}
diff --git a/g10/photoid.c b/g10/photoid.c
index 8b193b3d4..bcea64fbf 100644
--- a/g10/photoid.c
+++ b/g10/photoid.c
@@ -1,382 +1,382 @@
/* photoid.c - photo ID handling code
* Copyright (C) 2001, 2002, 2005, 2006, 2008, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
# ifndef VER_PLATFORM_WIN32_WINDOWS
# define VER_PLATFORM_WIN32_WINDOWS 1
# endif
#endif
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "exec.h"
#include "keydb.h"
-#include "i18n.h"
-#include "iobuf.h"
+#include "../common/i18n.h"
+#include "../common/iobuf.h"
#include "options.h"
#include "main.h"
#include "photoid.h"
-#include "ttyio.h"
+#include "../common/ttyio.h"
#include "trustdb.h"
/* Generate a new photo id packet, or return NULL if canceled.
FIXME: Should we add a duplicates check similar to generate_user_id? */
PKT_user_id *
generate_photo_id (ctrl_t ctrl, PKT_public_key *pk,const char *photo_name)
{
PKT_user_id *uid;
int error=1,i;
unsigned int len;
char *filename;
byte *photo=NULL;
byte header[16];
IOBUF file;
int overflow;
header[0]=0x10; /* little side of photo header length */
header[1]=0; /* big side of photo header length */
header[2]=1; /* 1 == version of photo header */
header[3]=1; /* 1 == JPEG */
for(i=4;i<16;i++) /* The reserved bytes */
header[i]=0;
#define EXTRA_UID_NAME_SPACE 71
uid=xmalloc_clear(sizeof(*uid)+71);
if(photo_name && *photo_name)
filename=make_filename(photo_name,(void *)NULL);
else
{
tty_printf(_("\nPick an image to use for your photo ID."
" The image must be a JPEG file.\n"
"Remember that the image is stored within your public key."
" If you use a\n"
"very large picture, your key will become very large"
" as well!\n"
"Keeping the image close to 240x288 is a good size"
" to use.\n"));
filename=NULL;
}
while(photo==NULL)
{
if(filename==NULL)
{
char *tempname;
tty_printf("\n");
tty_enable_completion(NULL);
tempname=cpr_get("photoid.jpeg.add",
_("Enter JPEG filename for photo ID: "));
tty_disable_completion();
filename=make_filename(tempname,(void *)NULL);
xfree(tempname);
if(strlen(filename)==0)
goto scram;
}
file=iobuf_open(filename);
if (file && is_secured_file (iobuf_get_fd (file)))
{
iobuf_close (file);
file = NULL;
gpg_err_set_errno (EPERM);
}
if(!file)
{
log_error(_("unable to open JPEG file '%s': %s\n"),
filename,strerror(errno));
xfree(filename);
filename=NULL;
continue;
}
len=iobuf_get_filelength(file, &overflow);
if(len>6144 || overflow)
{
tty_printf( _("This JPEG is really large (%d bytes) !\n"),len);
if(!cpr_get_answer_is_yes("photoid.jpeg.size",
_("Are you sure you want to use it? (y/N) ")))
{
iobuf_close(file);
xfree(filename);
filename=NULL;
continue;
}
}
photo=xmalloc(len);
iobuf_read(file,photo,len);
iobuf_close(file);
/* Is it a JPEG? */
if(photo[0]!=0xFF || photo[1]!=0xD8)
{
log_error(_("'%s' is not a JPEG file\n"),filename);
xfree(photo);
photo=NULL;
xfree(filename);
filename=NULL;
continue;
}
/* Build the packet */
build_attribute_subpkt(uid,1,photo,len,header,16);
parse_attribute_subpkts(uid);
make_attribute_uidname(uid, EXTRA_UID_NAME_SPACE);
/* Showing the photo is not safe when noninteractive since the
"user" may not be able to dismiss a viewer window! */
if(opt.command_fd==-1)
{
show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid);
switch(cpr_get_answer_yes_no_quit("photoid.jpeg.okay",
_("Is this photo correct (y/N/q)? ")))
{
case -1:
goto scram;
case 0:
free_attributes(uid);
xfree(photo);
photo=NULL;
xfree(filename);
filename=NULL;
continue;
}
}
}
error=0;
uid->ref=1;
scram:
xfree(filename);
xfree(photo);
if(error)
{
free_attributes(uid);
xfree(uid);
return NULL;
}
return uid;
}
/* Returns 0 for error, 1 for valid */
int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len)
{
u16 headerlen;
if(attr->len<3)
return 0;
/* For historical reasons (i.e. "oops!"), the header length is
little endian. */
headerlen=(attr->data[1]<<8) | attr->data[0];
if(headerlen>attr->len)
return 0;
if(type && attr->len>=4)
{
if(attr->data[2]==1) /* header version 1 */
*type=attr->data[3];
else
*type=0;
}
*len=attr->len-headerlen;
if(*len==0)
return 0;
return 1;
}
/* style==0 for extension, 1 for name, 2 for MIME type. Remember that
the "name" style string could be used in a user ID name field, so
make sure it is not too big (see parse-packet.c:parse_attribute).
Extensions should be 3 characters long for the best cross-platform
compatibility. */
char *image_type_to_string(byte type,int style)
{
char *string;
switch(type)
{
case 1: /* jpeg */
if(style==0)
string="jpg";
else if(style==1)
string="jpeg";
else
string="image/jpeg";
break;
default:
if(style==0)
string="bin";
else if(style==1)
string="unknown";
else
string="image/x-unknown";
break;
}
return string;
}
#if !defined(FIXED_PHOTO_VIEWER) && !defined(DISABLE_PHOTO_VIEWER)
static const char *get_default_photo_command(void)
{
#if defined(_WIN32)
OSVERSIONINFO osvi;
memset(&osvi,0,sizeof(osvi));
osvi.dwOSVersionInfoSize=sizeof(osvi);
GetVersionEx(&osvi);
if(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
return "start /w %i";
else
return "cmd /c start /w %i";
#elif defined(__APPLE__)
/* OS X. This really needs more than just __APPLE__. */
return "open %I";
#elif defined(__riscos__)
return "Filer_Run %I";
#else
return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin";
#endif
}
#endif
void
show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count,
PKT_public_key *pk, PKT_user_id *uid)
{
#ifdef DISABLE_PHOTO_VIEWER
(void)attrs;
(void)count;
(void)pk;
(void)uid;
#else /*!DISABLE_PHOTO_VIEWER*/
int i;
struct expando_args args;
u32 len;
u32 kid[2]={0,0};
memset (&args, 0, sizeof(args));
args.pk = pk;
args.validity_info = get_validity_info (ctrl, NULL, pk, uid);
args.validity_string = get_validity_string (ctrl, pk, uid);
namehash_from_uid (uid);
args.namehash = uid->namehash;
if (pk)
keyid_from_pk (pk, kid);
for(i=0;i<count;i++)
if(attrs[i].type==ATTRIB_IMAGE &&
parse_image_header(&attrs[i],&args.imagetype,&len))
{
char *command,*name;
struct exec_info *spawn;
int offset=attrs[i].len-len;
#ifdef FIXED_PHOTO_VIEWER
opt.photo_viewer=FIXED_PHOTO_VIEWER;
#else
if(!opt.photo_viewer)
opt.photo_viewer=get_default_photo_command();
#endif
/* make command grow */
command=pct_expando(opt.photo_viewer,&args);
if(!command)
goto fail;
name=xmalloc(16+strlen(EXTSEP_S)+
strlen(image_type_to_string(args.imagetype,0))+1);
/* Make the filename. Notice we are not using the image
encoding type for more than cosmetics. Most external image
viewers can handle a multitude of types, and even if one
cannot understand a particular type, we have no way to know
which. The spec permits this, by the way. -dms */
#ifdef USE_ONLY_8DOT3
sprintf(name,"%08lX" EXTSEP_S "%s",(ulong)kid[1],
image_type_to_string(args.imagetype,0));
#else
sprintf(name,"%08lX%08lX" EXTSEP_S "%s",(ulong)kid[0],(ulong)kid[1],
image_type_to_string(args.imagetype,0));
#endif
if(exec_write(&spawn,NULL,command,name,1,1)!=0)
{
xfree(name);
goto fail;
}
#ifdef __riscos__
riscos_set_filetype_by_mimetype(spawn->tempfile_in,
image_type_to_string(args.imagetype,2));
#endif
xfree(name);
fwrite(&attrs[i].data[offset],attrs[i].len-offset,1,spawn->tochild);
if(exec_read(spawn)!=0)
{
exec_finish(spawn);
goto fail;
}
if(exec_finish(spawn)!=0)
goto fail;
}
return;
fail:
log_error(_("unable to display photo ID!\n"));
#endif /*!DISABLE_PHOTO_VIEWER*/
}
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 012f751cf..698794ebf 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -1,1694 +1,1694 @@
/* pkclist.c - create a list of public keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
-#include "ttyio.h"
-#include "status.h"
+#include "../common/ttyio.h"
+#include "../common/status.h"
#include "photoid.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "tofu.h"
#define CONTROL_D ('D' - 'A' + 1)
static void
send_status_inv_recp (int reason, const char *name)
{
char buf[40];
snprintf (buf, sizeof buf, "%d ", reason);
write_status_text_and_buffer (STATUS_INV_RECP, buf,
name, strlen (name),
-1);
}
/****************
* Show the revocation reason as it is stored with the given signature
*/
static void
do_show_revocation_reason( PKT_signature *sig )
{
size_t n, nn;
const byte *p, *pp;
int seq = 0;
const char *text;
while( (p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REVOC_REASON,
&n, &seq, NULL )) ) {
if( !n )
continue; /* invalid - just skip it */
if( *p == 0 )
text = _("No reason specified");
else if( *p == 0x01 )
text = _("Key is superseded");
else if( *p == 0x02 )
text = _("Key has been compromised");
else if( *p == 0x03 )
text = _("Key is no longer used");
else if( *p == 0x20 )
text = _("User ID is no longer valid");
else
text = NULL;
log_info ( _("reason for revocation: "));
if (text)
log_printf ("%s\n", text);
else
log_printf ("code=%02x\n", *p );
n--; p++;
pp = NULL;
do {
/* We don't want any empty lines, so skip them */
while( n && *p == '\n' ) {
p++;
n--;
}
if( n ) {
pp = memchr( p, '\n', n );
nn = pp? pp - p : n;
log_info ( _("revocation comment: ") );
es_write_sanitized (log_get_stream(), p, nn, NULL, NULL);
log_printf ("\n");
p += nn; n -= nn;
}
} while( pp );
}
}
/* Mode 0: try and find the revocation based on the pk (i.e. check
subkeys, etc.) Mode 1: use only the revocation on the main pk */
void
show_revocation_reason( PKT_public_key *pk, int mode )
{
/* Hmmm, this is not so easy because we have to duplicate the code
* used in the trustbd to calculate the keyflags. We need to find
* a clean way to check revocation certificates on keys and
* signatures. And there should be no duplicate code. Because we
* enter this function only when the trustdb told us that we have
* a revoked key, we could simply look for a revocation cert and
* display this one, when there is only one. Let's try to do this
* until we have a better solution. */
KBNODE node, keyblock = NULL;
byte fingerprint[MAX_FINGERPRINT_LEN];
size_t fingerlen;
int rc;
/* get the keyblock */
fingerprint_from_pk( pk, fingerprint, &fingerlen );
rc = get_pubkey_byfprint(NULL, &keyblock, fingerprint, fingerlen);
if( rc ) { /* that should never happen */
log_debug( "failed to get the keyblock\n");
return;
}
for( node=keyblock; node; node = node->next ) {
if( (mode && node->pkt->pkttype == PKT_PUBLIC_KEY) ||
( ( node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
&& !cmp_public_keys( node->pkt->pkt.public_key, pk ) ) )
break;
}
if( !node ) {
log_debug("Oops, PK not in keyblock\n");
release_kbnode( keyblock );
return;
}
/* now find the revocation certificate */
for( node = node->next; node ; node = node->next ) {
if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
break;
if( node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class == 0x20
|| node->pkt->pkt.signature->sig_class == 0x28 ) ) {
/* FIXME: we should check the signature here */
do_show_revocation_reason ( node->pkt->pkt.signature );
break;
}
}
/* We didn't find it, so check if the whole key is revoked */
if(!node && !mode)
show_revocation_reason(pk,1);
release_kbnode( keyblock );
}
/****************
* mode: 0 = standard
* 1 = Without key info and additional menu option 'm'
* this does also add an option to set the key to ultimately trusted.
* Returns:
* -2 = nothing changed - caller should show some additional info
* -1 = quit operation
* 0 = nothing changed
* 1 = new ownertrust now in new_trust
*/
#ifndef NO_TRUST_MODELS
static int
do_edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode,
unsigned *new_trust, int defer_help )
{
char *p;
u32 keyid[2];
int changed=0;
int quit=0;
int show=0;
int min_num;
int did_help=defer_help;
unsigned int minimum = tdb_get_min_ownertrust (pk, 0);
switch(minimum)
{
default:
case TRUST_UNDEFINED: min_num=1; break;
case TRUST_NEVER: min_num=2; break;
case TRUST_MARGINAL: min_num=3; break;
case TRUST_FULLY: min_num=4; break;
}
keyid_from_pk (pk, keyid);
for(;;) {
/* A string with valid answers.
TRANSLATORS: These are the allowed answers in lower and
uppercase. Below you will find the matching strings which
should be translated accordingly and the letter changed to
match the one in the answer string.
i = please show me more information
m = back to the main menu
s = skip this key
q = quit
*/
const char *ans = _("iImMqQsS");
if( !did_help )
{
if( !mode )
{
KBNODE keyblock, un;
tty_printf (_("No trust value assigned to:\n"));
print_key_line (NULL, pk, 0);
p = get_user_id_native(keyid);
tty_printf (_(" \"%s\"\n"),p);
xfree (p);
keyblock = get_pubkeyblock (keyid);
if (!keyblock)
BUG ();
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype != PKT_USER_ID )
continue;
if (un->pkt->pkt.user_id->is_revoked )
continue;
if (un->pkt->pkt.user_id->is_expired )
continue;
/* Only skip textual primaries */
if (un->pkt->pkt.user_id->is_primary
&& !un->pkt->pkt.user_id->attrib_data )
continue;
if((opt.verify_options&VERIFY_SHOW_PHOTOS)
&& un->pkt->pkt.user_id->attrib_data)
show_photos (ctrl,
un->pkt->pkt.user_id->attribs,
un->pkt->pkt.user_id->numattribs, pk,
un->pkt->pkt.user_id);
p=utf8_to_native(un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len,0);
tty_printf(_(" aka \"%s\"\n"),p);
}
print_fingerprint (NULL, pk, 2);
tty_printf("\n");
release_kbnode (keyblock);
}
if(opt.trust_model==TM_DIRECT)
{
tty_printf(_("How much do you trust that this key actually "
"belongs to the named user?\n"));
tty_printf("\n");
}
else
{
/* This string also used in keyedit.c:trustsig_prompt */
tty_printf(_("Please decide how far you trust this user to"
" correctly verify other users' keys\n"
"(by looking at passports, checking fingerprints from"
" different sources, etc.)\n"));
tty_printf("\n");
}
if(min_num<=1)
tty_printf (_(" %d = I don't know or won't say\n"), 1);
if(min_num<=2)
tty_printf (_(" %d = I do NOT trust\n"), 2);
if(min_num<=3)
tty_printf (_(" %d = I trust marginally\n"), 3);
if(min_num<=4)
tty_printf (_(" %d = I trust fully\n"), 4);
if (mode)
tty_printf (_(" %d = I trust ultimately\n"), 5);
#if 0
/* not yet implemented */
tty_printf (" i = please show me more information\n");
#endif
if( mode )
tty_printf(_(" m = back to the main menu\n"));
else
{
tty_printf(_(" s = skip this key\n"));
tty_printf(_(" q = quit\n"));
}
tty_printf("\n");
if(minimum)
tty_printf(_("The minimum trust level for this key is: %s\n\n"),
trust_value_to_string(minimum));
did_help = 1;
}
if( strlen(ans) != 8 )
BUG();
p = cpr_get("edit_ownertrust.value",_("Your decision? "));
trim_spaces(p);
cpr_kill_prompt();
if( !*p )
did_help = 0;
else if( *p && p[1] )
;
else if( !p[1] && ((*p >= '0'+min_num) && *p <= (mode?'5':'4')) )
{
unsigned int trust;
switch( *p )
{
case '1': trust = TRUST_UNDEFINED; break;
case '2': trust = TRUST_NEVER ; break;
case '3': trust = TRUST_MARGINAL ; break;
case '4': trust = TRUST_FULLY ; break;
case '5': trust = TRUST_ULTIMATE ; break;
default: BUG();
}
if (trust == TRUST_ULTIMATE
&& !cpr_get_answer_is_yes ("edit_ownertrust.set_ultimate.okay",
_("Do you really want to set this key"
" to ultimate trust? (y/N) ")))
; /* no */
else
{
*new_trust = trust;
changed = 1;
break;
}
}
#if 0
/* not yet implemented */
else if( *p == ans[0] || *p == ans[1] )
{
tty_printf(_("Certificates leading to an ultimately trusted key:\n"));
show = 1;
break;
}
#endif
else if( mode && (*p == ans[2] || *p == ans[3] || *p == CONTROL_D ) )
{
break ; /* back to the menu */
}
else if( !mode && (*p == ans[6] || *p == ans[7] ) )
{
break; /* skip */
}
else if( !mode && (*p == ans[4] || *p == ans[5] ) )
{
quit = 1;
break ; /* back to the menu */
}
xfree(p); p = NULL;
}
xfree(p);
return show? -2: quit? -1 : changed;
}
#endif /*!NO_TRUST_MODELS*/
/*
* Display a menu to change the ownertrust of the key PK (which should
* be a primary key).
* For mode values see do_edit_ownertrust ()
*/
#ifndef NO_TRUST_MODELS
int
edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode )
{
unsigned int trust = 0;
int no_help = 0;
for(;;)
{
switch ( do_edit_ownertrust (ctrl, pk, mode, &trust, no_help ) )
{
case -1: /* quit */
return -1;
case -2: /* show info */
no_help = 1;
break;
case 1: /* trust value set */
trust &= ~TRUST_FLAG_DISABLED;
trust |= get_ownertrust (pk) & TRUST_FLAG_DISABLED;
update_ownertrust (pk, trust );
return 1;
default:
return 0;
}
}
}
#endif /*!NO_TRUST_MODELS*/
/****************
* Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL
* Returns: true if we trust.
*/
static int
do_we_trust( PKT_public_key *pk, unsigned int trustlevel )
{
/* We should not be able to get here with a revoked or expired
key */
if(trustlevel & TRUST_FLAG_REVOKED
|| trustlevel & TRUST_FLAG_SUB_REVOKED
|| (trustlevel & TRUST_MASK) == TRUST_EXPIRED)
BUG();
if( opt.trust_model==TM_ALWAYS )
{
if( opt.verbose )
log_info("No trust check due to '--trust-model always' option\n");
return 1;
}
switch(trustlevel & TRUST_MASK)
{
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
log_info(_("%s: There is no assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 0; /* no */
case TRUST_MARGINAL:
log_info(_("%s: There is limited assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 1; /* yes */
case TRUST_FULLY:
if( opt.verbose )
log_info(_("This key probably belongs to the named user\n"));
return 1; /* yes */
case TRUST_ULTIMATE:
if( opt.verbose )
log_info(_("This key belongs to us\n"));
return 1; /* yes */
case TRUST_NEVER:
/* This can be returned by TOFU, which can return negative
assertions. */
log_info(_("%s: This key is bad! It has been marked as untrusted!\n"),
keystr_from_pk(pk));
return 0; /* no */
}
return 1; /*NOTREACHED*/
}
/****************
* wrapper around do_we_trust, so we can ask whether to use the
* key anyway.
*/
static int
do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel )
{
int rc;
rc = do_we_trust( pk, trustlevel );
if( !opt.batch && !rc )
{
print_pubkey_info(NULL,pk);
print_fingerprint (NULL, pk, 2);
tty_printf("\n");
if ((trustlevel & TRUST_MASK) == TRUST_NEVER)
tty_printf(
_("This key is bad! It has been marked as untrusted! If you\n"
"*really* know what you are doing, you may answer the next\n"
"question with yes.\n"));
else
tty_printf(
_("It is NOT certain that the key belongs to the person named\n"
"in the user ID. If you *really* know what you are doing,\n"
"you may answer the next question with yes.\n"));
tty_printf("\n");
if (is_status_enabled ())
{
u32 kid[2];
char *hint_str;
keyid_from_pk (pk, kid);
hint_str = get_long_user_id_string ( kid );
write_status_text ( STATUS_USERID_HINT, hint_str );
xfree (hint_str);
}
if( cpr_get_answer_is_yes("untrusted_key.override",
_("Use this key anyway? (y/N) ")) )
rc = 1;
/* Hmmm: Should we set a flag to tell the user about
* his decision the next time he encrypts for this recipient?
*/
}
return rc;
}
/* Write a TRUST_foo status line inclduing the validation model. */
static void
write_trust_status (int statuscode, int trustlevel)
{
#ifdef NO_TRUST_MODELS
write_status (statuscode);
#else /* NO_TRUST_MODELS */
int tm;
/* For the combined tofu+pgp method, we return the trust model which
* was responsible for the trustlevel. */
if (opt.trust_model == TM_TOFU_PGP)
tm = (trustlevel & TRUST_FLAG_TOFU_BASED)? TM_TOFU : TM_PGP;
else
tm = opt.trust_model;
write_status_strings (statuscode, "0 ", trust_model_string (tm), NULL);
#endif /* NO_TRUST_MODELS */
}
/****************
* Check whether we can trust this signature.
* Returns an error code if we should not trust this signature.
*/
int
check_signatures_trust (ctrl_t ctrl, PKT_signature *sig)
{
PKT_public_key *pk = xmalloc_clear( sizeof *pk );
unsigned int trustlevel = TRUST_UNKNOWN;
int rc=0;
rc = get_pubkey( pk, sig->keyid );
if (rc)
{ /* this should not happen */
log_error("Ooops; the key vanished - can't check the trust\n");
rc = GPG_ERR_NO_PUBKEY;
goto leave;
}
if ( opt.trust_model==TM_ALWAYS )
{
if( !opt.quiet )
log_info(_("WARNING: Using untrusted key!\n"));
if (opt.with_fingerprint)
print_fingerprint (NULL, pk, 1);
goto leave;
}
if(pk->flags.maybe_revoked && !pk->flags.revoked)
log_info(_("WARNING: this key might be revoked (revocation key"
" not present)\n"));
trustlevel = get_validity (ctrl, NULL, pk, NULL, sig, 1);
if ( (trustlevel & TRUST_FLAG_REVOKED) )
{
write_status( STATUS_KEYREVOKED );
if(pk->flags.revoked == 2)
log_info(_("WARNING: This key has been revoked by its"
" designated revoker!\n"));
else
log_info(_("WARNING: This key has been revoked by its owner!\n"));
log_info(_(" This could mean that the signature is forged.\n"));
show_revocation_reason( pk, 0 );
}
else if ((trustlevel & TRUST_FLAG_SUB_REVOKED) )
{
write_status( STATUS_KEYREVOKED );
log_info(_("WARNING: This subkey has been revoked by its owner!\n"));
show_revocation_reason( pk, 0 );
}
if ((trustlevel & TRUST_FLAG_DISABLED))
log_info (_("Note: This key has been disabled.\n"));
/* If we have PKA information adjust the trustlevel. */
if (sig->pka_info && sig->pka_info->valid)
{
unsigned char fpr[MAX_FINGERPRINT_LEN];
PKT_public_key *primary_pk;
size_t fprlen;
int okay;
primary_pk = xmalloc_clear (sizeof *primary_pk);
get_pubkey (primary_pk, pk->main_keyid);
fingerprint_from_pk (primary_pk, fpr, &fprlen);
free_public_key (primary_pk);
if ( fprlen == 20 && !memcmp (sig->pka_info->fpr, fpr, 20) )
{
okay = 1;
write_status_text (STATUS_PKA_TRUST_GOOD, sig->pka_info->email);
log_info (_("Note: Verified signer's address is '%s'\n"),
sig->pka_info->email);
}
else
{
okay = 0;
write_status_text (STATUS_PKA_TRUST_BAD, sig->pka_info->email);
log_info (_("Note: Signer's address '%s' "
"does not match DNS entry\n"), sig->pka_info->email);
}
switch ( (trustlevel & TRUST_MASK) )
{
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
case TRUST_MARGINAL:
if (okay && opt.verify_options&VERIFY_PKA_TRUST_INCREASE)
{
trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_FULLY);
log_info (_("trustlevel adjusted to FULL"
" due to valid PKA info\n"));
}
/* (fall through) */
case TRUST_FULLY:
if (!okay)
{
trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_NEVER);
log_info (_("trustlevel adjusted to NEVER"
" due to bad PKA info\n"));
}
break;
}
}
/* Now let the user know what up with the trustlevel. */
switch ( (trustlevel & TRUST_MASK) )
{
case TRUST_EXPIRED:
log_info(_("Note: This key has expired!\n"));
print_fingerprint (NULL, pk, 1);
break;
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
write_trust_status (STATUS_TRUST_UNDEFINED, trustlevel);
log_info(_("WARNING: This key is not certified with"
" a trusted signature!\n"));
log_info(_(" There is no indication that the "
"signature belongs to the owner.\n" ));
print_fingerprint (NULL, pk, 1);
break;
case TRUST_NEVER:
/* This level can be returned by TOFU, which supports negative
* assertions. */
write_trust_status (STATUS_TRUST_NEVER, trustlevel);
log_info(_("WARNING: We do NOT trust this key!\n"));
log_info(_(" The signature is probably a FORGERY.\n"));
if (opt.with_fingerprint)
print_fingerprint (NULL, pk, 1);
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
break;
case TRUST_MARGINAL:
write_trust_status (STATUS_TRUST_MARGINAL, trustlevel);
log_info(_("WARNING: This key is not certified with"
" sufficiently trusted signatures!\n"));
log_info(_(" It is not certain that the"
" signature belongs to the owner.\n" ));
print_fingerprint (NULL, pk, 1);
break;
case TRUST_FULLY:
write_trust_status (STATUS_TRUST_FULLY, trustlevel);
if (opt.with_fingerprint)
print_fingerprint (NULL, pk, 1);
break;
case TRUST_ULTIMATE:
write_trust_status (STATUS_TRUST_ULTIMATE, trustlevel);
if (opt.with_fingerprint)
print_fingerprint (NULL, pk, 1);
break;
}
leave:
free_public_key( pk );
return rc;
}
void
release_pk_list (pk_list_t pk_list)
{
PK_LIST pk_rover;
for ( ; pk_list; pk_list = pk_rover)
{
pk_rover = pk_list->next;
free_public_key ( pk_list->pk );
xfree ( pk_list );
}
}
static int
key_present_in_pk_list(PK_LIST pk_list, PKT_public_key *pk)
{
for( ; pk_list; pk_list = pk_list->next)
if (cmp_public_keys(pk_list->pk, pk) == 0)
return 0;
return -1;
}
/****************
* Return a malloced string with a default recipient if there is any
*/
static char *
default_recipient(ctrl_t ctrl)
{
PKT_public_key *pk;
byte fpr[MAX_FINGERPRINT_LEN+1];
size_t n;
char *p;
int i;
if( opt.def_recipient )
return xstrdup( opt.def_recipient );
if( !opt.def_recipient_self )
return NULL;
pk = xmalloc_clear( sizeof *pk );
i = get_seckey_default (ctrl, pk);
if( i ) {
free_public_key( pk );
return NULL;
}
n = MAX_FINGERPRINT_LEN;
fingerprint_from_pk( pk, fpr, &n );
free_public_key( pk );
p = xmalloc( 2*n+3 );
*p++ = '0';
*p++ = 'x';
for(i=0; i < n; i++ )
sprintf( p+2*i, "%02X", fpr[i] );
p -= 2;
return p;
}
static int
expand_id(const char *id,strlist_t *into,unsigned int flags)
{
struct groupitem *groups;
int count=0;
for(groups=opt.grouplist;groups;groups=groups->next)
{
/* need strcasecmp() here, as this should be localized */
if(strcasecmp(groups->name,id)==0)
{
strlist_t each,sl;
/* this maintains the current utf8-ness */
for(each=groups->values;each;each=each->next)
{
sl=add_to_strlist(into,each->d);
sl->flags=flags;
count++;
}
break;
}
}
return count;
}
/* For simplicity, and to avoid potential loops, we only expand once -
* you can't make an alias that points to an alias. */
static strlist_t
expand_group (strlist_t input)
{
strlist_t output = NULL;
strlist_t sl, rover;
for (rover = input; rover; rover = rover->next)
if (!(rover->flags & PK_LIST_FROM_FILE)
&& !expand_id(rover->d,&output,rover->flags))
{
/* Didn't find any groups, so use the existing string */
sl=add_to_strlist(&output,rover->d);
sl->flags=rover->flags;
}
return output;
}
/* Helper for build_pk_list to find and check one key. This helper is
* also used directly in server mode by the RECIPIENTS command. On
* success the new key is added to PK_LIST_ADDR. NAME is the user id
* of the key. USE the requested usage and a set MARK_HIDDEN will
* mark the key in the updated list as a hidden recipient. If
* FROM_FILE is true, NAME is not a user ID but the name of a file
* holding a key. */
gpg_error_t
find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
int mark_hidden, int from_file, pk_list_t *pk_list_addr)
{
int rc;
PKT_public_key *pk;
if (!name || !*name)
return gpg_error (GPG_ERR_INV_USER_ID);
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
pk->req_usage = use;
if (from_file)
rc = get_pubkey_fromfile (ctrl, pk, name);
else
rc = get_best_pubkey_byname (ctrl, NULL, pk, name, NULL, 0, 0);
if (rc)
{
int code;
/* Key not found or other error. */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_SECKEY:
case GPG_ERR_NO_PUBKEY: code = 1; break;
case GPG_ERR_INV_USER_ID: code = 14; break;
default: code = 0; break;
}
send_status_inv_recp (code, name);
free_public_key (pk);
return rc;
}
rc = openpgp_pk_test_algo2 (pk->pubkey_algo, use);
if (rc)
{
/* Key found but not usable for us (e.g. sign-only key). */
send_status_inv_recp (3, name); /* Wrong key usage */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
free_public_key (pk);
return rc;
}
/* Key found and usable. Check validity. */
if (!from_file)
{
int trustlevel;
trustlevel = get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
/* Key has been disabled. */
send_status_inv_recp (13, name);
log_info (_("%s: skipped: public key is disabled\n"), name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
if ( !do_we_trust_pre (pk, trustlevel) )
{
/* We don't trust this key. */
send_status_inv_recp (10, name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
}
/* Skip the actual key if the key is already present in the
list. */
if (!key_present_in_pk_list (*pk_list_addr, pk))
{
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"), name);
free_public_key (pk);
}
else
{
pk_list_t r;
r = xtrymalloc (sizeof *r);
if (!r)
{
rc = gpg_error_from_syserror ();
free_public_key (pk);
return rc;
}
r->pk = pk;
r->next = *pk_list_addr;
r->flags = mark_hidden? 1:0;
*pk_list_addr = r;
}
return 0;
}
/* This is the central function to collect the keys for recipients.
* It is thus used to prepare a public key encryption. encrypt-to
* keys, default keys and the keys for the actual recipients are all
* collected here. When not in batch mode and no recipient has been
* passed on the commandline, the function will also ask for
* recipients.
*
* RCPTS is a string list with the recipients; NULL is an allowed
* value but not very useful. Group expansion is done on these names;
* they may be in any of the user Id formats we can handle. The flags
* bits for each string in the string list are used for:
*
* - PK_LIST_ENCRYPT_TO :: This is an encrypt-to recipient.
* - PK_LIST_HIDDEN :: This is a hidden recipient.
* - PK_LIST_FROM_FILE :: The argument is a file with a key.
*
* On success a list of keys is stored at the address RET_PK_LIST; the
* caller must free this list. On error the value at this address is
* not changed.
*/
int
build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
{
PK_LIST pk_list = NULL;
PKT_public_key *pk=NULL;
int rc=0;
int any_recipients=0;
strlist_t rov,remusr;
char *def_rec = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Try to expand groups if any have been defined. */
if (opt.grouplist)
remusr = expand_group (rcpts);
else
remusr = rcpts;
/* XXX: Change this function to use get_pubkeys instead of
get_pubkey_byname to detect ambiguous key specifications and warn
about duplicate keyblocks. For ambiguous key specifications on
the command line or provided interactively, prompt the user to
select the best key. If a key specification is ambiguous and we
are in batch mode, die. */
if (opt.encrypt_to_default_key)
{
static int warned;
const char *default_key = parse_def_secret_key (ctrl);
if (default_key)
{
PK_LIST r = xmalloc_clear (sizeof *r);
r->pk = xmalloc_clear (sizeof *r->pk);
r->pk->req_usage = PUBKEY_USAGE_ENC;
rc = get_pubkey_byname (ctrl, NULL, r->pk, default_key,
NULL, NULL, 0, 1);
if (rc)
{
xfree (r->pk);
xfree (r);
log_error (_("can't encrypt to '%s'\n"), default_key);
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"),
"--default-key");
}
else
{
r->next = pk_list;
r->flags = 0;
pk_list = r;
}
}
else if (opt.def_secret_key)
{
if (! warned)
log_info (_("option '%s' given, but no valid default keys given\n"),
"--encrypt-to-default-key");
warned = 1;
}
else
{
if (! warned)
log_info (_("option '%s' given, but option '%s' not given\n"),
"--encrypt-to-default-key", "--default-key");
warned = 1;
}
}
/* Check whether there are any recipients in the list and build the
* list of the encrypt-to ones (we always trust them). */
for ( rov = remusr; rov; rov = rov->next )
{
if ( !(rov->flags & PK_LIST_ENCRYPT_TO) )
{
/* This is a regular recipient; i.e. not an encrypt-to
one. */
any_recipients = 1;
/* Hidden recipients are not allowed while in PGP mode,
issue a warning and switch into GnuPG mode. */
if ((rov->flags & PK_LIST_HIDDEN) && (PGP6 || PGP7 || PGP8))
{
log_info(_("you may not use %s while in %s mode\n"),
"--hidden-recipient",
compliance_option_string());
compliance_failure();
}
}
else if (!opt.no_encrypt_to)
{
/* --encrypt-to has not been disabled. Check this
encrypt-to key. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* We explicitly allow encrypt-to to an disabled key; thus
we pass 1 for the second last argument and 1 as the last
argument to disable AKL. */
if ( (rc = get_pubkey_byname (ctrl,
NULL, pk, rov->d, NULL, NULL, 1, 1)) )
{
free_public_key ( pk ); pk = NULL;
log_error (_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (0, rov->d);
goto fail;
}
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Skip the actual key if the key is already present
* in the list. Add it to our list if not. */
if (key_present_in_pk_list(pk_list, pk) == 0)
{
free_public_key (pk); pk = NULL;
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"),
rov->d);
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = (rov->flags&PK_LIST_HIDDEN)?1:0;
pk_list = r;
/* Hidden encrypt-to recipients are not allowed while
in PGP mode, issue a warning and switch into
GnuPG mode. */
if ((r->flags&PK_LIST_ENCRYPT_TO) && (PGP6 || PGP7 || PGP8))
{
log_info(_("you may not use %s while in %s mode\n"),
"--hidden-encrypt-to",
compliance_option_string());
compliance_failure();
}
}
}
else
{
/* The public key is not usable for encryption. */
free_public_key( pk ); pk = NULL;
log_error(_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (3, rov->d); /* Wrong key usage */
goto fail;
}
}
}
/* If we don't have any recipients yet and we are not in batch mode
drop into interactive selection mode. */
if ( !any_recipients && !opt.batch )
{
int have_def_rec;
char *answer = NULL;
strlist_t backlog = NULL;
if (pk_list)
any_recipients = 1;
def_rec = default_recipient(ctrl);
have_def_rec = !!def_rec;
if ( !have_def_rec )
tty_printf(_("You did not specify a user ID. (you may use \"-r\")\n"));
for (;;)
{
rc = 0;
xfree(answer);
if ( have_def_rec )
{
/* A default recipient is taken as the first entry. */
answer = def_rec;
def_rec = NULL;
}
else if (backlog)
{
/* This is part of our trick to expand and display groups. */
answer = strlist_pop (&backlog);
}
else
{
/* Show the list of already collected recipients and ask
for more. */
PK_LIST iter;
tty_printf("\n");
tty_printf(_("Current recipients:\n"));
for (iter=pk_list;iter;iter=iter->next)
{
u32 keyid[2];
keyid_from_pk(iter->pk,keyid);
tty_printf ("%s/%s %s \"",
pubkey_string (iter->pk,
pkstrbuf, sizeof pkstrbuf),
keystr(keyid),
datestr_from_pk (iter->pk));
if (iter->pk->user_id)
tty_print_utf8_string(iter->pk->user_id->name,
iter->pk->user_id->len);
else
{
size_t n;
char *p = get_user_id( keyid, &n );
tty_print_utf8_string( p, n );
xfree(p);
}
tty_printf("\"\n");
}
answer = cpr_get_utf8("pklist.user_id.enter",
_("\nEnter the user ID. "
"End with an empty line: "));
trim_spaces(answer);
cpr_kill_prompt();
}
if ( !answer || !*answer )
{
xfree(answer);
break; /* No more recipients entered - get out of loop. */
}
/* Do group expand here too. The trick here is to continue
the loop if any expansion occurred. The code above will
then list all expanded keys. */
if (expand_id(answer,&backlog,0))
continue;
/* Get and check key for the current name. */
free_public_key (pk);
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
rc = get_pubkey_byname (ctrl, NULL, pk, answer, NULL, NULL, 0, 0 );
if (rc)
tty_printf(_("No such user ID.\n"));
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
if ( have_def_rec )
{
/* No validation for a default recipient. */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info (_("skipped: public key "
"already set as default recipient\n") );
}
else
{
PK_LIST r = xmalloc (sizeof *r);
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
else
{ /* Check validity of this key. */
int trustlevel;
trustlevel =
get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
tty_printf (_("Public key is disabled.\n") );
}
else if ( do_we_trust_pre (pk, trustlevel) )
{
/* Skip the actual key if the key is already
* present in the list */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info(_("skipped: public key already set\n") );
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing interactive ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
}
}
xfree(def_rec); def_rec = NULL;
have_def_rec = 0;
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
}
else if ( !any_recipients && (def_rec = default_recipient(ctrl)) )
{
/* We are in batch mode and have only a default recipient. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* The default recipient is allowed to be disabled; thus pass 1
as second last argument. We also don't want an AKL. */
rc = get_pubkey_byname (ctrl, NULL, pk, def_rec, NULL, NULL, 1, 1);
if (rc)
log_error(_("unknown default recipient \"%s\"\n"), def_rec );
else if ( !(rc=openpgp_pk_test_algo2(pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Mark any_recipients here since the default recipient
would have been used if it wasn't already there. It
doesn't really matter if we got this key from the default
recipient or an encrypt-to. */
any_recipients = 1;
if (!key_present_in_pk_list(pk_list, pk))
log_info (_("skipped: public key already set "
"as default recipient\n"));
else
{
PK_LIST r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
xfree(def_rec); def_rec = NULL;
}
else
{
/* General case: Check all keys. */
any_recipients = 0;
for (; remusr; remusr = remusr->next )
{
if ( (remusr->flags & PK_LIST_ENCRYPT_TO) )
continue; /* encrypt-to keys are already handled. */
rc = find_and_check_key (ctrl, remusr->d, PUBKEY_USAGE_ENC,
!!(remusr->flags&PK_LIST_HIDDEN),
!!(remusr->flags&PK_LIST_FROM_FILE),
&pk_list);
if (rc)
goto fail;
any_recipients = 1;
}
}
if ( !rc && !any_recipients )
{
log_error(_("no valid addressees\n"));
write_status_text (STATUS_NO_RECP, "0");
rc = GPG_ERR_NO_USER_ID;
}
#ifdef USE_TOFU
if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
PK_LIST iter;
for (iter = pk_list; iter; iter = iter->next)
{
int rc2;
/* Note: we already resolved any conflict when looking up
the key. Don't annoy the user again if she selected
accept once. */
rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0);
if (rc2)
log_info ("WARNING: Failed to register encryption to %s"
" with TOFU engine\n",
keystr (pk_main_keyid (iter->pk)));
else if (DBG_TRUST)
log_debug ("Registered encryption to %s with TOFU DB.\n",
keystr (pk_main_keyid (iter->pk)));
}
}
#endif /*USE_TOFU*/
fail:
if ( rc )
release_pk_list( pk_list );
else
*ret_pk_list = pk_list;
if (opt.grouplist)
free_strlist(remusr);
return rc;
}
/* In pgp6 mode, disallow all ciphers except IDEA (1), 3DES (2), and
CAST5 (3), all hashes except MD5 (1), SHA1 (2), and RIPEMD160 (3),
and all compressions except none (0) and ZIP (1). pgp7 and pgp8
mode expands the cipher list to include AES128 (7), AES192 (8),
AES256 (9), and TWOFISH (10). pgp8 adds the SHA-256 hash (8). For
a true PGP key all of this is unneeded as they are the only items
present in the preferences subpacket, but checking here covers the
weird case of encrypting to a key that had preferences from a
different implementation which was then used with PGP. I am not
completely comfortable with this as the right thing to do, as it
slightly alters the list of what the user is supposedly requesting.
It is not against the RFC however, as the preference chosen will
never be one that the user didn't specify somewhere ("The
implementation may use any mechanism to pick an algorithm in the
intersection"), and PGP has no mechanism to fix such a broken
preference list, so I'm including it. -dms */
int
algo_available( preftype_t preftype, int algo, const union pref_hint *hint)
{
if( preftype == PREFTYPE_SYM )
{
if(PGP6 && (algo != CIPHER_ALGO_IDEA
&& algo != CIPHER_ALGO_3DES
&& algo != CIPHER_ALGO_CAST5))
return 0;
if(PGP7 && (algo != CIPHER_ALGO_IDEA
&& algo != CIPHER_ALGO_3DES
&& algo != CIPHER_ALGO_CAST5
&& algo != CIPHER_ALGO_AES
&& algo != CIPHER_ALGO_AES192
&& algo != CIPHER_ALGO_AES256
&& algo != CIPHER_ALGO_TWOFISH))
return 0;
/* PGP8 supports all the ciphers we do.. */
return algo && !openpgp_cipher_test_algo ( algo );
}
else if( preftype == PREFTYPE_HASH )
{
if (hint && hint->digest_length)
{
if (hint->digest_length!=20 || opt.flags.dsa2)
{
/* If --enable-dsa2 is set or the hash isn't 160 bits
(which implies DSA2), then we'll accept a hash that
is larger than we need. Otherwise we won't accept
any hash that isn't exactly the right size. */
if (hint->digest_length > gcry_md_get_algo_dlen (algo))
return 0;
}
else if (hint->digest_length != gcry_md_get_algo_dlen (algo))
return 0;
}
if((PGP6 || PGP7) && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160))
return 0;
if(PGP8 && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160
&& algo != DIGEST_ALGO_SHA256))
return 0;
return algo && !openpgp_md_test_algo (algo);
}
else if( preftype == PREFTYPE_ZIP )
{
if((PGP6 || PGP7) && (algo != COMPRESS_ALGO_NONE
&& algo != COMPRESS_ALGO_ZIP))
return 0;
/* PGP8 supports all the compression algos we do */
return !check_compress_algo( algo );
}
else
return 0;
}
/****************
* Return -1 if we could not find an algorithm.
*/
int
select_algo_from_prefs(PK_LIST pk_list, int preftype,
int request, const union pref_hint *hint)
{
PK_LIST pkr;
u32 bits[8];
const prefitem_t *prefs;
int result=-1,i;
u16 scores[256];
if( !pk_list )
return -1;
memset(bits,0xFF,sizeof(bits));
memset(scores,0,sizeof(scores));
for( pkr = pk_list; pkr; pkr = pkr->next )
{
u32 mask[8];
int rank=1,implicit=-1;
memset(mask,0,sizeof(mask));
switch(preftype)
{
case PREFTYPE_SYM:
/* IDEA is implicitly there for v3 keys with v3 selfsigs if
--pgp2 mode is on. This was a 2440 thing that was
dropped from 4880 but is still relevant to GPG's 1991
support. All this doesn't mean IDEA is actually
available, of course. */
implicit=CIPHER_ALGO_3DES;
break;
case PREFTYPE_HASH:
/* While I am including this code for completeness, note
that currently --pgp2 mode locks the hash at MD5, so this
code will never even be called. Even if the hash wasn't
locked at MD5, we don't support sign+encrypt in --pgp2
mode, and that's the only time PREFTYPE_HASH is used
anyway. -dms */
implicit=DIGEST_ALGO_SHA1;
break;
case PREFTYPE_ZIP:
/* Uncompressed is always an option. */
implicit=COMPRESS_ALGO_NONE;
}
if (pkr->pk->user_id) /* selected by user ID */
prefs = pkr->pk->user_id->prefs;
else
prefs = pkr->pk->prefs;
if( prefs )
{
for (i=0; prefs[i].type; i++ )
{
if( prefs[i].type == preftype )
{
/* Make sure all scores don't add up past 0xFFFF
(and roll around) */
if(rank+scores[prefs[i].value]<=0xFFFF)
scores[prefs[i].value]+=rank;
else
scores[prefs[i].value]=0xFFFF;
mask[prefs[i].value/32] |= 1<<(prefs[i].value%32);
rank++;
/* We saw the implicit algorithm, so we don't need
tack it on the end ourselves. */
if(implicit==prefs[i].value)
implicit=-1;
}
}
}
if(rank==1 && preftype==PREFTYPE_ZIP)
{
/* If the compression preferences are not present, they are
assumed to be ZIP, Uncompressed (RFC4880:13.3.1) */
scores[1]=1; /* ZIP is first choice */
scores[0]=2; /* Uncompressed is second choice */
mask[0]|=3;
}
/* If the key didn't have the implicit algorithm listed
explicitly, add it here at the tail of the list. */
if(implicit>-1)
{
scores[implicit]+=rank;
mask[implicit/32] |= 1<<(implicit%32);
}
for(i=0;i<8;i++)
bits[i]&=mask[i];
}
/* We've now scored all of the algorithms, and the usable ones have
bits set. Let's pick the winner. */
/* The caller passed us a request. Can we use it? */
if(request>-1 && (bits[request/32] & (1<<(request%32))) &&
algo_available(preftype,request,hint))
result=request;
if(result==-1)
{
/* If we have personal prefs set, use them. */
prefs=NULL;
if(preftype==PREFTYPE_SYM && opt.personal_cipher_prefs)
prefs=opt.personal_cipher_prefs;
else if(preftype==PREFTYPE_HASH && opt.personal_digest_prefs)
prefs=opt.personal_digest_prefs;
else if(preftype==PREFTYPE_ZIP && opt.personal_compress_prefs)
prefs=opt.personal_compress_prefs;
if( prefs )
for(i=0; prefs[i].type; i++ )
{
if(bits[prefs[i].value/32] & (1<<(prefs[i].value%32))
&& algo_available( preftype, prefs[i].value, hint))
{
result = prefs[i].value;
break;
}
}
}
if(result==-1)
{
unsigned int best=-1;
/* At this point, we have not selected an algorithm due to a
special request or via personal prefs. Pick the highest
ranked algorithm (i.e. the one with the lowest score). */
if(preftype==PREFTYPE_HASH && scores[DIGEST_ALGO_MD5])
{
/* "If you are building an authentication system, the recipient
may specify a preferred signing algorithm. However, the
signer would be foolish to use a weak algorithm simply
because the recipient requests it." (RFC4880:14). If any
other hash algorithm is available, pretend that MD5 isn't.
Note that if the user intentionally chose MD5 by putting it
in their personal prefs, then we do what the user said (as we
never reach this code). */
for(i=DIGEST_ALGO_MD5+1;i<256;i++)
if(scores[i])
{
scores[DIGEST_ALGO_MD5]=0;
break;
}
}
for(i=0;i<256;i++)
{
/* Note the '<' here. This means in case of a tie, we will
favor the lower algorithm number. We have a choice
between the lower number (probably an older algorithm
with more time in use), or the higher number (probably a
newer algorithm with less time in use). Older is
probably safer here, even though the newer algorithms
tend to be "stronger". */
if(scores[i] && scores[i]<best
&& (bits[i/32] & (1<<(i%32)))
&& algo_available(preftype,i,hint))
{
best=scores[i];
result=i;
}
}
}
return result;
}
/*
* Select the MDC flag from the pk_list. We can only use MDC if all
* recipients support this feature.
*/
int
select_mdc_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
if ( !pk_list )
return 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.mdc;
else
mdc = pkr->pk->flags.mdc;
if (!mdc)
return 0; /* At least one recipient does not support it. */
}
return 1; /* Can be used. */
}
/* Print a warning for all keys in PK_LIST missing the MDC feature. */
void
warn_missing_mdc_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.mdc;
else
mdc = pkr->pk->flags.mdc;
if (!mdc)
log_info (_("Note: key %s has no %s feature\n"),
keystr_from_pk (pkr->pk), "MDC");
}
}
void
warn_missing_aes_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
const prefitem_t *prefs;
int i;
int gotit = 0;
prefs = pkr->pk->user_id? pkr->pk->user_id->prefs : pkr->pk->prefs;
if (prefs)
{
for (i=0; !gotit && prefs[i].type; i++ )
if (prefs[i].type == PREFTYPE_SYM
&& prefs[i].value == CIPHER_ALGO_AES)
gotit++;
}
if (!gotit)
log_info (_("Note: key %s has no preference for %s\n"),
keystr_from_pk (pkr->pk), "AES");
}
}
diff --git a/g10/pkglue.c b/g10/pkglue.c
index ce24a2ed5..8021a94db 100644
--- a/g10/pkglue.c
+++ b/g10/pkglue.c
@@ -1,422 +1,422 @@
/* pkglue.c - public key operations glue code
* Copyright (C) 2000, 2003, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "pkglue.h"
#include "main.h"
#include "options.h"
/* FIXME: Better change the function name because mpi_ is used by
gcrypt macros. */
gcry_mpi_t
get_mpi_from_sexp (gcry_sexp_t sexp, const char *item, int mpifmt)
{
gcry_sexp_t list;
gcry_mpi_t data;
list = gcry_sexp_find_token (sexp, item, 0);
log_assert (list);
data = gcry_sexp_nth_mpi (list, 1, mpifmt);
log_assert (data);
gcry_sexp_release (list);
return data;
}
/****************
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt.
*/
int
pk_verify (pubkey_algo_t pkalgo, gcry_mpi_t hash,
gcry_mpi_t *data, gcry_mpi_t *pkey)
{
gcry_sexp_t s_sig, s_hash, s_pkey;
int rc;
unsigned int neededfixedlen = 0;
/* Make a sexp from pkey. */
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2], pkey[3]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL_E || pkalgo == PUBKEY_ALGO_ELGAMAL)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(ecdsa(curve %s)(q%m)))",
curve, pkey[1]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(ecc(curve %s)"
"(flags eddsa)(q%m)))",
curve, pkey[1]);
xfree (curve);
}
if (openpgp_oid_is_ed25519 (pkey[0]))
neededfixedlen = 256 / 8;
}
else
return GPG_ERR_PUBKEY_ALGO;
if (rc)
BUG (); /* gcry_sexp_build should never fail. */
/* Put hash into a S-Exp s_hash. */
if (pkalgo == PUBKEY_ALGO_EDDSA)
{
if (gcry_sexp_build (&s_hash, NULL,
"(data(flags eddsa)(hash-algo sha512)(value %m))",
hash))
BUG (); /* gcry_sexp_build should never fail. */
}
else
{
if (gcry_sexp_build (&s_hash, NULL, "%m", hash))
BUG (); /* gcry_sexp_build should never fail. */
}
/* Put data into a S-Exp s_sig. */
s_sig = NULL;
if (pkalgo == PUBKEY_ALGO_DSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(dsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(ecdsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
gcry_mpi_t r = data[0];
gcry_mpi_t s = data[1];
size_t rlen, slen, n; /* (bytes) */
char buf[64];
log_assert (neededfixedlen <= sizeof buf);
if (!r || !s)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((rlen = (gcry_mpi_get_nbits (r)+7)/8) > neededfixedlen || !rlen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((slen = (gcry_mpi_get_nbits (s)+7)/8) > neededfixedlen || !slen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else
{
/* We need to fixup the length in case of leading zeroes.
* OpenPGP does not allow leading zeroes and the parser for
* the signature packet has no information on the use curve,
* thus we need to do it here. We won't do it for opaque
* MPIs under the assumption that they are known to be fine;
* we won't see them here anyway but the check is anyway
* required. Fixme: A nifty feature for gcry_sexp_build
* would be a format to left pad the value (e.g. "%*M"). */
rc = 0;
if (rlen < neededfixedlen
&& !gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, r)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
r = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
if (slen < neededfixedlen
&& !gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, s)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
s = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
if (!rc)
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(eddsa(r%M)(s%M)))", r, s);
if (r != data[0])
gcry_mpi_release (r);
if (s != data[1])
gcry_mpi_release (s);
}
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(elg(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
if (!data[0])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", data[0]);
}
else
BUG ();
if (!rc)
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return rc;
}
/****************
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt.
* PK is only required to compute the fingerprint for ECDH.
*/
int
pk_encrypt (pubkey_algo_t algo, gcry_mpi_t *resarr, gcry_mpi_t data,
PKT_public_key *pk, gcry_mpi_t *pkey)
{
gcry_sexp_t s_ciph = NULL;
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_pkey = NULL;
int rc;
/* Make a sexp from pkey. */
if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
/* Put DATA into a simplified S-expression. */
if (!rc)
rc = gcry_sexp_build (&s_data, NULL, "%m", data);
}
else if (algo == PUBKEY_ALGO_RSA || algo == PUBKEY_ALGO_RSA_E)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pkey[0], pkey[1]);
/* Put DATA into a simplified S-expression. */
if (!rc)
rc = gcry_sexp_build (&s_data, NULL, "%m", data);
}
else if (algo == PUBKEY_ALGO_ECDH)
{
gcry_mpi_t k;
rc = pk_ecdh_generate_ephemeral_key (pkey, &k);
if (!rc)
{
char *curve;
curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
int with_djb_tweak_flag = openpgp_oid_is_cv25519 (pkey[0]);
/* Now use the ephemeral secret to compute the shared point. */
rc = gcry_sexp_build (&s_pkey, NULL,
with_djb_tweak_flag ?
"(public-key(ecdh(curve%s)(flags djb-tweak)(q%m)))"
: "(public-key(ecdh(curve%s)(q%m)))",
curve, pkey[1]);
xfree (curve);
/* Put K into a simplified S-expression. */
if (!rc)
rc = gcry_sexp_build (&s_data, NULL, "%m", k);
}
gcry_mpi_release (k);
}
}
else
rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
/* Pass it to libgcrypt. */
if (!rc)
rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
if (rc)
;
else if (algo == PUBKEY_ALGO_ECDH)
{
gcry_mpi_t shared, public, result;
byte fp[MAX_FINGERPRINT_LEN];
size_t fpn;
/* Get the shared point and the ephemeral public key. */
shared = get_mpi_from_sexp (s_ciph, "s", GCRYMPI_FMT_USG);
public = get_mpi_from_sexp (s_ciph, "e", GCRYMPI_FMT_USG);
gcry_sexp_release (s_ciph);
s_ciph = NULL;
if (DBG_CRYPTO)
{
log_debug ("ECDH ephemeral key:");
gcry_mpi_dump (public);
log_printf ("\n");
}
result = NULL;
fingerprint_from_pk (pk, fp, &fpn);
if (fpn != 20)
rc = gpg_error (GPG_ERR_INV_LENGTH);
else
rc = pk_ecdh_encrypt_with_shared_point (1 /*=encrypton*/, shared,
fp, data, pkey, &result);
gcry_mpi_release (shared);
if (!rc)
{
resarr[0] = public;
resarr[1] = result;
}
else
{
gcry_mpi_release (public);
gcry_mpi_release (result);
}
}
else /* Elgamal or RSA case. */
{ /* Fixme: Add better error handling or make gnupg use
S-expressions directly. */
resarr[0] = get_mpi_from_sexp (s_ciph, "a", GCRYMPI_FMT_USG);
if (!is_RSA (algo))
resarr[1] = get_mpi_from_sexp (s_ciph, "b", GCRYMPI_FMT_USG);
}
gcry_sexp_release (s_ciph);
return rc;
}
/* Check whether SKEY is a suitable secret key. */
int
pk_check_secret_key (pubkey_algo_t pkalgo, gcry_mpi_t *skey)
{
gcry_sexp_t s_skey;
int rc;
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3]);
}
else if (is_RSA (pkalgo))
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4],
skey[5]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA || pkalgo == PUBKEY_ALGO_ECDH)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(ecc(curve%s)(q%m)(d%m)))",
curve, skey[1], skey[2]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(ecc(curve %s)"
"(flags eddsa)(q%m)(d%m)))",
curve, skey[1], skey[2]);
xfree (curve);
}
}
else
return GPG_ERR_PUBKEY_ALGO;
if (!rc)
{
rc = gcry_pk_testkey (s_skey);
gcry_sexp_release (s_skey);
}
return rc;
}
diff --git a/g10/plaintext.c b/g10/plaintext.c
index 40ce6030a..c5d1ddb7f 100644
--- a/g10/plaintext.c
+++ b/g10/plaintext.c
@@ -1,813 +1,813 @@
/* plaintext.c - process plaintext packets
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
#include "packet.h"
-#include "ttyio.h"
+#include "../common/ttyio.h"
#include "filter.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
/* Get the output filename. On success, the actual filename that is
used is set in *FNAMEP and a filepointer is returned in *FP.
EMBEDDED_NAME AND EMBEDDED_NAMELEN are normally stored in a
plaintext packet. EMBEDDED_NAMELEN should not include any NUL
terminator (EMBEDDED_NAME does not need to be NUL terminated).
DATA is the iobuf containing the input data. We just use it to get
the input file's filename.
On success, the caller is responsible for calling xfree on *FNAMEP
and calling es_close on *FPP. */
gpg_error_t
get_output_file (const byte *embedded_name, int embedded_namelen,
iobuf_t data, char **fnamep, estream_t *fpp)
{
gpg_error_t err = 0;
char *fname = NULL;
estream_t fp = NULL;
int nooutput = 0;
/* Create the filename as C string. */
if (opt.outfp)
{
fname = xtrystrdup ("[FP]");
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else if (opt.outfile)
{
fname = xtrystrdup (opt.outfile);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else if (embedded_namelen == 8 && !memcmp (embedded_name, "_CONSOLE", 8))
{
log_info (_("data not saved; use option \"--output\" to save it\n"));
nooutput = 1;
}
else if (!opt.flags.use_embedded_filename)
{
if (data)
fname = make_outfile_name (iobuf_get_real_fname (data));
if (!fname)
fname = ask_outfile_name (embedded_name, embedded_namelen);
if (!fname)
{
err = gpg_error (GPG_ERR_GENERAL); /* Can't create file. */
goto leave;
}
}
else
fname = utf8_to_native (embedded_name, embedded_namelen, 0);
if (nooutput)
;
else if (opt.outfp)
{
fp = opt.outfp;
es_set_binary (fp);
}
else if (iobuf_is_pipe_filename (fname) || !*fname)
{
/* Special file name, no filename, or "-" given; write to the
* file descriptor or to stdout. */
int fd;
char xname[64];
fd = check_special_filename (fname, 1, 0);
if (fd == -1)
{
/* Not a special filename, thus we want stdout. */
fp = es_stdout;
es_set_binary (fp);
}
else if (!(fp = es_fdopen_nc (fd, "wb")))
{
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
goto leave;
}
}
else
{
while (!overwrite_filep (fname))
{
char *tmp = ask_outfile_name (NULL, 0);
if (!tmp || !*tmp)
{
xfree (tmp);
/* FIXME: Below used to be GPG_ERR_CREATE_FILE */
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
xfree (fname);
fname = tmp;
}
}
#ifndef __riscos__
if (opt.outfp && is_secured_file (es_fileno (opt.outfp)))
{
err = gpg_error (GPG_ERR_EPERM);
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
else if (fp || nooutput)
;
else if (is_secured_filename (fname))
{
gpg_err_set_errno (EPERM);
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
else if (!(fp = es_fopen (fname, "wb")))
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
#else /* __riscos__ */
/* If no output filename was given, i.e. we constructed it, convert
all '.' in fname to '/' but not vice versa as we don't create
directories! */
if (!opt.outfile)
for (c = 0; fname[c]; ++c)
if (fname[c] == '.')
fname[c] = '/';
if (fp || nooutput)
;
else
{
/* Note: riscos stuff is not expected to work anymore. If we
want to port it again to riscos we should do most of the suff
in estream. FIXME: Consider to remove all riscos special
cases. */
fp = fopen (fname, "wb");
if (!fp)
{
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
err = GPG_ERR_CREATE_FILE;
if (errno == 106)
log_info ("Do output file and input file have the same name?\n");
goto leave;
}
/* If there's a ,xxx extension in the embedded filename,
use that, else check whether the user input (in fname)
has a ,xxx appended, then use that in preference */
if ((c = riscos_get_filetype_from_string (embedded_name,
embedded_namelen)) != -1)
filetype = c;
if ((c = riscos_get_filetype_from_string (fname, strlen (fname))) != -1)
filetype = c;
riscos_set_filetype_by_number (fname, filetype);
}
#endif /* __riscos__ */
leave:
if (err)
{
if (fp && fp != es_stdout && fp != opt.outfp)
es_fclose (fp);
xfree (fname);
return err;
}
*fnamep = fname;
*fpp = fp;
return 0;
}
/* Handle a plaintext packet. If MFX is not NULL, update the MDs
* Note: We should have used the filter stuff here, but we have to add
* some easy mimic to set a read limit, so we calculate only the bytes
* from the plaintext. */
int
handle_plaintext (PKT_plaintext * pt, md_filter_context_t * mfx,
int nooutput, int clearsig)
{
char *fname = NULL;
estream_t fp = NULL;
static off_t count = 0;
int err = 0;
int c;
int convert;
#ifdef __riscos__
int filetype = 0xfff;
#endif
if (pt->mode == 't' || pt->mode == 'u' || pt->mode == 'm')
convert = pt->mode;
else
convert = 0;
/* Let people know what the plaintext info is. This allows the
receiving program to try and do something different based on the
format code (say, recode UTF-8 to local). */
if (!nooutput && is_status_enabled ())
{
char status[50];
/* Better make sure that stdout has been flushed in case the
output will be written to it. This is to make sure that no
not-yet-flushed stuff will be written after the plaintext
status message. */
es_fflush (es_stdout);
snprintf (status, sizeof status,
"%X %lu ", (byte) pt->mode, (ulong) pt->timestamp);
write_status_text_and_buffer (STATUS_PLAINTEXT,
status, pt->name, pt->namelen, 0);
if (!pt->is_partial)
{
snprintf (status, sizeof status, "%lu", (ulong) pt->len);
write_status_text (STATUS_PLAINTEXT_LENGTH, status);
}
}
if (! nooutput)
{
err = get_output_file (pt->name, pt->namelen, pt->buf, &fname, &fp);
if (err)
goto leave;
}
if (!pt->is_partial)
{
/* We have an actual length (which might be zero). */
if (clearsig)
{
log_error ("clearsig encountered while not expected\n");
err = gpg_error (GPG_ERR_UNEXPECTED);
goto leave;
}
if (convert) /* Text mode. */
{
for (; pt->len; pt->len--)
{
if ((c = iobuf_get (pt->buf)) == -1)
{
err = gpg_error_from_syserror ();
log_error ("problem reading source (%u bytes remaining)\n",
(unsigned) pt->len);
goto leave;
}
if (mfx->md)
gcry_md_putc (mfx->md, c);
#ifndef HAVE_DOSISH_SYSTEM
/* Convert to native line ending. */
/* fixme: this hack might be too simple */
if (c == '\r' && convert != 'm')
continue;
#endif
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
}
}
else /* Binary mode. */
{
byte *buffer = xmalloc (32768);
while (pt->len)
{
int len = pt->len > 32768 ? 32768 : pt->len;
len = iobuf_read (pt->buf, buffer, len);
if (len == -1)
{
err = gpg_error_from_syserror ();
log_error ("problem reading source (%u bytes remaining)\n",
(unsigned) pt->len);
xfree (buffer);
goto leave;
}
if (mfx->md)
gcry_md_write (mfx->md, buffer, len);
if (fp)
{
if (opt.max_output && (count += len) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (buffer);
goto leave;
}
else if (es_fwrite (buffer, 1, len, fp) != len)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
xfree (buffer);
goto leave;
}
}
pt->len -= len;
}
xfree (buffer);
}
}
else if (!clearsig)
{
if (convert)
{ /* text mode */
while ((c = iobuf_get (pt->buf)) != -1)
{
if (mfx->md)
gcry_md_putc (mfx->md, c);
#ifndef HAVE_DOSISH_SYSTEM
if (c == '\r' && convert != 'm')
continue; /* fixme: this hack might be too simple */
#endif
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("Error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
}
}
else
{ /* binary mode */
byte *buffer;
int eof_seen = 0;
buffer = xtrymalloc (32768);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
while (!eof_seen)
{
/* Why do we check for len < 32768:
* If we won't, we would practically read 2 EOFs but
* the first one has already popped the block_filter
* off and therefore we don't catch the boundary.
* So, always assume EOF if iobuf_read returns less bytes
* then requested */
int len = iobuf_read (pt->buf, buffer, 32768);
if (len == -1)
break;
if (len < 32768)
eof_seen = 1;
if (mfx->md)
gcry_md_write (mfx->md, buffer, len);
if (fp)
{
if (opt.max_output && (count += len) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (buffer);
goto leave;
}
else if (es_fwrite (buffer, 1, len, fp) != len)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
xfree (buffer);
goto leave;
}
}
}
xfree (buffer);
}
pt->buf = NULL;
}
else /* Clear text signature - don't hash the last CR,LF. */
{
int state = 0;
while ((c = iobuf_get (pt->buf)) != -1)
{
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
if (!mfx->md)
continue;
if (state == 2)
{
gcry_md_putc (mfx->md, '\r');
gcry_md_putc (mfx->md, '\n');
state = 0;
}
if (!state)
{
if (c == '\r')
state = 1;
else if (c == '\n')
state = 2;
else
gcry_md_putc (mfx->md, c);
}
else if (state == 1)
{
if (c == '\n')
state = 2;
else
{
gcry_md_putc (mfx->md, '\r');
if (c == '\r')
state = 1;
else
{
state = 0;
gcry_md_putc (mfx->md, c);
}
}
}
}
pt->buf = NULL;
}
if (fp && fp != es_stdout && fp != opt.outfp && es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
fp = NULL;
goto leave;
}
fp = NULL;
leave:
/* Make sure that stdout gets flushed after the plaintext has been
handled. This is for extra security as we do a flush anyway
before checking the signature. */
if (es_fflush (es_stdout))
{
/* We need to check the return code to detect errors like disk
full for short plaintexts. See bug#1207. Checking return
values is a good idea in any case. */
if (!err)
err = gpg_error_from_syserror ();
log_error ("error flushing '%s': %s\n", "[stdout]",
gpg_strerror (err));
}
if (fp && fp != es_stdout && fp != opt.outfp)
es_fclose (fp);
xfree (fname);
return err;
}
static void
do_hash (gcry_md_hd_t md, gcry_md_hd_t md2, IOBUF fp, int textmode)
{
text_filter_context_t tfx;
int c;
if (textmode)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (fp, text_filter, &tfx);
}
if (md2)
{ /* work around a strange behaviour in pgp2 */
/* It seems that at least PGP5 converts a single CR to a CR,LF too */
int lc = -1;
while ((c = iobuf_get (fp)) != -1)
{
if (c == '\n' && lc == '\r')
gcry_md_putc (md2, c);
else if (c == '\n')
{
gcry_md_putc (md2, '\r');
gcry_md_putc (md2, c);
}
else if (c != '\n' && lc == '\r')
{
gcry_md_putc (md2, '\n');
gcry_md_putc (md2, c);
}
else
gcry_md_putc (md2, c);
if (md)
gcry_md_putc (md, c);
lc = c;
}
}
else
{
while ((c = iobuf_get (fp)) != -1)
{
if (md)
gcry_md_putc (md, c);
}
}
}
/****************
* Ask for the detached datafile and calculate the digest from it.
* INFILE is the name of the input file.
*/
int
ask_for_detached_datafile (gcry_md_hd_t md, gcry_md_hd_t md2,
const char *inname, int textmode)
{
progress_filter_context_t *pfx;
char *answer = NULL;
IOBUF fp;
int rc = 0;
pfx = new_progress_context ();
fp = open_sigfile (inname, pfx); /* Open default file. */
if (!fp && !opt.batch)
{
int any = 0;
tty_printf (_("Detached signature.\n"));
do
{
char *name;
xfree (answer);
tty_enable_completion (NULL);
name = cpr_get ("detached_signature.filename",
_("Please enter name of data file: "));
tty_disable_completion ();
cpr_kill_prompt ();
answer = make_filename (name, (void *) NULL);
xfree (name);
if (any && !*answer)
{
rc = gpg_error (GPG_ERR_GENERAL); /*G10ERR_READ_FILE */
goto leave;
}
fp = iobuf_open (answer);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp && errno == ENOENT)
{
tty_printf ("No such file, try again or hit enter to quit.\n");
any++;
}
else if (!fp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), answer,
strerror (errno));
goto leave;
}
}
while (!fp);
}
if (!fp)
{
if (opt.verbose)
log_info (_("reading stdin ...\n"));
fp = iobuf_open (NULL);
log_assert (fp);
}
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
leave:
xfree (answer);
release_progress_context (pfx);
return rc;
}
/* Hash the given files and append the hash to hash contexts MD and
* MD2. If FILES is NULL, stdin is hashed. */
int
hash_datafiles (gcry_md_hd_t md, gcry_md_hd_t md2, strlist_t files,
const char *sigfilename, int textmode)
{
progress_filter_context_t *pfx;
IOBUF fp;
strlist_t sl;
pfx = new_progress_context ();
if (!files)
{
/* Check whether we can open the signed material. We avoid
trying to open a file if run in batch mode. This assumed
data file for a sig file feature is just a convenience thing
for the command line and the user needs to read possible
warning messages. */
if (!opt.batch)
{
fp = open_sigfile (sigfilename, pfx);
if (fp)
{
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
release_progress_context (pfx);
return 0;
}
}
log_error (_("no signed data\n"));
release_progress_context (pfx);
return gpg_error (GPG_ERR_NO_DATA);
}
for (sl = files; sl; sl = sl->next)
{
fp = iobuf_open (sl->d);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
int rc = gpg_error_from_syserror ();
log_error (_("can't open signed data '%s'\n"),
print_fname_stdin (sl->d));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, sl->d);
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
}
release_progress_context (pfx);
return 0;
}
/* Hash the data from file descriptor DATA_FD and append the hash to hash
contexts MD and MD2. */
int
hash_datafile_by_fd (gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd,
int textmode)
{
progress_filter_context_t *pfx = new_progress_context ();
iobuf_t fp;
if (is_secured_file (data_fd))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_fdopen_nc (data_fd, "rb");
if (!fp)
{
int rc = gpg_error_from_syserror ();
log_error (_("can't open signed data fd=%d: %s\n"),
data_fd, strerror (errno));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, NULL);
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
release_progress_context (pfx);
return 0;
}
/* Set up a plaintext packet with the appropriate filename. If there
is a --set-filename, use it (it's already UTF8). If there is a
regular filename, UTF8-ize it if necessary. If there is no
filenames at all, set the field empty. */
PKT_plaintext *
setup_plaintext_name (const char *filename, IOBUF iobuf)
{
PKT_plaintext *pt;
if ((filename && !iobuf_is_pipe_filename (filename))
|| (opt.set_filename && !iobuf_is_pipe_filename (opt.set_filename)))
{
char *s;
if (opt.set_filename)
s = make_basename (opt.set_filename, iobuf_get_real_fname (iobuf));
else if (filename && !opt.flags.utf8_filename)
{
char *tmp = native_to_utf8 (filename);
s = make_basename (tmp, iobuf_get_real_fname (iobuf));
xfree (tmp);
}
else
s = make_basename (filename, iobuf_get_real_fname (iobuf));
pt = xmalloc (sizeof *pt + strlen (s) - 1);
pt->namelen = strlen (s);
memcpy (pt->name, s, pt->namelen);
xfree (s);
}
else
{
/* no filename */
pt = xmalloc (sizeof *pt - 1);
pt->namelen = 0;
}
return pt;
}
diff --git a/g10/progress.c b/g10/progress.c
index feb639e5f..7e777d4ab 100644
--- a/g10/progress.c
+++ b/g10/progress.c
@@ -1,202 +1,202 @@
/* progress.c - emit progress status lines
* Copyright (C) 2003, 2006 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include "gpg.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "filter.h"
-#include "status.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/util.h"
#include "options.h"
/* Create a new context for use with the progress filter. We need to
allocate such contexts on the heap because there is no guarantee
that at the end of a function the filter has already been popped
off. In general this will happen but with malformed packets it is
possible that a filter has not yet reached the end-of-stream when
the function has done all processing. Checking in each function
that end-of-stream has been reached would be to cumbersome.
What we also do is to shortcut the progress handler by having this
function return NULL if progress information has not been
requested.
*/
progress_filter_context_t *
new_progress_context (void)
{
progress_filter_context_t *pfx;
if (!opt.enable_progress_filter)
return NULL;
if (!is_status_enabled ())
return NULL;
pfx = xcalloc (1, sizeof *pfx);
pfx->refcount = 1;
return pfx;
}
/* Release a progress filter context. Passing NULL is explicitly
allowed and a no-op. */
void
release_progress_context (progress_filter_context_t *pfx)
{
if (!pfx)
return;
log_assert (pfx->refcount);
if ( --pfx->refcount )
return;
xfree (pfx->what);
xfree (pfx);
}
static void
write_status_progress (const char *what,
unsigned long current, unsigned long total_arg)
{
char buffer[60];
char units[] = "BKMGTPEZY?";
int unitidx = 0;
uint64_t total = total_arg;
/* Although we use an unsigned long for the values, 32 bit
* applications using GPGME will use an "int" and thus are limited
* in the total size which can be represented. On Windows, where
* sizeof(int)==sizeof(long), this is even worse and will lead to an
* integer overflow for all files larger than 2 GiB. Although, the
* allowed value range of TOTAL and CURRENT is nowhere specified, we
* better protect applications from the need to handle negative
* values. The common usage pattern of the progress information is
* to display how many percent of the operation has been done and
* thus scaling CURRENT and TOTAL down before they get to large,
* should not have a noticeable effect except for rounding
* imprecision. */
if (!total && opt.input_size_hint)
total = opt.input_size_hint;
if (total)
{
if (current > total)
current = total;
while (total > 1024*1024)
{
total /= 1024;
current /= 1024;
unitidx++;
}
}
else
{
while (current > 1024*1024)
{
current /= 1024;
unitidx++;
}
}
if (unitidx > 9)
unitidx = 9;
snprintf (buffer, sizeof buffer, "%.20s ? %lu %lu %c%s",
what? what : "?", current, (unsigned long)total,
units[unitidx],
unitidx? "iB" : "");
write_status_text (STATUS_PROGRESS, buffer);
}
/****************
* The filter is used to report progress to the user.
*/
static int
progress_filter (void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
int rc = 0;
progress_filter_context_t *pfx = opaque;
if (control == IOBUFCTRL_INIT)
{
pfx->last = 0;
pfx->offset = 0;
pfx->last_time = make_timestamp ();
write_status_progress (pfx->what, pfx->offset, pfx->total);
}
else if (control == IOBUFCTRL_UNDERFLOW)
{
u32 timestamp = make_timestamp ();
int len = iobuf_read (a, buf, *ret_len);
if (len >= 0)
{
pfx->offset += len;
*ret_len = len;
}
else
{
*ret_len = 0;
rc = -1;
}
if ((len == -1 && pfx->offset != pfx->last)
|| timestamp - pfx->last_time > 0)
{
write_status_progress (pfx->what, pfx->offset, pfx->total);
pfx->last = pfx->offset;
pfx->last_time = timestamp;
}
}
else if (control == IOBUFCTRL_FREE)
{
release_progress_context (pfx);
}
else if (control == IOBUFCTRL_DESC)
mem2str (buf, "progress_filter", *ret_len);
return rc;
}
void
handle_progress (progress_filter_context_t *pfx, IOBUF inp, const char *name)
{
off_t filesize = 0;
if (!pfx)
return;
log_assert (opt.enable_progress_filter);
log_assert (is_status_enabled ());
if ( !iobuf_is_pipe_filename (name) && *name )
filesize = iobuf_get_filelength (inp, NULL);
else if (opt.set_filesize)
filesize = opt.set_filesize;
/* register the progress filter */
pfx->what = xstrdup (name ? name : "stdin");
pfx->total = filesize;
pfx->refcount++;
iobuf_push_filter (inp, progress_filter, pfx);
}
diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c
index e037c120d..cc962cfde 100644
--- a/g10/pubkey-enc.c
+++ b/g10/pubkey-enc.c
@@ -1,458 +1,458 @@
/* pubkey-enc.c - Process a public key encoded packet.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "trustdb.h"
-#include "status.h"
+#include "../common/status.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "pkglue.h"
#include "call-agent.h"
-#include "host2net.h"
+#include "../common/host2net.h"
static gpg_error_t get_it (PKT_pubkey_enc *k,
DEK *dek, PKT_public_key *sk, u32 *keyid);
/* Check that the given algo is mentioned in one of the valid user-ids. */
static int
is_algo_in_prefs (kbnode_t keyblock, preftype_t type, int algo)
{
kbnode_t k;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
if (uid->created && prefs && !uid->is_revoked && !uid->is_expired)
{
for (; prefs->type; prefs++)
if (prefs->type == type && prefs->value == algo)
return 1;
}
}
}
return 0;
}
/*
* Get the session key from a pubkey enc packet and return it in DEK,
* which should have been allocated in secure memory by the caller.
*/
gpg_error_t
get_session_key (ctrl_t ctrl, PKT_pubkey_enc * k, DEK * dek)
{
PKT_public_key *sk = NULL;
int rc;
if (DBG_CLOCK)
log_clock ("get_session_key enter");
rc = openpgp_pk_test_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC);
if (rc)
goto leave;
if ((k->keyid[0] || k->keyid[1]) && !opt.try_all_secrets)
{
sk = xmalloc_clear (sizeof *sk);
sk->pubkey_algo = k->pubkey_algo; /* We want a pubkey with this algo. */
if (!(rc = get_seckey (sk, k->keyid)))
rc = get_it (k, dek, sk, k->keyid);
}
else if (opt.skip_hidden_recipients)
rc = gpg_error (GPG_ERR_NO_SECKEY);
else /* Anonymous receiver: Try all available secret keys. */
{
void *enum_context = NULL;
u32 keyid[2];
for (;;)
{
free_public_key (sk);
sk = xmalloc_clear (sizeof *sk);
rc = enum_secret_keys (ctrl, &enum_context, sk);
if (rc)
{
rc = GPG_ERR_NO_SECKEY;
break;
}
if (sk->pubkey_algo != k->pubkey_algo)
continue;
if (!(sk->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
keyid_from_pk (sk, keyid);
if (!opt.quiet)
log_info (_("anonymous recipient; trying secret key %s ...\n"),
keystr (keyid));
rc = get_it (k, dek, sk, keyid);
if (!rc)
{
if (!opt.quiet)
log_info (_("okay, we are the anonymous recipient.\n"));
break;
}
else if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try any more secret keys. */
}
enum_secret_keys (ctrl, &enum_context, NULL); /* free context */
}
leave:
free_public_key (sk);
if (DBG_CLOCK)
log_clock ("get_session_key leave");
return rc;
}
static gpg_error_t
get_it (PKT_pubkey_enc *enc, DEK *dek, PKT_public_key *sk, u32 *keyid)
{
gpg_error_t err;
byte *frame = NULL;
unsigned int n;
size_t nframe;
u16 csum, csum2;
int padding;
gcry_sexp_t s_data;
char *desc;
char *keygrip;
byte fp[MAX_FINGERPRINT_LEN];
size_t fpn;
if (DBG_CLOCK)
log_clock ("decryption start");
/* Get the keygrip. */
err = hexkeygrip_from_pk (sk, &keygrip);
if (err)
goto leave;
/* Convert the data to an S-expression. */
if (sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL
|| sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E)
{
if (!enc->data[0] || !enc->data[1])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(elg(a%m)(b%m)))",
enc->data[0], enc->data[1]);
}
else if (sk->pubkey_algo == PUBKEY_ALGO_RSA
|| sk->pubkey_algo == PUBKEY_ALGO_RSA_E)
{
if (!enc->data[0])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(rsa(a%m)))",
enc->data[0]);
}
else if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
if (!enc->data[0] || !enc->data[1])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(ecdh(s%m)(e%m)))",
enc->data[1], enc->data[0]);
}
else
err = gpg_error (GPG_ERR_BUG);
if (err)
goto leave;
if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
fingerprint_from_pk (sk, fp, &fpn);
log_assert (fpn == 20);
}
/* Decrypt. */
desc = gpg_format_keydesc (sk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_pkdecrypt (NULL, keygrip,
desc, sk->keyid, sk->main_keyid, sk->pubkey_algo,
s_data, &frame, &nframe, &padding);
xfree (desc);
gcry_sexp_release (s_data);
if (err)
goto leave;
/* Now get the DEK (data encryption key) from the frame
*
* Old versions encode the DEK in this format (msb is left):
*
* 0 1 DEK(16 bytes) CSUM(2 bytes) 0 RND(n bytes) 2
*
* Later versions encode the DEK like this:
*
* 0 2 RND(n bytes) 0 A DEK(k bytes) CSUM(2 bytes)
*
* (mpi_get_buffer already removed the leading zero).
*
* RND are non-zero randow bytes.
* A is the cipher algorithm
* DEK is the encryption key (session key) with length k
* CSUM
*/
if (DBG_CRYPTO)
log_printhex ("DEK frame:", frame, nframe);
n = 0;
if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
gcry_mpi_t shared_mpi;
gcry_mpi_t decoded;
/* At the beginning the frame are the bytes of shared point MPI. */
err = gcry_mpi_scan (&shared_mpi, GCRYMPI_FMT_USG, frame, nframe, NULL);
if (err)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
err = pk_ecdh_decrypt (&decoded, fp, enc->data[1]/*encr data as an MPI*/,
shared_mpi, sk->pkey);
mpi_release (shared_mpi);
if(err)
goto leave;
xfree (frame);
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &frame, &nframe, decoded);
mpi_release (decoded);
if (err)
goto leave;
/* Now the frame are the bytes decrypted but padded session key. */
/* Allow double padding for the benefit of DEK size concealment.
Higher than this is wasteful. */
if (!nframe || frame[nframe-1] > 8*2 || nframe <= 8
|| frame[nframe-1] > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
nframe -= frame[nframe-1]; /* Remove padding. */
log_assert (!n); /* (used just below) */
}
else
{
if (padding)
{
if (n + 7 > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
if (frame[n] == 1 && frame[nframe - 1] == 2)
{
log_info (_("old encoding of the DEK is not supported\n"));
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
if (frame[n] != 2) /* Something went wrong. */
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
for (n++; n < nframe && frame[n]; n++) /* Skip the random bytes. */
;
n++; /* Skip the zero byte. */
}
}
if (n + 4 > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
dek->keylen = nframe - (n + 1) - 2;
dek->algo = frame[n++];
err = openpgp_cipher_test_algo (dek->algo);
if (err)
{
if (!opt.quiet && gpg_err_code (err) == GPG_ERR_CIPHER_ALGO)
{
log_info (_("cipher algorithm %d%s is unknown or disabled\n"),
dek->algo,
dek->algo == CIPHER_ALGO_IDEA ? " (IDEA)" : "");
}
dek->algo = 0;
goto leave;
}
if (dek->keylen != openpgp_cipher_get_algo_keylen (dek->algo))
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
/* Copy the key to DEK and compare the checksum. */
csum = buf16_to_u16 (frame+nframe-2);
memcpy (dek->key, frame + n, dek->keylen);
for (csum2 = 0, n = 0; n < dek->keylen; n++)
csum2 += dek->key[n];
if (csum != csum2)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
if (DBG_CLOCK)
log_clock ("decryption ready");
if (DBG_CRYPTO)
log_printhex ("DEK is:", dek->key, dek->keylen);
/* Check that the algo is in the preferences and whether it has
* expired. Also print a status line with the key's fingerprint. */
{
PKT_public_key *pk = NULL;
PKT_public_key *mainpk = NULL;
KBNODE pkb = get_pubkeyblock (keyid);
if (!pkb)
{
err = -1;
log_error ("oops: public key not found for preference check\n");
}
else if (pkb->pkt->pkt.public_key->selfsigversion > 3
&& dek->algo != CIPHER_ALGO_3DES
&& !opt.quiet
&& !is_algo_in_prefs (pkb, PREFTYPE_SYM, dek->algo))
log_info (_("WARNING: cipher algorithm %s not found in recipient"
" preferences\n"), openpgp_cipher_algo_name (dek->algo));
if (!err)
{
kbnode_t k;
int first = 1;
for (k = pkb; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
if (first)
{
first = 0;
mainpk = k->pkt->pkt.public_key;
}
keyid_from_pk (k->pkt->pkt.public_key, aki);
if (aki[0] == keyid[0] && aki[1] == keyid[1])
{
pk = k->pkt->pkt.public_key;
break;
}
}
}
if (!pk)
BUG ();
if (pk->expiredate && pk->expiredate <= make_timestamp ())
{
log_info (_("Note: secret key %s expired at %s\n"),
keystr (keyid), asctimestamp (pk->expiredate));
}
}
if (pk && pk->flags.revoked)
{
log_info (_("Note: key has been revoked"));
log_printf ("\n");
show_revocation_reason (pk, 1);
}
if (is_status_enabled () && pk && mainpk)
{
char pkhex[MAX_FINGERPRINT_LEN*2+1];
char mainpkhex[MAX_FINGERPRINT_LEN*2+1];
hexfingerprint (pk, pkhex, sizeof pkhex);
hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex);
/* Note that we do not want to create a trustdb just for
* getting the ownertrust: If there is no trustdb there can't
* be ulitmately trusted key anyway and thus the ownertrust
* value is irrelevant. */
write_status_printf (STATUS_DECRYPTION_KEY, "%s %s %c",
pkhex, mainpkhex,
get_ownertrust_info (mainpk, 1));
}
release_kbnode (pkb);
err = 0;
}
leave:
xfree (frame);
xfree (keygrip);
return err;
}
/*
* Get the session key from the given string.
* String is supposed to be formatted as this:
* <algo-id>:<even-number-of-hex-digits>
*/
gpg_error_t
get_override_session_key (DEK *dek, const char *string)
{
const char *s;
int i;
if (!string)
return GPG_ERR_BAD_KEY;
dek->algo = atoi (string);
if (dek->algo < 1)
return GPG_ERR_BAD_KEY;
if (!(s = strchr (string, ':')))
return GPG_ERR_BAD_KEY;
s++;
for (i = 0; i < DIM (dek->key) && *s; i++, s += 2)
{
int c = hextobyte (s);
if (c == -1)
return GPG_ERR_BAD_KEY;
dek->key[i] = c;
}
if (*s)
return GPG_ERR_BAD_KEY;
dek->keylen = i;
return 0;
}
diff --git a/g10/revoke.c b/g10/revoke.c
index 3a2b068fc..122699f8c 100644
--- a/g10/revoke.c
+++ b/g10/revoke.c
@@ -1,883 +1,882 @@
/* revoke.c - Create recovation certificates.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
-#include "ttyio.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
#include "call-agent.h"
struct revocation_reason_info {
int code;
char *desc;
};
int
revocation_reason_build_cb( PKT_signature *sig, void *opaque )
{
struct revocation_reason_info *reason = opaque;
char *ud = NULL;
byte *buffer;
size_t buflen = 1;
if(!reason)
return 0;
if( reason->desc ) {
ud = native_to_utf8( reason->desc );
buflen += strlen(ud);
}
buffer = xmalloc( buflen );
*buffer = reason->code;
if( ud ) {
memcpy(buffer+1, ud, strlen(ud) );
xfree( ud );
}
build_sig_subpkt( sig, SIGSUBPKT_REVOC_REASON, buffer, buflen );
xfree( buffer );
return 0;
}
/* Outputs a minimal pk (as defined by 2440) from a keyblock. A
minimal pk consists of the public key packet and a user ID. We try
and pick a user ID that has a uid signature, and include it if
possible. */
static int
export_minimal_pk(IOBUF out,KBNODE keyblock,
PKT_signature *revsig,PKT_signature *revkey)
{
KBNODE node;
PACKET pkt;
PKT_user_id *uid=NULL;
PKT_signature *selfsig=NULL;
u32 keyid[2];
int rc;
node=find_kbnode(keyblock,PKT_PUBLIC_KEY);
if(!node)
{
log_error("key incomplete\n");
return GPG_ERR_GENERAL;
}
keyid_from_pk(node->pkt->pkt.public_key,keyid);
pkt=*node->pkt;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
init_packet(&pkt);
pkt.pkttype=PKT_SIGNATURE;
/* the revocation itself, if any. 2440 likes this to come first. */
if(revsig)
{
pkt.pkt.signature=revsig;
rc=build_packet(out,&pkt);
if(rc)
{
log_error("build_packet failed: %s\n", gpg_strerror (rc) );
return rc;
}
}
/* If a revkey in a 1F sig is present, include it too */
if(revkey)
{
pkt.pkt.signature=revkey;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
}
while(!selfsig)
{
KBNODE signode;
node=find_next_kbnode(node,PKT_USER_ID);
if(!node)
{
/* We're out of user IDs - none were self-signed. */
if(uid)
break;
else
{
log_error(_("key %s has no user IDs\n"),keystr(keyid));
return GPG_ERR_GENERAL;
}
}
if(node->pkt->pkt.user_id->attrib_data)
continue;
uid=node->pkt->pkt.user_id;
signode=node;
while((signode=find_next_kbnode(signode,PKT_SIGNATURE)))
{
if(keyid[0]==signode->pkt->pkt.signature->keyid[0] &&
keyid[1]==signode->pkt->pkt.signature->keyid[1] &&
IS_UID_SIG(signode->pkt->pkt.signature))
{
selfsig=signode->pkt->pkt.signature;
break;
}
}
}
pkt.pkttype=PKT_USER_ID;
pkt.pkt.user_id=uid;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
if(selfsig)
{
pkt.pkttype=PKT_SIGNATURE;
pkt.pkt.signature=selfsig;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
}
return 0;
}
/****************
* Generate a revocation certificate for UNAME via a designated revoker
*/
int
gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr)
{
int rc = 0;
armor_filter_context_t *afx;
PKT_public_key *pk = NULL;
PKT_public_key *pk2 = NULL;
PKT_signature *sig = NULL;
IOBUF out = NULL;
struct revocation_reason_info *reason = NULL;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
KBNODE keyblock=NULL,node;
u32 keyid[2];
int i,any=0;
SK_LIST sk_list=NULL;
if( opt.batch )
{
log_error(_("can't do this in batch mode\n"));
return GPG_ERR_GENERAL;
}
afx = new_armor_context ();
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = classify_user_id (uname, &desc, 1);
if (!rc)
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (rc) {
log_error (_("key \"%s\" not found: %s\n"),uname, gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (kdbhd, &keyblock );
if( rc ) {
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
/* To parse the revkeys */
merge_keys_and_selfsig(keyblock);
/* get the key from the keyblock */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if( !node )
BUG ();
pk=node->pkt->pkt.public_key;
keyid_from_pk(pk,keyid);
if(locusr)
{
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if(rc)
goto leave;
}
/* Are we a designated revoker for this key? */
if(!pk->revkey && pk->numrevkeys)
BUG();
for(i=0;i<pk->numrevkeys;i++)
{
SK_LIST list;
free_public_key (pk2);
pk2 = NULL;
if(sk_list)
{
for(list=sk_list;list;list=list->next)
{
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
fingerprint_from_pk (list->pk, fpr, &fprlen);
/* Don't get involved with keys that don't have 160
bit fingerprints */
if(fprlen!=20)
continue;
if(memcmp(fpr,pk->revkey[i].fpr,20)==0)
break;
}
if (list)
pk2 = copy_public_key (NULL, list->pk);
else
continue;
}
else
{
pk2 = xmalloc_clear (sizeof *pk2);
rc = get_pubkey_byfprint (pk2, NULL,
pk->revkey[i].fpr, MAX_FINGERPRINT_LEN);
}
/* We have the revocation key. */
if(!rc)
{
PKT_signature *revkey = NULL;
any = 1;
print_pubkey_info (NULL, pk);
tty_printf ("\n");
tty_printf (_("To be revoked by:\n"));
print_seckey_info (pk2);
if(pk->revkey[i].class&0x40)
tty_printf(_("(This is a sensitive revocation key)\n"));
tty_printf("\n");
rc = agent_probe_secret_key (ctrl, pk2);
if (rc)
{
tty_printf (_("Secret key is not available.\n"));
continue;
}
if( !cpr_get_answer_is_yes("gen_desig_revoke.okay",
_("Create a designated revocation certificate for this key? (y/N) ")))
continue;
/* get the reason for the revocation (this is always v4) */
reason = ask_revocation_reason( 1, 0, 1 );
if( !reason )
continue;
if( !opt.armor )
tty_printf(_("ASCII armored output forced.\n"));
if( (rc = open_outfile (-1, NULL, 0, 1, &out )) )
goto leave;
afx->what = 1;
afx->hdrlines = "Comment: A designated revocation certificate"
" should follow\n";
push_armor_filter (afx, out);
/* create it */
rc = make_keysig_packet( &sig, pk, NULL, NULL, pk2, 0x20, 0,
0, 0,
revocation_reason_build_cb, reason,
NULL);
if( rc ) {
log_error(_("make_keysig_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
/* Spit out a minimal pk as well, since otherwise there is
no way to know which key to attach this revocation to.
Also include the direct key signature that contains
this revocation key. We're allowed to include
sensitive revocation keys along with a revocation, as
this may be the only time the recipient has seen it.
Note that this means that if we have multiple different
sensitive revocation keys in a given direct key
signature, we're going to include them all here. This
is annoying, but the good outweighs the bad, since
without including this a sensitive revoker can't really
do their job. People should not include multiple
sensitive revocation keys in one signature: 2440 says
"Note that it may be appropriate to isolate this
subpacket within a separate signature so that it is not
combined with other subpackets that need to be
exported." -dms */
while(!revkey)
{
KBNODE signode;
signode=find_next_kbnode(node,PKT_SIGNATURE);
if(!signode)
break;
node=signode;
if(keyid[0]==signode->pkt->pkt.signature->keyid[0] &&
keyid[1]==signode->pkt->pkt.signature->keyid[1] &&
IS_KEY_SIG(signode->pkt->pkt.signature))
{
int j;
for(j=0;j<signode->pkt->pkt.signature->numrevkeys;j++)
{
if(pk->revkey[i].class==
signode->pkt->pkt.signature->revkey[j].class &&
pk->revkey[i].algid==
signode->pkt->pkt.signature->revkey[j].algid &&
memcmp(pk->revkey[i].fpr,
signode->pkt->pkt.signature->revkey[j].fpr,
MAX_FINGERPRINT_LEN)==0)
{
revkey=signode->pkt->pkt.signature;
break;
}
}
}
}
if(!revkey)
BUG();
rc=export_minimal_pk(out,keyblock,sig,revkey);
if(rc)
goto leave;
/* and issue a usage notice */
tty_printf(_("Revocation certificate created.\n"));
break;
}
}
if(!any)
log_error(_("no revocation keys found for \"%s\"\n"),uname);
leave:
free_public_key (pk);
free_public_key (pk2);
if( sig )
free_seckey_enc( sig );
release_sk_list(sk_list);
if( rc )
iobuf_cancel(out);
else
iobuf_close(out);
release_revocation_reason_info( reason );
release_armor_context (afx);
return rc;
}
/* Common core to create the revocation. FILENAME may be NULL to write
to stdout or the filename given by --output. REASON describes the
revocation reason. PSK is the public primary key - we expect that
a corresponding secret key is available. KEYBLOCK is the entire
KEYBLOCK which is used in PGP mode to write a minimal key and not
just the naked revocation signature; it may be NULL. If LEADINTEXT
is not NULL, it is written right before the (armored) output.*/
static int
create_revocation (const char *filename,
struct revocation_reason_info *reason,
PKT_public_key *psk,
kbnode_t keyblock,
const char *leadintext, int suffix,
const char *cache_nonce)
{
int rc;
iobuf_t out = NULL;
armor_filter_context_t *afx;
PKT_signature *sig = NULL;
PACKET pkt;
afx = new_armor_context ();
if ((rc = open_outfile (-1, filename, suffix, 1, &out)))
goto leave;
if (leadintext )
iobuf_writestr (out, leadintext);
afx->what = 1;
afx->hdrlines = "Comment: This is a revocation certificate\n";
push_armor_filter (afx, out);
rc = make_keysig_packet (&sig, psk, NULL, NULL, psk, 0x20, 0,
0, 0,
revocation_reason_build_cb, reason, cache_nonce);
if (rc)
{
log_error (_("make_keysig_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
if (keyblock && (PGP6 || PGP7 || PGP8))
{
/* Use a minimal pk for PGPx mode, since PGP can't import bare
revocation certificates. */
rc = export_minimal_pk (out, keyblock, sig, NULL);
if (rc)
goto leave;
}
else
{
init_packet (&pkt);
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
rc = build_packet (out, &pkt);
if (rc)
{
log_error (_("build_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
}
leave:
if (sig)
free_seckey_enc (sig);
if (rc)
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* This function is used to generate a standard revocation certificate
by gpg's interactive key generation function. The certificate is
stored at a dedicated place in a slightly modified form to avoid an
accidental import. PSK is the primary key; a corresponding secret
key must be available. CACHE_NONCE is optional but can be used to
help gpg-agent to avoid an extra passphrase prompt. */
int
gen_standard_revoke (PKT_public_key *psk, const char *cache_nonce)
{
int rc;
estream_t memfp;
struct revocation_reason_info reason;
char *dir, *tmpstr, *fname;
void *leadin;
size_t len;
u32 keyid[2];
int kl;
char *orig_codeset;
dir = get_openpgp_revocdir (gnupg_homedir ());
tmpstr = hexfingerprint (psk, NULL, 0);
fname = xstrconcat (dir, DIRSEP_S, tmpstr, NULL);
xfree (tmpstr);
xfree (dir);
keyid_from_pk (psk, keyid);
memfp = es_fopenmem (0, "r+");
if (!memfp)
log_fatal ("error creating memory stream\n");
orig_codeset = i18n_switchto_utf8 ();
es_fprintf (memfp, "%s\n\n",
_("This is a revocation certificate for the OpenPGP key:"));
print_key_line (memfp, psk, 0);
if (opt.keyid_format != KF_NONE)
print_fingerprint (memfp, psk, 3);
kl = opt.keyid_format == KF_NONE? 0 : keystrlen ();
tmpstr = get_user_id (keyid, &len);
es_fprintf (memfp, "uid%*s%.*s\n\n",
kl + 10, "",
(int)len, tmpstr);
xfree (tmpstr);
es_fprintf (memfp, "%s\n\n%s\n\n%s\n\n:",
_("A revocation certificate is a kind of \"kill switch\" to publicly\n"
"declare that a key shall not anymore be used. It is not possible\n"
"to retract such a revocation certificate once it has been published."),
_("Use it to revoke this key in case of a compromise or loss of\n"
"the secret key. However, if the secret key is still accessible,\n"
"it is better to generate a new revocation certificate and give\n"
"a reason for the revocation. For details see the description of\n"
"of the gpg command \"--generate-revocation\" in the "
"GnuPG manual."),
_("To avoid an accidental use of this file, a colon has been inserted\n"
"before the 5 dashes below. Remove this colon with a text editor\n"
"before importing and publishing this revocation certificate."));
es_putc (0, memfp);
i18n_switchback (orig_codeset);
if (es_fclose_snatch (memfp, &leadin, NULL))
log_fatal ("error snatching memory stream\n");
reason.code = 0x00; /* No particular reason. */
reason.desc = NULL;
rc = create_revocation (fname, &reason, psk, NULL, leadin, 3, cache_nonce);
if (!rc && !opt.quiet)
log_info (_("revocation certificate stored as '%s.rev'\n"), fname);
xfree (leadin);
xfree (fname);
return rc;
}
/****************
* Generate a revocation certificate for UNAME
*/
int
gen_revoke (const char *uname)
{
int rc = 0;
PKT_public_key *psk;
u32 keyid[2];
kbnode_t keyblock = NULL;
kbnode_t node;
KEYDB_HANDLE kdbhd;
struct revocation_reason_info *reason = NULL;
KEYDB_SEARCH_DESC desc;
if( opt.batch )
{
log_error(_("can't do this in batch mode\n"));
return GPG_ERR_GENERAL;
}
/* Search the userid; we don't want the whole getkey stuff here. */
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = classify_user_id (uname, &desc, 1);
if (!rc)
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
log_error (_("secret key \"%s\" not found\n"), uname);
else
log_error (_("secret key \"%s\" not found: %s\n"),
uname, gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (kdbhd, &keyblock );
if (rc)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
/* Not ambiguous. */
{
}
else if (rc == 0)
/* Ambiguous. */
{
char *info;
/* TRANSLATORS: The %s prints a key specification which
for example has been given at the command line. Several lines
lines with secret key infos are printed after this message. */
log_error (_("'%s' matches multiple secret keys:\n"), uname);
info = format_seckey_info (keyblock->pkt->pkt.public_key);
log_error (" %s\n", info);
xfree (info);
release_kbnode (keyblock);
rc = keydb_get_keyblock (kdbhd, &keyblock);
while (! rc)
{
info = format_seckey_info (keyblock->pkt->pkt.public_key);
log_info (" %s\n", info);
xfree (info);
release_kbnode (keyblock);
keyblock = NULL;
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (! rc)
rc = keydb_get_keyblock (kdbhd, &keyblock);
}
rc = GPG_ERR_AMBIGUOUS_NAME;
goto leave;
}
else
{
log_error (_("error searching the keyring: %s\n"), gpg_strerror (rc));
goto leave;
}
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
BUG ();
psk = node->pkt->pkt.public_key;
rc = agent_probe_secret_key (NULL, psk);
if (rc)
{
log_error (_("secret key \"%s\" not found: %s\n"),
uname, gpg_strerror (rc));
goto leave;
}
keyid_from_pk (psk, keyid );
print_seckey_info (psk);
tty_printf("\n");
if (!cpr_get_answer_is_yes ("gen_revoke.okay",
_("Create a revocation certificate for this key? (y/N) ")))
{
rc = 0;
goto leave;
}
/* Get the reason for the revocation. */
reason = ask_revocation_reason (1, 0, 1);
if (!reason)
{
/* User decided to cancel. */
rc = 0;
goto leave;
}
if (!opt.armor)
tty_printf (_("ASCII armored output forced.\n"));
rc = create_revocation (NULL, reason, psk, keyblock, NULL, 0, NULL);
if (rc)
goto leave;
/* and issue a usage notice */
tty_printf (_(
"Revocation certificate created.\n\n"
"Please move it to a medium which you can hide away; if Mallory gets\n"
"access to this certificate he can use it to make your key unusable.\n"
"It is smart to print this certificate and store it away, just in case\n"
"your media become unreadable. But have some caution: The print system of\n"
"your machine might store the data and make it available to others!\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
release_revocation_reason_info( reason );
return rc;
}
struct revocation_reason_info *
ask_revocation_reason( int key_rev, int cert_rev, int hint )
{
int code=-1;
char *description = NULL;
struct revocation_reason_info *reason;
const char *text_0 = _("No reason specified");
const char *text_1 = _("Key has been compromised");
const char *text_2 = _("Key is superseded");
const char *text_3 = _("Key is no longer used");
const char *text_4 = _("User ID is no longer valid");
const char *code_text = NULL;
do {
code=-1;
xfree(description);
description = NULL;
tty_printf(_("Please select the reason for the revocation:\n"));
tty_printf( " 0 = %s\n", text_0 );
if( key_rev )
tty_printf(" 1 = %s\n", text_1 );
if( key_rev )
tty_printf(" 2 = %s\n", text_2 );
if( key_rev )
tty_printf(" 3 = %s\n", text_3 );
if( cert_rev )
tty_printf(" 4 = %s\n", text_4 );
tty_printf( " Q = %s\n", _("Cancel") );
if( hint )
tty_printf(_("(Probably you want to select %d here)\n"), hint );
while(code==-1) {
int n;
char *answer = cpr_get("ask_revocation_reason.code",
_("Your decision? "));
trim_spaces( answer );
cpr_kill_prompt();
if( *answer == 'q' || *answer == 'Q')
return NULL; /* cancel */
if( hint && !*answer )
n = hint;
else if(!digitp( answer ) )
n = -1;
else
n = atoi(answer);
xfree(answer);
if( n == 0 ) {
code = 0x00; /* no particular reason */
code_text = text_0;
}
else if( key_rev && n == 1 ) {
code = 0x02; /* key has been compromised */
code_text = text_1;
}
else if( key_rev && n == 2 ) {
code = 0x01; /* key is superseded */
code_text = text_2;
}
else if( key_rev && n == 3 ) {
code = 0x03; /* key is no longer used */
code_text = text_3;
}
else if( cert_rev && n == 4 ) {
code = 0x20; /* uid is no longer valid */
code_text = text_4;
}
else
tty_printf(_("Invalid selection.\n"));
}
tty_printf(_("Enter an optional description; "
"end it with an empty line:\n") );
for(;;) {
char *answer = cpr_get("ask_revocation_reason.text", "> " );
trim_trailing_ws( answer, strlen(answer) );
cpr_kill_prompt();
if( !*answer ) {
xfree(answer);
break;
}
{
char *p = make_printable_string( answer, strlen(answer), 0 );
xfree(answer);
answer = p;
}
if( !description )
description = xstrdup(answer);
else {
char *p = xmalloc( strlen(description) + strlen(answer) + 2 );
strcpy(stpcpy(stpcpy( p, description),"\n"),answer);
xfree(description);
description = p;
}
xfree(answer);
}
tty_printf(_("Reason for revocation: %s\n"), code_text );
if( !description )
tty_printf(_("(No description given)\n") );
else
tty_printf("%s\n", description );
} while( !cpr_get_answer_is_yes("ask_revocation_reason.okay",
_("Is this okay? (y/N) ")) );
reason = xmalloc( sizeof *reason );
reason->code = code;
reason->desc = description;
return reason;
}
struct revocation_reason_info *
get_default_uid_revocation_reason(void)
{
struct revocation_reason_info *reason;
reason = xmalloc( sizeof *reason );
reason->code = 0x20; /* uid is no longer valid */
reason->desc = strdup(""); /* no text */
return reason;
}
void
release_revocation_reason_info( struct revocation_reason_info *reason )
{
if( reason ) {
xfree( reason->desc );
xfree( reason );
}
}
diff --git a/g10/server.c b/g10/server.c
index e3a3bad22..60b447c41 100644
--- a/g10/server.c
+++ b/g10/server.c
@@ -1,799 +1,799 @@
/* server.c - server mode for gpg
* Copyright (C) 2006, 2008 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include "gpg.h"
#include <assuan.h>
-#include "util.h"
-#include "i18n.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
#include "options.h"
#include "../common/server-help.h"
#include "../common/sysutils.h"
-#include "status.h"
+#include "../common/status.h"
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Data used to associate an Assuan context with local server data. */
struct server_local_s
{
/* Our current Assuan context. */
assuan_context_t assuan_ctx;
/* File descriptor as set by the MESSAGE command. */
gnupg_fd_t message_fd;
/* List of prepared recipients. */
pk_list_t recplist;
/* Set if pinentry notifications should be passed back to the
client. */
int allow_pinentry_notify;
};
/* Helper to close the message fd if it is open. */
static void
close_message_fd (ctrl_t ctrl)
{
if (ctrl->server_local->message_fd != GNUPG_INVALID_FD)
{
assuan_sock_close (ctrl->server_local->message_fd);
ctrl->server_local->message_fd = GNUPG_INVALID_FD;
}
}
/* Called by libassuan for Assuan options. See the Assuan manual for
details. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)value;
/* Fixme: Implement the tty and locale args. */
if (!strcmp (key, "display"))
{
}
else if (!strcmp (key, "ttyname"))
{
}
else if (!strcmp (key, "ttytype"))
{
}
else if (!strcmp (key, "lc-ctype"))
{
}
else if (!strcmp (key, "lc-messages"))
{
}
else if (!strcmp (key, "xauthority"))
{
}
else if (!strcmp (key, "pinentry_user_data"))
{
}
else if (!strcmp (key, "list-mode"))
{
/* This is for now a dummy option. */
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
ctrl->server_local->allow_pinentry_notify = 1;
}
else
return gpg_error (GPG_ERR_UNKNOWN_OPTION);
return 0;
}
/* Called by libassuan for RESET commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
release_pk_list (ctrl->server_local->recplist);
ctrl->server_local->recplist = NULL;
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
/* Called by libassuan for INPUT commands. */
static gpg_error_t
input_notify (assuan_context_t ctx, char *line)
{
/* ctrl_t ctrl = assuan_get_pointer (ctx); */
(void)ctx;
if (strstr (line, "--armor"))
; /* FIXME */
else if (strstr (line, "--base64"))
; /* FIXME */
else if (strstr (line, "--binary"))
;
else
{
/* FIXME (autodetect encoding) */
}
return 0;
}
/* Called by libassuan for OUTPUT commands. */
static gpg_error_t
output_notify (assuan_context_t ctx, char *line)
{
/* ctrl_t ctrl = assuan_get_pointer (ctx); */
(void)ctx;
if (strstr (line, "--armor"))
; /* FIXME */
else if (strstr (line, "--base64"))
{
/* FIXME */
}
return 0;
}
/* RECIPIENT [--hidden] <userID>
RECIPIENT [--hidden] --file <filename>
Set the recipient for the encryption. <userID> should be the
internal representation of the key; the server may accept any other
way of specification. If this is a valid and trusted recipient the
server does respond with OK, otherwise the return is an ERR with
the reason why the recipient can't be used, the encryption will
then not be done for this recipient. If the policy is not to
encrypt at all if not all recipients are valid, the client has to
take care of this. All RECIPIENT commands are cumulative until a
RESET or an successful ENCRYPT command. */
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int hidden, file;
hidden = has_option (line,"--hidden");
file = has_option (line,"--file");
line = skip_options (line);
/* FIXME: Expand groups
if (opt.grouplist)
remusr = expand_group (rcpts);
else
remusr = rcpts;
*/
err = find_and_check_key (ctrl, line, PUBKEY_USAGE_ENC, hidden, file,
&ctrl->server_local->recplist);
if (err)
log_error ("command '%s' failed: %s\n", "RECIPIENT", gpg_strerror (err));
return err;
}
/* SIGNER <userID>
Set the signer's keys for the signature creation. <userID> should
be the internal representation of the key; the server may accept
any other way of specification. If this is a valid and usable
signing key the server does respond with OK, otherwise it returns
an ERR with the reason why the key can't be used, the signing will
then not be done for this key. If the policy is not to sign at all
if not all signer keys are valid, the client has to take care of
this. All SIGNER commands are cumulative until a RESET but they
are *not* reset by an SIGN command because it can be expected that
set of signers are used for more than one sign operation.
Note that this command returns an INV_RECP status which is a bit
strange, but they are very similar. */
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* ENCRYPT
Do the actual encryption process. Takes the plaintext from the
INPUT command, writes the ciphertext to the file descriptor set
with the OUTPUT command, take the recipients from all the
recipients set so far with RECIPIENTS.
If this command fails the clients should try to delete all output
currently done or otherwise mark it as invalid. GPG does ensure
that there won't be any security problem with leftover data on the
output in this case.
In most cases this command won't fail because most necessary checks
have been done while setting the recipients. However some checks
can only be done right here and thus error may occur anyway (for
example, no recipients at all).
The input, output and message pipes are closed after this
command. */
static gpg_error_t
cmd_encrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int inp_fd, out_fd;
(void)line; /* LINE is not used. */
if ( !ctrl->server_local->recplist )
{
write_status_text (STATUS_NO_RECP, "0");
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
{
err = set_error (GPG_ERR_ASS_NO_INPUT, NULL);
goto leave;
}
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
{
err = set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
goto leave;
}
/* FIXME: GPGSM does this here: Add all encrypt-to marked recipients
from the default list. */
/* fixme: err = ctrl->audit? 0 : start_audit_session (ctrl);*/
err = encrypt_crypt (ctrl, inp_fd, NULL, NULL, 0,
ctrl->server_local->recplist,
out_fd);
leave:
/* Release the recipient list on success. */
if (!err)
{
release_pk_list (ctrl->server_local->recplist);
ctrl->server_local->recplist = NULL;
}
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
if (err)
log_error ("command '%s' failed: %s\n", "ENCRYPT", gpg_strerror (err));
return err;
}
/* DECRYPT
This performs the decrypt operation. */
static gpg_error_t
cmd_decrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int inp_fd, out_fd;
(void)line; /* LINE is not used. */
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
glo_ctrl.lasterr = 0;
err = decrypt_message_fd (ctrl, inp_fd, out_fd);
if (!err)
err = glo_ctrl.lasterr;
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
if (err)
log_error ("command '%s' failed: %s\n", "DECRYPT", gpg_strerror (err));
return err;
}
/* VERIFY
This does a verify operation on the message send to the input-FD.
The result is written out using status lines. If an output FD was
given, the signed text will be written to that.
If the signature is a detached one, the server will inquire about
the signed material and the client must provide it.
*/
static gpg_error_t
cmd_verify (assuan_context_t ctx, char *line)
{
int rc;
#ifdef HAVE_W32_SYSTEM
(void)ctx;
(void)line;
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#else
ctrl_t ctrl = assuan_get_pointer (ctx);
gnupg_fd_t fd = assuan_get_input_fd (ctx);
gnupg_fd_t out_fd = assuan_get_output_fd (ctx);
estream_t out_fp = NULL;
/* FIXME: Revamp this code it is nearly to 3 years old and was only
intended as a quick test. */
(void)line;
if (fd == GNUPG_INVALID_FD)
return gpg_error (GPG_ERR_ASS_NO_INPUT);
if (out_fd != GNUPG_INVALID_FD)
{
es_syshd_t syshd;
#ifdef HAVE_W32_SYSTEM
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = out_fd;
#else
syshd.type = ES_SYSHD_FD;
syshd.u.fd = out_fd;
#endif
out_fp = es_sysopen_nc (&syshd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
log_debug ("WARNING: The server mode is WORK "
"IN PROGRESS and not ready for use\n");
rc = gpg_verify (ctrl, fd, ctrl->server_local->message_fd, out_fp);
es_fclose (out_fp);
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
#endif
if (rc)
log_error ("command '%s' failed: %s\n", "VERIFY", gpg_strerror (rc));
return rc;
}
/* SIGN [--detached]
Sign the data set with the INPUT command and write it to the sink
set by OUTPUT. With "--detached" specified, a detached signature
is created. */
static gpg_error_t
cmd_sign (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* IMPORT
Import keys as read from the input-fd, return status message for
each imported one. The import checks the validity of the key. */
static gpg_error_t
cmd_import (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* EXPORT [--data [--armor|--base64]] [--] pattern
Similar to the --export command line command, this command exports
public keys matching PATTERN. The output is send to the output fd
unless the --data option has been used in which case the output
gets send inline using regular data lines. The options "--armor"
and "--base" ospecify an output format if "--data" has been used.
Recall that in general the output format is set with the OUTPUT
command.
*/
static gpg_error_t
cmd_export (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* DELKEYS
Fixme
*/
static gpg_error_t
cmd_delkeys (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* MESSAGE FD[=<n>]
Set the file descriptor to read a message which is used with
detached signatures. */
static gpg_error_t
cmd_message (assuan_context_t ctx, char *line)
{
int rc;
gnupg_fd_t fd;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = assuan_command_parse_fd (ctx, line, &fd);
if (rc)
return rc;
if (fd == GNUPG_INVALID_FD)
return gpg_error (GPG_ERR_ASS_NO_INPUT);
ctrl->server_local->message_fd = fd;
return 0;
}
/* LISTKEYS [<patterns>]
LISTSECRETKEYS [<patterns>]
fixme
*/
static gpg_error_t
do_listkeys (assuan_context_t ctx, char *line, int mode)
{
(void)ctx;
(void)line;
(void)mode;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static gpg_error_t
cmd_listkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 3);
}
static gpg_error_t
cmd_listsecretkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 2);
}
/* GENKEY
Read the parameters in native format from the input fd and create a
new OpenPGP key.
*/
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* GETINFO <what>
Multipurpose function to return a variety of information.
Supported values for WHAT are:
version - Return the version of the program.
pid - Return the process id of the server.
*/
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_passwd[] =
"PASSWD <userID>\n"
"\n"
"Change the passphrase of the secret key for USERID.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
/* ctrl_t ctrl = assuan_get_pointer (ctx); */
gpg_error_t err;
(void)ctx;
(void)line;
/* line = skip_options (line); */
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
return err;
}
/* Helper to register our commands with libassuan. */
static int
register_commands (assuan_context_t ctx)
{
static struct
{
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "RECIPIENT", cmd_recipient },
{ "SIGNER", cmd_signer },
{ "ENCRYPT", cmd_encrypt },
{ "DECRYPT", cmd_decrypt },
{ "VERIFY", cmd_verify },
{ "SIGN", cmd_sign },
{ "IMPORT", cmd_import },
{ "EXPORT", cmd_export },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "MESSAGE", cmd_message },
{ "LISTKEYS", cmd_listkeys },
{ "LISTSECRETKEYS",cmd_listsecretkeys },
{ "GENKEY", cmd_genkey },
{ "DELKEYS", cmd_delkeys },
{ "GETINFO", cmd_getinfo },
{ "PASSWD", cmd_passwd, hlp_passwd},
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name,
table[i].handler, table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Startup the server. CTRL must have been allocated by the caller
and set to the default values. */
int
gpg_server (ctrl_t ctrl)
{
int rc;
#ifndef HAVE_W32_SYSTEM
int filedes[2];
#endif
assuan_context_t ctx = NULL;
static const char hello[] = ("GNU Privacy Guard's OpenPGP server "
VERSION " ready");
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FILEDES in this case. */
#ifndef HAVE_W32_SYSTEM
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
#endif
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate the assuan context: %s\n",
gpg_strerror (rc));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#else
rc = assuan_init_pipe_server (ctx, filedes);
#endif
if (rc)
{
log_error ("failed to initialize the server: %s\n", gpg_strerror (rc));
goto leave;
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror(rc));
goto leave;
}
assuan_set_pointer (ctx, ctrl);
if (opt.verbose || opt.debug)
{
char *tmp;
tmp = xtryasprintf ("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
"fixme: need config filename",
hello);
if (tmp)
{
assuan_set_hello_line (ctx, tmp);
xfree (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_input_notify (ctx, input_notify);
assuan_register_output_notify (ctx, output_notify);
assuan_register_option_handler (ctx, option_handler);
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->message_fd = GNUPG_INVALID_FD;
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
rc = 0;
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
leave:
if (ctrl->server_local)
{
release_pk_list (ctrl->server_local->recplist);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
assuan_release (ctx);
return rc;
}
/* Helper to notify the client about Pinentry events. Because that
might disturb some older clients, this is only done when enabled
via an option. If it is not enabled we tell Windows to allow
setting the foreground window right here. Returns an gpg error
code. */
gpg_error_t
gpg_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
const char *s;
if (opt.verbose
&& !strncmp (line, "PINENTRY_LAUNCHED", 17)
&& (line[17]==' '||!line[17]))
{
for (s = line + 17; *s && spacep (s); s++)
;
log_info (_("pinentry launched (%s)\n"), s);
}
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
{
gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
/* Client might be interested in that event - send as status line. */
if (!strncmp (line, "PINENTRY_LAUNCHED", 17)
&& (line[17]==' '||!line[17]))
{
for (line += 17; *line && spacep (line); line++)
;
write_status_text (STATUS_PINENTRY_LAUNCHED, line);
}
return 0;
}
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
diff --git a/g10/seskey.c b/g10/seskey.c
index b2f716960..861793858 100644
--- a/g10/seskey.c
+++ b/g10/seskey.c
@@ -1,359 +1,359 @@
/* seskey.c - make sesssion keys etc.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2006, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/* Generate a new session key in *DEK that is appropriate for the
* algorithm DEK->ALGO (i.e., ensure that the key is not weak).
*
* This function overwrites DEK->KEYLEN, DEK->KEY. The rest of the
* fields are left as is. */
void
make_session_key( DEK *dek )
{
gcry_cipher_hd_t chd;
int i, rc;
dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo);
if (openpgp_cipher_open (&chd, dek->algo, GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| (dek->algo >= 100 ?
0 : GCRY_CIPHER_ENABLE_SYNC))) )
BUG();
gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM );
for (i=0; i < 16; i++ )
{
rc = gcry_cipher_setkey (chd, dek->key, dek->keylen);
if (!rc)
{
gcry_cipher_close (chd);
return;
}
if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY)
BUG();
log_info(_("weak key created - retrying\n") );
/* Renew the session key until we get a non-weak key. */
gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM);
}
log_fatal (_("cannot avoid weak key for symmetric cipher; "
"tried %d times!\n"), i);
}
/* Encode the session key stored in DEK as an MPI in preparation to
* encrypt it with the public key algorithm OPENPGP_PK_ALGO with a key
* whose length (the size of the public key) is NBITS.
*
* On success, returns an MPI, which the caller must free using
* gcry_mpi_release(). */
gcry_mpi_t
encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits)
{
size_t nframe = (nbits+7) / 8;
byte *p;
byte *frame;
int i,n;
u16 csum;
gcry_mpi_t a;
if (DBG_CRYPTO)
log_debug ("encode_session_key: encoding %d byte DEK", dek->keylen);
csum = 0;
for (p = dek->key, i=0; i < dek->keylen; i++)
csum += *p++;
/* Shortcut for ECDH. It's padding is minimal to simply make the
output be a multiple of 8 bytes. */
if (openpgp_pk_algo == PUBKEY_ALGO_ECDH)
{
/* Pad to 8 byte granulatiry; the padding byte is the number of
* padded bytes.
*
* A DEK(k bytes) CSUM(2 bytes) 0x 0x 0x 0x ... 0x
* +---- x times ---+
*/
nframe = (( 1 + dek->keylen + 2 /* The value so far is always odd. */
+ 7 ) & (~7));
/* alg+key+csum fit and the size is congruent to 8. */
log_assert (!(nframe%8) && nframe > 1 + dek->keylen + 2 );
frame = xmalloc_secure (nframe);
n = 0;
frame[n++] = dek->algo;
memcpy (frame+n, dek->key, dek->keylen);
n += dek->keylen;
frame[n++] = csum >> 8;
frame[n++] = csum;
i = nframe - n; /* Number of padded bytes. */
memset (frame+n, i, i); /* Use it as the value of each padded byte. */
log_assert (n+i == nframe);
if (DBG_CRYPTO)
log_debug ("encode_session_key: "
"[%d] %02x %02x %02x ... %02x %02x %02x\n",
(int) nframe, frame[0], frame[1], frame[2],
frame[nframe-3], frame[nframe-2], frame[nframe-1]);
if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, nframe, &nframe))
BUG();
xfree(frame);
return a;
}
/* The current limitation is that we can only use a session key
* whose length is a multiple of BITS_PER_MPI_LIMB
* I think we can live with that.
*/
if (dek->keylen + 7 > nframe || !nframe)
log_bug ("can't encode a %d bit key in a %d bits frame\n",
dek->keylen*8, nbits );
/* We encode the session key according to PKCS#1 v1.5 (see section
* 13.1.1 of RFC 4880):
*
* 0 2 RND(i bytes) 0 A DEK(k bytes) CSUM(2 bytes)
*
* (But how can we store the leading 0 - the external representaion
* of MPIs doesn't allow leading zeroes =:-)
*
* RND are (at least 1) non-zero random bytes.
* A is the cipher algorithm
* DEK is the encryption key (session key) length k depends on the
* cipher algorithm (20 is used with blowfish160).
* CSUM is the 16 bit checksum over the DEK
*/
frame = xmalloc_secure( nframe );
n = 0;
frame[n++] = 0;
frame[n++] = 2;
/* The number of random bytes are the number of otherwise unused
bytes. See diagram above. */
i = nframe - 6 - dek->keylen;
log_assert( i > 0 );
p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM);
/* Replace zero bytes by new values. */
for (;;)
{
int j, k;
byte *pp;
/* Count the zero bytes. */
for (j=k=0; j < i; j++ )
if (!p[j])
k++;
if (!k)
break; /* Okay: no zero bytes. */
k += k/128 + 3; /* Better get some more. */
pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM);
for (j=0; j < i && k ;)
{
if (!p[j])
p[j] = pp[--k];
if (p[j])
j++;
}
xfree (pp);
}
memcpy (frame+n, p, i);
xfree (p);
n += i;
frame[n++] = 0;
frame[n++] = dek->algo;
memcpy (frame+n, dek->key, dek->keylen );
n += dek->keylen;
frame[n++] = csum >>8;
frame[n++] = csum;
log_assert (n == nframe);
if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe))
BUG();
xfree (frame);
return a;
}
static gcry_mpi_t
do_encode_md( gcry_md_hd_t md, int algo, size_t len, unsigned nbits,
const byte *asn, size_t asnlen )
{
size_t nframe = (nbits+7) / 8;
byte *frame;
int i,n;
gcry_mpi_t a;
if (len + asnlen + 4 > nframe)
{
log_error ("can't encode a %d bit MD into a %d bits frame, algo=%d\n",
(int)(len*8), (int)nbits, algo);
return NULL;
}
/* We encode the MD in this way:
*
* 0 1 PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes)
*
* PAD consists of FF bytes.
*/
frame = gcry_md_is_secure (md)? xmalloc_secure (nframe) : xmalloc (nframe);
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* block type */
i = nframe - len - asnlen -3 ;
log_assert( i > 1 );
memset( frame+n, 0xff, i ); n += i;
frame[n++] = 0;
memcpy( frame+n, asn, asnlen ); n += asnlen;
memcpy( frame+n, gcry_md_read (md, algo), len ); n += len;
log_assert( n == nframe );
if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe ))
BUG();
xfree(frame);
/* Note that PGP before version 2.3 encoded the MD as:
*
* 0 1 MD(16 bytes) 0 PAD(n bytes) 1
*
* The MD is always 16 bytes here because it's always MD5. We do
* not support pre-v2.3 signatures, but I'm including this comment
* so the information is easily found in the future.
*/
return a;
}
/****************
* Encode a message digest into an MPI.
* If it's for a DSA signature, make sure that the hash is large
* enough to fill up q. If the hash is too big, take the leftmost
* bits.
*/
gcry_mpi_t
encode_md_value (PKT_public_key *pk, gcry_md_hd_t md, int hash_algo)
{
gcry_mpi_t frame;
size_t mdlen;
log_assert (hash_algo);
log_assert (pk);
if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
/* EdDSA signs data of arbitrary length. Thus no special
treatment is required. */
frame = gcry_mpi_set_opaque_copy (NULL, gcry_md_read (md, hash_algo),
8*gcry_md_get_algo_dlen (hash_algo));
}
else if (pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
/* It's a DSA signature, so find out the size of q. */
size_t qbits = gcry_mpi_get_nbits (pk->pkey[1]);
/* pkey[1] is Q for ECDSA, which is an uncompressed point,
i.e. 04 <x> <y> */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
qbits = ecdsa_qbits_from_Q (qbits);
/* Make sure it is a multiple of 8 bits. */
if ((qbits%8))
{
log_error(_("DSA requires the hash length to be a"
" multiple of 8 bits\n"));
return NULL;
}
/* Don't allow any q smaller than 160 bits. This might need a
revisit as the DSA2 design firms up, but for now, we don't
want someone to issue signatures from a key with a 16-bit q
or something like that, which would look correct but allow
trivial forgeries. Yes, I know this rules out using MD5 with
DSA. ;) */
if (qbits < 160)
{
log_error (_("%s key %s uses an unsafe (%zu bit) hash\n"),
openpgp_pk_algo_name (pk->pubkey_algo),
keystr_from_pk (pk), qbits);
return NULL;
}
/* ECDSA 521 is special has it is larger than the largest hash
we have (SHA-512). Thus we chnage the size for further
processing to 512. */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && qbits > 512)
qbits = 512;
/* Check if we're too short. Too long is safe as we'll
automatically left-truncate. */
mdlen = gcry_md_get_algo_dlen (hash_algo);
if (mdlen < qbits/8)
{
log_error (_("%s key %s requires a %zu bit or larger hash "
"(hash is %s)\n"),
openpgp_pk_algo_name (pk->pubkey_algo),
keystr_from_pk (pk), qbits,
gcry_md_algo_name (hash_algo));
return NULL;
}
/* Note that we do the truncation by passing QBITS/8 as length to
mpi_scan. */
if (gcry_mpi_scan (&frame, GCRYMPI_FMT_USG,
gcry_md_read (md, hash_algo), qbits/8, NULL))
BUG();
}
else
{
gpg_error_t rc;
byte *asn;
size_t asnlen;
rc = gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, NULL, &asnlen);
if (rc)
log_fatal ("can't get OID of digest algorithm %d: %s\n",
hash_algo, gpg_strerror (rc));
asn = xtrymalloc (asnlen);
if (!asn)
return NULL;
if ( gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, asn, &asnlen) )
BUG();
frame = do_encode_md (md, hash_algo, gcry_md_get_algo_dlen (hash_algo),
gcry_mpi_get_nbits (pk->pkey[0]), asn, asnlen);
xfree (asn);
}
return frame;
}
diff --git a/g10/sig-check.c b/g10/sig-check.c
index 4df29af7f..4e6ca4309 100644
--- a/g10/sig-check.c
+++ b/g10/sig-check.c
@@ -1,1108 +1,1108 @@
/* sig-check.c - Check a signature
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004, 2006 Free Software Foundation, Inc.
* Copyright (C) 2015, 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#include "options.h"
#include "pkglue.h"
static int check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
int *r_expired, int *r_revoked,
PKT_public_key *ret_pk);
static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest);
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int
check_signature (PKT_signature *sig, gcry_md_hd_t digest)
{
return check_signature2 (sig, digest, NULL, NULL, NULL, NULL);
}
/* Check a signature.
*
* Looks up the public key that created the signature (SIG->KEYID)
* from the key db. Makes sure that the signature is valid (it was
* not created prior to the key, the public key was created in the
* past, and the signature does not include any unsupported critical
* features), finishes computing the hash of the signature data, and
* checks that the signature verifies the digest. If the key that
* generated the signature is a subkey, this function also verifies
* that there is a valid backsig from the subkey to the primary key.
* Finally, if status fd is enabled and the signature class is 0x00 or
* 0x01, then a STATUS_SIG_ID is emitted on the status fd.
*
* SIG is the signature to check.
*
* DIGEST contains a valid hash context that already includes the
* signed data. This function adds the relevant meta-data from the
* signature packet to compute the final hash. (See Section 5.2 of
* RFC 4880: "The concatenation of the data being signed and the
* signature data from the version number through the hashed subpacket
* data (inclusive) is hashed.")
*
* If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's
* expiry.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If R_PK is not NULL, the public key is stored at that address if it
* was found; other wise NULL is stored.
*
* Returns 0 on success. An error code otherwise. */
gpg_error_t
check_signature2 (PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate,
int *r_expired, int *r_revoked, PKT_public_key **r_pk)
{
int rc=0;
PKT_public_key *pk;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
if (r_revoked)
*r_revoked = 0;
if (r_pk)
*r_pk = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
if ( (rc=openpgp_md_test_algo(sig->digest_algo)) )
; /* We don't have this digest. */
else if ((rc=openpgp_pk_test_algo(sig->pubkey_algo)))
; /* We don't have this pubkey algo. */
else if (!gcry_md_is_enabled (digest,sig->digest_algo))
{
/* Sanity check that the md has a context for the hash that the
sig is expecting. This can happen if a onepass sig header does
not match the actual sig, and also if the clearsign "Hash:"
header is missing or does not match the actual sig. */
log_info(_("WARNING: signature digest conflict in message\n"));
rc = gpg_error (GPG_ERR_GENERAL);
}
else if( get_pubkey( pk, sig->keyid ) )
rc = gpg_error (GPG_ERR_NO_PUBKEY);
else if(!pk->flags.valid)
{
/* You cannot have a good sig from an invalid key. */
rc = gpg_error (GPG_ERR_BAD_PUBKEY);
}
else
{
if(r_expiredate)
*r_expiredate = pk->expiredate;
rc = check_signature_end (pk, sig, digest, r_expired, r_revoked, NULL);
/* Check the backsig. This is a 0x19 signature from the
subkey on the primary key. The idea here is that it should
not be possible for someone to "steal" subkeys and claim
them as their own. The attacker couldn't actually use the
subkey, but they could try and claim ownership of any
signatures issued by it. */
if (!rc && !pk->flags.primary && pk->flags.backsig < 2)
{
if (!pk->flags.backsig)
{
log_info(_("WARNING: signing subkey %s is not"
" cross-certified\n"),keystr_from_pk(pk));
log_info(_("please see %s for more information\n"),
"https://gnupg.org/faq/subkey-cross-certify.html");
/* --require-cross-certification makes this warning an
error. TODO: change the default to require this
after more keys have backsigs. */
if(opt.flags.require_cross_cert)
rc = gpg_error (GPG_ERR_GENERAL);
}
else if(pk->flags.backsig == 1)
{
log_info(_("WARNING: signing subkey %s has an invalid"
" cross-certification\n"),keystr_from_pk(pk));
rc = gpg_error (GPG_ERR_GENERAL);
}
}
}
if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
/* This signature id works best with DLP algorithms because
* they use a random parameter for every signature. Instead of
* this sig-id we could have also used the hash of the document
* and the timestamp, but the drawback of this is, that it is
* not possible to sign more than one identical document within
* one second. Some remote batch processing applications might
* like this feature here.
*
* Note that before 2.0.10, we used RIPE-MD160 for the hash
* and accidentally didn't include the timestamp and algorithm
* information in the hash. Given that this feature is not
* commonly used and that a replay attacks detection should
* not solely be based on this feature (because it does not
* work with RSA), we take the freedom and switch to SHA-1
* with 2.0.10 to take advantage of hardware supported SHA-1
* implementations. We also include the missing information
* in the hash. Note also the SIG_ID as computed by gpg 1.x
* and gpg 2.x didn't matched either because 2.x used to print
* MPIs not in PGP format. */
u32 a = sig->timestamp;
int nsig = pubkey_get_nsig( sig->pubkey_algo );
unsigned char *p, *buffer;
size_t n, nbytes;
int i;
char hashbuf[20];
nbytes = 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n, sig->data[i]))
BUG();
nbytes += n;
}
/* Make buffer large enough to be later used as output buffer. */
if (nbytes < 100)
nbytes = 100;
nbytes += 10; /* Safety margin. */
/* Fill and hash buffer. */
buffer = p = xmalloc (nbytes);
*p++ = sig->pubkey_algo;
*p++ = sig->digest_algo;
*p++ = (a >> 24) & 0xff;
*p++ = (a >> 16) & 0xff;
*p++ = (a >> 8) & 0xff;
*p++ = a & 0xff;
nbytes -= 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, p, nbytes, &n, sig->data[i]))
BUG();
p += n;
nbytes -= n;
}
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, buffer, p-buffer);
p = make_radix64_string (hashbuf, 20);
sprintf (buffer, "%s %s %lu",
p, strtimestamp (sig->timestamp), (ulong)sig->timestamp);
xfree (p);
write_status_text (STATUS_SIG_ID, buffer);
xfree (buffer);
}
if (r_pk)
*r_pk = pk;
else
{
release_public_key_parts (pk);
xfree (pk);
}
return rc;
}
/* The signature SIG was generated with the public key PK. Check
* whether the signature is valid in the following sense:
*
* - Make sure the public key was created before the signature was
* generated.
*
* - Make sure the public key was created in the past
*
* - Check whether PK has expired (set *R_EXPIRED to 1 if so and 0
* otherwise)
*
* - Check whether PK has been revoked (set *R_REVOKED to 1 if so
* and 0 otherwise).
*
* If either of the first two tests fail, returns an error code.
* Otherwise returns 0. (Thus, this function doesn't fail if the
* public key is expired or revoked.) */
static int
check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig,
int *r_expired, int *r_revoked)
{
u32 cur_time;
if(r_expired)
*r_expired = 0;
if(r_revoked)
*r_revoked = 0;
if( pk->timestamp > sig->timestamp )
{
ulong d = pk->timestamp - sig->timestamp;
if ( d < 86400 )
{
log_info
(ngettext
("public key %s is %lu second newer than the signature\n",
"public key %s is %lu seconds newer than the signature\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info
(ngettext
("public key %s is %lu day newer than the signature\n",
"public key %s is %lu days newer than the signature\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT; /* pubkey newer than signature. */
}
cur_time = make_timestamp();
if( pk->timestamp > cur_time )
{
ulong d = pk->timestamp - cur_time;
if (d < 86400)
{
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info (ngettext("key %s was created %lu day"
" in the future (time warp or clock problem)\n",
"key %s was created %lu days"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT;
}
/* Check whether the key has expired. We check the has_expired
flag which is set after a full evaluation of the key (getkey.c)
as well as a simple compare to the current time in case the
merge has for whatever reasons not been done. */
if( pk->has_expired || (pk->expiredate && pk->expiredate < cur_time)) {
char buf[11];
if (opt.verbose)
log_info(_("Note: signature key %s expired %s\n"),
keystr_from_pk(pk), asctimestamp( pk->expiredate ) );
sprintf(buf,"%lu",(ulong)pk->expiredate);
write_status_text(STATUS_KEYEXPIRED,buf);
if(r_expired)
*r_expired = 1;
}
if (pk->flags.revoked)
{
if (opt.verbose)
log_info (_("Note: signature key %s has been revoked\n"),
keystr_from_pk(pk));
if (r_revoked)
*r_revoked=1;
}
return 0;
}
/* Finish generating a signature and check it. Concretely: make sure
* that the signature is valid (it was not created prior to the key,
* the public key was created in the past, and the signature does not
* include any unsupported critical features), finish computing the
* digest by adding the relevant data from the signature packet, and
* check that the signature verifies the digest.
*
* DIGEST contains a hash context, which has already hashed the signed
* data. This function adds the relevant meta-data from the signature
* packet to compute the final hash. (See Section 5.2 of RFC 4880:
* "The concatenation of the data being signed and the signature data
* from the version number through the hashed subpacket data
* (inclusive) is hashed.")
*
* SIG is the signature to check.
*
* PK is the public key used to generate the signature.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If RET_PK is not NULL, PK is copied into RET_PK on success.
*
* Returns 0 on success. An error code other. */
static int
check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
int *r_expired, int *r_revoked, PKT_public_key *ret_pk)
{
int rc = 0;
if ((rc = check_signature_metadata_validity (pk, sig,
r_expired, r_revoked)))
return rc;
if ((rc = check_signature_end_simple (pk, sig, digest)))
return rc;
if(!rc && ret_pk)
copy_public_key(ret_pk,pk);
return rc;
}
/* This function is similar to check_signature_end, but it only checks
whether the signature was generated by PK. It does not check
expiration, revocation, etc. */
static int
check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest)
{
gcry_mpi_t result = NULL;
int rc = 0;
const struct weakhash *weak;
if (!opt.flags.allow_weak_digest_algos)
for (weak = opt.weak_digests; weak; weak = weak->next)
if (sig->digest_algo == weak->algo)
{
print_digest_rejected_note(sig->digest_algo);
return GPG_ERR_DIGEST_ALGO;
}
/* Make sure the digest algo is enabled (in case of a detached
signature). */
gcry_md_enable (digest, sig->digest_algo);
/* Complete the digest. */
if( sig->version >= 4 )
gcry_md_putc( digest, sig->version );
gcry_md_putc( digest, sig->sig_class );
if( sig->version < 4 ) {
u32 a = sig->timestamp;
gcry_md_putc( digest, (a >> 24) & 0xff );
gcry_md_putc( digest, (a >> 16) & 0xff );
gcry_md_putc( digest, (a >> 8) & 0xff );
gcry_md_putc( digest, a & 0xff );
}
else {
byte buf[6];
size_t n;
gcry_md_putc( digest, sig->pubkey_algo );
gcry_md_putc( digest, sig->digest_algo );
if( sig->hashed ) {
n = sig->hashed->len;
gcry_md_putc (digest, (n >> 8) );
gcry_md_putc (digest, n );
gcry_md_write (digest, sig->hashed->data, n);
n += 6;
}
else {
/* Two octets for the (empty) length of the hashed
section. */
gcry_md_putc (digest, 0);
gcry_md_putc (digest, 0);
n = 6;
}
/* add some magic per Section 5.2.4 of RFC 4880. */
buf[0] = sig->version;
buf[1] = 0xff;
buf[2] = n >> 24;
buf[3] = n >> 16;
buf[4] = n >> 8;
buf[5] = n;
gcry_md_write( digest, buf, 6 );
}
gcry_md_final( digest );
/* Convert the digest to an MPI. */
result = encode_md_value (pk, digest, sig->digest_algo );
if (!result)
return GPG_ERR_GENERAL;
/* Verify the signature. */
rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey );
gcry_mpi_release (result);
if( !rc && sig->flags.unknown_critical )
{
log_info(_("assuming bad signature from key %s"
" due to an unknown critical bit\n"),keystr_from_pk(pk));
rc = GPG_ERR_BAD_SIGNATURE;
}
return rc;
}
/* Add a uid node to a hash context. See section 5.2.4, paragraph 4
of RFC 4880. */
static void
hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig )
{
if( uid->attrib_data ) {
if( sig->version >=4 ) {
byte buf[5];
buf[0] = 0xd1; /* packet of type 17 */
buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->attrib_data, uid->attrib_len );
}
else {
if( sig->version >=4 ) {
byte buf[5];
buf[0] = 0xb4; /* indicates a userid packet */
buf[1] = uid->len >> 24; /* always use 4 length bytes */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->name, uid->len );
}
}
static void
cache_sig_result ( PKT_signature *sig, int result )
{
if ( !result ) {
sig->flags.checked = 1;
sig->flags.valid = 1;
}
else if ( gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE ) {
sig->flags.checked = 1;
sig->flags.valid = 0;
}
else {
sig->flags.checked = 0;
sig->flags.valid = 0;
}
}
/* SIG is a key revocation signature. Check if this signature was
* generated by any of the public key PK's designated revokers.
*
* PK is the public key that SIG allegedly revokes.
*
* SIG is the revocation signature to check.
*
* This function avoids infinite recursion, which can happen if two
* keys are designed revokers for each other and they revoke each
* other. This is done by observing that if a key A is revoked by key
* B we still consider the revocation to be valid even if B is
* revoked. Thus, we don't need to determine whether B is revoked to
* determine whether A has been revoked by B, we just need to check
* the signature.
*
* Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not
* revoked. We are careful to make sure that GPG_ERR_NO_PUBKEY is
* only returned when a revocation signature is from a valid
* revocation key designated in a revkey subpacket, but the revocation
* key itself isn't present.
*
* XXX: This code will need to be modified if gpg ever becomes
* multi-threaded. Note that this guarantees that a designated
* revocation sig will never be considered valid unless it is actually
* valid, as well as being issued by a revocation key in a valid
* direct signature. Note also that this is written so that a revoked
* revoker can still issue revocations: i.e. If A revokes B, but A is
* revoked, B is still revoked. I'm not completely convinced this is
* the proper behavior, but it matches how PGP does it. -dms */
int
check_revocation_keys (PKT_public_key *pk, PKT_signature *sig)
{
static int busy=0;
int i;
int rc = GPG_ERR_GENERAL;
log_assert (IS_KEY_REV(sig));
log_assert ((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1]));
/* Avoid infinite recursion. Consider the following:
*
* - We want to check if A is revoked.
*
* - C is a designated revoker for B and has revoked B.
*
* - B is a designated revoker for A and has revoked A.
*
* When checking if A is revoked (in merge_selfsigs_main), we
* observe that A has a designed revoker. As such, we call this
* function. This function sees that there is a valid revocation
* signature, which is signed by B. It then calls check_signature()
* to verify that the signature is good. To check the sig, we need
* to lookup B. Looking up B means calling merge_selfsigs_main,
* which checks whether B is revoked, which calls this function to
* see if B was revoked by some key.
*
* In this case, the added level of indirection doesn't hurt. It
* just means a bit more work. However, if C == A, then we'd end up
* in a loop. But, it doesn't make sense to look up C anyways: even
* if B is revoked, we conservatively consider a valid revocation
* signed by B to revoke A. Since this is the only place where this
* type of recursion can occur, we simply cause this function to
* fail if it is entered recursively. */
if (busy)
{
/* Return an error (i.e. not revoked), but mark the pk as
uncacheable as we don't really know its revocation status
until it is checked directly. */
pk->flags.dont_cache = 1;
return rc;
}
busy=1;
/* es_printf("looking at %08lX with a sig from %08lX\n",(ulong)pk->keyid[1],
(ulong)sig->keyid[1]); */
/* is the issuer of the sig one of our revokers? */
if( !pk->revkey && pk->numrevkeys )
BUG();
else
for(i=0;i<pk->numrevkeys;i++)
{
/* The revoker's keyid. */
u32 keyid[2];
keyid_from_fingerprint(pk->revkey[i].fpr,MAX_FINGERPRINT_LEN,keyid);
if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1])
/* The signature was generated by a designated revoker.
Verify the signature. */
{
gcry_md_hd_t md;
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
hash_public_key(md,pk);
/* Note: check_signature only checks that the signature
is good. It does not fail if the key is revoked. */
rc=check_signature(sig,md);
cache_sig_result(sig,rc);
gcry_md_close (md);
break;
}
}
busy=0;
return rc;
}
/* Check that the backsig BACKSIG from the subkey SUB_PK to its
primary key MAIN_PK is valid.
Backsigs (0x19) have the same format as binding sigs (0x18), but
this function is simpler than check_key_signature in a few ways.
For example, there is no support for expiring backsigs since it is
questionable what such a thing actually means. Note also that the
sig cache check here, unlike other sig caches in GnuPG, is not
persistent. */
int
check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk,
PKT_signature *backsig)
{
gcry_md_hd_t md;
int rc;
/* Always check whether the algorithm is available. Although
gcry_md_open would throw an error, some libgcrypt versions will
print a debug message in that case too. */
if ((rc=openpgp_md_test_algo (backsig->digest_algo)))
return rc;
if(!opt.no_sig_cache && backsig->flags.checked)
return backsig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE);
rc = gcry_md_open (&md, backsig->digest_algo,0);
if (!rc)
{
hash_public_key(md,main_pk);
hash_public_key(md,sub_pk);
rc = check_signature_end (sub_pk, backsig, md, NULL, NULL, NULL);
cache_sig_result(backsig,rc);
gcry_md_close(md);
}
return rc;
}
/* Check that a signature over a key is valid. This is a
* specialization of check_key_signature2 with the unnamed parameters
* passed as NULL. See the documentation for that function for more
* details. */
int
check_key_signature (KBNODE root, KBNODE node, int *is_selfsig)
{
return check_key_signature2 (root, node, NULL, NULL, is_selfsig, NULL, NULL);
}
/* Returns whether SIGNER generated the signature SIG over the packet
PACKET, which is a key, subkey or uid, and comes from the key block
KB. (KB is PACKET's corresponding keyblock; we don't assume that
SIG has been added to the keyblock.)
If SIGNER is set, then checks whether SIGNER generated the
signature. Otherwise, uses SIG->KEYID to find the alleged signer.
This parameter can be used to effectively override the alleged
signer that is stored in SIG.
KB may be NULL if SIGNER is set.
Unlike check_key_signature, this function ignores any cached
results! That is, it does not consider SIG->FLAGS.CHECKED and
SIG->FLAGS.VALID nor does it set them.
This doesn't check the signature's semantic mean. Concretely, it
doesn't check whether a non-self signed revocation signature was
created by a designated revoker. In fact, it doesn't return an
error for a binding generated by a completely different key!
Returns 0 if the signature is valid. Returns GPG_ERR_SIG_CLASS if
this signature can't be over PACKET. Returns GPG_ERR_NOT_FOUND if
the key that generated the signature (according to SIG) could not
be found. Returns GPG_ERR_BAD_SIGNATURE if the signature is bad.
Other errors codes may be returned if something else goes wrong.
IF IS_SELFSIG is not NULL, sets *IS_SELFSIG to 1 if this is a
self-signature (by the key's primary key) or 0 if not.
If RET_PK is not NULL, returns a copy of the public key that
generated the signature (i.e., the signer) on success. This must
be released by the caller using release_public_key_parts (). */
gpg_error_t
check_signature_over_key_or_uid (PKT_public_key *signer,
PKT_signature *sig, KBNODE kb, PACKET *packet,
int *is_selfsig, PKT_public_key *ret_pk)
{
int rc;
PKT_public_key *pripk = kb->pkt->pkt.public_key;
gcry_md_hd_t md;
int signer_alloced = 0;
rc = openpgp_pk_test_algo (sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo (sig->digest_algo);
if (rc)
return rc;
/* A signature's class indicates the type of packet that it
signs. */
if (/* Primary key binding (made by a subkey). */
sig->sig_class == 0x19
/* Direct key signature. */
|| sig->sig_class == 0x1f
/* Primary key revocation. */
|| sig->sig_class == 0x20)
{
if (packet->pkttype != PKT_PUBLIC_KEY)
/* Key revocations can only be over primary keys. */
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (/* Subkey binding. */
sig->sig_class == 0x18
/* Subkey revocation. */
|| sig->sig_class == 0x28)
{
if (packet->pkttype != PKT_PUBLIC_SUBKEY)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (/* Certification. */
sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13
/* Certification revocation. */
|| sig->sig_class == 0x30)
{
if (packet->pkttype != PKT_USER_ID)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else
return gpg_error (GPG_ERR_SIG_CLASS);
/* PACKET is the right type for SIG. */
if (signer)
{
if (is_selfsig)
{
if (signer->keyid[0] == pripk->keyid[0]
&& signer->keyid[1] == pripk->keyid[1])
*is_selfsig = 1;
else
*is_selfsig = 0;
}
}
else
{
/* Get the signer. If possible, avoid a look up. */
if (sig->keyid[0] == pripk->keyid[0]
&& sig->keyid[1] == pripk->keyid[1])
/* Issued by the primary key. */
{
signer = pripk;
if (is_selfsig)
*is_selfsig = 1;
}
else
{
kbnode_t ctx = NULL;
kbnode_t n;
/* See if one of the subkeys was the signer (although this
is extremely unlikely). */
while ((n = walk_kbnode (kb, &ctx, 0)))
{
PKT_public_key *subk;
if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
subk = n->pkt->pkt.public_key;
if (sig->keyid[0] == subk->keyid[0]
&& sig->keyid[1] == subk->keyid[1])
/* Issued by a subkey. */
{
signer = subk;
break;
}
}
if (! signer)
/* Signer by some other key. */
{
if (is_selfsig)
*is_selfsig = 0;
if (ret_pk)
{
signer = ret_pk;
memset (signer, 0, sizeof (*signer));
signer_alloced = 1;
}
else
{
signer = xmalloc_clear (sizeof (*signer));
signer_alloced = 2;
}
rc = get_pubkey (signer, sig->keyid);
if (rc)
{
xfree (signer);
signer = NULL;
signer_alloced = 0;
goto out;
}
}
}
}
/* We checked above that we supported this algo, so an error here is
a bug. */
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
/* Hash the relevant data. */
if (/* Direct key signature. */
sig->sig_class == 0x1f
/* Primary key revocation. */
|| sig->sig_class == 0x20)
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md);
}
else if (/* Primary key binding (made by a subkey). */
sig->sig_class == 0x19)
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
hash_public_key (md, signer);
rc = check_signature_end_simple (signer, sig, md);
}
else if (/* Subkey binding. */
sig->sig_class == 0x18
/* Subkey revocation. */
|| sig->sig_class == 0x28)
{
log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY);
hash_public_key (md, pripk);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md);
}
else if (/* Certification. */
sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13
/* Certification revocation. */
|| sig->sig_class == 0x30)
{
log_assert (packet->pkttype == PKT_USER_ID);
hash_public_key (md, pripk);
hash_uid_packet (packet->pkt.user_id, md, sig);
rc = check_signature_end_simple (signer, sig, md);
}
else
/* We should never get here. (The first if above should have
already caught this error.) */
BUG ();
gcry_md_close (md);
out:
if (! rc && ret_pk && (signer_alloced == -1 || ret_pk != signer))
copy_public_key (ret_pk, signer);
if (signer_alloced == 1)
/* We looked up SIGNER; it is not a pointer into KB. */
{
release_public_key_parts (signer);
if (signer_alloced == 2)
/* We also allocated the memory. */
xfree (signer);
}
return rc;
}
/* Check that a signature over a key (e.g., a key revocation, key
* binding, user id certification, etc.) is valid. If the function
* detects a self-signature, it uses the public key from the specified
* key block and does not bother looking up the key specified in the
* signature packet.
*
* ROOT is a keyblock.
*
* NODE references a signature packet that appears in the keyblock
* that should be verified.
*
* If CHECK_PK is set, the specified key is sometimes preferred for
* verifying signatures. See the implementation for details.
*
* If RET_PK is not NULL, the public key that successfully verified
* the signature is copied into *RET_PK.
*
* If IS_SELFSIG is not NULL, *IS_SELFSIG is set to 1 if NODE is a
* self-signature.
*
* If R_EXPIREDATE is not NULL, *R_EXPIREDATE is set to the expiry
* date.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has been
* expired (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
*
* If OPT.NO_SIG_CACHE is not set, this function will first check if
* the result of a previous verification is already cached in the
* signature packet's data structure.
*
* TODO: add r_revoked here as well. It has the same problems as
* r_expiredate and r_expired and the cache. */
int
check_key_signature2 (kbnode_t root, kbnode_t node, PKT_public_key *check_pk,
PKT_public_key *ret_pk, int *is_selfsig,
u32 *r_expiredate, int *r_expired )
{
PKT_public_key *pk;
PKT_signature *sig;
int algo;
int rc;
if (is_selfsig)
*is_selfsig = 0;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
log_assert (node->pkt->pkttype == PKT_SIGNATURE);
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY);
pk = root->pkt->pkt.public_key;
sig = node->pkt->pkt.signature;
algo = sig->digest_algo;
/* Check whether we have cached the result of a previous signature
check. Note that we may no longer have the pubkey or hash
needed to verify a sig, but can still use the cached value. A
cache refresh detects and clears these cases. */
if ( !opt.no_sig_cache )
{
if (sig->flags.checked) /* Cached status available. */
{
if (is_selfsig)
{
u32 keyid[2];
keyid_from_pk (pk, keyid);
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
*is_selfsig = 1;
}
/* BUG: This is wrong for non-self-sigs... needs to be the
actual pk. */
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (rc)
return rc;
return sig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE);
}
}
rc = openpgp_pk_test_algo(sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo(algo);
if (rc)
return rc;
if (sig->sig_class == 0x20) /* key revocation */
{
u32 keyid[2];
keyid_from_pk( pk, keyid );
/* Is it a designated revoker? */
if (keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1])
rc = check_revocation_keys (pk, sig);
else
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (pk, sig, root, root->pkt,
is_selfsig, ret_pk);
}
}
else if (sig->sig_class == 0x28 /* subkey revocation */
|| sig->sig_class == 0x18) /* key binding */
{
kbnode_t snode = find_prev_kbnode (root, node, PKT_PUBLIC_SUBKEY);
if (snode)
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
/* 0x28 must be a self-sig, but 0x18 needn't be. */
rc = check_signature_over_key_or_uid (sig->sig_class == 0x18
? NULL : pk,
sig, root, snode->pkt,
is_selfsig, ret_pk);
}
else
{
if (opt.verbose)
{
if (sig->sig_class == 0x28)
log_info (_("key %s: no subkey for subkey"
" revocation signature\n"), keystr_from_pk(pk));
else if (sig->sig_class == 0x18)
log_info(_("key %s: no subkey for subkey"
" binding signature\n"), keystr_from_pk(pk));
}
rc = GPG_ERR_SIG_CLASS;
}
}
else if (sig->sig_class == 0x1f) /* direct key signature */
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (pk, sig, root, root->pkt,
is_selfsig, ret_pk);
}
else if (/* Certification. */
sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13
/* Certification revocation. */
|| sig->sig_class == 0x30)
{
kbnode_t unode = find_prev_kbnode (root, node, PKT_USER_ID);
if (unode)
{
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (! rc)
/* If this is a self-sig, ignore check_pk. */
rc = check_signature_over_key_or_uid
(keyid_cmp (pk_keyid (pk), sig->keyid) == 0 ? pk : check_pk,
sig, root, unode->pkt, NULL, ret_pk);
}
else
{
if (!opt.quiet)
log_info ("key %s: no user ID for key signature packet"
" of class %02x\n",keystr_from_pk(pk),sig->sig_class);
rc = GPG_ERR_SIG_CLASS;
}
}
else
{
log_info ("sig issued by %s with class %d (digest: %02x %02x)"
" is not valid over a user id or a key id, ignoring.\n",
keystr (sig->keyid), sig->sig_class,
sig->digest_start[0], sig->digest_start[1]);
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
}
cache_sig_result (sig, rc);
return rc;
}
diff --git a/g10/sign.c b/g10/sign.c
index ff099b31c..801c809ad 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -1,1625 +1,1625 @@
/* sign.c - sign data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2012 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
#include "filter.h"
-#include "ttyio.h"
+#include "../common/ttyio.h"
#include "trustdb.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
#include "pkglue.h"
-#include "sysutils.h"
+#include "../common/sysutils.h"
#include "call-agent.h"
-#include "mbox-util.h"
+#include "../common/mbox-util.h"
#ifdef HAVE_DOSISH_SYSTEM
#define LF "\r\n"
#else
#define LF "\n"
#endif
static int recipient_digest_algo=0;
/****************
* Create notations and other stuff. It is assumed that the stings in
* STRLIST are already checked to contain only printable data and have
* a valid NAME=VALUE format.
*/
static void
mk_notation_policy_etc (PKT_signature *sig,
PKT_public_key *pk, PKT_public_key *pksk)
{
const char *string;
char *p = NULL;
strlist_t pu = NULL;
struct notation *nd = NULL;
struct expando_args args;
log_assert (sig->version >= 4);
memset (&args, 0, sizeof(args));
args.pk = pk;
args.pksk = pksk;
/* Notation data. */
if (IS_SIG(sig) && opt.sig_notations)
nd = opt.sig_notations;
else if (IS_CERT(sig) && opt.cert_notations)
nd = opt.cert_notations;
if (nd)
{
struct notation *item;
for (item = nd; item; item = item->next)
{
item->altvalue = pct_expando (item->value,&args);
if (!item->altvalue)
log_error (_("WARNING: unable to %%-expand notation "
"(too large). Using unexpanded.\n"));
}
keygen_add_notations (sig, nd);
for (item = nd; item; item = item->next)
{
xfree (item->altvalue);
item->altvalue = NULL;
}
}
/* Set policy URL. */
if (IS_SIG(sig) && opt.sig_policy_url)
pu = opt.sig_policy_url;
else if (IS_CERT(sig) && opt.cert_policy_url)
pu = opt.cert_policy_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (string, &args);
if (!p)
{
log_error(_("WARNING: unable to %%-expand policy URL "
"(too large). Using unexpanded.\n"));
p = xstrdup(string);
}
build_sig_subpkt (sig, (SIGSUBPKT_POLICY
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Preferred keyserver URL. */
if (IS_SIG(sig) && opt.sig_keyserver_url)
pu = opt.sig_keyserver_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (string, &args);
if (!p)
{
log_error (_("WARNING: unable to %%-expand preferred keyserver URL"
" (too large). Using unexpanded.\n"));
p = xstrdup (string);
}
build_sig_subpkt (sig, (SIGSUBPKT_PREF_KS
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Set signer's user id. */
if (IS_SIG (sig) && !opt.flags.disable_signer_uid)
{
char *mbox;
/* For now we use the uid which was used to locate the key. */
if (pksk->user_id && (mbox = mailbox_from_userid (pksk->user_id->name)))
{
if (DBG_LOOKUP)
log_debug ("setting Signer's UID to '%s'\n", mbox);
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, mbox, strlen (mbox));
xfree (mbox);
}
else if (opt.sender_list)
{
/* If a list of --sender was given we scan that list and use
* the first one matching a user id of the current key. */
/* FIXME: We need to get the list of user ids for the PKSK
* packet. That requires either a function to look it up
* again or we need to extend the key packet struct to link
* to the primary key which in turn could link to the user
* ids. Too much of a change right now. Let's take just
* one from the supplied list and hope that the caller
* passed a matching one. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID,
opt.sender_list->d, strlen (opt.sender_list->d));
}
}
}
/*
* Helper to hash a user ID packet.
*/
static void
hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid)
{
byte buf[5];
(void)sigversion;
if (uid->attrib_data)
{
buf[0] = 0xd1; /* Indicates an attribute packet. */
buf[1] = uid->attrib_len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
}
else
{
buf[0] = 0xb4; /* Indicates a userid packet. */
buf[1] = uid->len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
}
gcry_md_write( md, buf, 5 );
if (uid->attrib_data)
gcry_md_write (md, uid->attrib_data, uid->attrib_len );
else
gcry_md_write (md, uid->name, uid->len );
}
/*
* Helper to hash some parts from the signature
*/
static void
hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig)
{
byte buf[6];
size_t n;
gcry_md_putc (md, sig->version);
gcry_md_putc (md, sig->sig_class);
gcry_md_putc (md, sig->pubkey_algo);
gcry_md_putc (md, sig->digest_algo);
if (sig->hashed)
{
n = sig->hashed->len;
gcry_md_putc (md, (n >> 8) );
gcry_md_putc (md, n );
gcry_md_write (md, sig->hashed->data, n );
n += 6;
}
else
{
gcry_md_putc (md, 0); /* Always hash the length of the subpacket. */
gcry_md_putc (md, 0);
n = 6;
}
/* Add some magic. */
buf[0] = sig->version;
buf[1] = 0xff;
buf[2] = n >> 24; /* (n is only 16 bit, so this is always 0) */
buf[3] = n >> 16;
buf[4] = n >> 8;
buf[5] = n;
gcry_md_write (md, buf, 6);
}
/* Perform the sign operation. If CACHE_NONCE is given the agent is
advised to use that cached passphrase fro the key. */
static int
do_sign (PKT_public_key *pksk, PKT_signature *sig,
gcry_md_hd_t md, int mdalgo, const char *cache_nonce)
{
gpg_error_t err;
byte *dp;
char *hexgrip;
if (pksk->timestamp > sig->timestamp )
{
ulong d = pksk->timestamp - sig->timestamp;
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pksk), d);
if (!opt.ignore_time_conflict)
return gpg_error (GPG_ERR_TIME_CONFLICT);
}
print_pubkey_algo_note (pksk->pubkey_algo);
if (!mdalgo)
mdalgo = gcry_md_get_algo (md);
print_digest_algo_note (mdalgo);
dp = gcry_md_read (md, mdalgo);
sig->digest_algo = mdalgo;
sig->digest_start[0] = dp[0];
sig->digest_start[1] = dp[1];
mpi_release (sig->data[0]);
sig->data[0] = NULL;
mpi_release (sig->data[1]);
sig->data[1] = NULL;
err = hexkeygrip_from_pk (pksk, &hexgrip);
if (!err)
{
char *desc;
gcry_sexp_t s_sigval;
desc = gpg_format_keydesc (pksk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_pksign (NULL/*ctrl*/, cache_nonce, hexgrip, desc,
pksk->keyid, pksk->main_keyid, pksk->pubkey_algo,
dp, gcry_md_get_algo_dlen (mdalgo), mdalgo,
&s_sigval);
xfree (desc);
if (err)
;
else if (pksk->pubkey_algo == GCRY_PK_RSA
|| pksk->pubkey_algo == GCRY_PK_RSA_S)
sig->data[0] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
else if (openpgp_oid_is_ed25519 (pksk->pkey[0]))
{
sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_OPAQUE);
sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_OPAQUE);
}
else
{
sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_USG);
sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
}
gcry_sexp_release (s_sigval);
}
xfree (hexgrip);
if (err)
log_error (_("signing failed: %s\n"), gpg_strerror (err));
else
{
if (opt.verbose)
{
char *ustr = get_user_id_string_native (sig->keyid);
log_info (_("%s/%s signature from: \"%s\"\n"),
openpgp_pk_algo_name (pksk->pubkey_algo),
openpgp_md_algo_name (sig->digest_algo),
ustr);
xfree (ustr);
}
}
return err;
}
int
complete_sig (PKT_signature *sig, PKT_public_key *pksk, gcry_md_hd_t md,
const char *cache_nonce)
{
int rc;
/* if (!(rc = check_secret_key (pksk, 0))) */
rc = do_sign (pksk, sig, md, 0, cache_nonce);
return rc;
}
/* Return true if the key seems to be on a version 1 OpenPGP card.
This works by asking the agent and may fail if the card has not yet
been used with the agent. */
static int
openpgp_card_v1_p (PKT_public_key *pk)
{
gpg_error_t err;
int result;
/* Shortcut if we are not using RSA: The v1 cards only support RSA
thus there is no point in looking any further. */
if (!is_RSA (pk->pubkey_algo))
return 0;
if (!pk->flags.serialno_valid)
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n", gpg_strerror (err));
return 0; /* Ooops. */
}
xfree (pk->serialno);
agent_get_keyinfo (NULL, hexgrip, &pk->serialno, NULL);
xfree (hexgrip);
pk->flags.serialno_valid = 1;
}
if (!pk->serialno)
result = 0; /* Error from a past agent_get_keyinfo or no card. */
else
{
/* The version number of the card is included in the serialno. */
result = !strncmp (pk->serialno, "D2760001240101", 14);
}
return result;
}
static int
match_dsa_hash (unsigned int qbytes)
{
if (qbytes <= 20)
return DIGEST_ALGO_SHA1;
if (qbytes <= 28)
return DIGEST_ALGO_SHA224;
if (qbytes <= 32)
return DIGEST_ALGO_SHA256;
if (qbytes <= 48)
return DIGEST_ALGO_SHA384;
if (qbytes <= 66 ) /* 66 corresponds to 521 (64 to 512) */
return DIGEST_ALGO_SHA512;
return DEFAULT_DIGEST_ALGO;
/* DEFAULT_DIGEST_ALGO will certainly fail, but it's the best wrong
answer we have if a digest larger than 512 bits is requested. */
}
/*
First try --digest-algo. If that isn't set, see if the recipient
has a preferred algorithm (which is also filtered through
--personal-digest-prefs). If we're making a signature without a
particular recipient (i.e. signing, rather than signing+encrypting)
then take the first algorithm in --personal-digest-prefs that is
usable for the pubkey algorithm. If --personal-digest-prefs isn't
set, then take the OpenPGP default (i.e. SHA-1).
Note that Ed25519+EdDSA takes an input of arbitrary length and thus
we don't enforce any particular algorithm like we do for standard
ECDSA. However, we use SHA256 as the default algorithm.
Possible improvement: Use the highest-ranked usable algorithm from
the signing key prefs either before or after using the personal
list?
*/
static int
hash_for (PKT_public_key *pk)
{
if (opt.def_digest_algo)
{
return opt.def_digest_algo;
}
else if (recipient_digest_algo)
{
return recipient_digest_algo;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA
&& openpgp_oid_is_ed25519 (pk->pkey[0]))
{
if (opt.personal_digest_prefs)
return opt.personal_digest_prefs[0].value;
else
return DIGEST_ALGO_SHA256;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
unsigned int qbytes = gcry_mpi_get_nbits (pk->pkey[1]);
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
qbytes = ecdsa_qbits_from_Q (qbytes);
qbytes = qbytes/8;
/* It's a DSA key, so find a hash that is the same size as q or
larger. If q is 160, assume it is an old DSA key and use a
160-bit hash unless --enable-dsa2 is set, in which case act
like a new DSA key that just happens to have a 160-bit q
(i.e. allow truncation). If q is not 160, by definition it
must be a new DSA key. */
if (opt.personal_digest_prefs)
{
prefitem_t *prefs;
if (qbytes != 20 || opt.flags.dsa2)
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) >= qbytes)
return prefs->value;
}
else
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) == qbytes)
return prefs->value;
}
}
return match_dsa_hash(qbytes);
}
else if (openpgp_card_v1_p (pk))
{
/* The sk lives on a smartcard, and old smartcards only handle
SHA-1 and RIPEMD/160. Newer smartcards (v2.0) don't have
this restriction anymore. Fortunately the serial number
encodes the version of the card and thus we know that this
key is on a v1 card. */
if(opt.personal_digest_prefs)
{
prefitem_t *prefs;
for (prefs=opt.personal_digest_prefs;prefs->type;prefs++)
if (prefs->value==DIGEST_ALGO_SHA1
|| prefs->value==DIGEST_ALGO_RMD160)
return prefs->value;
}
return DIGEST_ALGO_SHA1;
}
else if (opt.personal_digest_prefs)
{
/* It's not DSA, so we can use whatever the first hash algorithm
is in the pref list */
return opt.personal_digest_prefs[0].value;
}
else
return DEFAULT_DIGEST_ALGO;
}
static void
print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what)
{
byte array[MAX_FINGERPRINT_LEN];
char buf[100+MAX_FINGERPRINT_LEN*2];
size_t n;
snprintf (buf, sizeof buf - 2*MAX_FINGERPRINT_LEN, "%c %d %d %02x %lu ",
what, sig->pubkey_algo, sig->digest_algo, sig->sig_class,
(ulong)sig->timestamp );
fingerprint_from_pk (pk, array, &n);
bin2hex (array, n, buf + strlen (buf));
write_status_text( STATUS_SIG_CREATED, buf );
}
/*
* Loop over the secret certificates in SK_LIST and build the one pass
* signature packets. OpenPGP says that the data should be bracket by
* the onepass-sig and signature-packet; so we build these onepass
* packet here in reverse order
*/
static int
write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass )
{
int skcount;
SK_LIST sk_rover;
for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next)
skcount++;
for (; skcount; skcount--) {
PKT_public_key *pk;
PKT_onepass_sig *ops;
PACKET pkt;
int i, rc;
for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) {
if (++i == skcount)
break;
}
pk = sk_rover->pk;
ops = xmalloc_clear (sizeof *ops);
ops->sig_class = sigclass;
ops->digest_algo = hash_for (pk);
ops->pubkey_algo = pk->pubkey_algo;
keyid_from_pk (pk, ops->keyid);
ops->last = (skcount == 1);
init_packet(&pkt);
pkt.pkttype = PKT_ONEPASS_SIG;
pkt.pkt.onepass_sig = ops;
rc = build_packet (out, &pkt);
free_packet (&pkt);
if (rc) {
log_error ("build onepass_sig packet failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0;
}
/*
* Helper to write the plaintext (literal data) packet
*/
static int
write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode)
{
PKT_plaintext *pt = NULL;
u32 filesize;
int rc = 0;
if (!opt.no_literal)
pt=setup_plaintext_name(fname,inp);
/* try to calculate the length of the data */
if ( !iobuf_is_pipe_filename (fname) && *fname )
{
off_t tmpsize;
int overflow;
if( !(tmpsize = iobuf_get_filelength(inp, &overflow))
&& !overflow && opt.verbose)
log_info (_("WARNING: '%s' is an empty file\n"), fname);
/* We can't encode the length of very large files because
OpenPGP uses only 32 bit for file sizes. So if the size of
a file is larger than 2^32 minus some bytes for packet
headers, we switch to partial length encoding. */
if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
filesize = tmpsize;
else
filesize = 0;
/* Because the text_filter modifies the length of the
* data, it is not possible to know the used length
* without a double read of the file - to avoid that
* we simple use partial length packets. */
if ( ptmode == 't' || ptmode == 'u' || ptmode == 'm')
filesize = 0;
}
else
filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */
if (!opt.no_literal) {
PACKET pkt;
/* Note that PT has been initialized above in no_literal mode. */
pt->timestamp = make_timestamp ();
pt->mode = ptmode;
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
init_packet(&pkt);
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
/*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/
if( (rc = build_packet (out, &pkt)) )
log_error ("build_packet(PLAINTEXT) failed: %s\n",
gpg_strerror (rc) );
pt->buf = NULL;
free_packet (&pkt);
}
else {
byte copy_buffer[4096];
int bytes_copied;
while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1)
if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) {
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc));
break;
}
wipememory(copy_buffer,4096); /* burn buffer */
}
/* fixme: it seems that we never freed pt/pkt */
return rc;
}
/*
* Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized
* hash which will not be changes here.
*/
static int
write_signature_packets (SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash,
int sigclass, u32 timestamp, u32 duration,
int status_letter, const char *cache_nonce)
{
SK_LIST sk_rover;
/* Loop over the certificates with secret keys. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
PKT_public_key *pk;
PKT_signature *sig;
gcry_md_hd_t md;
int rc;
pk = sk_rover->pk;
/* Build the signature packet. */
sig = xtrycalloc (1, sizeof *sig);
if (!sig)
return gpg_error_from_syserror ();
if (duration || opt.sig_policy_url
|| opt.sig_notations || opt.sig_keyserver_url)
sig->version = 4;
else
sig->version = pk->version;
keyid_from_pk (pk, sig->keyid);
sig->digest_algo = hash_for (pk);
sig->pubkey_algo = pk->pubkey_algo;
if (timestamp)
sig->timestamp = timestamp;
else
sig->timestamp = make_timestamp();
if (duration)
sig->expiredate = sig->timestamp + duration;
sig->sig_class = sigclass;
if (gcry_md_copy (&md, hash))
BUG ();
if (sig->version >= 4)
{
build_sig_subpkt_from_sig (sig, pk);
mk_notation_policy_etc (sig, NULL, pk);
}
hash_sigversion_to_magic (md, sig);
gcry_md_final (md);
rc = do_sign (pk, sig, md, hash_for (pk), cache_nonce);
gcry_md_close (md);
if (!rc)
{
/* Write the packet. */
PACKET pkt;
init_packet (&pkt);
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
rc = build_packet (out, &pkt);
if (!rc && is_status_enabled())
print_status_sig_created (pk, sig, status_letter);
free_packet (&pkt);
if (rc)
log_error ("build signature packet failed: %s\n",
gpg_strerror (rc));
}
else
xfree (sig);
if (rc)
return rc;
}
return 0;
}
/****************
* Sign the files whose names are in FILENAME.
* If DETACHED has the value true,
* make a detached signature. If FILENAMES->d is NULL read from stdin
* and ignore the detached mode. Sign the file with all secret keys
* which can be taken from LOCUSR, if this is NULL, use the default one
* If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to encrypt the
* signed data for these users.
* If OUTFILE is not NULL; this file is used for output and the function
* does not ask for overwrite permission; output is then always
* uncompressed, non-armored and in binary mode.
*/
int
sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int encryptflag, strlist_t remusr, const char *outfile )
{
const char *fname;
armor_filter_context_t *afx;
compress_filter_context_t zfx;
md_filter_context_t mfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
encrypt_filter_context_t efx;
IOBUF inp = NULL, out = NULL;
PACKET pkt;
int rc = 0;
PK_LIST pk_list = NULL;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int multifile = 0;
u32 duration=0;
pfx = new_progress_context ();
afx = new_armor_context ();
memset( &zfx, 0, sizeof zfx);
memset( &mfx, 0, sizeof mfx);
memset( &efx, 0, sizeof efx);
init_packet( &pkt );
if( filenames ) {
fname = filenames->d;
multifile = !!filenames->next;
}
else
fname = NULL;
if( fname && filenames->next && (!detached || encryptflag) )
log_bug("multiple files can only be detached signed");
if(encryptflag==2
&& (rc=setup_symkey(&efx.symkey_s2k,&efx.symkey_dek)))
goto leave;
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval(1,opt.def_sig_expire);
else
duration = parse_expire_string(opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
unprotect the secret key. This is now done on demand by the agent. */
if( (rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) )
goto leave;
if (encryptflag
&& (rc=build_pk_list (ctrl, remusr, &pk_list)))
goto leave;
/* prepare iobufs */
if( multifile ) /* have list of filenames */
inp = NULL; /* we do it later */
else {
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if( !inp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]",
strerror(errno) );
goto leave;
}
handle_progress (pfx, inp, fname);
}
if( outfile ) {
if (is_secured_filename ( outfile )) {
out = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if( !out )
{
rc = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) );
goto leave;
}
else if( opt.verbose )
log_info(_("writing to '%s'\n"), outfile );
}
else if( (rc = open_outfile (-1, fname,
opt.armor? 1: detached? 2:0, 0, &out)))
goto leave;
/* prepare to calculate the MD over the input */
if( opt.textmode && !outfile && !multifile )
{
memset( &tfx, 0, sizeof tfx);
iobuf_push_filter( inp, text_filter, &tfx );
}
if ( gcry_md_open (&mfx.md, 0, 0) )
BUG ();
if (DBG_HASHING)
gcry_md_debug (mfx.md, "sign");
/* If we're encrypting and signing, it is reasonable to pick the
hash algorithm to use out of the recipient key prefs. This is
best effort only, as in a DSA2 and smartcard world there are
cases where we cannot please everyone with a single hash (DSA2
wants >160 and smartcards want =160). In the future this could
be more complex with different hashes for each sk, but the
current design requires a single hash for all SKs. */
if(pk_list)
{
if(opt.def_digest_algo)
{
if(!opt.expert &&
select_algo_from_prefs(pk_list,PREFTYPE_HASH,
opt.def_digest_algo,
NULL)!=opt.def_digest_algo)
log_info(_("WARNING: forcing digest algorithm %s (%d)"
" violates recipient preferences\n"),
gcry_md_algo_name (opt.def_digest_algo),
opt.def_digest_algo );
}
else
{
int algo, smartcard=0;
union pref_hint hint;
hint.digest_length = 0;
/* Of course, if the recipient asks for something
unreasonable (like the wrong hash for a DSA key) then
don't do it. Check all sk's - if any are DSA or live
on a smartcard, then the hash has restrictions and we
may not be able to give the recipient what they want.
For DSA, pass a hint for the largest q we have. Note
that this means that a q>160 key will override a q=160
key and force the use of truncation for the q=160 key.
The alternative would be to ignore the recipient prefs
completely and get a different hash for each DSA key in
hash_for(). The override behavior here is more or less
reasonable as it is under the control of the user which
keys they sign with for a given message and the fact
that the message with multiple signatures won't be
usable on an implementation that doesn't understand
DSA2 anyway. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next )
{
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA
|| sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
int temp_hashlen = (gcry_mpi_get_nbits
(sk_rover->pk->pkey[1]));
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen);
temp_hashlen = (temp_hashlen+7)/8;
/* Pick a hash that is large enough for our
largest q */
if (hint.digest_length<temp_hashlen)
hint.digest_length=temp_hashlen;
}
/* FIXME: need to check gpg-agent for this. */
/* else if (sk_rover->pk->is_protected */
/* && sk_rover->pk->protect.s2k.mode == 1002) */
/* smartcard = 1; */
}
/* Current smartcards only do 160-bit hashes. If we have
to have a >160-bit hash, then we can't use the
recipient prefs as we'd need both =160 and >160 at the
same time and recipient prefs currently require a
single hash for all signatures. All this may well have
to change as the cards add algorithms. */
if (!smartcard || (smartcard && hint.digest_length==20))
if ( (algo=
select_algo_from_prefs(pk_list,PREFTYPE_HASH,-1,&hint)) > 0)
recipient_digest_algo=algo;
}
}
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (mfx.md, hash_for (sk_rover->pk));
if( !multifile )
iobuf_push_filter( inp, md_filter, &mfx );
if( detached && !encryptflag)
afx->what = 2;
if( opt.armor && !outfile )
push_armor_filter (afx, out);
if( encryptflag ) {
efx.pk_list = pk_list;
/* fixme: set efx.cfx.datalen if known */
iobuf_push_filter( out, encrypt_filter, &efx );
}
if (opt.compress_algo && !outfile && !detached)
{
int compr_algo=opt.compress_algo;
/* If not forced by user */
if(compr_algo==-1)
{
/* If we're not encrypting, then select_algo_from_prefs
will fail and we'll end up with the default. If we are
encrypting, select_algo_from_prefs cannot fail since
there is an assumed preference for uncompressed data.
Still, if it did fail, we'll also end up with the
default. */
if((compr_algo=
select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1)
compr_algo=default_compress_algo();
}
else if(!opt.expert && pk_list
&& select_algo_from_prefs(pk_list,PREFTYPE_ZIP,
compr_algo,NULL)!=compr_algo)
log_info(_("WARNING: forcing compression algorithm %s (%d)"
" violates recipient preferences\n"),
compress_algo_to_string(compr_algo),compr_algo);
/* algo 0 means no compression */
if( compr_algo )
push_compress_filter(out,&zfx,compr_algo);
}
/* Write the one-pass signature packets if needed */
if (!detached) {
rc = write_onepass_sig_packets (sk_list, out,
opt.textmode && !outfile ? 0x01:0x00);
if (rc)
goto leave;
}
write_status_begin_signing (mfx.md);
/* Setup the inner packet. */
if( detached ) {
if( multifile ) {
strlist_t sl;
if( opt.verbose )
log_info(_("signing:") );
/* must walk reverse trough this list */
for( sl = strlist_last(filenames); sl;
sl = strlist_prev( filenames, sl ) ) {
inp = iobuf_open(sl->d);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if( !inp )
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"),
sl->d,strerror(errno));
goto leave;
}
handle_progress (pfx, inp, sl->d);
if( opt.verbose )
log_printf (" '%s'", sl->d );
if(opt.textmode)
{
memset( &tfx, 0, sizeof tfx);
iobuf_push_filter( inp, text_filter, &tfx );
}
iobuf_push_filter( inp, md_filter, &mfx );
while( iobuf_get(inp) != -1 )
;
iobuf_close(inp); inp = NULL;
}
if( opt.verbose )
log_printf ("\n");
}
else {
/* read, so that the filter can calculate the digest */
while( iobuf_get(inp) != -1 )
;
}
}
else {
rc = write_plaintext_packet (out, inp, fname,
opt.textmode && !outfile ?
(opt.mimemode? 'm':'t'):'b');
}
/* catch errors from above */
if (rc)
goto leave;
/* write the signatures */
rc = write_signature_packets (sk_list, out, mfx.md,
opt.textmode && !outfile? 0x01 : 0x00,
0, duration, detached ? 'D':'S', NULL);
if( rc )
goto leave;
leave:
if( rc )
iobuf_cancel(out);
else {
iobuf_close(out);
if (encryptflag)
write_status( STATUS_END_ENCRYPTION );
}
iobuf_close(inp);
gcry_md_close ( mfx.md );
release_sk_list( sk_list );
release_pk_list( pk_list );
recipient_digest_algo=0;
release_progress_context (pfx);
release_armor_context (afx);
return rc;
}
/****************
* make a clear signature. note that opt.armor is not needed
*/
int
clearsign_file (ctrl_t ctrl,
const char *fname, strlist_t locusr, const char *outfile )
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
gcry_md_hd_t textmd = NULL;
IOBUF inp = NULL, out = NULL;
PACKET pkt;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
u32 duration=0;
pfx = new_progress_context ();
afx = new_armor_context ();
init_packet( &pkt );
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1,opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
unprotect the secret key. This is now done on demand by the agent. */
if( (rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) )
goto leave;
/* prepare iobufs */
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if( !inp ) {
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", strerror(errno) );
goto leave;
}
handle_progress (pfx, inp, fname);
if( outfile ) {
if (is_secured_filename (outfile) ) {
outfile = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if( !out )
{
rc = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) );
goto leave;
}
else if( opt.verbose )
log_info(_("writing to '%s'\n"), outfile );
}
else if ((rc = open_outfile (-1, fname, 1, 0, &out)))
goto leave;
iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF );
{
const char *s;
int any = 0;
byte hashs_seen[256];
memset( hashs_seen, 0, sizeof hashs_seen );
iobuf_writestr(out, "Hash: " );
for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) {
int i = hash_for (sk_rover->pk);
if( !hashs_seen[ i & 0xff ] ) {
s = gcry_md_algo_name ( i );
if( s ) {
hashs_seen[ i & 0xff ] = 1;
if( any )
iobuf_put(out, ',' );
iobuf_writestr(out, s );
any = 1;
}
}
}
log_assert(any);
iobuf_writestr(out, LF );
}
if( opt.not_dash_escaped )
iobuf_writestr( out,
"NotDashEscaped: You need "GPG_NAME
" to verify this message" LF );
iobuf_writestr(out, LF );
if ( gcry_md_open (&textmd, 0, 0) )
BUG ();
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (textmd, hash_for(sk_rover->pk));
if ( DBG_HASHING )
gcry_md_debug ( textmd, "clearsign" );
copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped,
opt.escape_from);
/* fixme: check for read errors */
/* now write the armor */
afx->what = 2;
push_armor_filter (afx, out);
/* Write the signatures. */
rc = write_signature_packets (sk_list, out, textmd, 0x01, 0, duration, 'C',
NULL);
if( rc )
goto leave;
leave:
if( rc )
iobuf_cancel(out);
else
iobuf_close(out);
iobuf_close(inp);
gcry_md_close ( textmd );
release_sk_list( sk_list );
release_progress_context (pfx);
release_armor_context (afx);
return rc;
}
/*
* Sign and conventionally encrypt the given file.
* FIXME: Far too much code is duplicated - revamp the whole file.
*/
int
sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr)
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
compress_filter_context_t zfx;
md_filter_context_t mfx;
text_filter_context_t tfx;
cipher_filter_context_t cfx;
IOBUF inp = NULL, out = NULL;
PACKET pkt;
STRING2KEY *s2k = NULL;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int algo;
u32 duration=0;
int canceled;
pfx = new_progress_context ();
afx = new_armor_context ();
memset( &zfx, 0, sizeof zfx);
memset( &mfx, 0, sizeof mfx);
memset( &tfx, 0, sizeof tfx);
memset( &cfx, 0, sizeof cfx);
init_packet( &pkt );
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1, opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
unprotect the secret key. This is now done on demand by the agent. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG);
if (rc)
goto leave;
/* prepare iobufs */
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if( !inp ) {
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", strerror(errno) );
goto leave;
}
handle_progress (pfx, inp, fname);
/* prepare key */
s2k = xmalloc_clear( sizeof *s2k );
s2k->mode = opt.s2k_mode;
s2k->hash_algo = S2K_DIGEST_ALGO;
algo = default_cipher_algo();
if (!opt.quiet || !opt.batch)
log_info (_("%s encryption will be used\n"),
openpgp_cipher_algo_name (algo) );
cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled);
if (!cfx.dek || !cfx.dek->keylen) {
rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE);
log_error(_("error creating passphrase: %s\n"), gpg_strerror (rc) );
goto leave;
}
cfx.dek->use_mdc = use_mdc (NULL, cfx.dek->algo);
/* now create the outfile */
rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out);
if (rc)
goto leave;
/* prepare to calculate the MD over the input */
if (opt.textmode)
iobuf_push_filter (inp, text_filter, &tfx);
if ( gcry_md_open (&mfx.md, 0, 0) )
BUG ();
if ( DBG_HASHING )
gcry_md_debug (mfx.md, "symc-sign");
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (mfx.md, hash_for (sk_rover->pk));
iobuf_push_filter (inp, md_filter, &mfx);
/* Push armor output filter */
if (opt.armor)
push_armor_filter (afx, out);
/* Write the symmetric key packet */
/*(current filters: armor)*/
{
PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc );
enc->version = 4;
enc->cipher_algo = cfx.dek->algo;
enc->s2k = *s2k;
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if( (rc = build_packet( out, &pkt )) )
log_error("build symkey packet failed: %s\n", gpg_strerror (rc) );
xfree(enc);
}
/* Push the encryption filter */
iobuf_push_filter( out, cipher_filter, &cfx );
/* Push the compress filter */
if (default_compress_algo())
{
if (cfx.dek && cfx.dek->use_mdc)
zfx.new_ctb = 1;
push_compress_filter (out, &zfx,default_compress_algo() );
}
/* Write the one-pass signature packets */
/*(current filters: zip - encrypt - armor)*/
rc = write_onepass_sig_packets (sk_list, out,
opt.textmode? 0x01:0x00);
if (rc)
goto leave;
write_status_begin_signing (mfx.md);
/* Pipe data through all filters; i.e. write the signed stuff */
/*(current filters: zip - encrypt - armor)*/
rc = write_plaintext_packet (out, inp, fname,
opt.textmode ? (opt.mimemode?'m':'t'):'b');
if (rc)
goto leave;
/* Write the signatures */
/*(current filters: zip - encrypt - armor)*/
rc = write_signature_packets (sk_list, out, mfx.md,
opt.textmode? 0x01 : 0x00,
0, duration, 'S', NULL);
if( rc )
goto leave;
leave:
if( rc )
iobuf_cancel(out);
else {
iobuf_close(out);
write_status( STATUS_END_ENCRYPTION );
}
iobuf_close(inp);
release_sk_list( sk_list );
gcry_md_close( mfx.md );
xfree(cfx.dek);
xfree(s2k);
release_progress_context (pfx);
release_armor_context (afx);
return rc;
}
/****************
* Create a v4 signature in *RET_SIG.
*
* PK is the primary key to sign (required for all sigs)
* UID is the user id to sign (required for 0x10..0x13, 0x30)
* SUBPK is subkey to sign (required for 0x18, 0x19, 0x28)
*
* PKSK is the signing key
*
* SIGCLASS is the type of signature to create.
*
* DIGEST_ALGO is the digest algorithm. If it is 0 the function
* selects an appropriate one.
*
* TIMESTAMP is the timestamp to use for the signature. 0 means "now"
*
* DURATION is the amount of time (in seconds) until the signature
* expires.
*
* This function creates the following subpackets: issuer, created,
* and expire (if duration is not 0). Additional subpackets can be
* added using MKSUBPKT, which is called after these subpackets are
* added and before the signature is generated. OPAQUE is passed to
* MKSUBPKT.
*/
int
make_keysig_packet (PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
PKT_public_key *pksk,
int sigclass, int digest_algo,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *), void *opaque,
const char *cache_nonce)
{
PKT_signature *sig;
int rc=0;
int sigversion;
gcry_md_hd_t md;
log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F
|| sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19
|| sigclass == 0x30 || sigclass == 0x28 );
sigversion = 4;
if (sigversion < pksk->version)
sigversion = pksk->version;
if( !digest_algo )
{
/* Basically, this means use SHA1 always unless the user
specified something (use whatever they said), or it's DSA
(use the best match). They still can't pick an
inappropriate hash for DSA or the signature will fail.
Note that this still allows the caller of
make_keysig_packet to override the user setting if it
must. */
if(opt.cert_digest_algo)
digest_algo=opt.cert_digest_algo;
else if(pksk->pubkey_algo == PUBKEY_ALGO_DSA)
digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8);
else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
if (openpgp_oid_is_ed25519 (pksk->pkey[0]))
digest_algo = DIGEST_ALGO_SHA256;
else
digest_algo = match_dsa_hash
(ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
}
else
digest_algo = DEFAULT_DIGEST_ALGO;
}
if ( gcry_md_open (&md, digest_algo, 0 ) )
BUG ();
/* Hash the public key certificate. */
hash_public_key( md, pk );
if( sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28 )
{
/* hash the subkey binding/backsig/revocation */
hash_public_key( md, subpk );
}
else if( sigclass != 0x1F && sigclass != 0x20 )
{
/* hash the user id */
hash_uid (md, sigversion, uid);
}
/* and make the signature packet */
sig = xmalloc_clear( sizeof *sig );
sig->version = sigversion;
sig->flags.exportable=1;
sig->flags.revocable=1;
keyid_from_pk (pksk, sig->keyid);
sig->pubkey_algo = pksk->pubkey_algo;
sig->digest_algo = digest_algo;
if(timestamp)
sig->timestamp=timestamp;
else
sig->timestamp=make_timestamp();
if(duration)
sig->expiredate=sig->timestamp+duration;
sig->sig_class = sigclass;
build_sig_subpkt_from_sig (sig, pksk);
mk_notation_policy_etc (sig, pk, pksk);
/* Crucial that the call to mksubpkt comes LAST before the calls
to finalize the sig as that makes it possible for the mksubpkt
function to get a reliable pointer to the subpacket area. */
if (mksubpkt)
rc = (*mksubpkt)( sig, opaque );
if( !rc ) {
hash_sigversion_to_magic (md, sig);
gcry_md_final (md);
rc = complete_sig (sig, pksk, md, cache_nonce);
}
gcry_md_close (md);
if( rc )
free_seckey_enc( sig );
else
*ret_sig = sig;
return rc;
}
/****************
* Create a new signature packet based on an existing one.
* Only user ID signatures are supported for now.
* PK is the public key to work on.
* PKSK is the key used to make the signature.
*
* TODO: Merge this with make_keysig_packet.
*/
gpg_error_t
update_keysig_packet( PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque)
{
PKT_signature *sig;
gpg_error_t rc = 0;
int digest_algo;
gcry_md_hd_t md;
if ((!orig_sig || !pk || !pksk)
|| (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid)
|| (orig_sig->sig_class == 0x18 && !subpk))
return GPG_ERR_GENERAL;
if ( opt.cert_digest_algo )
digest_algo = opt.cert_digest_algo;
else
digest_algo = orig_sig->digest_algo;
if ( gcry_md_open (&md, digest_algo, 0 ) )
BUG ();
/* Hash the public key certificate and the user id. */
hash_public_key( md, pk );
if( orig_sig->sig_class == 0x18 )
hash_public_key( md, subpk );
else
hash_uid (md, orig_sig->version, uid);
/* create a new signature packet */
sig = copy_signature (NULL, orig_sig);
sig->digest_algo=digest_algo;
/* We need to create a new timestamp so that new sig expiration
calculations are done correctly... */
sig->timestamp=make_timestamp();
/* ... but we won't make a timestamp earlier than the existing
one. */
{
int tmout = 0;
while(sig->timestamp<=orig_sig->timestamp)
{
if (++tmout > 5 && !opt.ignore_time_conflict)
{
rc = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
gnupg_sleep (1);
sig->timestamp=make_timestamp();
}
}
/* Note that already expired sigs will remain expired (with a
duration of 1) since build-packet.c:build_sig_subpkt_from_sig
detects this case. */
/* Put the updated timestamp into the sig. Note that this will
automagically lower any sig expiration dates to correctly
correspond to the differences in the timestamps (i.e. the
duration will shrink). */
build_sig_subpkt_from_sig (sig, pksk);
if (mksubpkt)
rc = (*mksubpkt)(sig, opaque);
if (!rc) {
hash_sigversion_to_magic (md, sig);
gcry_md_final (md);
rc = complete_sig (sig, pksk, md, NULL);
}
leave:
gcry_md_close (md);
if( rc )
free_seckey_enc (sig);
else
*ret_sig = sig;
return rc;
}
diff --git a/g10/skclist.c b/g10/skclist.c
index cedbce79f..7a791b302 100644
--- a/g10/skclist.c
+++ b/g10/skclist.c
@@ -1,270 +1,270 @@
/* skclist.c - Build a list of secret keys
* Copyright (C) 1998, 1999, 2000, 2001, 2006,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
+#include "../common/status.h"
#include "keydb.h"
-#include "util.h"
-#include "i18n.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
/* Return true if Libgcrypt's RNG is in faked mode. */
int
random_is_faked (void)
{
return !!gcry_control (GCRYCTL_FAKED_RANDOM_P, 0);
}
void
release_sk_list (SK_LIST sk_list)
{
SK_LIST sk_rover;
for (; sk_list; sk_list = sk_rover)
{
sk_rover = sk_list->next;
free_public_key (sk_list->pk);
xfree (sk_list);
}
}
/* Check that we are only using keys which don't have
* the string "(insecure!)" or "not secure" or "do not use"
* in one of the user ids. */
static int
is_insecure (PKT_public_key *pk)
{
u32 keyid[2];
KBNODE node = NULL, u;
int insecure = 0;
keyid_from_pk (pk, keyid);
node = get_pubkeyblock (keyid);
for (u = node; u; u = u->next)
{
if (u->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *id = u->pkt->pkt.user_id;
if (id->attrib_data)
continue; /* skip attribute packets */
if (strstr (id->name, "(insecure!)")
|| strstr (id->name, "not secure")
|| strstr (id->name, "do not use")
|| strstr (id->name, "(INSECURE!)"))
{
insecure = 1;
break;
}
}
}
release_kbnode (node);
return insecure;
}
static int
key_present_in_sk_list (SK_LIST sk_list, PKT_public_key *pk)
{
for (; sk_list; sk_list = sk_list->next)
{
if (!cmp_public_keys (sk_list->pk, pk))
return 0;
}
return -1;
}
static int
is_duplicated_entry (strlist_t list, strlist_t item)
{
for (; list && list != item; list = list->next)
{
if (!strcmp (list->d, item->d))
return 1;
}
return 0;
}
gpg_error_t
build_sk_list (ctrl_t ctrl,
strlist_t locusr, SK_LIST *ret_sk_list, unsigned int use)
{
gpg_error_t err;
SK_LIST sk_list = NULL;
/* XXX: Change this function to use get_pubkeys instead of
getkey_byname to detect ambiguous key specifications and warn
about duplicate keyblocks. For ambiguous key specifications on
the command line or provided interactively, prompt the user to
select the best key. If a key specification is ambiguous and we
are in batch mode, die. */
if (!locusr) /* No user ids given - use the default key. */
{
PKT_public_key *pk;
pk = xmalloc_clear (sizeof *pk);
pk->req_usage = use;
if ((err = getkey_byname (ctrl, NULL, pk, NULL, 1, NULL)))
{
free_public_key (pk);
pk = NULL;
log_error ("no default secret key: %s\n", gpg_strerror (err));
write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err));
}
else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use)))
{
free_public_key (pk);
pk = NULL;
log_error ("invalid default secret key: %s\n", gpg_strerror (err));
write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err));
}
else
{
SK_LIST r;
if (random_is_faked () && !is_insecure (pk))
{
log_info (_("key is not flagged as insecure - "
"can't use it with the faked RNG!\n"));
free_public_key (pk);
pk = NULL;
write_status_text (STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED));
}
else
{
r = xmalloc (sizeof *r);
r->pk = pk;
pk = NULL;
r->next = sk_list;
r->mark = 0;
sk_list = r;
}
}
}
else /* Check the given user ids. */
{
strlist_t locusr_orig = locusr;
for (; locusr; locusr = locusr->next)
{
PKT_public_key *pk;
err = 0;
/* Do an early check against duplicated entries. However
* this won't catch all duplicates because the user IDs may
* be specified in different ways. */
if (is_duplicated_entry (locusr_orig, locusr))
{
log_info (_("skipped \"%s\": duplicated\n"), locusr->d);
continue;
}
pk = xmalloc_clear (sizeof *pk);
pk->req_usage = use;
if ((err = getkey_byname (ctrl, NULL, pk, locusr->d, 1, NULL)))
{
free_public_key (pk);
pk = NULL;
log_error (_("skipped \"%s\": %s\n"),
locusr->d, gpg_strerror (err));
write_status_text_and_buffer
(STATUS_INV_SGNR, get_inv_recpsgnr_code (err),
locusr->d, strlen (locusr->d), -1);
}
else if (!key_present_in_sk_list (sk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info (_("skipped: secret key already present\n"));
}
else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use)))
{
free_public_key (pk);
pk = NULL;
log_error ("skipped \"%s\": %s\n", locusr->d, gpg_strerror (err));
write_status_text_and_buffer
(STATUS_INV_SGNR, get_inv_recpsgnr_code (err),
locusr->d, strlen (locusr->d), -1);
}
else
{
SK_LIST r;
if (pk->version == 4 && (use & PUBKEY_USAGE_SIG)
&& pk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E)
{
log_info (_("skipped \"%s\": %s\n"), locusr->d,
_("this is a PGP generated Elgamal key which"
" is not secure for signatures!"));
free_public_key (pk);
pk = NULL;
write_status_text_and_buffer
(STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_WRONG_KEY_USAGE),
locusr->d, strlen (locusr->d), -1);
}
else if (random_is_faked () && !is_insecure (pk))
{
log_info (_("key is not flagged as insecure - "
"can't use it with the faked RNG!\n"));
free_public_key (pk);
pk = NULL;
write_status_text_and_buffer
(STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED),
locusr->d, strlen (locusr->d), -1);
}
else
{
r = xmalloc (sizeof *r);
r->pk = pk;
pk = NULL;
r->next = sk_list;
r->mark = 0;
sk_list = r;
}
}
}
}
if (!err && !sk_list)
{
log_error ("no valid signators\n");
write_status_text (STATUS_NO_SGNR, "0");
err = gpg_error (GPG_ERR_NO_USER_ID);
}
if (err)
release_sk_list (sk_list);
else
*ret_sk_list = sk_list;
return err;
}
diff --git a/g10/t-stutter.c b/g10/t-stutter.c
index 359cdf622..f4a9a5988 100644
--- a/g10/t-stutter.c
+++ b/g10/t-stutter.c
@@ -1,614 +1,614 @@
/* t-stutter.c - Test the stutter exploit.
* 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 <https://www.gnu.org/licenses/>.
*/
/* This test is based on the paper: "An Attack on CFB Mode Encryption
* as Used by OpenPGP." This attack uses a padding oracle to decrypt
* the first two bytes of each block (which are normally 16 bytes
* large). Concretely, if an attacker can use this attack if it can
* sense whether the quick integrity check failed. See RFC 4880,
* Section 5.7 for an explanation of this quick check.
*
* The concrete attack, as described in the paper, only works for
* PKT_ENCRYPTED packets; it does not work for PKT_ENCRYPTED_MDC
* packets, which use a slightly different CFB mode (they don't
* include a sync after the IV). But, small modifications should
* allow the attack to work for PKT_ENCRYPTED_MDC packets.
*
* The cost of this attack is 2^15 + i * 2^15 oracle queries, where i
* is the number of blocks the attack wants to decrypt. This attack
* is completely unfeasible when gpg is used interactively, but it
* could work when used as a service.
*
* How to generate a test message:
*
* $ echo 0123456789abcdefghijklmnopqrstuvwxyz | \
* gpg --disable-mdc -z 0 -c > msg.asc
* $ gpg --list-packets msg.asc
* # Make sure the encryption packet contains a literal packet (without
* # any nesting).
* $ gpgsplit msg.asc
* $ gpg --show-session-key -d msg.asc
* $ ./t-stutter --debug SESSION_KEY 000002-009.encrypted
*/
#include <config.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "main.h"
#include "../common/types.h"
-#include "util.h"
+#include "../common/util.h"
#include "dek.h"
#include "../common/logging.h"
static void
log_hexdump (byte *buffer, int length)
{
int written = 0;
fprintf (stderr, "%d bytes:\n", length);
while (length > 0)
{
int have = length > 16 ? 16 : length;
int i;
char formatted[2 * 16 + 1];
char text[16 + 1];
fprintf (stderr, "%-8d ", written);
bin2hex (buffer, have, formatted);
for (i = 0; i < 16; i ++)
{
if (i % 2 == 0)
fputc (' ', stderr);
if (i % 8 == 0)
fputc (' ', stderr);
if (i < have)
fwrite (&formatted[2 * i], 2, 1, stderr);
else
fwrite (" ", 2, 1, stderr);
}
for (i = 0; i < have; i ++)
{
if (isprint (buffer[i]))
text[i] = buffer[i];
else
text[i] = '.';
}
text[i] = 0;
fprintf (stderr, " ");
if (strlen (text) > 8)
{
fwrite (text, 8, 1, stderr);
fputc (' ', stderr);
fwrite (&text[8], strlen (text) - 8, 1, stderr);
}
else
fwrite (text, strlen (text), 1, stderr);
fputc ('\n', stderr);
buffer += have;
length -= have;
written += have;
}
return;
}
static char *
hexstr (const byte *bytes)
{
static int i;
static char bufs[100][7];
i ++;
if (i == 100)
i = 0;
sprintf (bufs[i], "0x%02X%02X", bytes[0], bytes[1]);
return bufs[i];
}
/* xor the two bytes starting at A with the two bytes starting at B
and return the result. */
static byte *
bufxor2 (const byte *a, const byte *b)
{
static int i;
static char bufs[100][2];
i ++;
if (i == 100)
i = 0;
bufs[i][0] = a[0] ^ b[0];
bufs[i][1] = a[1] ^ b[1];
return bufs[i];
}
/* The session key stays constant. */
static DEK dek;
int blocksize;
/* Decode the session key, which is in the format output by gpg
--show-session-key. */
static void
parse_session_key (char *session_key)
{
char *tail;
char *p = session_key;
errno = 0;
dek.algo = strtol (p, &tail, 10);
if (errno || (tail && *tail != ':'))
log_fatal ("Invalid session key specification. "
"Expected: cipher-id:HEXADECIMAL-CHRACTERS\n");
/* Skip the ':'. */
p = tail + 1;
if (strlen (p) % 2 != 0)
log_fatal ("Session key must consist of an even number of hexadecimal characters.\n");
dek.keylen = strlen (p) / 2;
log_assert (dek.keylen <= sizeof (dek.key));
if (hex2bin (p, dek.key, dek.keylen) == -1)
log_fatal ("Session key must only contain hexadecimal characters\n");
blocksize = openpgp_cipher_get_algo_blklen (dek.algo);
if ( !blocksize || blocksize > 16 )
log_fatal ("unsupported blocksize %u\n", blocksize );
return;
}
/* The ciphertext, the plaintext as decrypted by the good session key,
and the cfb stream (derived from the ciphertext and the
plaintext). */
static int msg_len;
static byte *msg;
static byte *msg_plaintext;
static byte *msg_cfb;
/* Whether we need to resynchronize the CFB after writing the random
data (this is the case for encrypted packets, but not encrypted and
integrity protected packets). */
static int sync;
static int
block_offset (int i)
{
int extra = 0;
log_assert (i >= 1);
/* Make sure blocksize has been initialized. */
log_assert (blocksize);
if (i > 2)
{
i -= 2;
extra = blocksize + 2;
}
return (i - 1) * blocksize + extra;
}
/* Return the ith block from TEXT. The first block is labeled 1.
Note: consistent with the OpenPGP message format, the second block
(i=2) is just 2 bytes. */
static byte *
block (byte *text, int len, int i)
{
int offset = block_offset (i);
log_assert (offset < len);
return &text[offset];
}
/* Return true if the quick integrity check passes. Also, if
PLAINTEXTP is not NULL, return the decrypted plaintext in
*PLAINTEXTP. If CFBP is not NULL, return the CFB byte stream in
*CFBP. */
static int
oracle (int debug, byte *ciphertext, int len, byte **plaintextp, byte **cfbp)
{
int rc = 0;
unsigned nprefix;
gcry_cipher_hd_t cipher_hd = NULL;
byte *plaintext = NULL;
byte *cfb = NULL;
/* Make sure DEK was initialized. */
log_assert (dek.algo);
log_assert (dek.keylen);
log_assert (blocksize);
nprefix = blocksize;
if (len < nprefix + 2)
{
/* An invalid message. We can't check that during parsing
because we may not know the used cipher then. */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
rc = openpgp_cipher_open (&cipher_hd, dek.algo,
GCRY_CIPHER_MODE_CFB,
(! sync /* ed->mdc_method || dek.algo >= 100 */ ?
0 : GCRY_CIPHER_ENABLE_SYNC));
if (rc)
log_fatal ("Failed to open cipher: %s\n", gpg_strerror (rc));
rc = gcry_cipher_setkey (cipher_hd, dek.key, dek.keylen);
if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY)
{
log_info ("WARNING: message was encrypted with"
" a weak key in the symmetric cipher.\n");
rc=0;
}
else if( rc )
log_fatal ("key setup failed: %s\n", gpg_strerror (rc));
gcry_cipher_setiv (cipher_hd, NULL, 0);
if (debug)
{
log_debug ("Encrypted data:\n");
log_hexdump(ciphertext, len);
}
plaintext = xmalloc_clear (len);
gcry_cipher_decrypt (cipher_hd, plaintext, blocksize + 2,
ciphertext, blocksize + 2);
gcry_cipher_sync (cipher_hd);
if (len > blocksize+2)
gcry_cipher_decrypt (cipher_hd,
&plaintext[blocksize+2], len-(blocksize+2),
&ciphertext[blocksize+2], len-(blocksize+2));
if (debug)
{
log_debug ("Decrypted data:\n");
log_hexdump (plaintext, len);
log_debug ("R_{b-1,b} = %s\n", hexstr (&plaintext[blocksize - 2]));
log_debug ("R_{b+1,b+2} = %s\n", hexstr (&plaintext[blocksize]));
}
if (cfbp || debug)
{
int i;
cfb = xmalloc (len);
for (i = 0; i < len; i ++)
cfb[i] = plaintext[i] ^ ciphertext[i];
log_assert (len >= blocksize + 2);
if (debug)
{
log_debug ("cfb:\n");
log_hexdump (cfb, len);
log_debug ("E_k([C_1]_{1,2}) = C_2 xor R (%s xor %s) = %s\n",
hexstr (&ciphertext[blocksize]),
hexstr (&plaintext[blocksize]),
hexstr (bufxor2 (&ciphertext[blocksize],
&plaintext[blocksize])));
if (len >= blocksize + 4)
log_debug ("D = Ek([C1]_{3-b} || C_2)_{1-2} (%s) xor C2 (%s) xor E_k(0)_{b-1,b} (%s) = %s\n",
hexstr (&cfb[blocksize + 2]),
hexstr (&ciphertext[blocksize]),
hexstr (&cfb[blocksize - 2]),
hexstr (bufxor2 (bufxor2 (&cfb[blocksize + 2],
&ciphertext[blocksize]),
&cfb[blocksize - 2])));
}
}
if (plaintext[nprefix-2] != plaintext[nprefix]
|| plaintext[nprefix-1] != plaintext[nprefix+1])
{
rc = gpg_error (GPG_ERR_BAD_KEY);
goto leave;
}
leave:
if (! rc && plaintextp)
*plaintextp = plaintext;
else
xfree (plaintext);
if (! rc && cfbp)
*cfbp = cfb;
else
xfree (cfb);
if (cipher_hd)
gcry_cipher_close (cipher_hd);
return rc;
}
/* Query the oracle with D=D for block B. */
static int
oracle_test (unsigned int d, int b, int debug)
{
byte probe[32 + 2];
log_assert (blocksize + 2 <= sizeof probe);
log_assert (d < 256 * 256);
if (b == 1)
memcpy (probe, &msg[2], blocksize);
else
memcpy (probe, block (msg, msg_len, b), blocksize);
probe[blocksize] = d >> 8;
probe[blocksize + 1] = d & 0xff;
if (debug)
log_debug ("oracle (0x%04X):\n", d);
return oracle (debug, probe, blocksize + 2, NULL, NULL) == 0;
}
int
main (int argc, char *argv[])
{
int i;
int debug = 0;
char *filename = NULL;
int help = 0;
byte *raw_data;
int raw_data_len;
int failed = 0;
for (i = 1; i < argc; i ++)
{
if (strcmp (argv[i], "--debug") == 0)
debug = 1;
else if (! blocksize)
parse_session_key (argv[i]);
else if (! filename)
filename = argv[i];
else
{
help = 1;
break;
}
}
if (! blocksize && ! filename && (filename = getenv ("srcdir")))
/* Try defaults. */
{
parse_session_key ("9:9274A8EC128E850C6DDDF9EAC68BFA84FC7BC05F340DA41D78C93D0640C7C503");
filename = xasprintf ("%s/t-stutter-data.asc", filename);
}
if (help || ! blocksize || ! filename)
log_fatal ("Usage: %s [--debug] SESSION_KEY ENCRYPTED_PKT\n", argv[0]);
/* Don't read more than a KB. */
raw_data_len = 1024;
raw_data = xmalloc (raw_data_len);
{
FILE *fp;
int r;
fp = fopen (filename, "r");
if (! fp)
log_fatal ("Opening %s: %s\n", filename, strerror (errno));
r = fread (raw_data, 1, raw_data_len, fp);
fclose (fp);
/* We need at least the random data, the encrypted and literal
packets' headers and some body. */
if (r < (blocksize + 2 /* Random data. */
+ 2 * blocksize /* Header + some plaintext. */))
log_fatal ("Not enough data (need at least %d bytes of plain text): %s.\n",
blocksize + 2, strerror (errno));
raw_data_len = r;
if (debug)
{
log_debug ("First few bytes of the raw data:\n");
log_hexdump (raw_data, raw_data_len > 8 ? 8 : raw_data_len);
}
}
/* Parse the packet's header. */
{
int ctb = raw_data[0];
int new_format = ctb & (1 << 7);
int pkttype = (ctb & ((1 << 5) - 1)) >> (new_format ? 0 : 2);
int hdrlen;
if (new_format)
{
if (debug)
log_debug ("len encoded: 0x%x (%d)\n", raw_data[1], raw_data[1]);
if (raw_data[1] < 192)
hdrlen = 2;
else if (raw_data[1] < 224)
hdrlen = 3;
else if (raw_data[1] == 255)
hdrlen = 5;
else
hdrlen = 2;
}
else
{
int lentype = ctb & 0x3;
if (lentype == 0)
hdrlen = 2;
else if (lentype == 1)
hdrlen = 3;
else if (lentype == 2)
hdrlen = 5;
else
/* Indeterminate. */
hdrlen = 1;
}
if (debug)
log_debug ("ctb = %x; %s format, hdrlen: %d, packet: %s\n",
ctb, new_format ? "new" : "old",
hdrlen,
pkttype_str (pkttype));
if (! (pkttype == PKT_ENCRYPTED || pkttype == PKT_ENCRYPTED_MDC))
log_fatal ("%s does not contain an encrypted packet, but a %s.\n",
filename, pkttype_str (pkttype));
if (pkttype == PKT_ENCRYPTED_MDC)
{
/* The first byte following the header is the version, which
is 1. */
log_assert (raw_data[hdrlen] == 1);
hdrlen ++;
sync = 0;
}
else
sync = 1;
msg = &raw_data[hdrlen];
msg_len = raw_data_len - hdrlen;
}
log_assert (msg_len >= blocksize + 2);
{
/* This can at least partially be guessed. So we just assume that
it is known. */
int d;
int found;
const byte *m1;
byte e_k_zero[2];
if (oracle (debug, msg, msg_len, &msg_plaintext, &msg_cfb) == 0)
{
if (debug)
log_debug ("Session key appears to be good.\n");
}
else
log_fatal ("Session key is bad!\n");
m1 = &msg_plaintext[blocksize + 2];
if (debug)
log_debug ("First two bytes of plaintext are: %02X (%c) %02X (%c)\n",
m1[0], isprint (m1[0]) ? m1[0] : '?',
m1[1], isprint (m1[1]) ? m1[1] : '?');
for (d = 0; d < 256 * 256; d ++)
if ((found = oracle_test (d, 1, 0)))
break;
if (! found)
log_fatal ("Failed to find d!\n");
if (debug)
oracle_test (d, 1, 1);
if (debug)
log_debug ("D = %d (%x) looks good.\n", d, d);
{
byte *c2 = block (msg, msg_len, 2);
byte D[2] = { d >> 8, d & 0xFF };
byte *c3 = block (msg, msg_len, 3);
memcpy (e_k_zero,
bufxor2 (bufxor2 (c2, D),
bufxor2 (c3, m1)),
sizeof (e_k_zero));
if (debug)
{
log_debug ("C2 = %s\n", hexstr (c2));
log_debug ("D = %s\n", hexstr (D));
log_debug ("C3 = %s\n", hexstr (c3));
log_debug ("M = %s\n", hexstr (m1));
log_debug ("E_k([C1]_{3-b} || C_2) = C3 xor M1 = %s\n",
hexstr (bufxor2 (c3, m1)));
log_debug ("E_k(0)_{b-1,b} = %s\n", hexstr (e_k_zero));
}
}
/* Figure out the first 2 bytes of M2... (offset 16 & 17 of the
plain text assuming the blocksize == 16 or bytes 34 & 35 of the
decrypted cipher text, i.e., C4). */
for (i = 1; block_offset (i + 3) + 2 <= msg_len; i ++)
{
byte e_k_prime[2];
byte m[2];
byte *ct = block (msg, msg_len, i + 2);
byte *pt = block (msg_plaintext, msg_len, 2 + i + 1);
for (d = 0; d < 256 * 256; d ++)
if (oracle_test (d, i + 2, 0))
{
found = 1;
break;
}
if (! found)
log_fatal ("Failed to find a valid d for block %d\n", i);
if (debug)
log_debug ("Block %d: oracle: D = %04X passes integrity check\n",
i, d);
{
byte D[2] = { d >> 8, d & 0xFF };
memcpy (e_k_prime,
bufxor2 (bufxor2 (&ct[blocksize - 2], D), e_k_zero),
sizeof (e_k_prime));
memcpy (m, bufxor2 (e_k_prime, block (msg, msg_len, i + 3)),
sizeof (m));
}
if (debug)
log_debug ("=> block %d starting at %zd starts with: "
"%s (%c%c)\n",
i, (size_t) pt - (size_t) msg_plaintext,
hexstr (m),
isprint (m[0]) ? m[0] : '?', isprint (m[1]) ? m[1] : '?');
if (m[0] != pt[0] || m[1] != pt[1])
{
log_debug ("oracle attack failed! Expected %s (%c%c), got %s\n",
hexstr (pt),
isprint (pt[0]) ? pt[0] : '?',
isprint (pt[1]) ? pt[1] : '?',
hexstr (m));
failed = 1;
}
}
if (i == 1)
log_fatal ("Message is too short, nothing to test.\n");
}
xfree (filename);
return failed;
}
diff --git a/g10/tdbdump.c b/g10/tdbdump.c
index ab2f0724d..a5ad32d6c 100644
--- a/g10/tdbdump.c
+++ b/g10/tdbdump.c
@@ -1,237 +1,237 @@
/* tdbdump.c
* Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "trustdb.h"
#include "options.h"
#include "packet.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "tdbio.h"
#define HEXTOBIN(x) ( (x) >= '0' && (x) <= '9' ? ((x)-'0') : \
(x) >= 'A' && (x) <= 'F' ? ((x)-'A'+10) : ((x)-'a'+10))
/*
* Write a record; die on error.
*/
static void
write_record( TRUSTREC *rec )
{
int rc = tdbio_write_record( rec );
if( !rc )
return;
log_error(_("trust record %lu, type %d: write failed: %s\n"),
rec->recnum, rec->rectype, gpg_strerror (rc) );
tdbio_invalid();
}
/*
* Dump the entire trustdb to FP or only the entries of one key.
*/
void
list_trustdb (estream_t fp, const char *username)
{
TRUSTREC rec;
(void)username;
init_trustdb (0);
/* For now we ignore the user ID. */
if (1)
{
ulong recnum;
int i;
es_fprintf (fp, "TrustDB: %s\n", tdbio_get_dbname ());
for (i = 9 + strlen (tdbio_get_dbname()); i > 0; i-- )
es_fputc ('-', fp);
es_putc ('\n', fp);
for (recnum=0; !tdbio_read_record (recnum, &rec, 0); recnum++)
tdbio_dump_record (&rec, fp);
}
}
/****************
* Print a list of all defined owner trust value.
*/
void
export_ownertrust()
{
TRUSTREC rec;
ulong recnum;
int i;
byte *p;
init_trustdb (0);
es_printf (_("# List of assigned trustvalues, created %s\n"
"# (Use \"gpg --import-ownertrust\" to restore them)\n"),
asctimestamp( make_timestamp() ) );
for (recnum=0; !tdbio_read_record (recnum, &rec, 0); recnum++ )
{
if (rec.rectype == RECTYPE_TRUST)
{
if (!rec.r.trust.ownertrust)
continue;
p = rec.r.trust.fingerprint;
for (i=0; i < 20; i++, p++ )
es_printf("%02X", *p );
es_printf (":%u:\n", (unsigned int)rec.r.trust.ownertrust );
}
}
}
void
import_ownertrust( const char *fname )
{
estream_t fp;
int is_stdin=0;
char line[256];
char *p;
size_t n, fprlen;
unsigned int otrust;
byte fpr[20];
int any = 0;
int rc;
init_trustdb (0);
if( iobuf_is_pipe_filename (fname) ) {
fp = es_stdin;
fname = "[stdin]";
is_stdin = 1;
}
else if( !(fp = es_fopen( fname, "r" )) ) {
log_error ( _("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
if (is_secured_file (es_fileno (fp)))
{
es_fclose (fp);
gpg_err_set_errno (EPERM);
log_error (_("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
while (es_fgets (line, DIM(line)-1, fp)) {
TRUSTREC rec;
if( !*line || *line == '#' )
continue;
n = strlen(line);
if( line[n-1] != '\n' ) {
log_error (_("error in '%s': %s\n"), fname, _("line too long") );
/* ... or last line does not have a LF */
break; /* can't continue */
}
for(p = line; *p && *p != ':' ; p++ )
if( !hexdigitp(p) )
break;
if( *p != ':' ) {
log_error (_("error in '%s': %s\n"), fname, _("colon missing") );
continue;
}
fprlen = p - line;
if( fprlen != 32 && fprlen != 40 ) {
log_error (_("error in '%s': %s\n"),
fname, _("invalid fingerprint") );
continue;
}
if( sscanf(p, ":%u:", &otrust ) != 1 ) {
log_error (_("error in '%s': %s\n"),
fname, _("ownertrust value missing"));
continue;
}
if( !otrust )
continue; /* no otrust defined - no need to update or insert */
/* convert the ascii fingerprint to binary */
for(p=line, fprlen=0; fprlen < 20 && *p != ':'; p += 2 )
fpr[fprlen++] = HEXTOBIN(p[0]) * 16 + HEXTOBIN(p[1]);
while (fprlen < 20)
fpr[fprlen++] = 0;
rc = tdbio_search_trust_byfpr (fpr, &rec);
if( !rc ) { /* found: update */
if (rec.r.trust.ownertrust != otrust)
{
if (!opt.quiet)
{
if( rec.r.trust.ownertrust )
log_info("changing ownertrust from %u to %u\n",
rec.r.trust.ownertrust, otrust );
else
log_info("setting ownertrust to %u\n", otrust );
}
rec.r.trust.ownertrust = otrust;
write_record (&rec );
any = 1;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* insert */
if (!opt.quiet)
log_info("inserting ownertrust of %u\n", otrust );
memset (&rec, 0, sizeof rec);
rec.recnum = tdbio_new_recnum ();
rec.rectype = RECTYPE_TRUST;
memcpy (rec.r.trust.fingerprint, fpr, 20);
rec.r.trust.ownertrust = otrust;
write_record (&rec );
any = 1;
}
else /* error */
log_error (_("error finding trust record in '%s': %s\n"),
fname, gpg_strerror (rc));
}
if (es_ferror (fp))
log_error ( _("read error in '%s': %s\n"), fname, strerror(errno) );
if (!is_stdin)
es_fclose (fp);
if (any)
{
revalidation_mark ();
rc = tdbio_sync ();
if (rc)
log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) );
}
}
diff --git a/g10/tdbio.c b/g10/tdbio.c
index c1cb31211..a7b7412a3 100644
--- a/g10/tdbio.c
+++ b/g10/tdbio.c
@@ -1,1868 +1,1868 @@
/* tdbio.c - trust database I/O operations
* Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc.
* Copyright (C) 1998-2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "options.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "trustdb.h"
#include "tdbio.h"
#if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate)
#define ftruncate chsize
#endif
#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY O_BINARY
#else
#define MY_O_BINARY 0
#endif
/* We use ERRNO despite that the cegcc provided open/read/write
functions don't set ERRNO - at least show that ERRNO does not make
sense. */
#ifdef HAVE_W32CE_SYSTEM
#undef strerror
#define strerror(a) ("[errno not available]")
#endif
/*
* Yes, this is a very simple implementation. We should really
* use a page aligned buffer and read complete pages.
* To implement a simple trannsaction system, this is sufficient.
*/
typedef struct cache_ctrl_struct *CACHE_CTRL;
struct cache_ctrl_struct
{
CACHE_CTRL next;
struct {
unsigned used:1;
unsigned dirty:1;
} flags;
ulong recno;
char data[TRUST_RECORD_LEN];
};
/* Size of the cache. The SOFT value is the general one. While in a
transaction this may not be sufficient and thus we may increase it
then up to the HARD limit. */
#define MAX_CACHE_ENTRIES_SOFT 200
#define MAX_CACHE_ENTRIES_HARD 10000
/* The cache is controlled by these variables. */
static CACHE_CTRL cache_list;
static int cache_entries;
static int cache_is_dirty;
/* An object to pass information to cmp_krec_fpr. */
struct cmp_krec_fpr_struct
{
int pubkey_algo;
const char *fpr;
int fprlen;
};
/* An object used to pass information to cmp_[s]dir. */
struct cmp_xdir_struct
{
int pubkey_algo;
u32 keyid[2];
};
/* The name of the trustdb file. */
static char *db_name;
/* The handle for locking the trustdb file and a flag to record
whether a lock has been taken. */
static dotlock_t lockhandle;
static int is_locked;
/* The file descriptor of the trustdb. */
static int db_fd = -1;
/* A flag indicating that a transaction is active. */
static int in_transaction;
static void open_db (void);
static void create_hashtable (TRUSTREC *vr, int type);
/*
* Take a lock on the trustdb file name. I a lock file can't be
* created the function terminates the process. Excvept for a
* different return code the function does nothing if the lock has
* already been taken.
*
* Returns: True if lock already exists, False if the lock has
* actually been taken.
*/
static int
take_write_lock (void)
{
if (!lockhandle)
lockhandle = dotlock_create (db_name, 0);
if (!lockhandle)
log_fatal ( _("can't create lock for '%s'\n"), db_name );
if (!is_locked)
{
if (dotlock_take (lockhandle, -1) )
log_fatal ( _("can't lock '%s'\n"), db_name );
else
is_locked = 1;
return 0;
}
else
return 1;
}
/*
* Release a lock from the trustdb file unless the global option
* --lock-once has been used.
*/
static void
release_write_lock (void)
{
if (!opt.lock_once)
if (!dotlock_release (lockhandle))
is_locked = 0;
}
/*************************************
************* record cache **********
*************************************/
/*
* Get the data from the record cache and return a pointer into that
* cache. Caller should copy the returned data. NULL is returned on
* a cache miss.
*/
static const char *
get_record_from_cache (ulong recno)
{
CACHE_CTRL r;
for (r = cache_list; r; r = r->next)
{
if (r->flags.used && r->recno == recno)
return r->data;
}
return NULL;
}
/*
* Write a cached item back to the trustdb file.
*
* Returns: 0 on success or an error code.
*/
static int
write_cache_item (CACHE_CTRL r)
{
gpg_error_t err;
int n;
if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: lseek failed: %s\n"),
r->recno, strerror (errno));
return err;
}
n = write (db_fd, r->data, TRUST_RECORD_LEN);
if (n != TRUST_RECORD_LEN)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
r->recno, n, strerror (errno) );
return err;
}
r->flags.dirty = 0;
return 0;
}
/*
* Put data into the cache. This function may flush
* some cache entries if the cache is filled up.
*
* Returns: 0 on success or an error code.
*/
static int
put_record_into_cache (ulong recno, const char *data)
{
CACHE_CTRL r, unused;
int dirty_count = 0;
int clean_count = 0;
/* See whether we already cached this one. */
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (!r->flags.used)
{
if (!unused)
unused = r;
}
else if (r->recno == recno)
{
if (!r->flags.dirty)
{
/* Hmmm: should we use a copy and compare? */
if (memcmp (r->data, data, TRUST_RECORD_LEN))
{
r->flags.dirty = 1;
cache_is_dirty = 1;
}
}
memcpy (r->data, data, TRUST_RECORD_LEN);
return 0;
}
if (r->flags.used)
{
if (r->flags.dirty)
dirty_count++;
else
clean_count++;
}
}
/* Not in the cache: add a new entry. */
if (unused)
{
/* Reuse this entry. */
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* See whether we reached the limit. */
if (cache_entries < MAX_CACHE_ENTRIES_SOFT)
{
/* No: Put into cache. */
r = xmalloc (sizeof *r);
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
r->next = cache_list;
cache_list = r;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* Cache is full: discard some clean entries. */
if (clean_count)
{
int n;
/* We discard a third of the clean entries. */
n = clean_count / 3;
if (!n)
n = 1;
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (r->flags.used && !r->flags.dirty)
{
if (!unused)
unused = r;
r->flags.used = 0;
cache_entries--;
if (!--n)
break;
}
}
/* Now put into the cache. */
log_assert (unused);
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* No clean entries: We have to flush some dirty entries. */
if (in_transaction)
{
/* But we can't do this while in a transaction. Thus we
* increase the cache size instead. */
if (cache_entries < MAX_CACHE_ENTRIES_HARD)
{
if (opt.debug && !(cache_entries % 100))
log_debug ("increasing tdbio cache size\n");
r = xmalloc (sizeof *r);
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
r->next = cache_list;
cache_list = r;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* Hard limit for the cache size reached. */
log_info (_("trustdb transaction too large\n"));
return GPG_ERR_RESOURCE_LIMIT;
}
if (dirty_count)
{
int n;
/* Discard some dirty entries. */
n = dirty_count / 5;
if (!n)
n = 1;
take_write_lock ();
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (r->flags.used && r->flags.dirty)
{
int rc;
rc = write_cache_item (r);
if (rc)
return rc;
if (!unused)
unused = r;
r->flags.used = 0;
cache_entries--;
if (!--n)
break;
}
}
release_write_lock ();
/* Now put into the cache. */
log_assert (unused);
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* We should never reach this. */
BUG();
}
/* Return true if the cache is dirty. */
int
tdbio_is_dirty()
{
return cache_is_dirty;
}
/*
* Flush the cache. This cannot be used while in a transaction.
*/
int
tdbio_sync()
{
CACHE_CTRL r;
int did_lock = 0;
if( db_fd == -1 )
open_db();
if( in_transaction )
log_bug("tdbio: syncing while in transaction\n");
if( !cache_is_dirty )
return 0;
if (!take_write_lock ())
did_lock = 1;
for( r = cache_list; r; r = r->next ) {
if( r->flags.used && r->flags.dirty ) {
int rc = write_cache_item( r );
if( rc )
return rc;
}
}
cache_is_dirty = 0;
if (did_lock)
release_write_lock ();
return 0;
}
#if 0 /* Not yet used. */
/*
* Simple transactions system:
* Everything between begin_transaction and end/cancel_transaction
* is not immediately written but at the time of end_transaction.
*
* NOTE: The transaction code is disabled in the 1.2 branch, as it is
* not yet used.
*/
int
tdbio_begin_transaction () /* Not yet used. */
{
int rc;
if (in_transaction)
log_bug ("tdbio: nested transactions\n");
/* Flush everything out. */
rc = tdbio_sync();
if (rc)
return rc;
in_transaction = 1;
return 0;
}
int
tdbio_end_transaction () /* Not yet used. */
{
int rc;
if (!in_transaction)
log_bug ("tdbio: no active transaction\n");
take_write_lock ();
gnupg_block_all_signals ();
in_transaction = 0;
rc = tdbio_sync();
gnupg_unblock_all_signals();
release_write_lock ();
return rc;
}
int
tdbio_cancel_transaction () /* Not yet used. */
{
CACHE_CTRL r;
if (!in_transaction)
log_bug ("tdbio: no active transaction\n");
/* Remove all dirty marked entries, so that the original ones are
* read back the next time. */
if (cache_is_dirty)
{
for (r = cache_list; r; r = r->next)
{
if (r->flags.used && r->flags.dirty)
{
r->flags.used = 0;
cache_entries--;
}
}
cache_is_dirty = 0;
}
in_transaction = 0;
return 0;
}
#endif /* Not yet used. */
/********************************************************
**************** cached I/O functions ******************
********************************************************/
/* The cleanup handler for this module. */
static void
cleanup (void)
{
if (is_locked)
{
if (!dotlock_release (lockhandle))
is_locked = 0;
}
}
/*
* Update an existing trustdb record. The caller must call
* tdbio_sync.
*
* Returns: 0 on success or an error code.
*/
int
tdbio_update_version_record (void)
{
TRUSTREC rec;
int rc;
memset (&rec, 0, sizeof rec);
rc = tdbio_read_record (0, &rec, RECTYPE_VER);
if (!rc)
{
rec.r.ver.created = make_timestamp();
rec.r.ver.marginals = opt.marginals_needed;
rec.r.ver.completes = opt.completes_needed;
rec.r.ver.cert_depth = opt.max_cert_depth;
rec.r.ver.trust_model = opt.trust_model;
rec.r.ver.min_cert_level = opt.min_cert_level;
rc=tdbio_write_record(&rec);
}
return rc;
}
/*
* Create and write the trustdb version record.
*
* Returns: 0 on success or an error code.
*/
static int
create_version_record (void)
{
TRUSTREC rec;
int rc;
memset (&rec, 0, sizeof rec);
rec.r.ver.version = 3;
rec.r.ver.created = make_timestamp ();
rec.r.ver.marginals = opt.marginals_needed;
rec.r.ver.completes = opt.completes_needed;
rec.r.ver.cert_depth = opt.max_cert_depth;
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC)
rec.r.ver.trust_model = opt.trust_model;
else
rec.r.ver.trust_model = TM_PGP;
rec.r.ver.min_cert_level = opt.min_cert_level;
rec.rectype = RECTYPE_VER;
rec.recnum = 0;
rc = tdbio_write_record (&rec);
if (!rc)
tdbio_sync ();
if (!rc)
create_hashtable (&rec, 0);
return rc;
}
/*
* Set the file name for the trustdb to NEW_DBNAME and if CREATE is
* true create that file. If NEW_DBNAME is NULL a default name is
* used, if the it does not contain a path component separator ('/')
* the global GnuPG home directory is used.
*
* Returns: 0 on success or an error code.
*
* On the first call this function registers an atexit handler.
*
*/
int
tdbio_set_dbname (const char *new_dbname, int create, int *r_nofile)
{
char *fname, *p;
struct stat statbuf;
static int initialized = 0;
int save_slash;
if (!initialized)
{
atexit (cleanup);
initialized = 1;
}
*r_nofile = 0;
if (!new_dbname)
{
fname = make_filename (gnupg_homedir (),
"trustdb" EXTSEP_S GPGEXT_GPG, NULL);
}
else if (*new_dbname != DIRSEP_C )
{
if (strchr (new_dbname, DIRSEP_C))
fname = make_filename (new_dbname, NULL);
else
fname = make_filename (gnupg_homedir (), new_dbname, NULL);
}
else
{
fname = xstrdup (new_dbname);
}
xfree (db_name);
db_name = fname;
/* Quick check for (likely) case where there already is a
* trustdb.gpg. This check is not required in theory, but it helps
* in practice avoiding costly operations of preparing and taking
* the lock. */
if (!stat (fname, &statbuf) && statbuf.st_size > 0)
{
/* OK, we have the valid trustdb.gpg already. */
return 0;
}
else if (!create)
{
*r_nofile = 1;
return 0;
}
/* Here comes: No valid trustdb.gpg AND CREATE==1 */
/*
* Make sure the directory exists. This should be done before
* acquiring the lock, which assumes the existence of the directory.
*/
p = strrchr (fname, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take
care of it. */
char *pp = strrchr (fname, '/');
if (!p || pp > p)
p = pp;
}
#endif /*HAVE_W32_SYSTEM*/
log_assert (p);
save_slash = *p;
*p = 0;
if (access (fname, F_OK))
{
try_make_homedir (fname);
if (access (fname, F_OK))
log_fatal (_("%s: directory does not exist!\n"), fname);
}
*p = save_slash;
take_write_lock ();
if (access (fname, R_OK) || stat (fname, &statbuf) || statbuf.st_size == 0)
{
FILE *fp;
TRUSTREC rec;
int rc;
mode_t oldmask;
#ifdef HAVE_W32CE_SYSTEM
/* We know how the cegcc implementation of access works ;-). */
if (GetLastError () == ERROR_FILE_NOT_FOUND)
gpg_err_set_errno (ENOENT);
else
gpg_err_set_errno (EIO);
#endif /*HAVE_W32CE_SYSTEM*/
if (errno && errno != ENOENT)
log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno));
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = fopen (fname, "wb");
umask(oldmask);
if (!fp)
log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno));
fclose (fp);
db_fd = open (db_name, O_RDWR | MY_O_BINARY);
if (db_fd == -1)
log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno));
rc = create_version_record ();
if (rc)
log_fatal (_("%s: failed to create version record: %s"),
fname, gpg_strerror (rc));
/* Read again to check that we are okay. */
if (tdbio_read_record (0, &rec, RECTYPE_VER))
log_fatal (_("%s: invalid trustdb created\n"), db_name);
if (!opt.quiet)
log_info (_("%s: trustdb created\n"), db_name);
}
release_write_lock ();
return 0;
}
/*
* Return the full name of the trustdb.
*/
const char *
tdbio_get_dbname ()
{
return db_name;
}
/*
* Open the trustdb. This may only be called if it has not yet been
* opened and after a successful call to tdbio_set_dbname. On return
* the trustdb handle (DB_FD) is guaranteed to be open.
*/
static void
open_db ()
{
TRUSTREC rec;
log_assert( db_fd == -1 );
#ifdef HAVE_W32CE_SYSTEM
{
DWORD prevrc = 0;
wchar_t *wname = utf8_to_wchar (db_name);
if (wname)
{
db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
xfree (wname);
}
if (db_fd == -1)
log_fatal ("can't open '%s': %d, %d\n", db_name,
(int)prevrc, (int)GetLastError ());
}
#else /*!HAVE_W32CE_SYSTEM*/
db_fd = open (db_name, O_RDWR | MY_O_BINARY );
if (db_fd == -1 && (errno == EACCES
#ifdef EROFS
|| errno == EROFS
#endif
)
) {
/* Take care of read-only trustdbs. */
db_fd = open (db_name, O_RDONLY | MY_O_BINARY );
if (db_fd != -1 && !opt.quiet)
log_info (_("Note: trustdb not writable\n"));
}
if ( db_fd == -1 )
log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) );
#endif /*!HAVE_W32CE_SYSTEM*/
register_secured_file (db_name);
/* Read the version record. */
if (tdbio_read_record (0, &rec, RECTYPE_VER ) )
log_fatal( _("%s: invalid trustdb\n"), db_name );
}
/*
* Append a new empty hashtable to the trustdb. TYPE gives the type
* of the hash table. The only defined type is 0 for a trust hash.
* On return the hashtable has been created, written, the version
* record update, and the data flushed to the disk. On a fatal error
* the function terminates the process.
*/
static void
create_hashtable( TRUSTREC *vr, int type )
{
TRUSTREC rec;
off_t offset;
ulong recnum;
int i, n, rc;
offset = lseek (db_fd, 0, SEEK_END);
if (offset == -1)
log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno));
recnum = offset / TRUST_RECORD_LEN;
log_assert (recnum); /* This is will never be the first record. */
if (!type)
vr->r.ver.trusthashtbl = recnum;
/* Now write the records making up the hash table. */
n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD;
for (i=0; i < n; i++, recnum++)
{
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HTBL;
rec.recnum = recnum;
rc = tdbio_write_record (&rec);
if (rc)
log_fatal (_("%s: failed to create hashtable: %s\n"),
db_name, gpg_strerror (rc));
}
/* Update the version record and flush. */
rc = tdbio_write_record (vr);
if (!rc)
rc = tdbio_sync ();
if (rc)
log_fatal (_("%s: error updating version record: %s\n"),
db_name, gpg_strerror (rc));
}
/*
* Check whether open trustdb matches the global trust options given
* for this process. On a read problem the process is terminated.
*
* Return: 1 for yes, 0 for no.
*/
int
tdbio_db_matches_options()
{
static int yes_no = -1;
if (yes_no == -1)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if( rc )
log_fatal( _("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
yes_no = vr.r.ver.marginals == opt.marginals_needed
&& vr.r.ver.completes == opt.completes_needed
&& vr.r.ver.cert_depth == opt.max_cert_depth
&& vr.r.ver.trust_model == opt.trust_model
&& vr.r.ver.min_cert_level == opt.min_cert_level;
}
return yes_no;
}
/*
* Read and return the trust model identifier from the trustdb. On a
* read problem the process is terminated.
*/
byte
tdbio_read_model (void)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER );
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
return vr.r.ver.trust_model;
}
/*
* Read and return the nextstamp value from the trustdb. On a read
* problem the process is terminated.
*/
ulong
tdbio_read_nextcheck ()
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
return vr.r.ver.nextcheck;
}
/*
* Write the STAMP nextstamp timestamp to the trustdb. On a read or
* write problem the process is terminated.
*
* Return: True if the stamp actually changed.
*/
int
tdbio_write_nextcheck (ulong stamp)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
if (vr.r.ver.nextcheck == stamp)
return 0;
vr.r.ver.nextcheck = stamp;
rc = tdbio_write_record( &vr );
if (rc)
log_fatal (_("%s: error writing version record: %s\n"),
db_name, gpg_strerror (rc));
return 1;
}
/*
* Return the record number of the trusthash table or create one if it
* does not yet exist. On a read or write problem the process is
* terminated.
*
* Return: record number
*/
static ulong
get_trusthashrec(void)
{
static ulong trusthashtbl; /* Record number of the trust hashtable. */
if (!trusthashtbl)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER );
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
trusthashtbl = vr.r.ver.trusthashtbl;
}
return trusthashtbl;
}
/*
* Update a hashtable in the trustdb. TABLE gives the start of the
* table, KEY and KEYLEN are the key, NEWRECNUM is the record number
* to insert into the table.
*
* Return: 0 on success or an error code.
*/
static int
upd_hashtable (ulong table, byte *key, int keylen, ulong newrecnum)
{
TRUSTREC lastrec, rec;
ulong hashrec, item;
int msb;
int level = 0;
int rc, i;
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL);
if (rc)
{
log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc));
return rc;
}
item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item) /* Insert a new item into the hash table. */
{
rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum;
rc = tdbio_write_record (&rec);
if (rc)
{
log_error ("upd_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else if (item != newrecnum) /* Must do an update. */
{
lastrec = rec;
rc = tdbio_read_record (item, &rec, 0);
if (rc)
{
log_error ("upd_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec.rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections.\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */
{
/* Check whether the key is already in this list. */
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec.r.hlst.rnum[i] == newrecnum)
{
return 0; /* Okay, already in the list. */
}
}
if (rec.r.hlst.next)
{
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("upd_hashtable: read hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
}
else
break; /* key is not in the list */
}
/* Find the next free entry and put it in. */
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (!rec.r.hlst.rnum[i])
{
/* Empty slot found. */
rec.r.hlst.rnum[i] = newrecnum;
rc = tdbio_write_record (&rec);
if (rc)
log_error ("upd_hashtable: write hlst failed: %s\n",
gpg_strerror (rc));
return rc; /* Done. */
}
}
if (rec.r.hlst.next)
{
/* read the next reord of the list. */
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("upd_hashtable: read hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else
{
/* Append a new record to the list. */
rec.r.hlst.next = item = tdbio_new_recnum ();
rc = tdbio_write_record (&rec);
if (rc)
{
log_error ("upd_hashtable: write hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HLST;
rec.recnum = item;
rec.r.hlst.rnum[0] = newrecnum;
rc = tdbio_write_record (&rec);
if (rc)
log_error ("upd_hashtable: write ext hlst failed: %s\n",
gpg_strerror (rc));
return rc; /* Done. */
}
} /* end loop over list slots */
}
else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record. */
{
if (rec.recnum == newrecnum)
{
return 0;
}
item = rec.recnum; /* Save number of key record. */
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HLST;
rec.recnum = tdbio_new_recnum ();
rec.r.hlst.rnum[0] = item; /* Old key record */
rec.r.hlst.rnum[1] = newrecnum; /* and new key record */
rc = tdbio_write_record (&rec);
if (rc)
{
log_error( "upd_hashtable: write new hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
/* Update the hashtable record. */
lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum;
rc = tdbio_write_record (&lastrec);
if (rc)
log_error ("upd_hashtable: update htbl failed: %s\n",
gpg_strerror (rc));
return rc; /* Ready. */
}
else
{
log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n",
table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
if (opt.verbose > 1)
list_trustdb (es_stderr, NULL);
return GPG_ERR_TRUSTDB;
}
}
return 0;
}
/*
* Drop an entry from a hashtable. TABLE gives the start of the
* table, KEY and KEYLEN are the key.
*
* Return: 0 on success or an error code.
*/
static int
drop_from_hashtable (ulong table, byte *key, int keylen, ulong recnum)
{
TRUSTREC rec;
ulong hashrec, item;
int msb;
int level = 0;
int rc, i;
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL );
if (rc)
{
log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc));
return rc;
}
item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item)
return 0; /* Not found - forget about it. */
if (item == recnum) /* Table points direct to the record. */
{
rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0;
rc = tdbio_write_record( &rec );
if (rc)
log_error ("drop_from_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
rc = tdbio_read_record (item, &rec, 0);
if (rc)
{
log_error ("drop_from_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec.rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections.\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
if (rec.rectype == RECTYPE_HLST)
{
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec.r.hlst.rnum[i] == recnum)
{
rec.r.hlst.rnum[i] = 0; /* Mark as free. */
rc = tdbio_write_record (&rec);
if (rc)
log_error("drop_from_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
if (rec.r.hlst.next)
{
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("drop_from_hashtable: read hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else
return 0; /* Key not in table. */
}
}
log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n",
table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
return GPG_ERR_TRUSTDB;
}
/*
* Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return
* the result in REC. The return value of CMP() should be True if the
* record is the desired one.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
static gpg_error_t
lookup_hashtable (ulong table, const byte *key, size_t keylen,
int (*cmpfnc)(const void*, const TRUSTREC *),
const void *cmpdata, TRUSTREC *rec )
{
int rc;
ulong hashrec, item;
int msb;
int level = 0;
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL);
if (rc)
{
log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) );
return rc;
}
item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item)
return gpg_error (GPG_ERR_NOT_FOUND);
rc = tdbio_read_record (item, rec, 0);
if (rc)
{
log_error( "hashtable read failed: %s\n", gpg_strerror (rc) );
return rc;
}
if (rec->rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
else if (rec->rectype == RECTYPE_HLST)
{
for (;;)
{
int i;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec->r.hlst.rnum[i])
{
TRUSTREC tmp;
rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0);
if (rc)
{
log_error ("lookup_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if ((*cmpfnc)(cmpdata, &tmp))
{
*rec = tmp;
return 0;
}
}
}
if (rec->r.hlst.next)
{
rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST);
if (rc)
{
log_error ("lookup_hashtable: read hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
}
else
return gpg_error (GPG_ERR_NOT_FOUND);
}
}
if ((*cmpfnc)(cmpdata, rec))
return 0; /* really found */
return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */
}
/*
* Update the trust hash table TR or create the table if it does not
* exist.
*
* Return: 0 on success or an error code.
*/
static int
update_trusthashtbl( TRUSTREC *tr )
{
return upd_hashtable (get_trusthashrec(),
tr->r.trust.fingerprint, 20, tr->recnum);
}
/*
* Dump the trustdb record REC to stream FP.
*/
void
tdbio_dump_record (TRUSTREC *rec, estream_t fp)
{
int i;
ulong rnum = rec->recnum;
es_fprintf (fp, "rec %5lu, ", rnum);
switch (rec->rectype)
{
case 0:
es_fprintf (fp, "blank\n");
break;
case RECTYPE_VER:
es_fprintf (fp,
"version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n",
rec->r.ver.trusthashtbl,
rec->r.ver.firstfree,
rec->r.ver.marginals,
rec->r.ver.completes,
rec->r.ver.cert_depth,
rec->r.ver.trust_model,
rec->r.ver.min_cert_level,
rec->r.ver.nextcheck,
strtimestamp(rec->r.ver.nextcheck)
);
break;
case RECTYPE_FREE:
es_fprintf (fp, "free, next=%lu\n", rec->r.free.next);
break;
case RECTYPE_HTBL:
es_fprintf (fp, "htbl,");
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
es_fprintf (fp, " %lu", rec->r.htbl.item[i]);
es_putc ('\n', fp);
break;
case RECTYPE_HLST:
es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next);
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]);
es_putc ('\n', fp);
break;
case RECTYPE_TRUST:
es_fprintf (fp, "trust ");
for (i=0; i < 20; i++)
es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]);
es_fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust,
rec->r.trust.depth, rec->r.trust.validlist);
break;
case RECTYPE_VALID:
es_fprintf (fp, "valid ");
for (i=0; i < 20; i++)
es_fprintf(fp, "%02X", rec->r.valid.namehash[i]);
es_fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity,
rec->r.valid.next);
break;
default:
es_fprintf (fp, "unknown type %d\n", rec->rectype );
break;
}
}
/*
* Read the record with number RECNUM into the structure REC. If
* EXPECTED is not 0 reading any other record type will return an
* error.
*
* Return: 0 on success, -1 on EOF, or an error code.
*/
int
tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected)
{
byte readbuf[TRUST_RECORD_LEN];
const byte *buf, *p;
gpg_error_t err = 0;
int n, i;
if (db_fd == -1)
open_db ();
buf = get_record_from_cache( recnum );
if (!buf)
{
if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb: lseek failed: %s\n"), strerror (errno));
return err;
}
n = read (db_fd, readbuf, TRUST_RECORD_LEN);
if (!n)
{
return -1; /* eof */
}
else if (n != TRUST_RECORD_LEN)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb: read failed (n=%d): %s\n"),
n, strerror(errno));
return err;
}
buf = readbuf;
}
rec->recnum = recnum;
rec->dirty = 0;
p = buf;
rec->rectype = *p++;
if (expected && rec->rectype != expected)
{
log_error ("%lu: read expected rec type %d, got %d\n",
recnum, expected, rec->rectype);
return gpg_error (GPG_ERR_TRUSTDB);
}
p++; /* Skip reserved byte. */
switch (rec->rectype)
{
case 0: /* unused (free) record */
break;
case RECTYPE_VER: /* version record */
if (memcmp(buf+1, GPGEXT_GPG, 3))
{
log_error (_("%s: not a trustdb file\n"), db_name );
err = gpg_error (GPG_ERR_TRUSTDB);
}
else
{
p += 2; /* skip "gpg" */
rec->r.ver.version = *p++;
rec->r.ver.marginals = *p++;
rec->r.ver.completes = *p++;
rec->r.ver.cert_depth = *p++;
rec->r.ver.trust_model = *p++;
rec->r.ver.min_cert_level = *p++;
p += 2;
rec->r.ver.created = buf32_to_ulong(p);
p += 4;
rec->r.ver.nextcheck = buf32_to_ulong(p);
p += 4;
p += 4;
p += 4;
rec->r.ver.firstfree = buf32_to_ulong(p);
p += 4;
p += 4;
rec->r.ver.trusthashtbl = buf32_to_ulong(p);
if (recnum)
{
log_error( _("%s: version record with recnum %lu\n"), db_name,
(ulong)recnum );
err = gpg_error (GPG_ERR_TRUSTDB);
}
else if (rec->r.ver.version != 3)
{
log_error( _("%s: invalid file version %d\n"), db_name,
rec->r.ver.version );
err = gpg_error (GPG_ERR_TRUSTDB);
}
}
break;
case RECTYPE_FREE:
rec->r.free.next = buf32_to_ulong(p);
break;
case RECTYPE_HTBL:
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
{
rec->r.htbl.item[i] = buf32_to_ulong(p);
p += 4;
}
break;
case RECTYPE_HLST:
rec->r.hlst.next = buf32_to_ulong(p);
p += 4;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
rec->r.hlst.rnum[i] = buf32_to_ulong(p);
p += 4;
}
break;
case RECTYPE_TRUST:
memcpy (rec->r.trust.fingerprint, p, 20);
p+=20;
rec->r.trust.ownertrust = *p++;
rec->r.trust.depth = *p++;
rec->r.trust.min_ownertrust = *p++;
p++;
rec->r.trust.validlist = buf32_to_ulong(p);
break;
case RECTYPE_VALID:
memcpy (rec->r.valid.namehash, p, 20);
p+=20;
rec->r.valid.validity = *p++;
rec->r.valid.next = buf32_to_ulong(p);
p += 4;
rec->r.valid.full_count = *p++;
rec->r.valid.marginal_count = *p++;
break;
default:
log_error ("%s: invalid record type %d at recnum %lu\n",
db_name, rec->rectype, (ulong)recnum);
err = gpg_error (GPG_ERR_TRUSTDB);
break;
}
return err;
}
/*
* Write the record from the struct REC.
*
* Return: 0 on success or an error code.
*/
int
tdbio_write_record( TRUSTREC *rec )
{
byte buf[TRUST_RECORD_LEN];
byte *p;
int rc = 0;
int i;
ulong recnum = rec->recnum;
if (db_fd == -1)
open_db ();
memset (buf, 0, TRUST_RECORD_LEN);
p = buf;
*p++ = rec->rectype; p++;
switch (rec->rectype)
{
case 0: /* unused record */
break;
case RECTYPE_VER: /* version record */
if (recnum)
BUG ();
memcpy(p-1, GPGEXT_GPG, 3 ); p += 2;
*p++ = rec->r.ver.version;
*p++ = rec->r.ver.marginals;
*p++ = rec->r.ver.completes;
*p++ = rec->r.ver.cert_depth;
*p++ = rec->r.ver.trust_model;
*p++ = rec->r.ver.min_cert_level;
p += 2;
ulongtobuf(p, rec->r.ver.created); p += 4;
ulongtobuf(p, rec->r.ver.nextcheck); p += 4;
p += 4;
p += 4;
ulongtobuf(p, rec->r.ver.firstfree ); p += 4;
p += 4;
ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4;
break;
case RECTYPE_FREE:
ulongtobuf(p, rec->r.free.next); p += 4;
break;
case RECTYPE_HTBL:
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
{
ulongtobuf( p, rec->r.htbl.item[i]); p += 4;
}
break;
case RECTYPE_HLST:
ulongtobuf( p, rec->r.hlst.next); p += 4;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++ )
{
ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4;
}
break;
case RECTYPE_TRUST:
memcpy (p, rec->r.trust.fingerprint, 20); p += 20;
*p++ = rec->r.trust.ownertrust;
*p++ = rec->r.trust.depth;
*p++ = rec->r.trust.min_ownertrust;
p++;
ulongtobuf( p, rec->r.trust.validlist); p += 4;
break;
case RECTYPE_VALID:
memcpy (p, rec->r.valid.namehash, 20); p += 20;
*p++ = rec->r.valid.validity;
ulongtobuf( p, rec->r.valid.next); p += 4;
*p++ = rec->r.valid.full_count;
*p++ = rec->r.valid.marginal_count;
break;
default:
BUG();
}
rc = put_record_into_cache (recnum, buf);
if (rc)
;
else if (rec->rectype == RECTYPE_TRUST)
rc = update_trusthashtbl (rec);
return rc;
}
/*
* Delete the record at record number RECNUm from the trustdb.
*
* Return: 0 on success or an error code.
*/
int
tdbio_delete_record (ulong recnum)
{
TRUSTREC vr, rec;
int rc;
/* Must read the record fist, so we can drop it from the hash tables */
rc = tdbio_read_record (recnum, &rec, 0);
if (rc)
;
else if (rec.rectype == RECTYPE_TRUST)
{
rc = drop_from_hashtable (get_trusthashrec(),
rec.r.trust.fingerprint, 20, rec.recnum);
}
if (rc)
return rc;
/* Now we can chnage it to a free record. */
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
rec.recnum = recnum;
rec.rectype = RECTYPE_FREE;
rec.r.free.next = vr.r.ver.firstfree;
vr.r.ver.firstfree = recnum;
rc = tdbio_write_record (&rec);
if (!rc)
rc = tdbio_write_record (&vr);
return rc;
}
/*
* Create a new record and return its record number.
*/
ulong
tdbio_new_recnum ()
{
off_t offset;
ulong recnum;
TRUSTREC vr, rec;
int rc;
/* Look for unused records. */
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal( _("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
if (vr.r.ver.firstfree)
{
recnum = vr.r.ver.firstfree;
rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE);
if (rc)
{
log_error (_("%s: error reading free record: %s\n"),
db_name, gpg_strerror (rc));
return rc;
}
/* Update dir record. */
vr.r.ver.firstfree = rec.r.free.next;
rc = tdbio_write_record (&vr);
if (rc)
{
log_error (_("%s: error writing dir record: %s\n"),
db_name, gpg_strerror (rc));
return rc;
}
/* Zero out the new record. */
memset (&rec, 0, sizeof rec);
rec.rectype = 0; /* Mark as unused record (actually already done
my the memset). */
rec.recnum = recnum;
rc = tdbio_write_record (&rec);
if (rc)
log_fatal (_("%s: failed to zero a record: %s\n"),
db_name, gpg_strerror (rc));
}
else /* Not found - append a new record. */
{
offset = lseek (db_fd, 0, SEEK_END);
if (offset == (off_t)(-1))
log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno));
recnum = offset / TRUST_RECORD_LEN;
log_assert (recnum); /* this is will never be the first record */
/* We must write a record, so that the next call to this
* function returns another recnum. */
memset (&rec, 0, sizeof rec);
rec.rectype = 0; /* unused record */
rec.recnum = recnum;
rc = 0;
if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
rc = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: lseek failed: %s\n"),
recnum, strerror (errno));
}
else
{
int n;
n = write (db_fd, &rec, TRUST_RECORD_LEN);
if (n != TRUST_RECORD_LEN)
{
rc = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
recnum, n, strerror (errno));
}
}
if (rc)
log_fatal (_("%s: failed to append a record: %s\n"),
db_name, gpg_strerror (rc));
}
return recnum ;
}
/* Helper function for tdbio_search_trust_byfpr. */
static int
cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec )
{
return (rec->rectype == RECTYPE_TRUST
&& !memcmp (rec->r.trust.fingerprint, fpr, 20));
}
/*
* Given a 20 byte FINGERPRINT search its trust record and return
* that at REC.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
gpg_error_t
tdbio_search_trust_byfpr (const byte *fingerprint, TRUSTREC *rec)
{
int rc;
/* Locate the trust record using the hash table */
rc = lookup_hashtable (get_trusthashrec(), fingerprint, 20,
cmp_trec_fpr, fingerprint, rec );
return rc;
}
/*
* Given a primary public key object PK search its trust record and
* return that at REC.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
gpg_error_t
tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec)
{
byte fingerprint[MAX_FINGERPRINT_LEN];
size_t fingerlen;
fingerprint_from_pk( pk, fingerprint, &fingerlen );
for (; fingerlen < 20; fingerlen++)
fingerprint[fingerlen] = 0;
return tdbio_search_trust_byfpr (fingerprint, rec);
}
/*
* Terminate the process with a message about a corrupted trustdb.
*/
void
tdbio_invalid (void)
{
log_error (_("Error: The trustdb is corrupted.\n"));
how_to_fix_the_trustdb ();
g10_exit (2);
}
diff --git a/g10/tdbio.h b/g10/tdbio.h
index 1f66b034e..e2cbbff2c 100644
--- a/g10/tdbio.h
+++ b/g10/tdbio.h
@@ -1,118 +1,118 @@
/* tdbio.h - Trust database I/O functions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2012 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef G10_TDBIO_H
#define G10_TDBIO_H
-#include "host2net.h"
+#include "../common/host2net.h"
#define TRUST_RECORD_LEN 40
#define SIGS_PER_RECORD ((TRUST_RECORD_LEN-10)/5)
#define ITEMS_PER_HTBL_RECORD ((TRUST_RECORD_LEN-2)/4)
#define ITEMS_PER_HLST_RECORD ((TRUST_RECORD_LEN-6)/5)
#define ITEMS_PER_PREF_RECORD (TRUST_RECORD_LEN-10)
#if ITEMS_PER_PREF_RECORD % 2
#error ITEMS_PER_PREF_RECORD must be even
#endif
#define MAX_LIST_SIGS_DEPTH 20
#define RECTYPE_VER 1
#define RECTYPE_HTBL 10
#define RECTYPE_HLST 11
#define RECTYPE_TRUST 12
#define RECTYPE_VALID 13
#define RECTYPE_FREE 254
struct trust_record {
int rectype;
int mark;
int dirty; /* for now only used internal by functions */
struct trust_record *next; /* help pointer to build lists in memory */
ulong recnum;
union {
struct { /* version record: */
byte version; /* should be 3 */
byte marginals;
byte completes;
byte cert_depth;
byte trust_model;
byte min_cert_level;
ulong created; /* timestamp of trustdb creation */
ulong nextcheck; /* timestamp of next scheduled check */
ulong reserved;
ulong reserved2;
ulong firstfree;
ulong reserved3;
ulong trusthashtbl;
} ver;
struct { /* free record */
ulong next;
} free;
struct {
ulong item[ITEMS_PER_HTBL_RECORD];
} htbl;
struct {
ulong next;
ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */
} hlst;
struct {
byte fingerprint[20];
byte ownertrust;
byte depth;
ulong validlist;
byte min_ownertrust;
} trust;
struct {
byte namehash[20];
ulong next;
byte validity;
byte full_count;
byte marginal_count;
} valid;
} r;
};
typedef struct trust_record TRUSTREC;
/*-- tdbio.c --*/
int tdbio_update_version_record(void);
int tdbio_set_dbname( const char *new_dbname, int create, int *r_nofile);
const char *tdbio_get_dbname(void);
void tdbio_dump_record( TRUSTREC *rec, estream_t fp );
int tdbio_read_record( ulong recnum, TRUSTREC *rec, int expected );
int tdbio_write_record( TRUSTREC *rec );
int tdbio_db_matches_options(void);
byte tdbio_read_model(void);
ulong tdbio_read_nextcheck (void);
int tdbio_write_nextcheck (ulong stamp);
int tdbio_is_dirty(void);
int tdbio_sync(void);
int tdbio_begin_transaction(void);
int tdbio_end_transaction(void);
int tdbio_cancel_transaction(void);
int tdbio_delete_record( ulong recnum );
ulong tdbio_new_recnum(void);
gpg_error_t tdbio_search_trust_byfpr (const byte *fingerprint, TRUSTREC *rec);
gpg_error_t tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec);
void tdbio_how_to_fix (void);
void tdbio_invalid(void);
#endif /*G10_TDBIO_H*/
diff --git a/g10/test-stubs.c b/g10/test-stubs.c
index a74df209c..d541e0d86 100644
--- a/g10/test-stubs.c
+++ b/g10/test-stubs.c
@@ -1,532 +1,532 @@
/* test-stubs.c - The GnuPG signature verify utility
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2005, 2006,
* 2008, 2009, 2012 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
#include "packet.h"
-#include "iobuf.h"
+#include "../common/iobuf.h"
#include "main.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "filter.h"
-#include "ttyio.h"
-#include "i18n.h"
-#include "sysutils.h"
-#include "status.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/status.h"
#include "call-agent.h"
int g10_errors_seen;
void
g10_exit( int rc )
{
rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0;
exit(rc );
}
/* Stub:
* We have to override the trustcheck from pkclist.c because
* this utility assumes that all keys in the keyring are trustworthy
*/
int
check_signatures_trust (ctrl_t ctrl, PKT_signature *sig)
{
(void)ctrl;
(void)sig;
return 0;
}
void
read_trust_options(byte *trust_model, ulong *created, ulong *nextcheck,
byte *marginals, byte *completes, byte *cert_depth,
byte *min_cert_level)
{
(void)trust_model;
(void)created;
(void)nextcheck;
(void)marginals;
(void)completes;
(void)cert_depth;
(void)min_cert_level;
}
/* Stub:
* We don't have the trustdb , so we have to provide some stub functions
* instead
*/
int
cache_disabled_value(PKT_public_key *pk)
{
(void)pk;
return 0;
}
void
check_trustdb_stale (ctrl_t ctrl)
{
(void)ctrl;
}
int
get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
PKT_user_id *uid)
{
(void)ctrl;
(void)kb;
(void)pk;
(void)uid;
return '?';
}
unsigned int
get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
PKT_signature *sig, int may_ask)
{
(void)ctrl;
(void)kb;
(void)pk;
(void)uid;
(void)sig;
(void)may_ask;
return 0;
}
const char *
trust_value_to_string (unsigned int value)
{
(void)value;
return "err";
}
const char *
uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid)
{
(void)ctrl;
(void)key;
(void)uid;
return "err";
}
int
get_ownertrust_info (PKT_public_key *pk, int no_create)
{
(void)pk;
(void)no_create;
return '?';
}
unsigned int
get_ownertrust (PKT_public_key *pk)
{
(void)pk;
return TRUST_UNKNOWN;
}
/* Stubs:
* Because we only work with trusted keys, it does not make sense to
* get them from a keyserver
*/
struct keyserver_spec *
keyserver_match (struct keyserver_spec *spec)
{
(void)spec;
return NULL;
}
int
keyserver_any_configured (ctrl_t ctrl)
{
(void)ctrl;
return 0;
}
int
keyserver_import_keyid (u32 *keyid, void *dummy, int quick)
{
(void)keyid;
(void)dummy;
(void)quick;
return -1;
}
int
keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len,
struct keyserver_spec *keyserver, int quick)
{
(void)ctrl;
(void)fprint;
(void)fprint_len;
(void)keyserver;
(void)quick;
return -1;
}
int
keyserver_import_cert (const char *name)
{
(void)name;
return -1;
}
int
keyserver_import_pka (const char *name,unsigned char *fpr)
{
(void)name;
(void)fpr;
return -1;
}
gpg_error_t
keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick,
unsigned char **fpr, size_t *fpr_len)
{
(void)ctrl;
(void)name;
(void)quick;
(void)fpr;
(void)fpr_len;
return GPG_ERR_BUG;
}
int
keyserver_import_name (const char *name,struct keyserver_spec *spec)
{
(void)name;
(void)spec;
return -1;
}
int
keyserver_import_ldap (const char *name)
{
(void)name;
return -1;
}
gpg_error_t
read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock)
{
(void)ctrl;
(void)fname;
(void)r_keyblock;
return -1;
}
/* Stub:
* No encryption here but mainproc links to these functions.
*/
gpg_error_t
get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek)
{
(void)ctrl;
(void)k;
(void)dek;
return GPG_ERR_GENERAL;
}
/* Stub: */
gpg_error_t
get_override_session_key (DEK *dek, const char *string)
{
(void)dek;
(void)string;
return GPG_ERR_GENERAL;
}
/* Stub: */
int
decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
{
(void)ctrl;
(void)procctx;
(void)ed;
(void)dek;
return GPG_ERR_GENERAL;
}
/* Stub:
* No interactive commands, so we don't need the helptexts
*/
void
display_online_help (const char *keyword)
{
(void)keyword;
}
/* Stub:
* We don't use secret keys, but getkey.c links to this
*/
int
check_secret_key (PKT_public_key *pk, int n)
{
(void)pk;
(void)n;
return GPG_ERR_GENERAL;
}
/* Stub:
* No secret key, so no passphrase needed
*/
DEK *
passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache,
const char *tmp, int *canceled)
{
(void)cipher_algo;
(void)s2k;
(void)create;
(void)nocache;
(void)tmp;
if (canceled)
*canceled = 0;
return NULL;
}
void
passphrase_clear_cache (const char *cacheid)
{
(void)cacheid;
}
struct keyserver_spec *
parse_preferred_keyserver(PKT_signature *sig)
{
(void)sig;
return NULL;
}
struct keyserver_spec *
parse_keyserver_uri (const char *uri, int require_scheme,
const char *configname, unsigned int configlineno)
{
(void)uri;
(void)require_scheme;
(void)configname;
(void)configlineno;
return NULL;
}
void
free_keyserver_spec (struct keyserver_spec *keyserver)
{
(void)keyserver;
}
/* Stubs to avoid linking to photoid.c */
void
show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk)
{
(void)attrs;
(void)count;
(void)pk;
}
int
parse_image_header (const struct user_attribute *attr, byte *type, u32 *len)
{
(void)attr;
(void)type;
(void)len;
return 0;
}
char *
image_type_to_string (byte type, int string)
{
(void)type;
(void)string;
return NULL;
}
#ifdef ENABLE_CARD_SUPPORT
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
(void)name;
(void)info;
return 0;
}
#endif /* ENABLE_CARD_SUPPORT */
/* We do not do any locking, so use these stubs here */
void
dotlock_disable (void)
{
}
dotlock_t
dotlock_create (const char *file_to_lock, unsigned int flags)
{
(void)file_to_lock;
(void)flags;
return NULL;
}
void
dotlock_destroy (dotlock_t h)
{
(void)h;
}
int
dotlock_take (dotlock_t h, long timeout)
{
(void)h;
(void)timeout;
return 0;
}
int
dotlock_release (dotlock_t h)
{
(void)h;
return 0;
}
void
dotlock_remove_lockfiles (void)
{
}
gpg_error_t
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
(void)ctrl;
(void)pk;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
(void)ctrl;
(void)keyblock;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
(void)ctrl;
(void)hexkeygrip;
(void)r_cleartext;
*r_serialno = NULL;
return gpg_error (GPG_ERR_NO_SECKEY);
}
gpg_error_t
gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
unsigned char **r_fpr, size_t *r_fprlen,
char **r_url)
{
(void)ctrl;
(void)userid;
if (r_fpr)
*r_fpr = NULL;
if (r_fprlen)
*r_fprlen = 0;
if (r_url)
*r_url = NULL;
return gpg_error (GPG_ERR_NOT_FOUND);
}
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
(void)ctrl;
(void)keyspec;
(void)options;
(void)stats;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
gpg_error_t
tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, const char *user_id)
{
(void)ctrl;
(void)fp;
(void)pk;
(void)user_id;
return gpg_error (GPG_ERR_GENERAL);
}
gpg_error_t
tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
(void)ctrl;
(void)pk;
(void)user_id;
(void)policy;
return gpg_error (GPG_ERR_GENERAL);
}
const char *
tofu_policy_str (enum tofu_policy policy)
{
(void)policy;
return "unknown";
}
void
tofu_begin_batch_update (ctrl_t ctrl)
{
(void)ctrl;
}
void
tofu_end_batch_update (ctrl_t ctrl)
{
(void)ctrl;
}
gpg_error_t
tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
{
(void) ctrl;
(void) kb;
return 0;
}
diff --git a/g10/textfilter.c b/g10/textfilter.c
index cb5d444ba..96666cf17 100644
--- a/g10/textfilter.c
+++ b/g10/textfilter.c
@@ -1,245 +1,245 @@
/* textfilter.c
* Copyright (C) 1998, 1999, 2000, 2001, 2004 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
-#include "util.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
+#include "../common/util.h"
#include "filter.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "options.h"
-#include "status.h"
+#include "../common/status.h"
#ifdef HAVE_DOSISH_SYSTEM
#define LF "\r\n"
#else
#define LF "\n"
#endif
#define MAX_LINELEN 19995 /* a little bit smaller than in armor.c */
/* to make sure that a warning is displayed while */
/* creating a message */
static unsigned
len_without_trailing_chars( byte *line, unsigned len, const char *trimchars )
{
byte *p, *mark;
unsigned n;
for(mark=NULL, p=line, n=0; n < len; n++, p++ ) {
if( strchr( trimchars, *p ) ) {
if( !mark )
mark = p;
}
else
mark = NULL;
}
return mark? (mark - line) : len;
}
static int
standard( text_filter_context_t *tfx, IOBUF a,
byte *buf, size_t size, size_t *ret_len)
{
int rc=0;
size_t len = 0;
unsigned maxlen;
log_assert( size > 10 );
size -= 2; /* reserve 2 bytes to append CR,LF */
while( !rc && len < size ) {
int lf_seen;
while( len < size && tfx->buffer_pos < tfx->buffer_len )
buf[len++] = tfx->buffer[tfx->buffer_pos++];
if( len >= size )
continue;
/* read the next line */
maxlen = MAX_LINELEN;
tfx->buffer_pos = 0;
tfx->buffer_len = iobuf_read_line( a, &tfx->buffer,
&tfx->buffer_size, &maxlen );
if( !maxlen )
tfx->truncated++;
if( !tfx->buffer_len ) {
if( !len )
rc = -1; /* eof */
break;
}
lf_seen = tfx->buffer[tfx->buffer_len-1] == '\n';
/* The story behind this is that 2440 says that textmode
hashes should canonicalize line endings to CRLF and remove
spaces and tabs. 2440bis-12 says to just canonicalize to
CRLF. 1.4.0 was released using the bis-12 behavior, but it
was discovered that many mail clients do not canonicalize
PGP/MIME signature text appropriately (and were relying on
GnuPG to handle trailing spaces). So, we default to the
2440 behavior, but use the 2440bis-12 behavior if the user
specifies --no-rfc2440-text. The default will be changed
at some point in the future when the mail clients have been
upgraded. Aside from PGP/MIME and broken mail clients,
this makes no difference to any signatures in the real
world except for a textmode detached signature. PGP always
used the 2440bis-12 behavior (ignoring 2440 itself), so
this actually makes us compatible with PGP textmode
detached signatures for the first time. */
if(opt.rfc2440_text)
tfx->buffer_len=trim_trailing_chars(tfx->buffer,tfx->buffer_len,
" \t\r\n");
else
tfx->buffer_len=trim_trailing_chars(tfx->buffer,tfx->buffer_len,
"\r\n");
if( lf_seen ) {
tfx->buffer[tfx->buffer_len++] = '\r';
tfx->buffer[tfx->buffer_len++] = '\n';
}
}
*ret_len = len;
return rc;
}
/****************
* The filter is used to make canonical text: Lines are terminated by
* CR, LF, trailing white spaces are removed.
*/
int
text_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
text_filter_context_t *tfx = opaque;
int rc=0;
if( control == IOBUFCTRL_UNDERFLOW ) {
rc = standard( tfx, a, buf, size, ret_len );
}
else if( control == IOBUFCTRL_FREE ) {
if( tfx->truncated )
log_error(_("can't handle text lines longer than %d characters\n"),
MAX_LINELEN );
xfree( tfx->buffer );
tfx->buffer = NULL;
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "text_filter", *ret_len);
return rc;
}
/****************
* Copy data from INP to OUT and do some escaping if requested.
* md is updated as required by rfc2440
*/
int
copy_clearsig_text( IOBUF out, IOBUF inp, gcry_md_hd_t md,
int escape_dash, int escape_from)
{
unsigned int maxlen;
byte *buffer = NULL; /* malloced buffer */
unsigned int bufsize; /* and size of this buffer */
unsigned int n;
int truncated = 0;
int pending_lf = 0;
if( !escape_dash )
escape_from = 0;
write_status_begin_signing (md);
for(;;) {
maxlen = MAX_LINELEN;
n = iobuf_read_line( inp, &buffer, &bufsize, &maxlen );
if( !maxlen )
truncated++;
if( !n )
break; /* read_line has returned eof */
/* update the message digest */
if( escape_dash ) {
if( pending_lf ) {
gcry_md_putc ( md, '\r' );
gcry_md_putc ( md, '\n' );
}
gcry_md_write ( md, buffer,
len_without_trailing_chars (buffer, n, " \t\r\n"));
}
else
gcry_md_write ( md, buffer, n );
pending_lf = buffer[n-1] == '\n';
/* write the output */
if( ( escape_dash && *buffer == '-')
|| ( escape_from && n > 4 && !memcmp(buffer, "From ", 5 ) ) ) {
iobuf_put( out, '-' );
iobuf_put( out, ' ' );
}
#if 0 /*defined(HAVE_DOSISH_SYSTEM)*/
/* We don't use this anymore because my interpretation of rfc2440 7.1
* is that there is no conversion needed. If one decides to
* clearsign a unix file on a DOS box he will get a mixed line endings.
* If at some point it turns out, that a conversion is a nice feature
* we can make an option out of it.
*/
/* make sure the lines do end in CR,LF */
if( n > 1 && ( (buffer[n-2] == '\r' && buffer[n-1] == '\n' )
|| (buffer[n-2] == '\n' && buffer[n-1] == '\r'))) {
iobuf_write( out, buffer, n-2 );
iobuf_put( out, '\r');
iobuf_put( out, '\n');
}
else if( n && buffer[n-1] == '\n' ) {
iobuf_write( out, buffer, n-1 );
iobuf_put( out, '\r');
iobuf_put( out, '\n');
}
else
iobuf_write( out, buffer, n );
#else
iobuf_write( out, buffer, n );
#endif
}
/* at eof */
if( !pending_lf ) { /* make sure that the file ends with a LF */
iobuf_writestr( out, LF );
if( !escape_dash )
gcry_md_putc( md, '\n' );
}
if( truncated )
log_info(_("input line longer than %d characters\n"), MAX_LINELEN );
xfree (buffer);
return 0; /* okay */
}
diff --git a/g10/tofu.c b/g10/tofu.c
index 8c41ad701..9d6a45762 100644
--- a/g10/tofu.c
+++ b/g10/tofu.c
@@ -1,3978 +1,3978 @@
/* tofu.c - TOFU trust model.
* Copyright (C) 2015, 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 <https://www.gnu.org/licenses/>.
*/
/* TODO:
- Format the fingerprints nicely when printing (similar to gpg
--list-keys)
*/
#include <config.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sqlite3.h>
#include <time.h>
#include "gpg.h"
-#include "types.h"
-#include "logging.h"
-#include "stringhelp.h"
+#include "../common/types.h"
+#include "../common/logging.h"
+#include "../common/stringhelp.h"
#include "options.h"
-#include "mbox-util.h"
-#include "i18n.h"
-#include "ttyio.h"
+#include "../common/mbox-util.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
#include "trustdb.h"
-#include "mkdir_p.h"
+#include "../common/mkdir_p.h"
#include "gpgsql.h"
-#include "status.h"
+#include "../common/status.h"
#include "sqrtu32.h"
#include "tofu.h"
#define CONTROL_L ('L' - 'A' + 1)
/* Number of days with signed / ecnrypted messages required to
* indicate that enough history is available for basic trust. */
#define BASIC_TRUST_THRESHOLD 4
/* Number of days with signed / encrypted messages required to
* indicate that a lot of history is available. */
#define FULL_TRUST_THRESHOLD 21
/* A struct with data pertaining to the tofu DB. There is one such
struct per session and it is cached in session's ctrl structure.
To initialize this or get the current singleton, call opendbs().
There is no need to explicitly release it; cleanup is done when the
CTRL object is released. */
struct tofu_dbs_s
{
sqlite3 *db;
char *want_lock_file;
time_t want_lock_file_ctime;
struct
{
sqlite3_stmt *savepoint_batch;
sqlite3_stmt *savepoint_batch_commit;
sqlite3_stmt *record_binding_get_old_policy;
sqlite3_stmt *record_binding_update;
sqlite3_stmt *get_policy_select_policy_and_conflict;
sqlite3_stmt *get_trust_bindings_with_this_email;
sqlite3_stmt *get_trust_gather_other_user_ids;
sqlite3_stmt *get_trust_gather_signature_stats;
sqlite3_stmt *get_trust_gather_encryption_stats;
sqlite3_stmt *register_already_seen;
sqlite3_stmt *register_signature;
sqlite3_stmt *register_encryption;
} s;
int in_batch_transaction;
int in_transaction;
time_t batch_update_started;
};
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume
some clock skew. */
#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
/* Days. */
#define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL)
/* Months. */
#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60)
#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM)
/* Years. */
#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60)
#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE)
/* Local prototypes. */
static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
static char *email_from_user_id (const char *user_id);
static int show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now);
const char *
tofu_policy_str (enum tofu_policy policy)
{
switch (policy)
{
case TOFU_POLICY_NONE: return "none";
case TOFU_POLICY_AUTO: return "auto";
case TOFU_POLICY_GOOD: return "good";
case TOFU_POLICY_UNKNOWN: return "unknown";
case TOFU_POLICY_BAD: return "bad";
case TOFU_POLICY_ASK: return "ask";
default: return "???";
}
}
/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
(e.g., TRUST_BAD) in light of the current configuration. */
int
tofu_policy_to_trust_level (enum tofu_policy policy)
{
if (policy == TOFU_POLICY_AUTO)
/* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */
policy = opt.tofu_default_policy;
switch (policy)
{
case TOFU_POLICY_AUTO:
/* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default
to marginal trust. */
return TRUST_MARGINAL;
case TOFU_POLICY_GOOD:
return TRUST_FULLY;
case TOFU_POLICY_UNKNOWN:
return TRUST_UNKNOWN;
case TOFU_POLICY_BAD:
return TRUST_NEVER;
case TOFU_POLICY_ASK:
return TRUST_UNKNOWN;
default:
log_bug ("Bad value for trust policy: %d\n",
opt.tofu_default_policy);
return 0;
}
}
/* Start a transaction on DB. If ONLY_BATCH is set, then this will
start a batch transaction if we haven't started a batch transaction
and one has been requested. */
static gpg_error_t
begin_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
/* If we've been in batch update mode for a while (on average, more
* than 500 ms), to prevent starving other gpg processes, we drop
* and retake the batch lock.
*
* Note: gnupg_get_time has a one second resolution, if we wanted a
* higher resolution, we could use npth_clock_gettime. */
if (/* No real transactions. */
dbs->in_transaction == 0
/* There is an open batch transaction. */
&& dbs->in_batch_transaction
/* And some time has gone by since it was started. */
&& dbs->batch_update_started != gnupg_get_time ())
{
struct stat statbuf;
/* If we are in a batch update, then batch updates better have
been enabled. */
log_assert (ctrl->tofu.batch_updated_wanted);
/* Check if another process wants to run. (We just ignore any
* stat failure. A waiter might have to wait a bit longer, but
* otherwise there should be no impact.) */
if (stat (dbs->want_lock_file, &statbuf) == 0
&& statbuf.st_ctime != dbs->want_lock_file_ctime)
{
end_transaction (ctrl, 2);
/* Yield to allow another process a chance to run. Note:
* testing suggests that anything less than a 100ms tends to
* not result in the other process getting the lock. */
gnupg_usleep (100000);
}
else
dbs->batch_update_started = gnupg_get_time ();
}
if (/* We don't have an open batch transaction. */
!dbs->in_batch_transaction
&& (/* Batch mode is enabled or we are starting a new transaction. */
ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0))
{
struct stat statbuf;
/* We are in batch mode, but we don't have an open batch
* transaction. Since the batch save point must be the outer
* save point, it must be taken before the inner save point. */
log_assert (dbs->in_transaction == 0);
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch,
NULL, NULL, &err,
"begin immediate transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
dbs->in_batch_transaction = 1;
dbs->batch_update_started = gnupg_get_time ();
if (stat (dbs->want_lock_file, &statbuf) == 0)
dbs->want_lock_file_ctime = statbuf.st_ctime;
}
if (only_batch)
return 0;
log_assert (dbs->in_transaction >= 0);
dbs->in_transaction ++;
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"savepoint inner%d;",
dbs->in_transaction);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
/* Commit a transaction. If ONLY_BATCH is 1, then this only ends the
* batch transaction if we have left batch mode. If ONLY_BATCH is 2,
* this commits any open batch transaction even if we are still in
* batch mode. */
static gpg_error_t
end_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
if (only_batch || (! only_batch && dbs->in_transaction == 1))
{
if (!dbs)
return 0; /* Shortcut to allow for easier cleanup code. */
/* If we are releasing the batch transaction, then we better not
be in a normal transaction. */
if (only_batch)
log_assert (dbs->in_transaction == 0);
if (/* Batch mode disabled? */
(!ctrl->tofu.batch_updated_wanted || only_batch == 2)
/* But, we still have an open batch transaction? */
&& dbs->in_batch_transaction)
{
/* The batch transaction is still in open, but we've left
* batch mode. */
dbs->in_batch_transaction = 0;
dbs->in_transaction = 0;
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit,
NULL, NULL, &err,
"commit transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
if (only_batch)
return 0;
}
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"release inner%d;", dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
static gpg_error_t
rollback_transaction (ctrl_t ctrl)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
/* Be careful to not undo any progress made by closed transactions in
batch mode. */
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"rollback to inner%d;",
dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
void
tofu_begin_batch_update (ctrl_t ctrl)
{
ctrl->tofu.batch_updated_wanted ++;
}
void
tofu_end_batch_update (ctrl_t ctrl)
{
log_assert (ctrl->tofu.batch_updated_wanted > 0);
ctrl->tofu.batch_updated_wanted --;
end_transaction (ctrl, 1);
}
/* Suspend any extant batch transaction (it is safe to call this even
no batch transaction has been started). Note: you cannot suspend a
batch transaction if you are in a normal transaction. The batch
transaction can be resumed explicitly by calling
tofu_resume_batch_transaction or implicitly by starting a normal
transaction. */
static void
tofu_suspend_batch_transaction (ctrl_t ctrl)
{
end_transaction (ctrl, 2);
}
/* Resume a batch transaction if there is no extant batch transaction
and one has been requested using tofu_begin_batch_transaction. */
static void
tofu_resume_batch_transaction (ctrl_t ctrl)
{
begin_transaction (ctrl, 1);
}
/* Wrapper around strtol which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_long (long *r_value, const char *string, long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtol (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Wrapper around strtoul which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_ulong (unsigned long *r_value, const char *string,
unsigned long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtoul (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
char **azColName)
{
unsigned long int *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_ulong (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_unsigned_long_cb (cookie, argc, argv, azColName);
}
/* We expect a single integer column whose name is "version". COOKIE
must point to an int. This function always aborts. On error or a
if the version is bad, sets *VERSION to -1. */
static int
version_check_cb (void *cookie, int argc, char **argv, char **azColName)
{
int *version = cookie;
if (argc != 1 || strcmp (azColName[0], "version") != 0)
{
*version = -1;
return 1;
}
if (strcmp (argv[0], "1") == 0)
*version = 1;
else
{
log_error (_("unsupported TOFU database version: %s\n"), argv[0]);
*version = -1;
}
/* Don't run again. */
return 1;
}
static int
check_utks (sqlite3 *db)
{
int rc;
char *err = NULL;
struct key_item *utks;
struct key_item *ki;
int utk_count;
char *utks_string = NULL;
char keyid_str[16+1];
long utks_unchanged = 0;
/* An early version of the v1 format did not include the list of
* known ultimately trusted keys.
*
* This list is used to detect when the set of ultimately trusted
* keys changes. We need to detect this to invalidate the effective
* policy, which can change if an ultimately trusted key is added or
* removed. */
rc = sqlite3_exec (db,
"create table if not exists ultimately_trusted_keys"
" (keyid);\n",
NULL, NULL, &err);
if (rc)
{
log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"),
err);
sqlite3_free (err);
goto out;
}
utks = tdb_utks ();
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
;
if (utk_count)
{
/* Build a list of keyids of the form "XXX","YYY","ZZZ". */
int len = (1 + 16 + 1 + 1) * utk_count;
int o = 0;
utks_string = xmalloc (len);
*utks_string = 0;
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
{
utks_string[o ++] = '\'';
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
memcpy (&utks_string[o], keyid_str, 16);
o += 16;
utks_string[o ++] = '\'';
utks_string[o ++] = ',';
}
utks_string[o - 1] = 0;
log_assert (o == len);
}
rc = gpgsql_exec_printf
(db, get_single_unsigned_long_cb, &utks_unchanged, &err,
"select"
/* Removed UTKs? (Known UTKs in current UTKs.) */
" ((select count(*) from ultimately_trusted_keys"
" where (keyid in (%s))) == %d)"
" and"
/* New UTKs? */
" ((select count(*) from ultimately_trusted_keys"
" where keyid not in (%s)) == 0);",
utks_string ? utks_string : "",
utk_count,
utks_string ? utks_string : "");
xfree (utks_string);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("checking if ultimately trusted keys changed: %s",
err);
sqlite3_free (err);
goto out;
}
if (utks_unchanged)
goto out;
if (DBG_TRUST)
log_debug ("TOFU: ultimately trusted keys changed.\n");
/* Given that the set of ultimately trusted keys
* changed, clear any cached policies. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"update bindings set effective_policy = %d;",
TOFU_POLICY_NONE);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("clearing cached policies: %s", err);
sqlite3_free (err);
goto out;
}
/* Now, update the UTK table. */
rc = sqlite3_exec (db,
"drop table ultimately_trusted_keys;",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("dropping ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
rc = sqlite3_exec (db,
"create table if not exists"
" ultimately_trusted_keys (keyid);\n",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("creating ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
for (ki = utks; ki; ki = ki->next)
{
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"insert into ultimately_trusted_keys values ('%s');",
keyid_str);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("updating ultimately_trusted_keys: %s",
err);
sqlite3_free (err);
goto out;
}
}
out:
return rc;
}
/* If the DB is new, initialize it. Otherwise, check the DB's
version.
Return 0 if the database is okay and 1 otherwise. */
static int
initdb (sqlite3 *db)
{
char *err = NULL;
int rc;
unsigned long int count;
int version = -1;
rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
/* If the DB has no tables, then assume this is a new DB that needs
to be initialized. */
rc = sqlite3_exec (db,
"select count(*) from sqlite_master where type='table';",
get_single_unsigned_long_cb, &count, &err);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("query available tables");
sqlite3_free (err);
goto out;
}
else if (count != 0)
/* Assume that the DB is already initialized. Make sure the
version is okay. */
{
rc = sqlite3_exec (db, "select version from version;", version_check_cb,
&version, &err);
if (rc == SQLITE_ABORT && version == 1)
/* Happy, happy, joy, joy. */
{
sqlite3_free (err);
rc = 0;
goto out;
}
else if (rc == SQLITE_ABORT && version == -1)
/* Unsupported version. */
{
/* An error message was already displayed. */
sqlite3_free (err);
goto out;
}
else if (rc)
/* Some error. */
{
log_error (_("error determining TOFU database's version: %s\n"), err);
sqlite3_free (err);
goto out;
}
else
{
/* Unexpected success. This can only happen if there are no
rows. (select returned 0, but expected ABORT.) */
log_error (_("error determining TOFU database's version: %s\n"),
gpg_strerror (GPG_ERR_NO_DATA));
rc = 1;
goto out;
}
}
/* Create the version table. */
rc = sqlite3_exec (db,
"create table version (version INTEGER);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create version");
sqlite3_free (err);
goto out;
}
/* Initialize the version table, which contains a single integer
value. */
rc = sqlite3_exec (db,
"insert into version values (1);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("insert version");
sqlite3_free (err);
goto out;
}
/* The list of <fingerprint, email> bindings and auxiliary data.
*
* OID is a unique ID identifying this binding (and used by the
* signatures table, see below). Note: OIDs will never be
* reused.
*
* FINGERPRINT: The key's fingerprint.
*
* EMAIL: The normalized email address.
*
* USER_ID: The unmodified user id from which EMAIL was extracted.
*
* TIME: The time this binding was first observed.
*
* POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer).
*
* CONFLICT is either NULL or a fingerprint. Assume that we have
* a binding <0xdeadbeef, foo@example.com> and then we observe
* <0xbaddecaf, foo@example.com>. There two bindings conflict
* (they have the same email address). When we observe the
* latter binding, we warn the user about the conflict and ask
* for a policy decision about the new binding. We also change
* the old binding's policy to ask if it was auto. So that we
* know why this occurred, we also set conflict to 0xbaddecaf.
*/
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"create table bindings\n"
" (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n"
" policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n"
" conflict STRING,\n"
" unique (fingerprint, email));\n"
"create index bindings_fingerprint_email\n"
" on bindings (fingerprint, email);\n"
"create index bindings_email on bindings (email);\n",
TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN,
TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create bindings");
sqlite3_free (err);
goto out;
}
/* The signatures that we have observed.
*
* BINDING refers to a record in the bindings table, which
* describes the binding (i.e., this is a foreign key that
* references bindings.oid).
*
* SIG_DIGEST is the digest stored in the signature.
*
* SIG_TIME is the timestamp stored in the signature.
*
* ORIGIN is a free-form string that describes who fed this
* signature to GnuPG (e.g., email:claws).
*
* TIME is the time this signature was registered. */
rc = sqlite3_exec (db,
"create table signatures "
" (binding INTEGER NOT NULL, sig_digest TEXT,"
" origin TEXT, sig_time INTEGER, time INTEGER,"
" primary key (binding, sig_digest, origin));",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create signatures");
sqlite3_free (err);
goto out;
}
out:
if (! rc)
{
/* Early version of the v1 format did not include the encryption
table. Add it. */
rc = sqlite3_exec (db,
"create table if not exists encryptions"
" (binding INTEGER NOT NULL,"
" time INTEGER);"
"create index if not exists encryptions_binding"
" on encryptions (binding);\n",
NULL, NULL, &err);
if (rc)
{
log_error (_("error creating 'encryptions' TOFU table: %s\n"),
err);
sqlite3_free (err);
}
}
if (! rc)
{
/* The effective policy for a binding. If a key is ultimately
* trusted, then the effective policy of all of its bindings is
* good. Likewise if a key is signed by an ultimately trusted
* key, etc. If the effective policy is NONE, then we need to
* recompute the effective policy. Otherwise, the effective
* policy is considered to be up to date, i.e., effective_policy
* is a cache of the computed policy. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"alter table bindings"
" add column effective_policy INTEGER"
" DEFAULT %d"
" CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));",
TOFU_POLICY_NONE,
TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD,
TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
if (rc == SQLITE_ERROR)
/* Almost certainly "duplicate column name", which we can
* safely ignore. */
rc = 0;
else
log_error (_("adding column effective_policy to bindings DB: %s\n"),
err);
sqlite3_free (err);
}
}
if (! rc)
rc = check_utks (db);
if (rc)
{
rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
}
return 1;
}
else
{
rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
return 0;
}
}
static int
busy_handler (void *cookie, int call_count)
{
ctrl_t ctrl = cookie;
tofu_dbs_t dbs = ctrl->tofu.dbs;
(void) call_count;
/* Update the want-lock-file time stamp (specifically, the ctime) so
* that the current owner knows that we (well, someone) want the
* lock. */
if (dbs)
{
/* Note: we don't fail if we can't create the lock file: this
* process will have to wait a bit longer, but otherwise nothing
* horrible should happen. */
estream_t fp;
fp = es_fopen (dbs->want_lock_file, "w");
if (! fp)
log_debug ("TOFU: Error opening '%s': %s\n",
dbs->want_lock_file, strerror (errno));
else
es_fclose (fp);
}
/* Call again. */
return 1;
}
/* Create a new DB handle. Returns NULL on error. */
/* FIXME: Change to return an error code for better reporting by the
caller. */
static tofu_dbs_t
opendbs (ctrl_t ctrl)
{
char *filename;
sqlite3 *db;
int rc;
if (!ctrl->tofu.dbs)
{
filename = make_filename (gnupg_homedir (), "tofu.db", NULL);
rc = sqlite3_open (filename, &db);
if (rc)
{
log_error (_("error opening TOFU database '%s': %s\n"),
filename, sqlite3_errmsg (db));
/* Even if an error occurs, DB is guaranteed to be valid. */
sqlite3_close (db);
db = NULL;
}
/* If a DB is locked wait up to 5 seconds for the lock to be cleared
before failing. */
if (db)
{
sqlite3_busy_timeout (db, 5 * 1000);
sqlite3_busy_handler (db, busy_handler, ctrl);
}
if (db && initdb (db))
{
sqlite3_close (db);
db = NULL;
}
if (db)
{
ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs);
ctrl->tofu.dbs->db = db;
ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename);
}
xfree (filename);
}
else
log_assert (ctrl->tofu.dbs->db);
return ctrl->tofu.dbs;
}
/* Release all of the resources associated with the DB handle. */
void
tofu_closedbs (ctrl_t ctrl)
{
tofu_dbs_t dbs;
sqlite3_stmt **statements;
dbs = ctrl->tofu.dbs;
if (!dbs)
return; /* Not initialized. */
log_assert (dbs->in_transaction == 0);
end_transaction (ctrl, 2);
/* Arghh, that is a surprising use of the struct. */
for (statements = (void *) &dbs->s;
(void *) statements < (void *) &(&dbs->s)[1];
statements ++)
sqlite3_finalize (*statements);
sqlite3_close (dbs->db);
xfree (dbs->want_lock_file);
xfree (dbs);
ctrl->tofu.dbs = NULL;
}
/* Collect results of a select min (foo) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
{
long *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_long (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_long_cb (cookie, argc, argv, azColName);
}
/* Record (or update) a trust policy about a (possibly new)
binding.
If SHOW_OLD is set, the binding's old policy is displayed. */
static gpg_error_t
record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
const char *user_id,
enum tofu_policy policy, enum tofu_policy effective_policy,
const char *conflict, int set_conflict,
int show_old, time_t now)
{
char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
gpg_error_t rc;
char *err = NULL;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy);
if (DBG_TRUST || show_old)
{
/* Get the old policy. Since this is just for informational
* purposes, there is no need to start a transaction or to die
* if there is a failure. */
/* policy_old needs to be a long and not an enum tofu_policy,
because we pass it by reference to get_single_long_cb2, which
expects a long. */
long policy_old = TOFU_POLICY_NONE;
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_get_old_policy,
get_single_long_cb2, &policy_old, &err,
"select policy from bindings where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_debug ("TOFU: Error reading from binding database"
" (reading policy for <key: %s, user id: %s>): %s\n",
fingerprint, email, err);
sqlite3_free (err);
}
if (policy_old != TOFU_POLICY_NONE)
(show_old ? log_info : log_debug)
("Changing TOFU trust policy for binding"
" <key: %s, user id: %s> from %s to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy_old),
tofu_policy_str (policy));
else
(show_old ? log_info : log_debug)
("Setting TOFU trust policy for new binding"
" <key: %s, user id: %s> to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy));
}
if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
rc = 0;
goto leave;
}
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err,
"insert or replace into bindings\n"
" (oid, fingerprint, email, user_id, time,"
" policy, conflict, effective_policy)\n"
" values (\n"
/* If we don't explicitly reuse the OID, then SQLite will
* reallocate a new one. We just need to search for the OID
* based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?, ?,"
/* If SET_CONFLICT is 0, then preserve conflict's current value. */
" case ?"
" when 0 then"
" (select conflict from bindings where fingerprint = ? and email = ?)"
" else ?"
" end,"
" ?);",
/* oid subquery. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
/* values 2 through 6. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, user_id,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_INT, (int) policy,
/* conflict subquery. */
GPGSQL_ARG_INT, set_conflict ? 1 : 0,
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, conflict ? conflict : "",
GPGSQL_ARG_INT, (int) effective_policy,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info (" insert bindings <key: %s, user id: %s> = %s",
fingerprint, email, tofu_policy_str (policy));
sqlite3_free (err);
goto leave;
}
leave:
xfree (fingerprint_pp);
return rc;
}
/* Collect the strings returned by a query in a simple string list.
Any NULL values are converted to the empty string.
If a result has 3 rows and each row contains two columns, then the
results are added to the list as follows (the value is parentheses
is the 1-based index in the final list):
row 1, col 2 (6)
row 1, col 1 (5)
row 2, col 2 (4)
row 2, col 1 (3)
row 3, col 2 (2)
row 3, col 1 (1)
This is because add_to_strlist pushes the results onto the front of
the list. The end result is that the rows are backwards, but the
columns are in the expected order. */
static int
strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
{
int i;
strlist_t *strlist = cookie;
(void) azColName;
for (i = argc - 1; i >= 0; i --)
add_to_strlist (strlist, argv[i] ? argv[i] : "");
return 0;
}
static int
strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return strings_collect_cb (cookie, argc, argv, azColName);
}
/* Auxiliary data structure to collect statistics about
signatures. */
struct signature_stats
{
struct signature_stats *next;
/* The user-assigned policy for this binding. */
enum tofu_policy policy;
/* How long ago the signature was created (rounded to a multiple of
TIME_AGO_UNIT_SMALL, etc.). */
long time_ago;
/* Number of signatures during this time. */
unsigned long count;
/* If the corresponding key/user id has been expired / revoked. */
int is_expired;
int is_revoked;
/* The key that generated this signature. */
char fingerprint[1];
};
static void
signature_stats_free (struct signature_stats *stats)
{
while (stats)
{
struct signature_stats *next = stats->next;
xfree (stats);
stats = next;
}
}
static void
signature_stats_prepend (struct signature_stats **statsp,
const char *fingerprint,
enum tofu_policy policy,
long time_ago,
unsigned long count)
{
struct signature_stats *stats =
xmalloc_clear (sizeof (*stats) + strlen (fingerprint));
stats->next = *statsp;
*statsp = stats;
strcpy (stats->fingerprint, fingerprint);
stats->policy = policy;
stats->time_ago = time_ago;
stats->count = count;
}
/* Process rows that contain the four columns:
<fingerprint, policy, time ago, count>. */
static int
signature_stats_collect_cb (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
struct signature_stats **statsp = cookie;
int i = 0;
enum tofu_policy policy;
long time_ago;
unsigned long count;
long along;
(void) azColName;
(void) stmt;
i ++;
if (string_to_long (&along, argv[i], 0, __LINE__))
return 1; /* Abort */
policy = along;
i ++;
if (! argv[i])
time_ago = 0;
else
{
if (string_to_long (&time_ago, argv[i], 0, __LINE__))
return 1; /* Abort. */
}
i ++;
/* If time_ago is NULL, then we had no messages, but we still have a
single row, which count(*) turns into 1. */
if (! argv[i - 1])
count = 0;
else
{
if (string_to_ulong (&count, argv[i], 0, __LINE__))
return 1; /* Abort */
}
i ++;
log_assert (argc == i);
signature_stats_prepend (statsp, argv[0], policy, time_ago, count);
return 0;
}
/* Format the first part of a conflict message and return that as a
* malloced string. */
static char *
format_conflict_msg_part1 (int policy, strlist_t conflict_set,
const char *email)
{
estream_t fp;
char *fingerprint;
char *tmpstr, *text;
log_assert (conflict_set);
fingerprint = conflict_set->d;
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (policy == TOFU_POLICY_NONE)
{
es_fprintf (fp,
_("This is the first time the email address \"%s\" is "
"being used with key %s."),
email, fingerprint);
es_fputs (" ", fp);
}
else if (policy == TOFU_POLICY_ASK && conflict_set->next)
{
int conflicts = strlist_length (conflict_set);
es_fprintf
(fp, ngettext("The email address \"%s\" is associated with %d key!",
"The email address \"%s\" is associated with %d keys!",
conflicts),
email, conflicts);
if (opt.verbose)
es_fprintf (fp,
_(" Since this binding's policy was 'auto', it has been "
"changed to 'ask'."));
es_fputs (" ", fp);
}
es_fprintf (fp,
_("Please indicate whether this email address should"
" be associated with key %s or whether you think someone"
" is impersonating \"%s\"."),
fingerprint, email);
es_fputc ('\n', fp);
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
log_fatal ("error snatching memory stream\n");
text = format_text (tmpstr, 0, 72, 80);
es_free (tmpstr);
return text;
}
/* Return 1 if A signed B and B signed A. */
static int
cross_sigs (const char *email, kbnode_t a, kbnode_t b)
{
int i;
PKT_public_key *a_pk = a->pkt->pkt.public_key;
PKT_public_key *b_pk = b->pkt->pkt.public_key;
char a_keyid[33];
char b_keyid[33];
if (DBG_TRUST)
{
format_keyid (pk_main_keyid (a_pk),
KF_LONG, a_keyid, sizeof (a_keyid));
format_keyid (pk_main_keyid (b_pk),
KF_LONG, b_keyid, sizeof (b_keyid));
}
for (i = 0; i < 2; i ++)
{
/* See if SIGNER signed SIGNEE. */
kbnode_t signer = i == 0 ? a : b;
kbnode_t signee = i == 0 ? b : a;
PKT_public_key *signer_pk = signer->pkt->pkt.public_key;
u32 *signer_kid = pk_main_keyid (signer_pk);
kbnode_t n;
int saw_email = 0;
/* Iterate over SIGNEE's keyblock and see if there is a valid
signature from SIGNER. */
for (n = signee; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (keyid_cmp (sig->keyid, signer_kid) == 0)
/* Match! */
break;
}
if (! n)
/* We didn't find a signature from signer over signee. */
{
if (DBG_TRUST)
log_debug ("No cross sig between %s and %s\n",
a_keyid, b_keyid);
return 0;
}
}
/* A signed B and B signed A. */
if (DBG_TRUST)
log_debug ("Cross sig between %s and %s\n",
a_keyid, b_keyid);
return 1;
}
/* Return whether the key was signed by an ultimately trusted key. */
static int
signed_by_utk (const char *email, kbnode_t a)
{
kbnode_t n;
int saw_email = 0;
for (n = a; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (tdb_keyid_is_utk (sig->keyid))
{
/* Match! */
if (DBG_TRUST)
log_debug ("TOFU: %s is signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 1;
}
}
if (DBG_TRUST)
log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 0;
}
enum
{
BINDING_NEW = 1 << 0,
BINDING_CONFLICT = 1 << 1,
BINDING_EXPIRED = 1 << 2,
BINDING_REVOKED = 1 << 3
};
/* Ask the user about the binding. There are three ways we could end
* up here:
*
* - This is a new binding and there is a conflict
* (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
*
* - This is a new binding and opt.tofu_default_policy is set to
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
* TOFU_POLICY_ASK), or,
*
* - The policy is ask (the user deferred last time) (policy ==
* TOFU_POLICY_ASK).
*
* Note: this function must not be called while in a transaction!
*
* CONFLICT_SET includes all of the conflicting bindings
* with FINGERPRINT first. FLAGS is a bit-wise or of
* BINDING_NEW, etc.
*/
static void
ask_about_binding (ctrl_t ctrl,
enum tofu_policy *policy,
int *trust_level,
strlist_t conflict_set,
const char *fingerprint,
const char *email,
const char *user_id,
time_t now)
{
tofu_dbs_t dbs;
strlist_t iter;
int conflict_set_count = strlist_length (conflict_set);
char *sqerr = NULL;
int rc;
estream_t fp;
strlist_t other_user_ids = NULL;
struct signature_stats *stats = NULL;
struct signature_stats *stats_iter = NULL;
char *prompt = NULL;
char *choices;
dbs = ctrl->tofu.dbs;
log_assert (dbs);
log_assert (dbs->in_transaction == 0);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
{
char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
es_fputs (text, fp);
es_fputc ('\n', fp);
xfree (text);
}
begin_transaction (ctrl, 0);
/* Find other user ids associated with this key and whether the
* bindings are marked as good or bad. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_other_user_ids,
strings_collect_cb2, &other_user_ids, &sqerr,
"select user_id, policy from bindings where fingerprint = ?;",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END);
if (rc)
{
log_error (_("error gathering other user IDs: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
if (other_user_ids)
{
strlist_t strlist_iter;
es_fprintf (fp, _("This key's user IDs:\n"));
for (strlist_iter = other_user_ids;
strlist_iter;
strlist_iter = strlist_iter->next)
{
char *other_user_id = strlist_iter->d;
char *other_thing;
enum tofu_policy other_policy;
log_assert (strlist_iter->next);
strlist_iter = strlist_iter->next;
other_thing = strlist_iter->d;
other_policy = atoi (other_thing);
es_fprintf (fp, " %s (", other_user_id);
es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
es_fprintf (fp, ")\n");
}
es_fprintf (fp, "\n");
free_strlist (other_user_ids);
}
/* Get the stats for all the keys in CONFLICT_SET. */
strlist_rev (&conflict_set);
for (iter = conflict_set; iter && ! rc; iter = iter->next)
{
#define STATS_SQL(table, time, sign) \
"select fingerprint, policy, time_ago, count(*)\n" \
" from\n" \
" (select bindings.*,\n" \
" "sign" case\n" \
" when delta ISNULL then 1\n" \
/* From the future (but if its just a couple of hours in the \
* future don't turn it into a warning)? Or should we use \
* small, medium or large units? (Note: whatever we do, we \
* keep the value in seconds. Then when we group, everything \
* that rounds to the same number of seconds is grouped.) */ \
" when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
" then 3\n" \
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
" then 4\n" \
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
" then 5\n" \
" else 6\n" \
" end time_ago,\n" \
" delta time_ago_raw\n" \
" from bindings\n" \
" left join\n" \
" (select *,\n" \
" cast(? - " time " as real) delta\n" \
" from " table ") ss\n" \
" on ss.binding = bindings.oid)\n" \
" where email = ? and fingerprint = ?\n" \
" group by time_ago\n" \
/* Make sure the current key is first. */ \
" order by time_ago desc;\n"
/* Use the time when we saw the signature, not when the
signature was created as that can be forged. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_signature_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("signatures", "time", ""),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_encryption_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("encryptions", "time", "-"),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
#undef STATS_SQL
if (!stats || strcmp (iter->d, stats->fingerprint) != 0
|| stats->time_ago > 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
}
end_transaction (ctrl, 0);
strlist_rev (&conflict_set);
if (rc)
{
strlist_t strlist_iter;
log_error (_("error gathering signature stats: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
es_fprintf (fp, ngettext("The email address \"%s\" is"
" associated with %d key:\n",
"The email address \"%s\" is"
" associated with %d keys:\n",
conflict_set_count),
email, conflict_set_count);
for (strlist_iter = conflict_set;
strlist_iter;
strlist_iter = strlist_iter->next)
es_fprintf (fp, " %s\n", strlist_iter->d);
}
else
{
char *key = NULL;
strlist_t binding;
int seen_in_past = 0;
int encrypted = 1;
es_fprintf (fp, _("Statistics for keys"
" with the email address \"%s\":\n"),
email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{
#if 0
log_debug ("%s: time_ago: %ld; count: %ld\n",
stats_iter->fingerprint,
stats_iter->time_ago,
stats_iter->count);
#endif
if (stats_iter->time_ago > 0 && encrypted)
{
/* We've change from the encrypted stats to the verified
* stats. Reset SEEN_IN_PAST. */
encrypted = 0;
seen_in_past = 0;
}
if (! key || strcmp (key, stats_iter->fingerprint))
{
int this_key;
char *key_pp;
key = stats_iter->fingerprint;
this_key = strcmp (key, fingerprint) == 0;
key_pp = format_hexfingerprint (key, NULL, 0);
es_fprintf (fp, " %s (", key_pp);
/* Find the associated binding. */
for (binding = conflict_set;
binding;
binding = binding->next)
if (strcmp (key, binding->d) == 0)
break;
log_assert (binding);
if ((binding->flags & BINDING_REVOKED))
{
es_fprintf (fp, _("revoked"));
es_fprintf (fp, _(", "));
}
else if ((binding->flags & BINDING_EXPIRED))
{
es_fprintf (fp, _("expired"));
es_fprintf (fp, _(", "));
}
if (this_key)
es_fprintf (fp, _("this key"));
else
es_fprintf (fp, _("policy: %s"),
tofu_policy_str (stats_iter->policy));
es_fputs ("):\n", fp);
xfree (key_pp);
seen_in_past = 0;
show_statistics (dbs, stats_iter->fingerprint, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
if (labs(stats_iter->time_ago) == 1)
{
/* The 1 in this case is the NULL entry. */
log_assert (stats_iter->count == 1);
stats_iter->count = 0;
}
seen_in_past += stats_iter->count;
es_fputs (" ", fp);
if (!stats_iter->count)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message.",
"Verified %d messages.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message.",
"Encrypted %d messages.",
seen_in_past), seen_in_past);
}
else if (labs(stats_iter->time_ago) == 2)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message in the future.",
"Verified %d messages in the future.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message in the future.",
"Encrypted %d messages in the future.",
seen_in_past), seen_in_past);
/* Reset it. */
seen_in_past = 0;
}
else
{
if (labs(stats_iter->time_ago) == 3)
{
int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d day: %d.",
"Messages verified over the past %d days: %d.",
days), days, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d day: %d.",
"Messages encrypted over the past %d days: %d.",
days), days, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 4)
{
int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d month: %d.",
"Messages verified over the past %d months: %d.",
months), months, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d month: %d.",
"Messages encrypted over the past %d months: %d.",
months), months, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 5)
{
int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d year: %d.",
"Messages verified over the past %d years: %d.",
years), years, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d year: %d.",
"Messages encrypted over the past %d years: %d.",
years), years, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 6)
{
if (stats_iter->time_ago > 0)
es_fprintf
(fp, _("Messages verified in the past: %d."),
seen_in_past);
else
es_fprintf
(fp, _("Messages encrypted in the past: %d."),
seen_in_past);
}
else
log_assert (! "Broken SQL.\n");
}
es_fputs ("\n", fp);
}
}
if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT))
{
/* This is a conflict. */
/* TRANSLATORS: Please translate the text found in the source
* file below. We don't directly internationalize that text so
* that we can tweak it without breaking translations. */
char *text = _("TOFU detected a binding conflict");
char *textbuf;
if (!strcmp (text, "TOFU detected a binding conflict"))
{
/* No translation. Use the English text. */
text =
"Normally, an email address is associated with a single key. "
"However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this association, you should talk to or "
"call the person to make sure this new key is legitimate.";
}
textbuf = format_text (text, 0, 72, 80);
es_fprintf (fp, "\n%s\n", textbuf);
xfree (textbuf);
}
es_fputc ('\n', fp);
/* Add a NUL terminator. */
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &prompt, NULL))
log_fatal ("error snatching memory stream\n");
/* I think showing the large message once is sufficient. If we
* would move it right before the cpr_get many lines will scroll
* away and the user might not realize that he merely entered a
* wrong choise (because he does not see that either). As a small
* benefit we allow C-L to redisplay everything. */
tty_printf ("%s", prompt);
/* Suspend any transaction: it could take a while until the user
responds. */
tofu_suspend_batch_transaction (ctrl);
while (1)
{
char *response;
/* TRANSLATORS: Two letters (normally the lower and upper case
* version of the hotkey) for each of the five choices. If
* there is only one choice in your language, repeat it. */
choices = _("gG" "aA" "uU" "rR" "bB");
if (strlen (choices) != 10)
log_bug ("Bad TOFU conflict translation! Please report.");
response = cpr_get
("tofu.conflict",
_("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
trim_spaces (response);
cpr_kill_prompt ();
if (*response == CONTROL_L)
tty_printf ("%s", prompt);
else if (!response[0])
/* Default to unknown. Don't save it. */
{
tty_printf (_("Defaulting to unknown.\n"));
*policy = TOFU_POLICY_UNKNOWN;
break;
}
else if (!response[1])
{
char *choice = strchr (choices, *response);
if (choice)
{
int c = ((size_t) choice - (size_t) choices) / 2;
switch (c)
{
case 0: /* Good. */
*policy = TOFU_POLICY_GOOD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 1: /* Accept once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
break;
case 2: /* Unknown. */
*policy = TOFU_POLICY_UNKNOWN;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 3: /* Reject once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD);
break;
case 4: /* Bad. */
*policy = TOFU_POLICY_BAD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
default:
log_bug ("c should be between 0 and 4 but it is %d!", c);
}
if (record_binding (dbs, fingerprint, email, user_id,
*policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
{
/* If there's an error registering the
* binding, don't save the signature. */
*trust_level = _tofu_GET_TRUST_ERROR;
}
break;
}
}
xfree (response);
}
tofu_resume_batch_transaction (ctrl);
xfree (prompt);
signature_stats_free (stats);
}
/* Return the set of keys that conflict with the binding <fingerprint,
email> (including the binding itself, which will be first in the
list). For each returned key also sets BINDING_NEW, etc. */
static strlist_t
build_conflict_set (tofu_dbs_t dbs,
PKT_public_key *pk, const char *fingerprint,
const char *email)
{
gpg_error_t rc;
char *sqerr;
strlist_t conflict_set = NULL;
int conflict_set_count;
strlist_t iter;
kbnode_t *kb_all;
KEYDB_HANDLE hd;
int i;
/* Get the fingerprints of any bindings that share the email address
* and whether the bindings have a known conflict.
*
* Note: if the binding in question is in the DB, it will also be
* returned. Thus, if the result set is empty, then <email,
* fingerprint> is a new binding. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_bindings_with_this_email,
strings_collect_cb2, &conflict_set, &sqerr,
"select"
/* A binding should only appear once, but try not to break in the
* case of corruption. */
" fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end"
" from bindings where email = ?"
" group by fingerprint"
/* Make sure the current key comes first in the result list (if
it is present). */
" order by fingerprint = ? asc, fingerprint desc;",
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), sqerr);
print_further_info ("listing fingerprints");
sqlite3_free (sqerr);
rc = gpg_error (GPG_ERR_GENERAL);
return NULL;
}
/* Set BINDING_CONFLICT if the binding has a known conflict. This
* allows us to distinguish between bindings where the user
* explicitly set the policy to ask and bindings where we set the
* policy to ask due to a conflict. */
for (iter = conflict_set; iter; iter = iter->next)
{
int l = strlen (iter->d);
if (!(l == 2 * MAX_FINGERPRINT_LEN
|| l == 2 * MAX_FINGERPRINT_LEN + 1))
{
log_error (_("TOFU db corruption detected.\n"));
print_further_info ("fingerprint '%s' is not %d characters long",
iter->d, 2 * MAX_FINGERPRINT_LEN);
}
if (l >= 1 && iter->d[l - 1] == '!')
{
iter->flags |= BINDING_CONFLICT;
/* Remove the !. */
iter->d[l - 1] = 0;
}
}
/* If the current binding has not yet been recorded, add it to the
* list. (The order by above ensures that if it is present, it will
* be first.) */
if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0))
{
add_to_strlist (&conflict_set, fingerprint);
conflict_set->flags |= BINDING_NEW;
}
conflict_set_count = strlist_length (conflict_set);
/* Eliminate false conflicts. */
if (conflict_set_count == 1)
/* We only have a single key. There are no false conflicts to
eliminate. But, we do need to set the flags. */
{
if (pk->has_expired)
conflict_set->flags |= BINDING_EXPIRED;
if (pk->flags.revoked)
conflict_set->flags |= BINDING_REVOKED;
return conflict_set;
}
/* If two keys have cross signatures, then they are controlled by
* the same person and thus are not in conflict. */
kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
hd = keydb_new ();
for (i = 0, iter = conflict_set;
i < conflict_set_count;
i ++, iter = iter->next)
{
char *fp = iter->d;
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
PKT_public_key *binding_pk;
kbnode_t n;
int found_user_id;
rc = keydb_search_reset (hd);
if (rc)
{
log_error (_("resetting keydb: %s\n"),
gpg_strerror (rc));
continue;
}
rc = classify_user_id (fp, &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
/* Note: it is entirely possible that we don't have the key
corresponding to an entry in the TOFU DB. This can
happen if we merge two TOFU DBs, but not the key
rings. */
log_info (_("key \"%s\" not found: %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
print_further_info ("fingerprint: %s", fp);
continue;
}
merge_keys_and_selfsig (kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
kb_all[i] = kb;
/* Since we have the key block, use this opportunity to figure
* out if the binding is expired or revoked. */
binding_pk = kb->pkt->pkt.public_key;
/* The binding is always expired/revoked if the key is
* expired/revoked. */
if (binding_pk->has_expired)
iter->flags |= BINDING_EXPIRED;
if (binding_pk->flags.revoked)
iter->flags |= BINDING_REVOKED;
/* The binding is also expired/revoked if the user id is
* expired/revoked. */
n = kb;
found_user_id = 0;
while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
{
PKT_user_id *user_id2 = n->pkt->pkt.user_id;
char *email2;
if (user_id2->attrib_data)
continue;
email2 = email_from_user_id (user_id2->name);
if (strcmp (email, email2) == 0)
{
found_user_id = 1;
if (user_id2->is_revoked)
iter->flags |= BINDING_REVOKED;
if (user_id2->is_expired)
iter->flags |= BINDING_EXPIRED;
}
xfree (email2);
}
if (! found_user_id)
{
log_info (_("TOFU db corruption detected.\n"));
print_further_info ("user id '%s' not on key block '%s'",
email, fingerprint);
}
}
keydb_release (hd);
/* Now that we have the key blocks, check for cross sigs. */
{
int j;
strlist_t *prevp;
strlist_t iter_next;
int *die;
log_assert (conflict_set_count > 0);
die = xtrycalloc (conflict_set_count, sizeof *die);
if (!die)
{
/*err = gpg_error_from_syserror ();*/
xoutofcore (); /* Fixme: Let the fucntion return an error. */
}
for (i = 0; i < conflict_set_count; i ++)
{
/* Look for cross sigs between this key (i == 0) or a key
* that has cross sigs with i == 0 (i.e., transitively) */
if (! (i == 0 || die[i]))
continue;
for (j = i + 1; j < conflict_set_count; j ++)
/* Be careful: we might not have a key block for a key. */
if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j]))
die[j] = 1;
}
/* Free unconflicting bindings (and all of the key blocks). */
for (iter = conflict_set, prevp = &conflict_set, i = 0;
iter;
iter = iter_next, i ++)
{
iter_next = iter->next;
release_kbnode (kb_all[i]);
if (die[i])
{
*prevp = iter_next;
iter->next = NULL;
free_strlist (iter);
conflict_set_count --;
}
else
{
prevp = &iter->next;
}
}
/* We shouldn't have removed the head. */
log_assert (conflict_set);
log_assert (conflict_set_count >= 1);
xfree (die);
}
xfree (kb_all);
if (DBG_TRUST)
{
log_debug ("binding <key: %s, email: %s> conflicts:\n",
fingerprint, email);
for (iter = conflict_set; iter; iter = iter->next)
{
log_debug (" %s:%s%s%s%s\n",
iter->d,
(iter->flags & BINDING_NEW) ? " new" : "",
(iter->flags & BINDING_CONFLICT) ? " known_conflict" : "",
(iter->flags & BINDING_EXPIRED) ? " expired" : "",
(iter->flags & BINDING_REVOKED) ? " revoked" : "");
}
}
return conflict_set;
}
/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
* (email has already been normalized) and any conflict information in
* *CONFLICT_SETP, if CONFLICT_SETP is not NULL. Returns
* _tofu_GET_POLICY_ERROR if an error occurs.
*
* This function registers the binding in the bindings table if it has
* not yet been registered.
*/
static enum tofu_policy
get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
const char *fingerprint, const char *user_id, const char *email,
strlist_t *conflict_setp, time_t now)
{
int rc;
char *err = NULL;
strlist_t results = NULL;
enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE;
enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR;
long along;
char *conflict_orig = NULL;
char *conflict = NULL;
strlist_t conflict_set = NULL;
int conflict_set_count;
/* Check if the <FINGERPRINT, EMAIL> binding is known
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */
rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
strings_collect_cb2, &results, &err,
"select policy, conflict, effective_policy from bindings\n"
" where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("reading the policy");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist_length (results) == 0)
{
/* No results. Use the defaults. */
policy = TOFU_POLICY_NONE;
effective_policy = TOFU_POLICY_NONE;
}
else if (strlist_length (results) == 3)
{
/* Parse and sanity check the results. */
if (string_to_long (&along, results->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for policy: %s", results->d);
goto out;
}
policy = along;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for policy (%d)", policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (*results->next->d)
conflict = xstrdup (results->next->d);
if (string_to_long (&along, results->next->next->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for effective policy: %s",
results->next->next->d);
goto out;
}
effective_policy = along;
if (! (effective_policy == TOFU_POLICY_NONE
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for effective_policy (%d)",
effective_policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
}
else
{
/* The result has the wrong form. */
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("reading policy: expected 3 columns, got %d\n",
strlist_length (results));
goto out;
}
/* Save the effective policy and conflict so we know if we changed
* them. */
effective_policy_orig = effective_policy;
conflict_orig = conflict;
/* Unless there is a conflict, if the effective policy is cached,
* just return it. The reason we don't do this when there is a
* conflict is because of the following scenario: assume A and B
* conflict and B has signed A's key. Now, later we import A's
* signature on B. We need to recheck A, but the signature was on
* B, i.e., when B changes, we invalidate B's effective policy, but
* we also need to invalidate A's effective policy. Instead, we
* assume that conflicts are rare and don't optimize for them, which
* would complicate the code. */
if (effective_policy != TOFU_POLICY_NONE && !conflict)
goto out;
/* If the user explicitly set the policy, then respect that. */
if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE)
{
effective_policy = policy;
goto out;
}
/* Unless proven wrong, assume the effective policy is 'auto'. */
effective_policy = TOFU_POLICY_AUTO;
/* See if the key is ultimately trusted. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
/* See if the key is signed by an ultimately trusted key. */
{
int fingerprint_raw_len = strlen (fingerprint) / 2;
char fingerprint_raw[20];
int len = 0;
if (fingerprint_raw_len != sizeof fingerprint_raw
|| ((len = hex2bin (fingerprint,
fingerprint_raw, fingerprint_raw_len))
!= strlen (fingerprint)))
{
if (DBG_TRUST)
log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n",
fingerprint, strlen (fingerprint), len);
}
else
{
int lookup_err;
kbnode_t kb;
lookup_err = get_pubkey_byfprint (NULL, &kb,
fingerprint_raw,
fingerprint_raw_len);
if (lookup_err)
{
if (DBG_TRUST)
log_debug ("TOFU: Looking up %s: %s\n",
fingerprint, gpg_strerror (lookup_err));
}
else
{
int is_signed_by_utk = signed_by_utk (email, kb);
release_kbnode (kb);
if (is_signed_by_utk)
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
}
}
/* Check for any conflicts / see if a previously discovered conflict
* disappeared. The latter can happen if the conflicting bindings
* are now cross signed, for instance. */
conflict_set = build_conflict_set (dbs, pk, fingerprint, email);
conflict_set_count = strlist_length (conflict_set);
if (conflict_set_count == 0)
{
/* build_conflict_set should always at least return the current
binding. Something went wrong. */
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_NEW))
{
/* We've never observed a binding with this email address and we
* have a default policy, which is not to ask the user. */
/* If we've seen this binding, then we've seen this email and
* policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert (policy == TOFU_POLICY_NONE);
if (DBG_TRUST)
log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_CONFLICT))
{
/* No known conflicts now, but there was a conflict. This means
* at some point, there was a conflict and we changed this
* binding's policy to ask and set the conflicting key. The
* conflict can go away if there is not a cross sig between the
* two keys. In this case, just silently clear the conflict and
* reset the policy to auto. */
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
conflict = NULL;
goto out;
}
if (conflict_set_count == 1)
{
/* No conflicts and never marked as conflicting. */
log_assert (!conflict);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
/* There is a conflicting key. */
log_assert (conflict_set_count > 1);
effective_policy = TOFU_POLICY_ASK;
conflict = xstrdup (conflict_set->next->d);
out:
log_assert (policy == _tofu_GET_POLICY_ERROR
|| policy == TOFU_POLICY_NONE
|| policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK);
/* Everything but NONE. */
log_assert (effective_policy == _tofu_GET_POLICY_ERROR
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK);
if (effective_policy != TOFU_POLICY_ASK && conflict)
conflict = NULL;
/* If we don't have a record of this binding, its effective policy
* changed, or conflict changed, update the DB. */
if (effective_policy != _tofu_GET_POLICY_ERROR
&& (/* New binding. */
policy == TOFU_POLICY_NONE
/* effective_policy changed. */
|| effective_policy != effective_policy_orig
/* conflict changed. */
|| (conflict != conflict_orig
&& (!conflict || !conflict_orig
|| strcmp (conflict, conflict_orig) != 0))))
{
if (record_binding (dbs, fingerprint, email, user_id,
policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy,
effective_policy, conflict, 1, 0, now) != 0)
log_error (_("error setting TOFU binding's policy"
" to %s\n"), tofu_policy_str (policy));
}
/* If the caller wants the set of conflicts, return it. */
if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
{
if (! conflict_set)
conflict_set = build_conflict_set (dbs, pk, fingerprint, email);
*conflict_setp = conflict_set;
}
else
{
free_strlist (conflict_set);
if (conflict_setp)
*conflict_setp = NULL;
}
xfree (conflict_orig);
if (conflict != conflict_orig)
xfree (conflict);
free_strlist (results);
return effective_policy;
}
/* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs,
* returns _tofu_GET_TRUST_ERROR.
*
* PK is the public key object for FINGERPRINT.
*
* USER_ID is the unadulterated user id.
*
* If MAY_ASK is set, then we may interact with the user. This is
* necessary if there is a conflict or the binding's policy is
* TOFU_POLICY_ASK. In the case of a conflict, we set the new
* conflicting binding's policy to TOFU_POLICY_ASK. In either case,
* we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this
* function must not be called while in a transaction! */
static enum tofu_policy
get_trust (ctrl_t ctrl, PKT_public_key *pk,
const char *fingerprint, const char *email,
const char *user_id, int may_ask,
enum tofu_policy *policyp, strlist_t *conflict_setp,
time_t now)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int in_transaction = 0;
enum tofu_policy policy;
int rc;
char *sqerr = NULL;
strlist_t conflict_set = NULL;
int trust_level = TRUST_UNKNOWN;
strlist_t iter;
log_assert (dbs);
if (may_ask)
log_assert (dbs->in_transaction == 0);
if (opt.batch)
may_ask = 0;
log_assert (pk_is_primary (pk));
/* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
levels. */
log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
&& _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
&& _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
&& _tofu_GET_TRUST_ERROR != TRUST_NEVER
&& _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
&& _tofu_GET_TRUST_ERROR != TRUST_FULLY
&& _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
begin_transaction (ctrl, 0);
in_transaction = 1;
/* We need to call get_policy even if the key is ultimately trusted
* to make sure the binding has been registered. */
policy = get_policy (dbs, pk, fingerprint, user_id, email,
&conflict_set, now);
/* If the key is ultimately trusted, there is nothing to do. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
trust_level = TRUST_ULTIMATE;
policy = TOFU_POLICY_GOOD;
goto out;
}
}
if (policy == TOFU_POLICY_AUTO)
{
policy = opt.tofu_default_policy;
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
" auto (default: %s).\n",
fingerprint, email,
tofu_policy_str (opt.tofu_default_policy));
}
switch (policy)
{
case TOFU_POLICY_AUTO:
case TOFU_POLICY_GOOD:
case TOFU_POLICY_UNKNOWN:
case TOFU_POLICY_BAD:
/* The saved judgement is auto -> auto, good, unknown or bad.
* We don't need to ask the user anything. */
if (DBG_TRUST)
log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n",
fingerprint, email, tofu_policy_str (policy));
trust_level = tofu_policy_to_trust_level (policy);
goto out;
case TOFU_POLICY_ASK:
/* We need to ask the user what to do. */
break;
case _tofu_GET_POLICY_ERROR:
trust_level = _tofu_GET_TRUST_ERROR;
goto out;
default:
log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
}
/* We get here if:
*
* 1. The saved policy is auto and the default policy is ask
* (get_policy() == TOFU_POLICY_AUTO
* && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
* 2. The saved policy is ask (either last time the user selected
* accept once or reject once or there was a conflict and this
* binding's policy was changed from auto to ask)
* (policy == TOFU_POLICY_ASK).
*/
log_assert (policy == TOFU_POLICY_ASK);
if (may_ask)
{
/* We can't be in a normal transaction in ask_about_binding. */
end_transaction (ctrl, 0);
in_transaction = 0;
/* If we get here, we need to ask the user about the binding. */
ask_about_binding (ctrl,
&policy,
&trust_level,
conflict_set,
fingerprint,
email,
user_id,
now);
}
else
{
trust_level = TRUST_UNDEFINED;
}
/* Mark any conflicting bindings that have an automatic policy as
* now requiring confirmation. Note: we do this after we ask for
* confirmation so that when the current policy is printed, it is
* correct. */
if (! in_transaction)
{
begin_transaction (ctrl, 0);
in_transaction = 1;
}
/* The conflict set should always contain at least one element:
* the current key. */
log_assert (conflict_set);
for (iter = conflict_set->next; iter; iter = iter->next)
{
/* We don't immediately set the effective policy to 'ask,
because */
rc = gpgsql_exec_printf
(dbs->db, NULL, NULL, &sqerr,
"update bindings set effective_policy = %d, conflict = %Q"
" where email = %Q and fingerprint = %Q and effective_policy != %d;",
TOFU_POLICY_NONE, fingerprint,
email, iter->d, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error changing TOFU policy: %s\n"), sqerr);
print_further_info ("binding: <key: %s, user id: %s>",
fingerprint, user_id);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (DBG_TRUST)
log_debug ("Set %s to conflict with %s\n",
iter->d, fingerprint);
}
out:
if (in_transaction)
end_transaction (ctrl, 0);
if (policyp)
*policyp = policy;
if (conflict_setp)
*conflict_setp = conflict_set;
else
free_strlist (conflict_set);
return trust_level;
}
/* Return a malloced string of the form
* "7~months"
* The caller should replace all '~' in the returned string by a space
* and also free the returned string.
*
* This is actually a bad hack which may not work correctly with all
* languages.
*/
static char *
time_ago_str (long long int t)
{
/* It would be nice to use a macro to do this, but gettext
works on the unpreprocessed code. */
#define MIN_SECS (60)
#define HOUR_SECS (60 * MIN_SECS)
#define DAY_SECS (24 * HOUR_SECS)
#define WEEK_SECS (7 * DAY_SECS)
#define MONTH_SECS (30 * DAY_SECS)
#define YEAR_SECS (365 * DAY_SECS)
if (t > 2 * YEAR_SECS)
{
long long int c = t / YEAR_SECS;
return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
}
if (t > 2 * MONTH_SECS)
{
long long int c = t / MONTH_SECS;
return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
}
if (t > 2 * WEEK_SECS)
{
long long int c = t / WEEK_SECS;
return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
}
if (t > 2 * DAY_SECS)
{
long long int c = t / DAY_SECS;
return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
}
if (t > 2 * HOUR_SECS)
{
long long int c = t / HOUR_SECS;
return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
}
if (t > 2 * MIN_SECS)
{
long long int c = t / MIN_SECS;
return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
}
return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
}
/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL
* write a "tfs" record to that stream. */
static void
write_stats_status (estream_t fp,
enum tofu_policy policy,
unsigned long signature_count,
unsigned long signature_first_seen,
unsigned long signature_most_recent,
unsigned long signature_days,
unsigned long encryption_count,
unsigned long encryption_first_done,
unsigned long encryption_most_recent,
unsigned long encryption_days)
{
int summary;
int validity;
unsigned long days;
/* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages. */
days = sqrtu32 (signature_days * signature_days
+ encryption_days * encryption_days);
if (days < 1)
validity = 1; /* Key without history. */
else if (days < 2 * BASIC_TRUST_THRESHOLD)
validity = 2; /* Key with too little history. */
else if (days < 2 * FULL_TRUST_THRESHOLD)
validity = 3; /* Key with enough history for basic trust. */
else
validity = 4; /* Key with a lot of history. */
if (policy == TOFU_POLICY_ASK)
summary = 0; /* Key requires attention. */
else
summary = validity;
if (fp)
{
es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
summary, signature_count, encryption_count,
tofu_policy_str (policy),
signature_first_seen, signature_most_recent,
encryption_first_done, encryption_most_recent,
validity, signature_days, encryption_days);
}
else
{
write_status_printf (STATUS_TOFU_STATS,
"%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
summary,
signature_count,
encryption_count,
tofu_policy_str (policy),
signature_first_seen,
signature_most_recent,
encryption_first_done,
encryption_most_recent,
validity,
signature_days, encryption_days);
}
}
/* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
* to OUTFP.
*
* POLICY is the key's policy (as returned by get_policy).
*
* Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether
* the caller should call show_warning after iterating over all user
* ids.
*/
static int
show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now)
{
char *fingerprint_pp;
int rc;
strlist_t strlist = NULL;
char *err = NULL;
unsigned long signature_first_seen = 0;
unsigned long signature_most_recent = 0;
unsigned long signature_count = 0;
unsigned long signature_days = 0;
unsigned long encryption_first_done = 0;
unsigned long encryption_most_recent = 0;
unsigned long encryption_count = 0;
unsigned long encryption_days = 0;
int show_warning = 0;
if (only_status_fd && ! is_status_enabled ())
return 0;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
/* Get the signature stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (signatures.time), 0),\n"
" coalesce (max (signatures.time), 0)\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(signatures.time / (24 * 60 * 60)) day\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&signature_first_seen,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&signature_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
/* Get the encryption stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (encryptions.time), 0),\n"
" coalesce (max (encryptions.time), 0)\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(encryptions.time / (24 * 60 * 60)) day\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&encryption_first_done,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&encryption_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
if (!outfp)
write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
email, strlen (email), 0);
write_stats_status (outfp, policy,
signature_count,
signature_first_seen,
signature_most_recent,
signature_days,
encryption_count,
encryption_first_done,
encryption_most_recent,
encryption_days);
if (!outfp && !only_status_fd)
{
estream_t fp;
char *msg;
fp = es_fopenmem (0, "rw,samethread");
if (! fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (signature_count == 0 && encryption_count == 0)
{
es_fprintf (fp,
_("%s: Verified 0~signatures and encrypted 0~messages."),
email);
}
else
{
if (signature_count == 0)
es_fprintf (fp, _("%s: Verified 0 signatures."), email);
else
{
/* TRANSLATORS: The final %s is replaced by a string like
"7~months". */
char *ago_str = time_ago_str (now - signature_first_seen);
es_fprintf
(fp,
ngettext("%s: Verified %ld~signature in the past %s.",
"%s: Verified %ld~signatures in the past %s.",
signature_count),
email, signature_count, ago_str);
xfree (ago_str);
}
es_fputs (" ", fp);
if (encryption_count == 0)
es_fprintf (fp, _("Encrypted 0 messages."));
else
{
char *ago_str = time_ago_str (now - encryption_first_done);
/* TRANSLATORS: The final %s is replaced by a string like
"7~months". */
es_fprintf (fp,
ngettext("Encrypted %ld~message in the past %s.",
"Encrypted %ld~messages in the past %s.",
encryption_count),
encryption_count, ago_str);
xfree (ago_str);
}
}
if (opt.verbose)
{
es_fputs (" ", fp);
es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
}
es_fputs ("\n", fp);
{
char *tmpmsg, *p;
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
log_fatal ("error snatching memory stream\n");
msg = format_text (tmpmsg, 0, 72, 80);
es_free (tmpmsg);
/* Print a status line but suppress the trailing LF.
* Spaces are not percent escaped. */
if (*msg)
write_status_buffer (STATUS_TOFU_STATS_LONG,
msg, strlen (msg)-1, -1);
/* Remove the non-breaking space markers. */
for (p=msg; *p; p++)
if (*p == '~')
*p = ' ';
}
log_string (GPGRT_LOG_INFO, msg);
xfree (msg);
if (policy == TOFU_POLICY_AUTO)
{
if (signature_count == 0)
log_info (_("Warning: we have yet to see"
" a message signed using this key and user id!\n"));
else if (signature_count == 1)
log_info (_("Warning: we've only seen one message"
" signed using this key and user id!\n"));
if (encryption_count == 0)
log_info (_("Warning: you have yet to encrypt"
" a message to this key!\n"));
else if (encryption_count == 1)
log_info (_("Warning: you have only encrypted"
" one message to this key!\n"));
/* Cf. write_stats_status */
if (sqrtu32 (encryption_count * encryption_count
+ signature_count * signature_count)
< 2 * BASIC_TRUST_THRESHOLD)
show_warning = 1;
}
}
out:
xfree (fingerprint_pp);
return show_warning;
}
static void
show_warning (const char *fingerprint, strlist_t user_id_list)
{
char *set_policy_command;
char *text;
char *tmpmsg;
set_policy_command =
xasprintf ("gpg --tofu-policy bad %s", fingerprint);
tmpmsg = xasprintf
(ngettext
("Warning: if you think you've seen more signatures "
"by this key and user id, then this key might be a "
"forgery! Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
"Warning: if you think you've seen more signatures "
"by this key and these user ids, then this key might be a "
"forgery! Carefully examine the email addresses for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
strlist_length (user_id_list)),
set_policy_command);
text = format_text (tmpmsg, 0, 72, 80);
xfree (tmpmsg);
log_string (GPGRT_LOG_INFO, text);
xfree (text);
es_free (set_policy_command);
}
/* Extract the email address from a user id and normalize it. If the
user id doesn't contain an email address, then we use the whole
user_id and normalize that. The returned string must be freed. */
static char *
email_from_user_id (const char *user_id)
{
char *email = mailbox_from_userid (user_id);
if (! email)
{
/* Hmm, no email address was provided or we are out of core. Just
take the lower-case version of the whole user id. It could be
a hostname, for instance. */
email = ascii_strlwr (xstrdup (user_id));
}
return email;
}
/* Register the signature with the bindings <fingerprint, USER_ID>,
for each USER_ID in USER_ID_LIST. The fingerprint is taken from
the primary key packet PK.
SIG_DIGEST_BIN is the binary representation of the message's
digest. SIG_DIGEST_BIN_LEN is its length.
SIG_TIME is the time that the signature was generated.
ORIGIN is a free-formed string describing the origin of the
signature. If this was from an email and the Claws MUA was used,
then this should be something like: "email:claws". If this is
NULL, the default is simply "unknown".
If MAY_ASK is 1, then this function may interact with the user.
This is necessary if there is a conflict or the binding's policy is
TOFU_POLICY_ASK.
This function returns 0 on success and an error code if an error
occurred. */
gpg_error_t
tofu_register_signature (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin)
{
time_t now = gnupg_get_time ();
gpg_error_t rc;
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
char *email = NULL;
char *err = NULL;
char *sig_digest;
unsigned long c;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
/* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */
rc = begin_transaction (ctrl, 0);
if (rc)
return rc;
log_assert (pk_is_primary (pk));
sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
fingerprint = hexfingerprint (pk, NULL, 0);
if (! origin)
/* The default origin is simply "unknown". */
origin = "unknown";
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
email = email_from_user_id (user_id->d);
if (DBG_TRUST)
log_debug ("TOFU: Registering signature %s with binding"
" <key: %s, user id: %s>\n",
sig_digest, fingerprint, email);
/* Make sure the binding exists and record any TOFU
conflicts. */
if (get_trust (ctrl, pk, fingerprint, email, user_id->d,
0, NULL, NULL, now)
== _tofu_GET_TRUST_ERROR)
{
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
break;
}
/* If we've already seen this signature before, then don't add
it again. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_already_seen,
get_single_unsigned_long_cb2, &c, &err,
"select count (*)\n"
" from signatures left join bindings\n"
" on signatures.binding = bindings.oid\n"
" where fingerprint = ? and email = ? and sig_time = ?\n"
" and sig_digest = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_STRING, sig_digest,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("checking existence");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (c > 1)
/* Duplicates! This should not happen. In particular,
because <fingerprint, email, sig_time, sig_digest> is the
primary key! */
log_debug ("SIGNATURES DB contains duplicate records"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>."
" Please report.\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
else if (c == 1)
{
if (DBG_TRUST)
log_debug ("Already observed the signature and binding"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
}
else if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
}
else
/* This is the first time that we've seen this signature and
binding. Record it. */
{
if (DBG_TRUST)
log_debug ("TOFU: Saving signature"
" <key: %s, user id: %s, sig: %s>\n",
fingerprint, email, sig_digest);
log_assert (c == 0);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_signature, NULL, NULL, &err,
"insert into signatures\n"
" (binding, sig_digest, origin, sig_time, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info ("insert signatures");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
}
}
xfree (email);
if (rc)
break;
}
if (rc)
rollback_transaction (ctrl);
else
rc = end_transaction (ctrl, 0);
xfree (fingerprint);
xfree (sig_digest);
return rc;
}
gpg_error_t
tofu_register_encryption (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
gpg_error_t rc = 0;
tofu_dbs_t dbs;
kbnode_t kb = NULL;
int free_user_id_list = 0;
char *fingerprint = NULL;
strlist_t user_id;
char *err = NULL;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
if (/* We need the key block to find the primary key. */
! pk_is_primary (pk)
/* We need the key block to find all user ids. */
|| ! user_id_list)
kb = get_pubkeyblock (pk->keyid);
/* Make sure PK is a primary key. */
if (! pk_is_primary (pk))
pk = kb->pkt->pkt.public_key;
if (! user_id_list)
{
/* Use all non-revoked user ids. Do use expired user ids. */
kbnode_t n = kb;
while ((n = find_next_kbnode (n, PKT_USER_ID)))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
if (uid->is_revoked)
continue;
add_to_strlist (&user_id_list, uid->name);
}
free_user_id_list = 1;
if (! user_id_list)
log_info (_("WARNING: Encrypting to %s, which has no "
"non-revoked user ids\n"),
keystr (pk->keyid));
}
fingerprint = hexfingerprint (pk, NULL, 0);
tofu_begin_batch_update (ctrl);
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Make sure the binding exists and that we recognize any
conflicts. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
goto die;
}
/* If there is a conflict and MAY_ASK is true, we need to show
* the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode, then
* they have already been printed (this is required to make sure
* the information is available to the caller before cpr_get is
* called). */
if (policy == TOFU_POLICY_ASK && may_ask && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
free_strlist (conflict_set);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_encryption, NULL, NULL, &err,
"insert into encryptions\n"
" (binding, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info ("insert encryption");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
}
xfree (email);
}
die:
tofu_end_batch_update (ctrl);
if (kb)
release_kbnode (kb);
if (free_user_id_list)
free_strlist (user_id_list);
xfree (fingerprint);
return rc;
}
/* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of
interest when the trust model is tofu+pgp (TM_TOFU_PGP).
This function ors together the upper bits (the values not covered
by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */
int
tofu_wot_trust_combine (int tofu_base, int wot_base)
{
int tofu = tofu_base & TRUST_MASK;
int wot = wot_base & TRUST_MASK;
int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
log_assert (tofu == TRUST_UNKNOWN
|| tofu == TRUST_EXPIRED
|| tofu == TRUST_UNDEFINED
|| tofu == TRUST_NEVER
|| tofu == TRUST_MARGINAL
|| tofu == TRUST_FULLY
|| tofu == TRUST_ULTIMATE);
log_assert (wot == TRUST_UNKNOWN
|| wot == TRUST_EXPIRED
|| wot == TRUST_UNDEFINED
|| wot == TRUST_NEVER
|| wot == TRUST_MARGINAL
|| wot == TRUST_FULLY
|| wot == TRUST_ULTIMATE);
/* We first consider negative trust policys. These trump positive
trust policies. */
if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
/* TRUST_NEVER trumps everything else. */
return upper | TRUST_NEVER;
if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
return upper | TRUST_EXPIRED;
/* Now we only have positive or neutral trust policies. We take
the max. */
if (tofu == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE;
if (tofu == TRUST_FULLY)
return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_FULLY)
return upper | TRUST_FULLY;
if (tofu == TRUST_MARGINAL)
return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_MARGINAL)
return upper | TRUST_MARGINAL;
if (tofu == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED;
return upper | TRUST_UNKNOWN;
}
/* Write a "tfs" record for a --with-colons listing. */
gpg_error_t
tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, const char *user_id)
{
time_t now = gnupg_get_time ();
gpg_error_t err;
tofu_dbs_t dbs;
char *fingerprint;
char *email;
enum tofu_policy policy;
if (!*user_id)
return 0; /* No TOFU stats possible for an empty ID. */
dbs = opendbs (ctrl);
if (!dbs)
{
err = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err));
return err;
}
fingerprint = hexfingerprint (pk, NULL, 0);
email = email_from_user_id (user_id);
policy = get_policy (dbs, pk, fingerprint, user_id, email, NULL, now);
show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
xfree (email);
xfree (fingerprint);
return 0;
}
/* Return the validity (TRUST_NEVER, etc.) of the bindings
<FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST. If
USER_ID_LIST->FLAG is set, then the id is considered to be expired.
PK is the primary key packet.
If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
will be prompted to choose a policy. If MAY_ASK is 0 and the
policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
Returns TRUST_UNDEFINED if an error occurs. */
int
tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
int trust_level = TRUST_UNKNOWN;
int bindings = 0;
int bindings_valid = 0;
int need_warning = 0;
int had_conflict = 0;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return TRUST_UNDEFINED;
}
fingerprint = hexfingerprint (pk, NULL, 0);
tofu_begin_batch_update (ctrl);
/* Start the batch transaction now. */
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Always call get_trust to make sure the binding is
registered. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
trust_level = TRUST_UNDEFINED;
xfree (email);
goto die;
}
if (DBG_TRUST)
log_debug ("TOFU: validity for <key: %s, user id: %s>: %s%s.\n",
fingerprint, email,
trust_value_to_string (tl),
user_id->flags ? " (but expired)" : "");
if (user_id->flags)
tl = TRUST_EXPIRED;
if (tl != TRUST_EXPIRED)
bindings_valid ++;
if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
{
/* If policy is ask, then we already printed out the
* conflict information in ask_about_binding or will do so
* in a moment. */
if (policy != TOFU_POLICY_ASK)
need_warning |=
show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
/* If there is a conflict and MAY_ASK is true, we need to
* show the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode,
* then they have already been printed (this is required to
* make sure the information is available to the caller
* before cpr_get is called). */
if (policy == TOFU_POLICY_ASK && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
had_conflict = 1;
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
}
free_strlist (conflict_set);
if (tl == TRUST_NEVER)
trust_level = TRUST_NEVER;
else if (tl == TRUST_EXPIRED)
/* Ignore expired bindings in the trust calculation. */
;
else if (tl > trust_level)
{
/* The expected values: */
log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED
|| tl == TRUST_MARGINAL || tl == TRUST_FULLY
|| tl == TRUST_ULTIMATE);
/* We assume the following ordering: */
log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED);
log_assert (TRUST_UNDEFINED < TRUST_MARGINAL);
log_assert (TRUST_MARGINAL < TRUST_FULLY);
log_assert (TRUST_FULLY < TRUST_ULTIMATE);
trust_level = tl;
}
xfree (email);
}
if (need_warning && ! had_conflict)
show_warning (fingerprint, user_id_list);
die:
tofu_end_batch_update (ctrl);
xfree (fingerprint);
if (bindings_valid == 0)
{
if (DBG_TRUST)
log_debug ("no (of %d) valid bindings."
" Can't get TOFU validity for this set of user ids.\n",
bindings);
return TRUST_NEVER;
}
return trust_level;
}
/* Set the policy for all non-revoked user ids in the keyblock KB to
POLICY.
If no key is available with the specified key id, then this
function returns GPG_ERR_NO_PUBKEY.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
{
gpg_error_t err;
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint = NULL;
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
if (DBG_TRUST)
log_debug ("Setting TOFU policy for %s to %s\n",
keystr (pk->keyid), tofu_policy_str (policy));
if (! pk_is_primary (pk))
log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
fingerprint = hexfingerprint (pk, NULL, 0);
begin_transaction (ctrl, 0);
for (; kb; kb = kb->next)
{
PKT_user_id *user_id;
char *email;
if (kb->pkt->pkttype != PKT_USER_ID)
continue;
user_id = kb->pkt->pkt.user_id;
if (user_id->is_revoked)
/* Skip revoked user ids. (Don't skip expired user ids, the
expiry can be changed.) */
continue;
email = email_from_user_id (user_id->name);
err = record_binding (dbs, fingerprint, email, user_id->name,
policy, TOFU_POLICY_NONE, NULL, 0, 1, now);
if (err)
{
log_error (_("error setting policy for key %s, user id \"%s\": %s"),
fingerprint, email, gpg_strerror (err));
xfree (email);
break;
}
xfree (email);
}
if (err)
rollback_transaction (ctrl);
else
end_transaction (ctrl, 0);
xfree (fingerprint);
return err;
}
/* Return the TOFU policy for the specified binding in *POLICY. If no
policy has been set for the binding, sets *POLICY to
TOFU_POLICY_NONE.
PK is a primary public key and USER_ID is a user id.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint;
char *email;
/* Make sure PK is a primary key. */
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
email = email_from_user_id (user_id->name);
*policy = get_policy (dbs, pk, fingerprint, user_id->name, email, NULL, now);
xfree (email);
xfree (fingerprint);
if (*policy == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
gpg_error_t
tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
{
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint;
char *sqlerr = NULL;
int rc;
/* Make sure PK is a primary key. */
setup_main_keyids (kb);
pk = kb->pkt->pkt.public_key;
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr,
"update bindings set effective_policy = ?"
" where fingerprint = ?;",
GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
xfree (fingerprint);
if (rc == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
diff --git a/g10/trust.c b/g10/trust.c
index d0ea77efd..77fde4cf6 100644
--- a/g10/trust.c
+++ b/g10/trust.c
@@ -1,806 +1,806 @@
/* trust.c - High level trust functions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2012 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
#include "packet.h"
#include "main.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "trustdb.h"
-#include "host2net.h"
+#include "../common/host2net.h"
/* Return true if key is disabled. Note that this is usually used via
the pk_is_disabled macro. */
int
cache_disabled_value (PKT_public_key *pk)
{
#ifdef NO_TRUST_MODELS
(void)pk;
return 0;
#else
return tdb_cache_disabled_value (pk);
#endif
}
void
register_trusted_keyid (u32 *keyid)
{
#ifdef NO_TRUST_MODELS
(void)keyid;
#else
tdb_register_trusted_keyid (keyid);
#endif
}
void
register_trusted_key (const char *string)
{
#ifdef NO_TRUST_MODELS
(void)string;
#else
tdb_register_trusted_key (string);
#endif
}
/*
* This function returns a letter for a trust value. Trust flags
* are ignored.
*/
static int
trust_letter (unsigned int value)
{
switch( (value & TRUST_MASK) )
{
case TRUST_UNKNOWN: return '-';
case TRUST_EXPIRED: return 'e';
case TRUST_UNDEFINED: return 'q';
case TRUST_NEVER: return 'n';
case TRUST_MARGINAL: return 'm';
case TRUST_FULLY: return 'f';
case TRUST_ULTIMATE: return 'u';
default: return '?';
}
}
/* The strings here are similar to those in
pkclist.c:do_edit_ownertrust() */
const char *
trust_value_to_string (unsigned int value)
{
switch ((value & TRUST_MASK))
{
case TRUST_UNKNOWN: return _("unknown");
case TRUST_EXPIRED: return _("expired");
case TRUST_UNDEFINED: return _("undefined");
case TRUST_NEVER: return _("never");
case TRUST_MARGINAL: return _("marginal");
case TRUST_FULLY: return _("full");
case TRUST_ULTIMATE: return _("ultimate");
default: return "err";
}
}
int
string_to_trust_value (const char *str)
{
if (!ascii_strcasecmp (str, "undefined"))
return TRUST_UNDEFINED;
else if (!ascii_strcasecmp (str, "never"))
return TRUST_NEVER;
else if (!ascii_strcasecmp (str, "marginal"))
return TRUST_MARGINAL;
else if (!ascii_strcasecmp (str, "full"))
return TRUST_FULLY;
else if (!ascii_strcasecmp(str, "ultimate"))
return TRUST_ULTIMATE;
else
return -1;
}
const char *
uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid)
{
if (!key && !uid)
{
/* TRANSLATORS: these strings are similar to those in
trust_value_to_string(), but are a fixed length. This is needed to
make attractive information listings where columns line up
properly. The value "10" should be the length of the strings you
choose to translate to. This is the length in printable columns.
It gets passed to atoi() so everything after the number is
essentially a comment and need not be translated. Either key and
uid are both NULL, or neither are NULL. */
return _("10 translator see trust.c:uid_trust_string_fixed");
}
else if(uid->is_revoked || (key && key->flags.revoked))
return _("[ revoked]");
else if(uid->is_expired)
return _("[ expired]");
else if(key)
{
switch (get_validity (ctrl, NULL, key, uid, NULL, 0) & TRUST_MASK)
{
case TRUST_UNKNOWN: return _("[ unknown]");
case TRUST_EXPIRED: return _("[ expired]");
case TRUST_UNDEFINED: return _("[ undef ]");
case TRUST_NEVER: return _("[ never ]");
case TRUST_MARGINAL: return _("[marginal]");
case TRUST_FULLY: return _("[ full ]");
case TRUST_ULTIMATE: return _("[ultimate]");
}
}
return "err";
}
/*
* Return the assigned ownertrust value for the given public key.
* The key should be the primary key.
*/
unsigned int
get_ownertrust (PKT_public_key *pk)
{
#ifdef NO_TRUST_MODELS
(void)pk;
return TRUST_UNKNOWN;
#else
return tdb_get_ownertrust (pk, 0);
#endif
}
/*
* Same as get_ownertrust but this takes the minimum ownertrust value
* into account, and will bump up the value as needed. NO_CREATE
* inhibits creation of a trustdb it that does not yet exists.
*/
static int
get_ownertrust_with_min (PKT_public_key *pk, int no_create)
{
#ifdef NO_TRUST_MODELS
(void)pk;
return TRUST_UNKNOWN;
#else
unsigned int otrust, otrust_min;
/* Shortcut instead of doing the same twice in the two tdb_get
* functions: If the caller asked not to create a trustdb we call
* init_trustdb directly and allow it to fail with an error code for
* a non-existing trustdb. */
if (no_create && init_trustdb (1))
return TRUST_UNKNOWN;
otrust = (tdb_get_ownertrust (pk, no_create) & TRUST_MASK);
otrust_min = tdb_get_min_ownertrust (pk, no_create);
if (otrust < otrust_min)
{
/* If the trust that the user has set is less than the trust
that was calculated from a trust signature chain, use the
higher of the two. We do this here and not in
get_ownertrust since the underlying ownertrust should not
really be set - just the appearance of the ownertrust. */
otrust = otrust_min;
}
return otrust;
#endif
}
/*
* Same as get_ownertrust but return a trust letter instead of an
* value. This takes the minimum ownertrust value into account. If
* NO_CREATE is set, no efforts for creating a trustdb will be taken.
*/
int
get_ownertrust_info (PKT_public_key *pk, int no_create)
{
return trust_letter (get_ownertrust_with_min (pk, no_create));
}
/*
* Same as get_ownertrust but return a trust string instead of an
* value. This takes the minimum ownertrust value into account. If
* NO_CREATE is set, no efforts for creating a trustdb will be taken.
*/
const char *
get_ownertrust_string (PKT_public_key *pk, int no_create)
{
return trust_value_to_string (get_ownertrust_with_min (pk, no_create));
}
/*
* Set the trust value of the given public key to the new value.
* The key should be a primary one.
*/
void
update_ownertrust (PKT_public_key *pk, unsigned int new_trust)
{
#ifdef NO_TRUST_MODELS
(void)pk;
(void)new_trust;
#else
tdb_update_ownertrust (pk, new_trust);
#endif
}
int
clear_ownertrusts (PKT_public_key *pk)
{
#ifdef NO_TRUST_MODELS
(void)pk;
return 0;
#else
return tdb_clear_ownertrusts (pk);
#endif
}
void
revalidation_mark (void)
{
#ifndef NO_TRUST_MODELS
tdb_revalidation_mark ();
#endif
}
void
check_trustdb_stale (ctrl_t ctrl)
{
#ifndef NO_TRUST_MODELS
tdb_check_trustdb_stale (ctrl);
#else
(void)ctrl;
#endif
}
void
check_or_update_trustdb (ctrl_t ctrl)
{
#ifndef NO_TRUST_MODELS
tdb_check_or_update (ctrl);
#else
(void)ctrl;
#endif
}
/*
* Return the validity information for KB/PK (at least one must be
* non-NULL). If the namehash is not NULL, the validity of the
* corresponding user ID is returned, otherwise, a reasonable value
* for the entire key is returned.
*/
unsigned int
get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
PKT_signature *sig, int may_ask)
{
int rc;
unsigned int validity;
u32 kid[2];
PKT_public_key *main_pk;
if (kb && pk)
log_assert (keyid_cmp (pk_main_keyid (pk),
pk_main_keyid (kb->pkt->pkt.public_key)) == 0);
if (! pk)
{
log_assert (kb);
pk = kb->pkt->pkt.public_key;
}
if (uid)
namehash_from_uid (uid);
keyid_from_pk (pk, kid);
if (pk->main_keyid[0] != kid[0] || pk->main_keyid[1] != kid[1])
{
/* This is a subkey - get the mainkey. */
if (kb)
main_pk = kb->pkt->pkt.public_key;
else
{
main_pk = xmalloc_clear (sizeof *main_pk);
rc = get_pubkey (main_pk, pk->main_keyid);
if (rc)
{
char *tempkeystr = xstrdup (keystr (pk->main_keyid));
log_error ("error getting main key %s of subkey %s: %s\n",
tempkeystr, keystr (kid), gpg_strerror (rc));
xfree (tempkeystr);
validity = TRUST_UNKNOWN;
goto leave;
}
}
}
else
main_pk = pk;
#ifdef NO_TRUST_MODELS
validity = TRUST_UNKNOWN;
#else
validity = tdb_get_validity_core (ctrl, kb, pk, uid, main_pk, sig, may_ask);
#endif
leave:
/* Set some flags direct from the key */
if (main_pk->flags.revoked)
validity |= TRUST_FLAG_REVOKED;
if (main_pk != pk && pk->flags.revoked)
validity |= TRUST_FLAG_SUB_REVOKED;
/* Note: expiration is a trust value and not a flag - don't know why
* I initially designed it that way. */
if (main_pk->has_expired || pk->has_expired)
validity = ((validity & (~TRUST_MASK | TRUST_FLAG_PENDING_CHECK))
| TRUST_EXPIRED);
if (main_pk != pk && !kb)
free_public_key (main_pk);
return validity;
}
int
get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
PKT_user_id *uid)
{
int trustlevel;
if (kb && pk)
log_assert (keyid_cmp (pk_main_keyid (pk),
pk_main_keyid (kb->pkt->pkt.public_key)) == 0);
if (! pk && kb)
pk = kb->pkt->pkt.public_key;
if (!pk)
return '?'; /* Just in case a NULL PK is passed. */
trustlevel = get_validity (ctrl, kb, pk, uid, NULL, 0);
if ((trustlevel & TRUST_FLAG_REVOKED))
return 'r';
return trust_letter (trustlevel);
}
const char *
get_validity_string (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
{
int trustlevel;
if (!pk)
return "err"; /* Just in case a NULL PK is passed. */
trustlevel = get_validity (ctrl, NULL, pk, uid, NULL, 0);
if ((trustlevel & TRUST_FLAG_REVOKED))
return _("revoked");
return trust_value_to_string (trustlevel);
}
/*
* Mark the signature of the given UID which are used to certify it.
* To do this, we first revmove all signatures which are not valid and
* from the remain ones we look for the latest one. If this is not a
* certification revocation signature we mark the signature by setting
* node flag bit 8. Revocations are marked with flag 11, and sigs
* from unavailable keys are marked with flag 12. Note that flag bits
* 9 and 10 are used for internal purposes.
*/
void
mark_usable_uid_certs (kbnode_t keyblock, kbnode_t uidnode,
u32 *main_kid, struct key_item *klist,
u32 curtime, u32 *next_expire)
{
kbnode_t node;
PKT_signature *sig;
/* First check all signatures. */
for (node=uidnode->next; node; node = node->next)
{
int rc;
node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12);
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready */
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_kid
&& sig->keyid[0] == main_kid[0] && sig->keyid[1] == main_kid[1])
continue; /* ignore self-signatures if we pass in a main_kid */
if (!IS_UID_SIG(sig) && !IS_UID_REV(sig))
continue; /* we only look at these signature classes */
if(sig->sig_class>=0x11 && sig->sig_class<=0x13 &&
sig->sig_class-0x10<opt.min_cert_level)
continue; /* treat anything under our min_cert_level as an
invalid signature */
if (klist && !is_in_klist (klist, sig))
continue; /* no need to check it then */
if ((rc=check_key_signature (keyblock, node, NULL)))
{
/* we ignore anything that won't verify, but tag the
no_pubkey case */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
node->flag |= 1<<12;
continue;
}
node->flag |= 1<<9;
}
/* Reset the remaining flags. */
for (; node; node = node->next)
node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12);
/* kbnode flag usage: bit 9 is here set for signatures to consider,
* bit 10 will be set by the loop to keep track of keyIDs already
* processed, bit 8 will be set for the usable signatures, and bit
* 11 will be set for usable revocations. */
/* For each cert figure out the latest valid one. */
for (node=uidnode->next; node; node = node->next)
{
KBNODE n, signode;
u32 kid[2];
u32 sigdate;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
if ( !(node->flag & (1<<9)) )
continue; /* not a node to look at */
if ( (node->flag & (1<<10)) )
continue; /* signature with a keyID already processed */
node->flag |= (1<<10); /* mark this node as processed */
sig = node->pkt->pkt.signature;
signode = node;
sigdate = sig->timestamp;
kid[0] = sig->keyid[0]; kid[1] = sig->keyid[1];
/* Now find the latest and greatest signature */
for (n=uidnode->next; n; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
if ( !(n->flag & (1<<9)) )
continue;
if ( (n->flag & (1<<10)) )
continue; /* shortcut already processed signatures */
sig = n->pkt->pkt.signature;
if (kid[0] != sig->keyid[0] || kid[1] != sig->keyid[1])
continue;
n->flag |= (1<<10); /* mark this node as processed */
/* If signode is nonrevocable and unexpired and n isn't,
then take signode (skip). It doesn't matter which is
older: if signode was older then we don't want to take n
as signode is nonrevocable. If n was older then we're
automatically fine. */
if(((IS_UID_SIG(signode->pkt->pkt.signature) &&
!signode->pkt->pkt.signature->flags.revocable &&
(signode->pkt->pkt.signature->expiredate==0 ||
signode->pkt->pkt.signature->expiredate>curtime))) &&
(!(IS_UID_SIG(n->pkt->pkt.signature) &&
!n->pkt->pkt.signature->flags.revocable &&
(n->pkt->pkt.signature->expiredate==0 ||
n->pkt->pkt.signature->expiredate>curtime))))
continue;
/* If n is nonrevocable and unexpired and signode isn't,
then take n. Again, it doesn't matter which is older: if
n was older then we don't want to take signode as n is
nonrevocable. If signode was older then we're
automatically fine. */
if((!(IS_UID_SIG(signode->pkt->pkt.signature) &&
!signode->pkt->pkt.signature->flags.revocable &&
(signode->pkt->pkt.signature->expiredate==0 ||
signode->pkt->pkt.signature->expiredate>curtime))) &&
((IS_UID_SIG(n->pkt->pkt.signature) &&
!n->pkt->pkt.signature->flags.revocable &&
(n->pkt->pkt.signature->expiredate==0 ||
n->pkt->pkt.signature->expiredate>curtime))))
{
signode = n;
sigdate = sig->timestamp;
continue;
}
/* At this point, if it's newer, it goes in as the only
remaining possibilities are signode and n are both either
revocable or expired or both nonrevocable and unexpired.
If the timestamps are equal take the later ordered
packet, presuming that the key packets are hopefully in
their original order. */
if (sig->timestamp >= sigdate)
{
signode = n;
sigdate = sig->timestamp;
}
}
sig = signode->pkt->pkt.signature;
if (IS_UID_SIG (sig))
{ /* this seems to be a usable one which is not revoked.
* Just need to check whether there is an expiration time,
* We do the expired certification after finding a suitable
* certification, the assumption is that a signator does not
* want that after the expiration of his certificate the
* system falls back to an older certification which has a
* different expiration time */
const byte *p;
u32 expire;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL );
expire = p? sig->timestamp + buf32_to_u32(p) : 0;
if (expire==0 || expire > curtime )
{
signode->flag |= (1<<8); /* yeah, found a good cert */
if (next_expire && expire && expire < *next_expire)
*next_expire = expire;
}
}
else
signode->flag |= (1<<11);
}
}
static int
clean_sigs_from_uid (kbnode_t keyblock, kbnode_t uidnode,
int noisy, int self_only)
{
int deleted = 0;
kbnode_t node;
u32 keyid[2];
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
/* Passing in a 0 for current time here means that we'll never weed
out an expired sig. This is correct behavior since we want to
keep the most recent expired sig in a series. */
mark_usable_uid_certs (keyblock, uidnode, NULL, NULL, 0, NULL);
/* What we want to do here is remove signatures that are not
considered as part of the trust calculations. Thus, all invalid
signatures are out, as are any signatures that aren't the last of
a series of uid sigs or revocations It breaks down like this:
coming out of mark_usable_uid_certs, if a sig is unflagged, it is
not even a candidate. If a sig has flag 9 or 10, that means it
was selected as a candidate and vetted. If a sig has flag 8 it
is a usable signature. If a sig has flag 11 it is a usable
revocation. If a sig has flag 12 it was issued by an unavailable
key. "Usable" here means the most recent valid
signature/revocation in a series from a particular signer.
Delete everything that isn't a usable uid sig (which might be
expired), a usable revocation, or a sig from an unavailable
key. */
for (node=uidnode->next;
node && node->pkt->pkttype==PKT_SIGNATURE;
node=node->next)
{
int keep;
keep = self_only? (node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1]) : 1;
/* Keep usable uid sigs ... */
if ((node->flag & (1<<8)) && keep)
continue;
/* ... and usable revocations... */
if ((node->flag & (1<<11)) && keep)
continue;
/* ... and sigs from unavailable keys. */
/* disabled for now since more people seem to want sigs from
unavailable keys removed altogether. */
/*
if(node->flag & (1<<12))
continue;
*/
/* Everything else we delete */
/* At this point, if 12 is set, the signing key was unavailable.
If 9 or 10 is set, it's superseded. Otherwise, it's
invalid. */
if (noisy)
log_info ("removing signature from key %s on user ID \"%s\": %s\n",
keystr (node->pkt->pkt.signature->keyid),
uidnode->pkt->pkt.user_id->name,
node->flag&(1<<12)? "key unavailable":
node->flag&(1<<9)? "signature superseded"
/* */ :"invalid signature" );
delete_kbnode (node);
deleted++;
}
return deleted;
}
/* This is substantially easier than clean_sigs_from_uid since we just
have to establish if the uid has a valid self-sig, is not revoked,
and is not expired. Note that this does not take into account
whether the uid has a trust path to it - just whether the keyholder
themselves has certified the uid. Returns true if the uid was
compacted. To "compact" a user ID, we simply remove ALL signatures
except the self-sig that caused the user ID to be remove-worthy.
We don't actually remove the user ID packet itself since it might
be resurrected in a later merge. Note that this function requires
that the caller has already done a merge_keys_and_selfsig().
TODO: change the import code to allow importing a uid with only a
revocation if the uid already exists on the keyring. */
static int
clean_uid_from_key (kbnode_t keyblock, kbnode_t uidnode, int noisy)
{
kbnode_t node;
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
int deleted = 0;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
log_assert (uidnode->pkt->pkttype==PKT_USER_ID);
/* Skip valid user IDs, compacted user IDs, and non-self-signed user
IDs if --allow-non-selfsigned-uid is set. */
if (uid->created
|| uid->flags.compacted
|| (!uid->is_expired && !uid->is_revoked && opt.allow_non_selfsigned_uid))
return 0;
for (node=uidnode->next;
node && node->pkt->pkttype == PKT_SIGNATURE;
node=node->next)
{
if (!node->pkt->pkt.signature->flags.chosen_selfsig)
{
delete_kbnode (node);
deleted = 1;
uidnode->pkt->pkt.user_id->flags.compacted = 1;
}
}
if (noisy)
{
const char *reason;
char *user = utf8_to_native (uid->name, uid->len, 0);
if (uid->is_revoked)
reason = _("revoked");
else if (uid->is_expired)
reason = _("expired");
else
reason = _("invalid");
log_info ("compacting user ID \"%s\" on key %s: %s\n",
user, keystr_from_pk (keyblock->pkt->pkt.public_key),
reason);
xfree (user);
}
return deleted;
}
/* Needs to be called after a merge_keys_and_selfsig() */
void
clean_one_uid (kbnode_t keyblock, kbnode_t uidnode, int noisy, int self_only,
int *uids_cleaned, int *sigs_cleaned)
{
int dummy = 0;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
log_assert (uidnode->pkt->pkttype==PKT_USER_ID);
if (!uids_cleaned)
uids_cleaned = &dummy;
if (!sigs_cleaned)
sigs_cleaned = &dummy;
/* Do clean_uid_from_key first since if it fires off, we don't have
to bother with the other. */
*uids_cleaned += clean_uid_from_key (keyblock, uidnode, noisy);
if (!uidnode->pkt->pkt.user_id->flags.compacted)
*sigs_cleaned += clean_sigs_from_uid (keyblock, uidnode, noisy, self_only);
}
/* NB: This function marks the deleted nodes only and the caller is
* responsible to skip or remove them. */
void
clean_key (kbnode_t keyblock, int noisy, int self_only,
int *uids_cleaned, int *sigs_cleaned)
{
kbnode_t node;
merge_keys_and_selfsig (keyblock);
for (node = keyblock->next;
node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
clean_one_uid (keyblock, node, noisy, self_only,
uids_cleaned, sigs_cleaned);
}
/* Remove bogus subkey binding signatures: The only signatures
* allowed are of class 0x18 and 0x28. */
log_assert (!node || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY));
for (; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_SIGNATURE
&& !(IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature)))
{
delete_kbnode (node);
if (sigs_cleaned)
++*sigs_cleaned;
}
}
}
diff --git a/g10/trustdb.c b/g10/trustdb.c
index 74430510d..a0b9d5f48 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -1,2245 +1,2245 @@
/* trustdb.c
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2012 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef DISABLE_REGEX
#include <sys/types.h>
#include <regex.h>
#endif /* !DISABLE_REGEX */
#include "gpg.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "options.h"
#include "packet.h"
#include "main.h"
-#include "mbox-util.h"
-#include "i18n.h"
+#include "../common/mbox-util.h"
+#include "../common/i18n.h"
#include "tdbio.h"
#include "trustdb.h"
#include "tofu.h"
typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */
/*
* Structure to keep track of keys, this is used as an array wherre
* the item right after the last one has a keyblock set to NULL.
* Maybe we can drop this thing and replace it by key_item
*/
struct key_array
{
KBNODE keyblock;
};
/* Control information for the trust DB. */
static struct
{
int init;
int level;
char *dbname;
int no_trustdb;
} trustdb_args;
/* Some globals. */
static struct key_item *user_utk_list; /* temp. used to store --trusted-keys */
static struct key_item *utk_list; /* all ultimately trusted keys */
static int pending_check_trustdb;
static int validate_keys (ctrl_t ctrl, int interactive);
/**********************************************
************* some helpers *******************
**********************************************/
static struct key_item *
new_key_item (void)
{
struct key_item *k;
k = xmalloc_clear (sizeof *k);
return k;
}
static void
release_key_items (struct key_item *k)
{
struct key_item *k2;
for (; k; k = k2)
{
k2 = k->next;
xfree (k->trust_regexp);
xfree (k);
}
}
#define KEY_HASH_TABLE_SIZE 1024
/*
* For fast keylook up we need a hash table. Each byte of a KeyID
* should be distributed equally over the 256 possible values (except
* for v3 keyIDs but we consider them as not important here). So we
* can just use 10 bits to index a table of KEY_HASH_TABLE_SIZE key items.
* Possible optimization: Do not use key_items but other hash_table when the
* duplicates lists get too large.
*/
static KeyHashTable
new_key_hash_table (void)
{
struct key_item **tbl;
tbl = xmalloc_clear (KEY_HASH_TABLE_SIZE * sizeof *tbl);
return tbl;
}
static void
release_key_hash_table (KeyHashTable tbl)
{
int i;
if (!tbl)
return;
for (i=0; i < KEY_HASH_TABLE_SIZE; i++)
release_key_items (tbl[i]);
xfree (tbl);
}
/*
* Returns: True if the keyID is in the given hash table
*/
static int
test_key_hash_table (KeyHashTable tbl, u32 *kid)
{
struct key_item *k;
for (k = tbl[(kid[1] % KEY_HASH_TABLE_SIZE)]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return 1;
return 0;
}
/*
* Add a new key to the hash table. The key is identified by its key ID.
*/
static void
add_key_hash_table (KeyHashTable tbl, u32 *kid)
{
int i = kid[1] % KEY_HASH_TABLE_SIZE;
struct key_item *k, *kk;
for (k = tbl[i]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return; /* already in table */
kk = new_key_item ();
kk->kid[0] = kid[0];
kk->kid[1] = kid[1];
kk->next = tbl[i];
tbl[i] = kk;
}
/*
* Release a key_array
*/
static void
release_key_array ( struct key_array *keys )
{
struct key_array *k;
if (keys) {
for (k=keys; k->keyblock; k++)
release_kbnode (k->keyblock);
xfree (keys);
}
}
/*********************************************
********** Initialization *****************
*********************************************/
/*
* Used to register extra ultimately trusted keys - this has to be done
* before initializing the validation module.
* FIXME: Should be replaced by a function to add those keys to the trustdb.
*/
void
tdb_register_trusted_keyid (u32 *keyid)
{
struct key_item *k;
k = new_key_item ();
k->kid[0] = keyid[0];
k->kid[1] = keyid[1];
k->next = user_utk_list;
user_utk_list = k;
}
void
tdb_register_trusted_key( const char *string )
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
err = classify_user_id (string, &desc, 1);
if (err || desc.mode != KEYDB_SEARCH_MODE_LONG_KID )
{
log_error(_("'%s' is not a valid long keyID\n"), string );
return;
}
register_trusted_keyid(desc.u.kid);
}
/*
* Helper to add a key to the global list of ultimately trusted keys.
* Returns: true = inserted, false = already in list.
*/
static int
add_utk (u32 *kid)
{
struct key_item *k;
if (tdb_keyid_is_utk (kid))
return 0;
k = new_key_item ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->ownertrust = TRUST_ULTIMATE;
k->next = utk_list;
utk_list = k;
if( opt.verbose > 1 )
log_info(_("key %s: accepted as trusted key\n"), keystr(kid));
return 1;
}
/****************
* Verify that all our secret keys are usable and put them into the utk_list.
*/
static void
verify_own_keys(void)
{
TRUSTREC rec;
ulong recnum;
int rc;
struct key_item *k;
if (utk_list)
return;
/* scan the trustdb to find all ultimately trusted keys */
for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ )
{
if ( rec.rectype == RECTYPE_TRUST
&& (rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE)
{
byte *fpr = rec.r.trust.fingerprint;
int fprlen;
u32 kid[2];
/* Problem: We do only use fingerprints in the trustdb but
* we need the keyID here to indetify the key; we can only
* use that ugly hack to distinguish between 16 and 20
* butes fpr - it does not work always so we better change
* the whole validation code to only work with
* fingerprints */
fprlen = (!fpr[16] && !fpr[17] && !fpr[18] && !fpr[19])? 16:20;
keyid_from_fingerprint (fpr, fprlen, kid);
if (!add_utk (kid))
log_info(_("key %s occurs more than once in the trustdb\n"),
keystr(kid));
}
}
/* Put any --trusted-key keys into the trustdb */
for (k = user_utk_list; k; k = k->next)
{
if ( add_utk (k->kid) )
{ /* not yet in trustDB as ultimately trusted */
PKT_public_key pk;
memset (&pk, 0, sizeof pk);
rc = get_pubkey (&pk, k->kid);
if (rc)
log_info(_("key %s: no public key for trusted key - skipped\n"),
keystr(k->kid));
else
{
tdb_update_ownertrust
(&pk, ((tdb_get_ownertrust (&pk, 0) & ~TRUST_MASK)
| TRUST_ULTIMATE ));
release_public_key_parts (&pk);
}
log_info (_("key %s marked as ultimately trusted\n"),keystr(k->kid));
}
}
/* release the helper table table */
release_key_items (user_utk_list);
user_utk_list = NULL;
return;
}
/* Returns whether KID is on the list of ultimately trusted keys. */
int
tdb_keyid_is_utk (u32 *kid)
{
struct key_item *k;
for (k = utk_list; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return 1;
return 0;
}
/* Return the list of ultimately trusted keys. */
struct key_item *
tdb_utks (void)
{
return utk_list;
}
/*********************************************
*********** TrustDB stuff *******************
*********************************************/
/*
* Read a record but die if it does not exist
*/
static void
read_record (ulong recno, TRUSTREC *rec, int rectype )
{
int rc = tdbio_read_record (recno, rec, rectype);
if (rc)
{
log_error(_("trust record %lu, req type %d: read failed: %s\n"),
recno, rec->rectype, gpg_strerror (rc) );
tdbio_invalid();
}
if (rectype != rec->rectype)
{
log_error(_("trust record %lu is not of requested type %d\n"),
rec->recnum, rectype);
tdbio_invalid();
}
}
/*
* Write a record and die on error
*/
static void
write_record (TRUSTREC *rec)
{
int rc = tdbio_write_record (rec);
if (rc)
{
log_error(_("trust record %lu, type %d: write failed: %s\n"),
rec->recnum, rec->rectype, gpg_strerror (rc) );
tdbio_invalid();
}
}
/*
* sync the TrustDb and die on error
*/
static void
do_sync(void)
{
int rc = tdbio_sync ();
if(rc)
{
log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) );
g10_exit(2);
}
}
const char *
trust_model_string (int model)
{
switch (model)
{
case TM_CLASSIC: return "classic";
case TM_PGP: return "pgp";
case TM_EXTERNAL: return "external";
case TM_TOFU: return "tofu";
case TM_TOFU_PGP: return "tofu+pgp";
case TM_ALWAYS: return "always";
case TM_DIRECT: return "direct";
default: return "unknown";
}
}
/****************
* Perform some checks over the trustdb
* level 0: only open the db
* 1: used for initial program startup
*/
int
setup_trustdb( int level, const char *dbname )
{
/* just store the args */
if( trustdb_args.init )
return 0;
trustdb_args.level = level;
trustdb_args.dbname = dbname? xstrdup(dbname): NULL;
return 0;
}
void
how_to_fix_the_trustdb ()
{
const char *name = trustdb_args.dbname;
if (!name)
name = "trustdb.gpg";
log_info (_("You may try to re-create the trustdb using the commands:\n"));
log_info (" cd %s\n", default_homedir ());
log_info (" %s --export-ownertrust > otrust.tmp\n", GPG_NAME);
#ifdef HAVE_W32_SYSTEM
log_info (" del %s\n", name);
#else
log_info (" rm %s\n", name);
#endif
log_info (" %s --import-ownertrust < otrust.tmp\n", GPG_NAME);
log_info (_("If that does not work, please consult the manual\n"));
}
/* Initialize the trustdb. With NO_CREATE set a missing trustdb is
* not an error and the function won't terminate the process on error;
* in that case 0 is returned if there is a trustdb or an error code
* if no trustdb is available. */
gpg_error_t
init_trustdb (int no_create)
{
int level = trustdb_args.level;
const char* dbname = trustdb_args.dbname;
if( trustdb_args.init )
return 0;
trustdb_args.init = 1;
if(level==0 || level==1)
{
int rc = tdbio_set_dbname (dbname, (!no_create && level),
&trustdb_args.no_trustdb);
if (no_create && trustdb_args.no_trustdb)
{
/* No trustdb found and the caller asked us not to create
* it. Return an error and set the initialization state
* back so that we always test for an existing trustdb. */
trustdb_args.init = 0;
return gpg_error (GPG_ERR_ENOENT);
}
if (rc)
log_fatal("can't init trustdb: %s\n", gpg_strerror (rc) );
}
else
BUG();
if(opt.trust_model==TM_AUTO)
{
/* Try and set the trust model off of whatever the trustdb says
it is. */
opt.trust_model=tdbio_read_model();
/* Sanity check this ;) */
if(opt.trust_model != TM_CLASSIC
&& opt.trust_model != TM_PGP
&& opt.trust_model != TM_TOFU_PGP
&& opt.trust_model != TM_TOFU
&& opt.trust_model != TM_EXTERNAL)
{
log_info(_("unable to use unknown trust model (%d) - "
"assuming %s trust model\n"),opt.trust_model,"pgp");
opt.trust_model = TM_PGP;
}
if(opt.verbose)
log_info(_("using %s trust model\n"),
trust_model_string (opt.trust_model));
}
if (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC
|| opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
/* Verify the list of ultimately trusted keys and move the
--trusted-keys list there as well. */
if(level==1)
verify_own_keys();
if(!tdbio_db_matches_options())
pending_check_trustdb=1;
}
return 0;
}
/* Check whether we have a trust database, initializing it if
necessary if the trust model is not 'always trust'. Returns true
if we do have a usable trust database. */
int
have_trustdb (void)
{
return init_trustdb (opt.trust_model == TM_ALWAYS) == 0;
}
/****************
* Recreate the WoT but do not ask for new ownertrusts. Special
* feature: In batch mode and without a forced yes, this is only done
* when a check is due. This can be used to run the check from a crontab
*/
void
check_trustdb (ctrl_t ctrl)
{
init_trustdb (0);
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)
{
if (opt.batch && !opt.answer_yes)
{
ulong scheduled;
scheduled = tdbio_read_nextcheck ();
if (!scheduled)
{
log_info (_("no need for a trustdb check\n"));
return;
}
if (scheduled > make_timestamp ())
{
log_info (_("next trustdb check due at %s\n"),
strtimestamp (scheduled));
return;
}
}
validate_keys (ctrl, 0);
}
else
log_info (_("no need for a trustdb check with '%s' trust model\n"),
trust_model_string(opt.trust_model));
}
/*
* Recreate the WoT.
*/
void
update_trustdb (ctrl_t ctrl)
{
init_trustdb (0);
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)
validate_keys (ctrl, 1);
else
log_info (_("no need for a trustdb update with '%s' trust model\n"),
trust_model_string(opt.trust_model));
}
void
tdb_revalidation_mark (void)
{
init_trustdb (0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
/* We simply set the time for the next check to 1 (far back in 1970)
so that a --update-trustdb will be scheduled. */
if (tdbio_write_nextcheck (1))
do_sync ();
pending_check_trustdb = 1;
}
int
trustdb_pending_check(void)
{
return pending_check_trustdb;
}
/* If the trustdb is dirty, and we're interactive, update it.
Otherwise, check it unless no-auto-check-trustdb is set. */
void
tdb_check_or_update (ctrl_t ctrl)
{
if (trustdb_pending_check ())
{
if (opt.interactive)
update_trustdb (ctrl);
else if (!opt.no_auto_check_trustdb)
check_trustdb (ctrl);
}
}
void
read_trust_options(byte *trust_model,ulong *created,ulong *nextcheck,
byte *marginals,byte *completes,byte *cert_depth,
byte *min_cert_level)
{
TRUSTREC opts;
init_trustdb (0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
memset (&opts, 0, sizeof opts);
else
read_record (0, &opts, RECTYPE_VER);
if(trust_model)
*trust_model=opts.r.ver.trust_model;
if(created)
*created=opts.r.ver.created;
if(nextcheck)
*nextcheck=opts.r.ver.nextcheck;
if(marginals)
*marginals=opts.r.ver.marginals;
if(completes)
*completes=opts.r.ver.completes;
if(cert_depth)
*cert_depth=opts.r.ver.cert_depth;
if(min_cert_level)
*min_cert_level=opts.r.ver.min_cert_level;
}
/***********************************************
*********** Ownertrust et al. ****************
***********************************************/
static int
read_trust_record (PKT_public_key *pk, TRUSTREC *rec)
{
int rc;
init_trustdb (0);
rc = tdbio_search_trust_bypk (pk, rec);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("trustdb: searching trust record failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec->rectype != RECTYPE_TRUST)
{
log_error ("trustdb: record %lu is not a trust record\n",
rec->recnum);
return GPG_ERR_TRUSTDB;
}
return 0;
}
/*
* Return the assigned ownertrust value for the given public key. The
* key should be the primary key. If NO_CREATE is set a missing
* trustdb will not be created. This comes for example handy when we
* want to print status lines (DECRYPTION_KEY) which carry ownertrust
* values but we usually use --always-trust.
*/
unsigned int
tdb_get_ownertrust (PKT_public_key *pk, int no_create)
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
/* If the caller asked not to create a trustdb we call init_trustdb
* directly and allow it to fail with an error code for a
* non-existing trustdb. */
if (no_create && init_trustdb (1))
return TRUST_UNKNOWN;
err = read_trust_record (pk, &rec);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
return TRUST_UNKNOWN; /* no record yet */
if (err)
{
tdbio_invalid ();
return TRUST_UNKNOWN; /* actually never reached */
}
return rec.r.trust.ownertrust;
}
unsigned int
tdb_get_min_ownertrust (PKT_public_key *pk, int no_create)
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
/* If the caller asked not to create a trustdb we call init_trustdb
* directly and allow it to fail with an error code for a
* non-existing trustdb. */
if (no_create && init_trustdb (1))
return TRUST_UNKNOWN;
err = read_trust_record (pk, &rec);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
return TRUST_UNKNOWN; /* no record yet */
if (err)
{
tdbio_invalid ();
return TRUST_UNKNOWN; /* actually never reached */
}
return rec.r.trust.min_ownertrust;
}
/*
* Set the trust value of the given public key to the new value.
* The key should be a primary one.
*/
void
tdb_update_ownertrust (PKT_public_key *pk, unsigned int new_trust )
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
err = read_trust_record (pk, &rec);
if (!err)
{
if (DBG_TRUST)
log_debug ("update ownertrust from %u to %u\n",
(unsigned int)rec.r.trust.ownertrust, new_trust );
if (rec.r.trust.ownertrust != new_trust)
{
rec.r.trust.ownertrust = new_trust;
write_record( &rec );
tdb_revalidation_mark ();
do_sync ();
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{ /* no record yet - create a new one */
size_t dummy;
if (DBG_TRUST)
log_debug ("insert ownertrust %u\n", new_trust );
memset (&rec, 0, sizeof rec);
rec.recnum = tdbio_new_recnum ();
rec.rectype = RECTYPE_TRUST;
fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy);
rec.r.trust.ownertrust = new_trust;
write_record (&rec);
tdb_revalidation_mark ();
do_sync ();
}
else
{
tdbio_invalid ();
}
}
static void
update_min_ownertrust (u32 *kid, unsigned int new_trust )
{
PKT_public_key *pk;
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
pk = xmalloc_clear (sizeof *pk);
err = get_pubkey (pk, kid);
if (err)
{
log_error (_("public key %s not found: %s\n"),
keystr (kid), gpg_strerror (err));
return;
}
err = read_trust_record (pk, &rec);
if (!err)
{
if (DBG_TRUST)
log_debug ("key %08lX%08lX: update min_ownertrust from %u to %u\n",
(ulong)kid[0],(ulong)kid[1],
(unsigned int)rec.r.trust.min_ownertrust,
new_trust );
if (rec.r.trust.min_ownertrust != new_trust)
{
rec.r.trust.min_ownertrust = new_trust;
write_record( &rec );
tdb_revalidation_mark ();
do_sync ();
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{ /* no record yet - create a new one */
size_t dummy;
if (DBG_TRUST)
log_debug ("insert min_ownertrust %u\n", new_trust );
memset (&rec, 0, sizeof rec);
rec.recnum = tdbio_new_recnum ();
rec.rectype = RECTYPE_TRUST;
fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy);
rec.r.trust.min_ownertrust = new_trust;
write_record (&rec);
tdb_revalidation_mark ();
do_sync ();
}
else
{
tdbio_invalid ();
}
}
/*
* Clear the ownertrust and min_ownertrust values.
*
* Return: True if a change actually happened.
*/
int
tdb_clear_ownertrusts (PKT_public_key *pk)
{
TRUSTREC rec;
gpg_error_t err;
init_trustdb (0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return 0;
err = read_trust_record (pk, &rec);
if (!err)
{
if (DBG_TRUST)
{
log_debug ("clearing ownertrust (old value %u)\n",
(unsigned int)rec.r.trust.ownertrust);
log_debug ("clearing min_ownertrust (old value %u)\n",
(unsigned int)rec.r.trust.min_ownertrust);
}
if (rec.r.trust.ownertrust || rec.r.trust.min_ownertrust)
{
rec.r.trust.ownertrust = 0;
rec.r.trust.min_ownertrust = 0;
write_record( &rec );
tdb_revalidation_mark ();
do_sync ();
return 1;
}
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
}
return 0;
}
/*
* Note: Caller has to do a sync
*/
static void
update_validity (PKT_public_key *pk, PKT_user_id *uid,
int depth, int validity)
{
TRUSTREC trec, vrec;
gpg_error_t err;
ulong recno;
namehash_from_uid(uid);
err = read_trust_record (pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
return;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record yet - create a new one. */
size_t dummy;
memset (&trec, 0, sizeof trec);
trec.recnum = tdbio_new_recnum ();
trec.rectype = RECTYPE_TRUST;
fingerprint_from_pk (pk, trec.r.trust.fingerprint, &dummy);
trec.r.trust.ownertrust = 0;
}
/* locate an existing one */
recno = trec.r.trust.validlist;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if ( !memcmp (vrec.r.valid.namehash, uid->namehash, 20) )
break;
recno = vrec.r.valid.next;
}
if (!recno) /* insert a new validity record */
{
memset (&vrec, 0, sizeof vrec);
vrec.recnum = tdbio_new_recnum ();
vrec.rectype = RECTYPE_VALID;
memcpy (vrec.r.valid.namehash, uid->namehash, 20);
vrec.r.valid.next = trec.r.trust.validlist;
trec.r.trust.validlist = vrec.recnum;
}
vrec.r.valid.validity = validity;
vrec.r.valid.full_count = uid->help_full_count;
vrec.r.valid.marginal_count = uid->help_marginal_count;
write_record (&vrec);
trec.r.trust.depth = depth;
write_record (&trec);
}
/***********************************************
********* Query trustdb values **************
***********************************************/
/* Return true if key is disabled. Note that this is usually used via
the pk_is_disabled macro. */
int
tdb_cache_disabled_value (PKT_public_key *pk)
{
gpg_error_t err;
TRUSTREC trec;
int disabled = 0;
if (pk->flags.disabled_valid)
return pk->flags.disabled;
init_trustdb (0);
if (trustdb_args.no_trustdb)
return 0; /* No trustdb => not disabled. */
err = read_trust_record (pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
goto leave;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record found, so assume not disabled. */
goto leave;
}
if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
disabled = 1;
/* Cache it for later so we don't need to look at the trustdb every
time */
pk->flags.disabled = disabled;
pk->flags.disabled_valid = 1;
leave:
return disabled;
}
void
tdb_check_trustdb_stale (ctrl_t ctrl)
{
static int did_nextcheck=0;
init_trustdb (0);
if (trustdb_args.no_trustdb)
return; /* No trustdb => can't be stale. */
if (!did_nextcheck
&& (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
ulong scheduled;
did_nextcheck = 1;
scheduled = tdbio_read_nextcheck ();
if ((scheduled && scheduled <= make_timestamp ())
|| pending_check_trustdb)
{
if (opt.no_auto_check_trustdb)
{
pending_check_trustdb = 1;
if (!opt.quiet)
log_info (_("please do a --check-trustdb\n"));
}
else
{
if (!opt.quiet)
log_info (_("checking the trustdb\n"));
validate_keys (ctrl, 0);
}
}
}
}
/*
* Return the validity information for KB/PK (at least one of them
* must be non-NULL). This is the core of get_validity. If SIG is
* not NULL, then the trust is being evaluated in the context of the
* provided signature. This is used by the TOFU code to record
* statistics.
*/
unsigned int
tdb_get_validity_core (ctrl_t ctrl,
kbnode_t kb,
PKT_public_key *pk, PKT_user_id *uid,
PKT_public_key *main_pk,
PKT_signature *sig,
int may_ask)
{
TRUSTREC trec, vrec;
gpg_error_t err = 0;
ulong recno;
#ifdef USE_TOFU
unsigned int tofu_validity = TRUST_UNKNOWN;
int free_kb = 0;
#endif
unsigned int validity = TRUST_UNKNOWN;
if (kb && pk)
log_assert (keyid_cmp (pk_main_keyid (pk),
pk_main_keyid (kb->pkt->pkt.public_key)) == 0);
if (! pk)
{
log_assert (kb);
pk = kb->pkt->pkt.public_key;
}
#ifndef USE_TOFU
(void)sig;
(void)may_ask;
#endif
init_trustdb (0);
/* If we have no trustdb (which also means it has not been created)
and the trust-model is always, we don't know the validity -
return immediately. If we won't do that the tdbio code would try
to open the trustdb and run into a fatal error. */
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
check_trustdb_stale (ctrl);
if(opt.trust_model==TM_DIRECT)
{
/* Note that this happens BEFORE any user ID stuff is checked.
The direct trust model applies to keys as a whole. */
validity = tdb_get_ownertrust (main_pk, 0);
goto leave;
}
#ifdef USE_TOFU
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
kbnode_t n = NULL;
strlist_t user_id_list = NULL;
int done = 0;
/* If the caller didn't supply a user id then use all uids. */
if (! uid)
{
if (! kb)
{
kb = get_pubkeyblock (main_pk->keyid);
free_kb = 1;
}
n = kb;
}
if (DBG_TRUST && sig && sig->signers_uid)
log_debug ("TOFU: only considering user id: '%s'\n",
sig->signers_uid);
while (!done && (uid || (n = find_next_kbnode (n, PKT_USER_ID))))
{
PKT_user_id *user_id;
int expired = 0;
if (uid)
{
user_id = uid;
/* If the caller specified a user id, then we only
process the specified user id and are done after the
first iteration. */
done = 1;
}
else
user_id = n->pkt->pkt.user_id;
if (user_id->attrib_data)
/* Skip user attributes. */
continue;
if (sig && sig->signers_uid)
/* Make sure the UID matches. */
{
char *email = mailbox_from_userid (user_id->name);
if (!email || !*email || strcmp (sig->signers_uid, email) != 0)
{
if (DBG_TRUST)
log_debug ("TOFU: skipping user id '%s', which does"
" not match the signer's email ('%s')\n",
email, sig->signers_uid);
xfree (email);
continue;
}
xfree (email);
}
/* If the user id is revoked or expired, then skip it. */
if (user_id->is_revoked || user_id->is_expired)
{
if (DBG_TRUST)
{
char *s;
if (user_id->is_revoked && user_id->is_expired)
s = "revoked and expired";
else if (user_id->is_revoked)
s = "revoked";
else
s = "expire";
log_debug ("TOFU: Ignoring %s user id (%s)\n",
s, user_id->name);
}
if (user_id->is_revoked)
continue;
expired = 1;
}
add_to_strlist (&user_id_list, user_id->name);
user_id_list->flags = expired;
}
/* Process the user ids in the order they appear in the key
block. */
strlist_rev (&user_id_list);
/* It only makes sense to observe any signature before getting
the validity. This is because if the current signature
results in a conflict, then we damn well want to take that
into account. */
if (sig)
{
err = tofu_register_signature (ctrl, main_pk, user_id_list,
sig->digest, sig->digest_len,
sig->timestamp, "unknown");
if (err)
{
log_error ("TOFU: error registering signature: %s\n",
gpg_strerror (err));
tofu_validity = TRUST_UNKNOWN;
}
}
if (! err)
tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list,
may_ask);
free_strlist (user_id_list);
if (free_kb)
release_kbnode (kb);
}
#endif /*USE_TOFU*/
if (opt.trust_model == TM_TOFU_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_PGP)
{
err = read_trust_record (main_pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
return 0;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record found. */
validity = TRUST_UNKNOWN;
goto leave;
}
/* Loop over all user IDs */
recno = trec.r.trust.validlist;
validity = 0;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if(uid)
{
/* If a user ID is given we return the validity for that
user ID ONLY. If the namehash is not found, then
there is no validity at all (i.e. the user ID wasn't
signed). */
if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
{
validity=(vrec.r.valid.validity & TRUST_MASK);
break;
}
}
else
{
/* If no user ID is given, we take the maximum validity
over all user IDs */
if (validity < (vrec.r.valid.validity & TRUST_MASK))
validity = (vrec.r.valid.validity & TRUST_MASK);
}
recno = vrec.r.valid.next;
}
if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
{
validity |= TRUST_FLAG_DISABLED;
pk->flags.disabled = 1;
}
else
pk->flags.disabled = 0;
pk->flags.disabled_valid = 1;
}
leave:
#ifdef USE_TOFU
validity = tofu_wot_trust_combine (tofu_validity, validity);
#else /*!USE_TOFU*/
validity &= TRUST_MASK;
if (validity == TRUST_NEVER)
/* TRUST_NEVER trumps everything else. */
validity |= TRUST_NEVER;
if (validity == TRUST_EXPIRED)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
validity |= TRUST_EXPIRED;
#endif /*!USE_TOFU*/
if (opt.trust_model != TM_TOFU
&& pending_check_trustdb)
validity |= TRUST_FLAG_PENDING_CHECK;
return validity;
}
static void
get_validity_counts (PKT_public_key *pk, PKT_user_id *uid)
{
TRUSTREC trec, vrec;
ulong recno;
if(pk==NULL || uid==NULL)
BUG();
namehash_from_uid(uid);
uid->help_marginal_count=uid->help_full_count=0;
init_trustdb (0);
if(read_trust_record (pk, &trec))
return;
/* loop over all user IDs */
recno = trec.r.trust.validlist;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
{
uid->help_marginal_count=vrec.r.valid.marginal_count;
uid->help_full_count=vrec.r.valid.full_count;
/* es_printf("Fetched marginal %d, full %d\n",uid->help_marginal_count,uid->help_full_count); */
break;
}
recno = vrec.r.valid.next;
}
}
void
list_trust_path( const char *username )
{
(void)username;
}
/****************
* Enumerate all keys, which are needed to build all trust paths for
* the given key. This function does not return the key itself or
* the ultimate key (the last point in cerificate chain). Only
* certificate chains which ends up at an ultimately trusted key
* are listed. If ownertrust or validity is not NULL, the corresponding
* value for the returned LID is also returned in these variable(s).
*
* 1) create a void pointer and initialize it to NULL
* 2) pass this void pointer by reference to this function.
* Set lid to the key you want to enumerate and pass it by reference.
* 3) call this function as long as it does not return -1
* to indicate EOF. LID does contain the next key used to build the web
* 4) Always call this function a last time with LID set to NULL,
* so that it can free its context.
*
* Returns: -1 on EOF or the level of the returned LID
*/
int
enum_cert_paths( void **context, ulong *lid,
unsigned *ownertrust, unsigned *validity )
{
(void)context;
(void)lid;
(void)ownertrust;
(void)validity;
return -1;
}
/****************
* Print the current path
*/
void
enum_cert_paths_print (void **context, FILE *fp,
int refresh, ulong selected_lid)
{
(void)context;
(void)fp;
(void)refresh;
(void)selected_lid;
}
/****************************************
*********** NEW NEW NEW ****************
****************************************/
static int
ask_ownertrust (ctrl_t ctrl, u32 *kid, int minimum)
{
PKT_public_key *pk;
int rc;
int ot;
pk = xmalloc_clear (sizeof *pk);
rc = get_pubkey (pk, kid);
if (rc)
{
log_error (_("public key %s not found: %s\n"),
keystr(kid), gpg_strerror (rc) );
return TRUST_UNKNOWN;
}
if(opt.force_ownertrust)
{
log_info("force trust for key %s to %s\n",
keystr(kid),trust_value_to_string(opt.force_ownertrust));
tdb_update_ownertrust (pk, opt.force_ownertrust);
ot=opt.force_ownertrust;
}
else
{
ot=edit_ownertrust (ctrl, pk, 0);
if(ot>0)
ot = tdb_get_ownertrust (pk, 0);
else if(ot==0)
ot = minimum?minimum:TRUST_UNDEFINED;
else
ot = -1; /* quit */
}
free_public_key( pk );
return ot;
}
static void
mark_keyblock_seen (KeyHashTable tbl, KBNODE node)
{
for ( ;node; node = node->next )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
keyid_from_pk (node->pkt->pkt.public_key, aki);
add_key_hash_table (tbl, aki);
}
}
static void
dump_key_array (int depth, struct key_array *keys)
{
struct key_array *kar;
for (kar=keys; kar->keyblock; kar++)
{
KBNODE node = kar->keyblock;
u32 kid[2];
keyid_from_pk(node->pkt->pkt.public_key, kid);
es_printf ("%d:%08lX%08lX:K::%c::::\n",
depth, (ulong)kid[0], (ulong)kid[1], '?');
for (; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
int len = node->pkt->pkt.user_id->len;
if (len > 30)
len = 30;
es_printf ("%d:%08lX%08lX:U:::%c:::",
depth, (ulong)kid[0], (ulong)kid[1],
(node->flag & 4)? 'f':
(node->flag & 2)? 'm':
(node->flag & 1)? 'q':'-');
es_write_sanitized (es_stdout, node->pkt->pkt.user_id->name,
len, ":", NULL);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
}
}
}
static void
store_validation_status (int depth, KBNODE keyblock, KeyHashTable stored)
{
KBNODE node;
int status;
int any = 0;
for (node=keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (node->flag & 4)
status = TRUST_FULLY;
else if (node->flag & 2)
status = TRUST_MARGINAL;
else if (node->flag & 1)
status = TRUST_UNDEFINED;
else
status = 0;
if (status)
{
update_validity (keyblock->pkt->pkt.public_key,
uid, depth, status);
mark_keyblock_seen(stored,keyblock);
any = 1;
}
}
}
if (any)
do_sync ();
}
/* Returns a sanitized copy of the regexp (which might be "", but not
NULL). */
#ifndef DISABLE_REGEX
static char *
sanitize_regexp(const char *old)
{
size_t start=0,len=strlen(old),idx=0;
int escaped=0,standard_bracket=0;
char *new=xmalloc((len*2)+1); /* enough to \-escape everything if we
have to */
/* There are basically two commonly-used regexps here. GPG and most
versions of PGP use "<[^>]+[@.]example\.com>$" and PGP (9)
command line uses "example.com" (i.e. whatever the user specfies,
and we can't expect users know to use "\." instead of "."). So
here are the rules: we're allowed to start with "<[^>]+[@.]" and
end with ">$" or start and end with nothing. In between, the
only legal regex character is ".", and everything else gets
escaped. Part of the gotcha here is that some regex packages
allow more than RFC-4880 requires. For example, 4880 has no "{}"
operator, but GNU regex does. Commenting removes these operators
from consideration. A possible future enhancement is to use
commenting to effectively back off a given regex to the Henry
Spencer syntax in 4880. -dshaw */
/* Are we bracketed between "<[^>]+[@.]" and ">$" ? */
if(len>=12 && strncmp(old,"<[^>]+[@.]",10)==0
&& old[len-2]=='>' && old[len-1]=='$')
{
strcpy(new,"<[^>]+[@.]");
idx=strlen(new);
standard_bracket=1;
start+=10;
len-=2;
}
/* Walk the remaining characters and ensure that everything that is
left is not an operational regex character. */
for(;start<len;start++)
{
if(!escaped && old[start]=='\\')
escaped=1;
else if(!escaped && old[start]!='.')
new[idx++]='\\';
else
escaped=0;
new[idx++]=old[start];
}
new[idx]='\0';
/* Note that the (sub)string we look at might end with a bare "\".
If it does, leave it that way. If the regexp actually ended with
">$", then it was escaping the ">" and is fine. If the regexp
actually ended with the bare "\", then it's an illegal regexp and
regcomp should kick it out. */
if(standard_bracket)
strcat(new,">$");
return new;
}
#endif /*!DISABLE_REGEX*/
/* Used by validate_one_keyblock to confirm a regexp within a trust
signature. Returns 1 for match, and 0 for no match or regex
error. */
static int
check_regexp(const char *expr,const char *string)
{
#ifdef DISABLE_REGEX
(void)expr;
(void)string;
/* When DISABLE_REGEX is defined, assume all regexps do not
match. */
return 0;
#else
int ret;
char *regexp;
regexp=sanitize_regexp(expr);
#ifdef __riscos__
ret=riscos_check_regexp(expr, string, DBG_TRUST);
#else
{
regex_t pat;
ret=regcomp(&pat,regexp,REG_ICASE|REG_NOSUB|REG_EXTENDED);
if(ret==0)
{
ret=regexec(&pat,string,0,NULL,0);
regfree(&pat);
}
ret=(ret==0);
}
#endif
if(DBG_TRUST)
log_debug("regexp '%s' ('%s') on '%s': %s\n",
regexp,expr,string,ret?"YES":"NO");
xfree(regexp);
return ret;
#endif
}
/*
* Return true if the key is signed by one of the keys in the given
* key ID list. User IDs with a valid signature are marked by node
* flags as follows:
* flag bit 0: There is at least one signature
* 1: There is marginal confidence that this is a legitimate uid
* 2: There is full confidence that this is a legitimate uid.
* 8: Used for internal purposes.
* 9: Ditto (in mark_usable_uid_certs())
* 10: Ditto (ditto)
* This function assumes that all kbnode flags are cleared on entry.
*/
static int
validate_one_keyblock (KBNODE kb, struct key_item *klist,
u32 curtime, u32 *next_expire)
{
struct key_item *kr;
KBNODE node, uidnode=NULL;
PKT_user_id *uid=NULL;
PKT_public_key *pk = kb->pkt->pkt.public_key;
u32 main_kid[2];
int issigned=0, any_signed = 0;
keyid_from_pk(pk, main_kid);
for (node=kb; node; node = node->next)
{
/* A bit of discussion here: is it better for the web of trust
to be built among only self-signed uids? On the one hand, a
self-signed uid is a statement that the key owner definitely
intended that uid to be there, but on the other hand, a
signed (but not self-signed) uid does carry trust, of a sort,
even if it is a statement being made by people other than the
key owner "through" the uids on the key owner's key. I'm
going with the latter. However, if the user ID was
explicitly revoked, or passively allowed to expire, that
should stop validity through the user ID until it is
resigned. -dshaw */
if (node->pkt->pkttype == PKT_USER_ID
&& !node->pkt->pkt.user_id->is_revoked
&& !node->pkt->pkt.user_id->is_expired)
{
if (uidnode && issigned)
{
if (uid->help_full_count >= opt.completes_needed
|| uid->help_marginal_count >= opt.marginals_needed )
uidnode->flag |= 4;
else if (uid->help_full_count || uid->help_marginal_count)
uidnode->flag |= 2;
uidnode->flag |= 1;
any_signed = 1;
}
uidnode = node;
uid=uidnode->pkt->pkt.user_id;
/* If the selfsig is going to expire... */
if(uid->expiredate && uid->expiredate<*next_expire)
*next_expire = uid->expiredate;
issigned = 0;
get_validity_counts(pk,uid);
mark_usable_uid_certs (kb, uidnode, main_kid, klist,
curtime, next_expire);
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (node->flag & (1<<8)) && uid)
{
/* Note that we are only seeing unrevoked sigs here */
PKT_signature *sig = node->pkt->pkt.signature;
kr = is_in_klist (klist, sig);
/* If the trust_regexp does not match, it's as if the sig
did not exist. This is safe for non-trust sigs as well
since we don't accept a regexp on the sig unless it's a
trust sig. */
if (kr && (!kr->trust_regexp
|| !(opt.trust_model == TM_PGP
|| opt.trust_model == TM_TOFU_PGP)
|| (uidnode
&& check_regexp(kr->trust_regexp,
uidnode->pkt->pkt.user_id->name))))
{
/* Are we part of a trust sig chain? We always favor
the latest trust sig, rather than the greater or
lesser trust sig or value. I could make a decent
argument for any of these cases, but this seems to be
what PGP does, and I'd like to be compatible. -dms */
if ((opt.trust_model == TM_PGP
|| opt.trust_model == TM_TOFU_PGP)
&& sig->trust_depth
&& pk->trust_timestamp <= sig->timestamp)
{
unsigned char depth;
/* If the depth on the signature is less than the
chain currently has, then use the signature depth
so we don't increase the depth beyond what the
signer wanted. If the depth on the signature is
more than the chain currently has, then use the
chain depth so we use as much of the signature
depth as the chain will permit. An ultimately
trusted signature can restart the depth to
whatever level it likes. */
if (sig->trust_depth < kr->trust_depth
|| kr->ownertrust == TRUST_ULTIMATE)
depth = sig->trust_depth;
else
depth = kr->trust_depth;
if (depth)
{
if(DBG_TRUST)
log_debug ("trust sig on %s, sig depth is %d,"
" kr depth is %d\n",
uidnode->pkt->pkt.user_id->name,
sig->trust_depth,
kr->trust_depth);
/* If we got here, we know that:
this is a trust sig.
it's a newer trust sig than any previous trust
sig on this key (not uid).
it is legal in that it was either generated by an
ultimate key, or a key that was part of a trust
chain, and the depth does not violate the
original trust sig.
if there is a regexp attached, it matched
successfully.
*/
if (DBG_TRUST)
log_debug ("replacing trust value %d with %d and "
"depth %d with %d\n",
pk->trust_value,sig->trust_value,
pk->trust_depth,depth);
pk->trust_value = sig->trust_value;
pk->trust_depth = depth-1;
/* If the trust sig contains a regexp, record it
on the pk for the next round. */
if (sig->trust_regexp)
pk->trust_regexp = sig->trust_regexp;
}
}
if (kr->ownertrust == TRUST_ULTIMATE)
uid->help_full_count = opt.completes_needed;
else if (kr->ownertrust == TRUST_FULLY)
uid->help_full_count++;
else if (kr->ownertrust == TRUST_MARGINAL)
uid->help_marginal_count++;
issigned = 1;
}
}
}
if (uidnode && issigned)
{
if (uid->help_full_count >= opt.completes_needed
|| uid->help_marginal_count >= opt.marginals_needed )
uidnode->flag |= 4;
else if (uid->help_full_count || uid->help_marginal_count)
uidnode->flag |= 2;
uidnode->flag |= 1;
any_signed = 1;
}
return any_signed;
}
static int
search_skipfnc (void *opaque, u32 *kid, int dummy_uid_no)
{
(void)dummy_uid_no;
return test_key_hash_table ((KeyHashTable)opaque, kid);
}
/*
* Scan all keys and return a key_array of all suitable keys from
* kllist. The caller has to pass keydb handle so that we don't use
* to create our own. Returns either a key_array or NULL in case of
* an error. No results found are indicated by an empty array.
* Caller hast to release the returned array.
*/
static struct key_array *
validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust,
struct key_item *klist, u32 curtime, u32 *next_expire)
{
KBNODE keyblock = NULL;
struct key_array *keys = NULL;
size_t nkeys, maxkeys;
int rc;
KEYDB_SEARCH_DESC desc;
maxkeys = 1000;
keys = xmalloc ((maxkeys+1) * sizeof *keys);
nkeys = 0;
rc = keydb_search_reset (hd);
if (rc)
{
log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc));
xfree (keys);
return NULL;
}
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
desc.skipfnc = search_skipfnc;
desc.skipfncvalue = full_trust;
rc = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keys[nkeys].keyblock = NULL;
return keys;
}
if (rc)
{
log_error ("keydb_search(first) failed: %s\n", gpg_strerror (rc));
goto die;
}
desc.mode = KEYDB_SEARCH_MODE_NEXT; /* change mode */
do
{
PKT_public_key *pk;
rc = keydb_get_keyblock (hd, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto die;
}
if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
log_debug ("ooops: invalid pkttype %d encountered\n",
keyblock->pkt->pkttype);
dump_kbnode (keyblock);
release_kbnode(keyblock);
continue;
}
/* prepare the keyblock for further processing */
merge_keys_and_selfsig (keyblock);
clear_kbnode_flags (keyblock);
pk = keyblock->pkt->pkt.public_key;
if (pk->has_expired || pk->flags.revoked)
{
/* it does not make sense to look further at those keys */
mark_keyblock_seen (full_trust, keyblock);
}
else if (validate_one_keyblock (keyblock, klist, curtime, next_expire))
{
KBNODE node;
if (pk->expiredate && pk->expiredate >= curtime
&& pk->expiredate < *next_expire)
*next_expire = pk->expiredate;
if (nkeys == maxkeys) {
maxkeys += 1000;
keys = xrealloc (keys, (maxkeys+1) * sizeof *keys);
}
keys[nkeys++].keyblock = keyblock;
/* Optimization - if all uids are fully trusted, then we
never need to consider this key as a candidate again. */
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & 4))
break;
if(node==NULL)
mark_keyblock_seen (full_trust, keyblock);
keyblock = NULL;
}
release_kbnode (keyblock);
keyblock = NULL;
}
while (!(rc = keydb_search (hd, &desc, 1, NULL)));
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
{
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
goto die;
}
keys[nkeys].keyblock = NULL;
return keys;
die:
keys[nkeys].keyblock = NULL;
release_key_array (keys);
return NULL;
}
/* Caller must sync */
static void
reset_trust_records(void)
{
TRUSTREC rec;
ulong recnum;
int count = 0, nreset = 0;
for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ )
{
if(rec.rectype==RECTYPE_TRUST)
{
count++;
if(rec.r.trust.min_ownertrust)
{
rec.r.trust.min_ownertrust=0;
write_record(&rec);
}
}
else if(rec.rectype==RECTYPE_VALID
&& ((rec.r.valid.validity&TRUST_MASK)
|| rec.r.valid.marginal_count
|| rec.r.valid.full_count))
{
rec.r.valid.validity &= ~TRUST_MASK;
rec.r.valid.marginal_count=rec.r.valid.full_count=0;
nreset++;
write_record(&rec);
}
}
if (opt.verbose)
{
log_info (ngettext("%d key processed",
"%d keys processed",
count), count);
log_printf (ngettext(" (%d validity count cleared)\n",
" (%d validity counts cleared)\n",
nreset), nreset);
}
}
/*
* Run the key validation procedure.
*
* This works this way:
* Step 1: Find all ultimately trusted keys (UTK).
* mark them all as seen and put them into klist.
* Step 2: loop max_cert_times
* Step 3: if OWNERTRUST of any key in klist is undefined
* ask user to assign ownertrust
* Step 4: Loop over all keys in the keyDB which are not marked seen
* Step 5: if key is revoked or expired
* mark key as seen
* continue loop at Step 4
* Step 6: For each user ID of that key signed by a key in klist
* Calculate validity by counting trusted signatures.
* Set validity of user ID
* Step 7: If any signed user ID was found
* mark key as seen
* End Loop
* Step 8: Build a new klist from all fully trusted keys from step 6
* End Loop
* Ready
*
*/
static int
validate_keys (ctrl_t ctrl, int interactive)
{
int rc = 0;
int quit=0;
struct key_item *klist = NULL;
struct key_item *k;
struct key_array *keys = NULL;
struct key_array *kar;
KEYDB_HANDLE kdb = NULL;
KBNODE node;
int depth;
int ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate;
KeyHashTable stored,used,full_trust;
u32 start_time, next_expire;
/* Make sure we have all sigs cached. TODO: This is going to
require some architectural re-thinking, as it is agonizingly slow.
Perhaps combine this with reset_trust_records(), or only check
the caches on keys that are actually involved in the web of
trust. */
keydb_rebuild_caches(0);
kdb = keydb_new ();
if (!kdb)
return gpg_error_from_syserror ();
start_time = make_timestamp ();
next_expire = 0xffffffff; /* set next expire to the year 2106 */
stored = new_key_hash_table ();
used = new_key_hash_table ();
full_trust = new_key_hash_table ();
reset_trust_records();
/* Fixme: Instead of always building a UTK list, we could just build it
* here when needed */
if (!utk_list)
{
if (!opt.quiet)
log_info (_("no ultimately trusted keys found\n"));
goto leave;
}
/* mark all UTKs as used and fully_trusted and set validity to
ultimate */
for (k=utk_list; k; k = k->next)
{
KBNODE keyblock;
PKT_public_key *pk;
keyblock = get_pubkeyblock (k->kid);
if (!keyblock)
{
log_error (_("public key of ultimately"
" trusted key %s not found\n"), keystr(k->kid));
continue;
}
mark_keyblock_seen (used, keyblock);
mark_keyblock_seen (stored, keyblock);
mark_keyblock_seen (full_trust, keyblock);
pk = keyblock->pkt->pkt.public_key;
for (node=keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
update_validity (pk, node->pkt->pkt.user_id, 0, TRUST_ULTIMATE);
}
if ( pk->expiredate && pk->expiredate >= start_time
&& pk->expiredate < next_expire)
next_expire = pk->expiredate;
release_kbnode (keyblock);
do_sync ();
}
if (opt.trust_model == TM_TOFU)
/* In the TOFU trust model, we only need to save the ultimately
trusted keys. */
goto leave;
klist = utk_list;
if (!opt.quiet)
log_info ("marginals needed: %d completes needed: %d trust model: %s\n",
opt.marginals_needed, opt.completes_needed,
trust_model_string (opt.trust_model));
for (depth=0; depth < opt.max_cert_depth; depth++)
{
int valids=0,key_count;
/* See whether we should assign ownertrust values to the keys in
klist. */
ot_unknown = ot_undefined = ot_never = 0;
ot_marginal = ot_full = ot_ultimate = 0;
for (k=klist; k; k = k->next)
{
int min=0;
/* 120 and 60 are as per RFC2440 */
if(k->trust_value>=120)
min=TRUST_FULLY;
else if(k->trust_value>=60)
min=TRUST_MARGINAL;
if(min!=k->min_ownertrust)
update_min_ownertrust(k->kid,min);
if (interactive && k->ownertrust == TRUST_UNKNOWN)
{
k->ownertrust = ask_ownertrust (ctrl, k->kid,min);
if (k->ownertrust == (unsigned int)(-1))
{
quit=1;
goto leave;
}
}
/* This can happen during transition from an old trustdb
before trust sigs. It can also happen if a user uses two
different versions of GnuPG or changes the --trust-model
setting. */
if(k->ownertrust<min)
{
if(DBG_TRUST)
log_debug("key %08lX%08lX:"
" overriding ownertrust '%s' with '%s'\n",
(ulong)k->kid[0],(ulong)k->kid[1],
trust_value_to_string(k->ownertrust),
trust_value_to_string(min));
k->ownertrust=min;
}
if (k->ownertrust == TRUST_UNKNOWN)
ot_unknown++;
else if (k->ownertrust == TRUST_UNDEFINED)
ot_undefined++;
else if (k->ownertrust == TRUST_NEVER)
ot_never++;
else if (k->ownertrust == TRUST_MARGINAL)
ot_marginal++;
else if (k->ownertrust == TRUST_FULLY)
ot_full++;
else if (k->ownertrust == TRUST_ULTIMATE)
ot_ultimate++;
valids++;
}
/* Find all keys which are signed by a key in kdlist */
keys = validate_key_list (kdb, full_trust, klist,
start_time, &next_expire);
if (!keys)
{
log_error ("validate_key_list failed\n");
rc = GPG_ERR_GENERAL;
goto leave;
}
for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++)
;
/* Store the calculated valididation status somewhere */
if (opt.verbose > 1 && DBG_TRUST)
dump_key_array (depth, keys);
for (kar=keys; kar->keyblock; kar++)
store_validation_status (depth, kar->keyblock, stored);
if (!opt.quiet)
log_info (_("depth: %d valid: %3d signed: %3d"
" trust: %d-, %dq, %dn, %dm, %df, %du\n"),
depth, valids, key_count, ot_unknown, ot_undefined,
ot_never, ot_marginal, ot_full, ot_ultimate );
/* Build a new kdlist from all fully valid keys in KEYS */
if (klist != utk_list)
release_key_items (klist);
klist = NULL;
for (kar=keys; kar->keyblock; kar++)
{
for (node=kar->keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & 4))
{
u32 kid[2];
/* have we used this key already? */
keyid_from_pk (kar->keyblock->pkt->pkt.public_key, kid);
if(test_key_hash_table(used,kid)==0)
{
/* Normally we add both the primary and subkey
ids to the hash via mark_keyblock_seen, but
since we aren't using this hash as a skipfnc,
that doesn't matter here. */
add_key_hash_table (used,kid);
k = new_key_item ();
k->kid[0]=kid[0];
k->kid[1]=kid[1];
k->ownertrust =
(tdb_get_ownertrust
(kar->keyblock->pkt->pkt.public_key, 0) & TRUST_MASK);
k->min_ownertrust = tdb_get_min_ownertrust
(kar->keyblock->pkt->pkt.public_key, 0);
k->trust_depth=
kar->keyblock->pkt->pkt.public_key->trust_depth;
k->trust_value=
kar->keyblock->pkt->pkt.public_key->trust_value;
if(kar->keyblock->pkt->pkt.public_key->trust_regexp)
k->trust_regexp=
xstrdup(kar->keyblock->pkt->
pkt.public_key->trust_regexp);
k->next = klist;
klist = k;
break;
}
}
}
}
release_key_array (keys);
keys = NULL;
if (!klist)
break; /* no need to dive in deeper */
}
leave:
keydb_release (kdb);
release_key_array (keys);
if (klist != utk_list)
release_key_items (klist);
release_key_hash_table (full_trust);
release_key_hash_table (used);
release_key_hash_table (stored);
if (!rc && !quit) /* mark trustDB as checked */
{
int rc2;
if (next_expire == 0xffffffff || next_expire < start_time )
tdbio_write_nextcheck (0);
else
{
tdbio_write_nextcheck (next_expire);
if (!opt.quiet)
log_info (_("next trustdb check due at %s\n"),
strtimestamp (next_expire));
}
rc2 = tdbio_update_version_record ();
if (rc2)
{
log_error (_("unable to update trustdb version record: "
"write failed: %s\n"), gpg_strerror (rc2));
tdbio_invalid ();
}
do_sync ();
pending_check_trustdb = 0;
}
return rc;
}
diff --git a/g10/verify.c b/g10/verify.c
index 7327e855b..4399f7111 100644
--- a/g10/verify.c
+++ b/g10/verify.c
@@ -1,276 +1,275 @@
/* verify.c - Verify signed data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006,
* 2007, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
#include "keydb.h"
-#include "util.h"
+#include "../common/util.h"
#include "main.h"
-#include "status.h"
#include "filter.h"
-#include "ttyio.h"
-#include "i18n.h"
+#include "../common/ttyio.h"
+#include "../common/i18n.h"
/****************
* Assume that the input is a signature and verify it without
* generating any output. With no arguments, the signature packet
* is read from stdin (it may be a detached signature when not
* used in batch mode). If only a sigfile is given, it may be a complete
* signature or a detached signature in which case the signed stuff
* is expected from stdin. With more than 1 argument, the first should
* be a detached signature and the remaining files are the signed stuff.
*/
int
verify_signatures (ctrl_t ctrl, int nfiles, char **files )
{
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx = new_progress_context ();
const char *sigfile;
int i, rc;
strlist_t sl;
/* Decide whether we should handle a detached or a normal signature,
* which is needed so that the code later can hash the correct data and
* not have a normal signature act as detached signature and ignoring the
* indended signed material from the 2nd file or stdin.
* 1. gpg <file - normal
* 2. gpg file - normal (or detached)
* 3. gpg file <file2 - detached
* 4. gpg file file2 - detached
* The question is how decide between case 2 and 3? The only way
* we can do it is by reading one byte from stdin and then unget
* it; the problem here is that we may be reading from the
* terminal (which could be detected using isatty() but won't work
* when under contol of a pty using program (e.g. expect)) and
* might get us in trouble when stdin is used for another purpose
* (--passphrase-fd 0). So we have to break with the behaviour
* prior to gpg 1.0.4 by assuming that case 3 is a normal
* signature (where file2 is ignored and require for a detached
* signature to indicate signed material comes from stdin by using
* case 4 with a file2 of "-".
*
* Actually we don't have to change anything here but can handle
* that all quite easily in mainproc.c
*/
sigfile = nfiles? *files : NULL;
/* open the signature file */
fp = iobuf_open(sigfile);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if( !fp ) {
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"),
print_fname_stdin(sigfile), gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, fp, sigfile);
if ( !opt.no_armor && use_armor_filter( fp ) )
{
afx = new_armor_context ();
push_armor_filter (afx, fp);
}
sl = NULL;
for(i=nfiles-1 ; i > 0 ; i-- )
add_to_strlist( &sl, files[i] );
rc = proc_signature_packets (ctrl, NULL, fp, sl, sigfile );
free_strlist(sl);
iobuf_close(fp);
if( (afx && afx->no_openpgp_data && rc == -1)
|| gpg_err_code (rc) == GPG_ERR_NO_DATA ) {
log_error(_("the signature could not be verified.\n"
"Please remember that the signature file (.sig or .asc)\n"
"should be the first file given on the command line.\n") );
rc = 0;
}
leave:
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
void
print_file_status( int status, const char *name, int what )
{
char *p = xmalloc(strlen(name)+10);
sprintf(p, "%d %s", what, name );
write_status_text( status, p );
xfree(p);
}
static int
verify_one_file (ctrl_t ctrl, const char *name )
{
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx = new_progress_context ();
int rc;
print_file_status( STATUS_FILE_START, name, 1 );
fp = iobuf_open(name);
if (fp)
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if( !fp ) {
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"),
print_fname_stdin(name), strerror (errno));
print_file_status( STATUS_FILE_ERROR, name, 1 );
goto leave;
}
handle_progress (pfx, fp, name);
if( !opt.no_armor ) {
if( use_armor_filter( fp ) ) {
afx = new_armor_context ();
push_armor_filter (afx, fp);
}
}
rc = proc_signature_packets (ctrl, NULL, fp, NULL, name );
iobuf_close(fp);
write_status( STATUS_FILE_DONE );
reset_literals_seen();
leave:
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
/****************
* Verify each file given in the files array or read the names of the
* files from stdin.
* Note: This function can not handle detached signatures.
*/
int
verify_files (ctrl_t ctrl, int nfiles, char **files )
{
int i;
if( !nfiles ) { /* read the filenames from stdin */
char line[2048];
unsigned int lno = 0;
while( fgets(line, DIM(line), stdin) ) {
lno++;
if( !*line || line[strlen(line)-1] != '\n' ) {
log_error(_("input line %u too long or missing LF\n"), lno );
return GPG_ERR_GENERAL;
}
/* This code does not work on MSDOS but how cares there are
* also no script languages available. We don't strip any
* spaces, so that we can process nearly all filenames */
line[strlen(line)-1] = 0;
verify_one_file (ctrl, line );
}
}
else { /* take filenames from the array */
for(i=0; i < nfiles; i++ )
verify_one_file (ctrl, files[i] );
}
return 0;
}
/* Perform a verify operation. To verify detached signatures, DATA_FD
shall be the descriptor of the signed data; for regular signatures
it needs to be -1. If OUT_FP is not NULL and DATA_FD is not -1 the
the signed material gets written that stream.
FIXME: OUTFP is not yet implemented.
*/
int
gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp)
{
int rc;
iobuf_t fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx = new_progress_context ();
(void)ctrl;
(void)out_fp;
if (is_secured_file (sig_fd))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_fdopen_nc (sig_fd, "rb");
if (!fp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open fd %d: %s\n"), sig_fd, strerror (errno));
goto leave;
}
handle_progress (pfx, fp, NULL);
if ( !opt.no_armor && use_armor_filter (fp) )
{
afx = new_armor_context ();
push_armor_filter (afx, fp);
}
rc = proc_signature_packets_by_fd (ctrl, NULL, fp, data_fd);
if ( afx && afx->no_openpgp_data
&& (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) )
rc = gpg_error (GPG_ERR_NO_DATA);
leave:
iobuf_close (fp);
release_progress_context (pfx);
release_armor_context (afx);
return rc;
}
diff --git a/g13/Makefile.am b/g13/Makefile.am
index cc0e6a809..dfacc993b 100644
--- a/g13/Makefile.am
+++ b/g13/Makefile.am
@@ -1,79 +1,79 @@
# g13/Makefile.am
# Copyright (C) 2009 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = ChangeLog-2011
bin_PROGRAMS = g13
sbin_PROGRAMS = g13-syshelp
noinst_PROGRAMS = $(module_tests)
TESTS = $(module_tests)
-AM_CPPFLAGS = -I$(top_srcdir)/common
+AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS)
g13_SOURCES = \
g13.c g13.h \
g13-common.c g13-common.h \
keyblob.c keyblob.h \
g13tuple.c g13tuple.h \
server.c server.h \
create.c create.h \
mount.c mount.h \
suspend.c suspend.h \
mountinfo.c mountinfo.h \
call-syshelp.c call-syshelp.h \
runner.c runner.h \
backend.c backend.h \
be-encfs.c be-encfs.h \
be-truecrypt.c be-truecrypt.h \
be-dmcrypt.c be-dmcrypt.h
g13_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
g13_syshelp_SOURCES = \
g13-syshelp.c g13-syshelp.h \
g13-common.c g13-common.h \
keyblob.c keyblob.h \
g13tuple.c g13tuple.h \
sh-cmd.c \
sh-blockdev.c \
sh-dmcrypt.c
g13_syshelp_LDADD = $(libcommon) \
$(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
module_tests = t-g13tuple
t_common_ldadd = $(libcommon) $(LIBGCRYPT_LIBS) \
$(LIBASSUAN_LIBS) $(LIBICONV)
t_g13tuple_SOURCES = t-g13tuple.c g13tuple.c
t_g13tuple_LDADD = $(t_common_ldadd)
$(PROGRAMS) : $(libcommon) $(libcommonpth)
diff --git a/g13/backend.c b/g13/backend.c
index a495f8ad8..0123b458c 100644
--- a/g13/backend.c
+++ b/g13/backend.c
@@ -1,295 +1,295 @@
/* backend.c - Dispatcher to the various backends.
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
#include "backend.h"
#include "be-encfs.h"
#include "be-truecrypt.h"
#include "be-dmcrypt.h"
#include "call-syshelp.h"
#define no_such_backend(a) _no_such_backend ((a), __func__)
static gpg_error_t
_no_such_backend (int conttype, const char *func)
{
log_error ("invalid backend %d given in %s - this is most likely a bug\n",
conttype, func);
return gpg_error (GPG_ERR_INTERNAL);
}
/* Parse NAME and return the corresponding content type. If the name
is not known, a error message is printed and zero returned. If
NAME is NULL the supported backend types are listed and 0 is
returned. */
int
be_parse_conttype_name (const char *name)
{
static struct { const char *name; int conttype; } names[] = {
{ "encfs", CONTTYPE_ENCFS },
{ "dm-crypt", CONTTYPE_DM_CRYPT }
};
int i;
if (!name)
{
log_info ("Known backend types:\n");
for (i=0; i < DIM (names); i++)
log_info (" %s\n", names[i].name);
return 0;
}
for (i=0; i < DIM (names); i++)
{
if (!strcmp (names[i].name, name))
return names[i].conttype;
}
log_error ("invalid backend type '%s' given\n", name);
return 0;
}
/* Return true if CONTTYPE is supported by us. */
int
be_is_supported_conttype (int conttype)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
case CONTTYPE_DM_CRYPT:
return 1;
default:
return 0;
}
}
/* Create a lock file for the container FNAME and store the lock at
* R_LOCK and return 0. On error return an error code and store NULL
* at R_LOCK. */
gpg_error_t
be_take_lock_for_create (ctrl_t ctrl, const char *fname, dotlock_t *r_lock)
{
gpg_error_t err;
dotlock_t lock = NULL;
struct stat sb;
*r_lock = NULL;
/* A DM-crypt container requires special treatment by using the
syshelper fucntions. */
if (ctrl->conttype == CONTTYPE_DM_CRYPT)
{
/* */
err = call_syshelp_set_device (ctrl, fname);
goto leave;
}
/* A quick check to see that no container with that name already
exists. */
if (!access (fname, F_OK))
{
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
/* Take a lock and proceed with the creation. If there is a lock we
immediately return an error because for creation it does not make
sense to wait. */
lock = dotlock_create (fname, 0);
if (!lock)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (dotlock_take (lock, 0))
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Check again that the file does not exist. */
err = stat (fname, &sb)? 0 : gpg_error (GPG_ERR_EEXIST);
leave:
if (!err)
{
*r_lock = lock;
lock = NULL;
}
dotlock_destroy (lock);
return err;
}
/* If the backend requires a separate file or directory for the
container, return its name by computing it from FNAME which gives
the g13 filename. The new file name is allocated and stored at
R_NAME, if this is expected to be a directory true is stored at
R_ISDIR. If no detached name is expected or an error occurs NULL
is stored at R_NAME. The function returns 0 on success or an error
code. */
gpg_error_t
be_get_detached_name (int conttype, const char *fname,
char **r_name, int *r_isdir)
{
*r_name = NULL;
*r_isdir = 0;
switch (conttype)
{
case CONTTYPE_ENCFS:
return be_encfs_get_detached_name (fname, r_name, r_isdir);
case CONTTYPE_DM_CRYPT:
return 0;
default:
return no_such_backend (conttype);
}
}
gpg_error_t
be_create_new_keys (int conttype, membuf_t *mb)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
return be_encfs_create_new_keys (mb);
case CONTTYPE_TRUECRYPT:
return be_truecrypt_create_new_keys (mb);
case CONTTYPE_DM_CRYPT:
return 0;
default:
return no_such_backend (conttype);
}
}
/* Dispatcher to the backend's create function. */
gpg_error_t
be_create_container (ctrl_t ctrl, int conttype,
const char *fname, int fd, tupledesc_t tuples,
unsigned int *r_id)
{
(void)fd; /* Not yet used. */
switch (conttype)
{
case CONTTYPE_ENCFS:
return be_encfs_create_container (ctrl, fname, tuples, r_id);
case CONTTYPE_DM_CRYPT:
return be_dmcrypt_create_container (ctrl);
default:
return no_such_backend (conttype);
}
}
/* Dispatcher to the backend's mount function. */
gpg_error_t
be_mount_container (ctrl_t ctrl, int conttype,
const char *fname, const char *mountpoint,
tupledesc_t tuples, unsigned int *r_id)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
return be_encfs_mount_container (ctrl, fname, mountpoint, tuples, r_id);
case CONTTYPE_DM_CRYPT:
return be_dmcrypt_mount_container (ctrl, fname, mountpoint, tuples);
default:
return no_such_backend (conttype);
}
}
/* Dispatcher to the backend's umount function. */
gpg_error_t
be_umount_container (ctrl_t ctrl, int conttype, const char *fname)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
return gpg_error (GPG_ERR_NOT_SUPPORTED);
case CONTTYPE_DM_CRYPT:
return be_dmcrypt_umount_container (ctrl, fname);
default:
return no_such_backend (conttype);
}
}
/* Dispatcher to the backend's suspend function. */
gpg_error_t
be_suspend_container (ctrl_t ctrl, int conttype, const char *fname)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
return gpg_error (GPG_ERR_NOT_SUPPORTED);
case CONTTYPE_DM_CRYPT:
return be_dmcrypt_suspend_container (ctrl, fname);
default:
return no_such_backend (conttype);
}
}
/* Dispatcher to the backend's resume function. */
gpg_error_t
be_resume_container (ctrl_t ctrl, int conttype, const char *fname,
tupledesc_t tuples)
{
switch (conttype)
{
case CONTTYPE_ENCFS:
return gpg_error (GPG_ERR_NOT_SUPPORTED);
case CONTTYPE_DM_CRYPT:
return be_dmcrypt_resume_container (ctrl, fname, tuples);
default:
return no_such_backend (conttype);
}
}
diff --git a/g13/be-dmcrypt.c b/g13/be-dmcrypt.c
index e048b9945..59b586d37 100644
--- a/g13/be-dmcrypt.c
+++ b/g13/be-dmcrypt.c
@@ -1,116 +1,116 @@
/* be-dmcrypt.c - The DM-Crypt based backend
* Copyright (C) 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
#include "call-syshelp.h"
#include "be-dmcrypt.h"
/* Create the container using the current device.
* information in TUPLES. */
gpg_error_t
be_dmcrypt_create_container (ctrl_t ctrl)
{
gpg_error_t err;
err = call_syshelp_run_create (ctrl, CONTTYPE_DM_CRYPT);
return err;
}
/* Mount the container described by the filename FNAME and the keyblob
* information in TUPLES. On success the runner id is stored at R_ID. */
gpg_error_t
be_dmcrypt_mount_container (ctrl_t ctrl,
const char *fname, const char *mountpoint,
tupledesc_t tuples)
{
gpg_error_t err;
err = call_syshelp_set_device (ctrl, fname);
if (err)
goto leave;
err = call_syshelp_run_mount (ctrl, CONTTYPE_DM_CRYPT, mountpoint, tuples);
leave:
return err;
}
/* Unmount the container described by the filename FNAME. */
gpg_error_t
be_dmcrypt_umount_container (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
err = call_syshelp_set_device (ctrl, fname);
if (err)
goto leave;
err = call_syshelp_run_umount (ctrl, CONTTYPE_DM_CRYPT);
leave:
return err;
}
/* Suspend the container described by the filename FNAME. */
gpg_error_t
be_dmcrypt_suspend_container (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
err = call_syshelp_set_device (ctrl, fname);
if (err)
goto leave;
err = call_syshelp_run_suspend (ctrl, CONTTYPE_DM_CRYPT);
leave:
return err;
}
/* Resume the container described by the filename FNAME and the keyblob
* information in TUPLES. */
gpg_error_t
be_dmcrypt_resume_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples)
{
gpg_error_t err;
err = call_syshelp_set_device (ctrl, fname);
if (err)
goto leave;
err = call_syshelp_run_resume (ctrl, CONTTYPE_DM_CRYPT, tuples);
leave:
return err;
}
diff --git a/g13/be-encfs.c b/g13/be-encfs.c
index 6c648ab58..0e2c68bf3 100644
--- a/g13/be-encfs.c
+++ b/g13/be-encfs.c
@@ -1,471 +1,471 @@
/* be-encfs.c - The EncFS based backend
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
#include "be-encfs.h"
#include "runner.h"
#include "../common/sysutils.h"
#include "../common/exechelp.h"
/* Command values used to run the encfs tool. */
enum encfs_cmds
{
ENCFS_CMD_CREATE,
ENCFS_CMD_MOUNT,
ENCFS_CMD_UMOUNT
};
/* An object to keep the private state of the encfs tool. It is
released by encfs_handler_cleanup. */
struct encfs_parm_s
{
enum encfs_cmds cmd; /* The current command. */
tupledesc_t tuples; /* NULL or the tuples object. */
char *mountpoint; /* The mountpoint. */
};
typedef struct encfs_parm_s *encfs_parm_t;
static gpg_error_t
send_cmd_bin (runner_t runner, const void *data, size_t datalen)
{
return runner_send_line (runner, data, datalen);
}
static gpg_error_t
send_cmd (runner_t runner, const char *string)
{
log_debug ("sending command -->%s<--\n", string);
return send_cmd_bin (runner, string, strlen (string));
}
static void
run_umount_helper (const char *mountpoint)
{
gpg_error_t err;
const char pgmname[] = FUSERMOUNT;
const char *args[3];
args[0] = "-u";
args[1] = mountpoint;
args[2] = NULL;
err = gnupg_spawn_process_detached (pgmname, args, NULL);
if (err)
log_error ("failed to run '%s': %s\n",
pgmname, gpg_strerror (err));
}
/* Handle one line of the encfs tool's output. This function is
allowed to modify the content of BUFFER. */
static gpg_error_t
handle_status_line (runner_t runner, const char *line,
enum encfs_cmds cmd, tupledesc_t tuples)
{
gpg_error_t err;
/* Check that encfs understands our new options. */
if (!strncmp (line, "$STATUS$", 8))
{
for (line +=8; *line && spacep (line); line++)
;
log_info ("got status '%s'\n", line);
if (!strcmp (line, "fuse_main_start"))
{
/* Send a special error code back to let the caller know
that everything has been setup by encfs. */
err = gpg_error (GPG_ERR_UNFINISHED);
}
else
err = 0;
}
else if (!strncmp (line, "$PROMPT$", 8))
{
for (line +=8; *line && spacep (line); line++)
;
log_info ("got prompt '%s'\n", line);
if (!strcmp (line, "create_root_dir"))
err = send_cmd (runner, cmd == ENCFS_CMD_CREATE? "y":"n");
else if (!strcmp (line, "create_mount_point"))
err = send_cmd (runner, "y");
else if (!strcmp (line, "passwd")
|| !strcmp (line, "new_passwd"))
{
if (tuples)
{
size_t n;
const void *value;
value = find_tuple (tuples, KEYBLOB_TAG_ENCKEY, &n);
if (!value)
err = gpg_error (GPG_ERR_INV_SESSION_KEY);
else if ((err = send_cmd_bin (runner, value, n)))
{
if (gpg_err_code (err) == GPG_ERR_BUG
&& gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
err = gpg_error (GPG_ERR_INV_SESSION_KEY);
}
}
else
err = gpg_error (GPG_ERR_NO_DATA);
}
else
err = send_cmd (runner, ""); /* Default to send an empty line. */
}
else if (strstr (line, "encfs: unrecognized option '"))
err = gpg_error (GPG_ERR_INV_ENGINE);
else
err = 0;
return err;
}
/* The main processing function as used by the runner. */
static gpg_error_t
encfs_handler (void *opaque, runner_t runner, const char *status_line)
{
encfs_parm_t parm = opaque;
gpg_error_t err;
if (!parm || !runner)
return gpg_error (GPG_ERR_BUG);
if (!status_line)
{
/* Runner requested internal flushing - nothing to do here. */
return 0;
}
err = handle_status_line (runner, status_line, parm->cmd, parm->tuples);
if (gpg_err_code (err) == GPG_ERR_UNFINISHED
&& gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
{
err = 0;
/* No more need for the tuples. */
destroy_tupledesc (parm->tuples);
parm->tuples = NULL;
if (parm->cmd == ENCFS_CMD_CREATE)
{
/* The encfs tool keeps on running after creation of the
container. We don't want that and thus need to stop the
encfs process. */
run_umount_helper (parm->mountpoint);
/* In case the umount helper does not work we try to kill
the engine. FIXME: We should figure out how to make
fusermount work. */
runner_cancel (runner);
}
}
return err;
}
/* Called by the runner to cleanup the private data. */
static void
encfs_handler_cleanup (void *opaque)
{
encfs_parm_t parm = opaque;
if (!parm)
return;
destroy_tupledesc (parm->tuples);
xfree (parm->mountpoint);
xfree (parm);
}
/* Run the encfs tool. */
static gpg_error_t
run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd,
const char *rawdir, const char *mountpoint, tupledesc_t tuples,
unsigned int *r_id)
{
gpg_error_t err;
encfs_parm_t parm;
runner_t runner = NULL;
int outbound[2] = { -1, -1 };
int inbound[2] = { -1, -1 };
const char *pgmname;
const char *argv[10];
pid_t pid = (pid_t)(-1);
int idx;
(void)ctrl;
parm = xtrycalloc (1, sizeof *parm);
if (!parm)
{
err = gpg_error_from_syserror ();
goto leave;
}
parm->cmd = cmd;
parm->tuples = ref_tupledesc (tuples);
parm->mountpoint = xtrystrdup (mountpoint);
if (!parm->mountpoint)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = runner_new (&runner, "encfs");
if (err)
goto leave;
err = gnupg_create_inbound_pipe (inbound, NULL, 0);
if (!err)
err = gnupg_create_outbound_pipe (outbound, NULL, 0);
if (err)
{
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
goto leave;
}
pgmname = ENCFS;
idx = 0;
argv[idx++] = "-f";
if (opt.verbose)
argv[idx++] = "-v";
argv[idx++] = "--stdinpass";
argv[idx++] = "--annotate";
argv[idx++] = rawdir;
argv[idx++] = mountpoint;
argv[idx++] = NULL;
assert (idx <= DIM (argv));
err = gnupg_spawn_process_fd (pgmname, argv,
outbound[0], -1, inbound[1], &pid);
if (err)
{
log_error ("error spawning '%s': %s\n", pgmname, gpg_strerror (err));
goto leave;
}
close (outbound[0]); outbound[0] = -1;
close ( inbound[1]); inbound[1] = -1;
runner_set_fds (runner, inbound[0], outbound[1]);
inbound[0] = -1; /* Now owned by RUNNER. */
outbound[1] = -1; /* Now owned by RUNNER. */
runner_set_handler (runner, encfs_handler, encfs_handler_cleanup, parm);
parm = NULL; /* Now owned by RUNNER. */
runner_set_pid (runner, pid);
pid = (pid_t)(-1); /* The process is now owned by RUNNER. */
err = runner_spawn (runner);
if (err)
goto leave;
*r_id = runner_get_rid (runner);
log_info ("running '%s' in the background\n", pgmname);
leave:
if (inbound[0] != -1)
close (inbound[0]);
if (inbound[1] != -1)
close (inbound[1]);
if (outbound[0] != -1)
close (outbound[0]);
if (outbound[1] != -1)
close (outbound[1]);
if (pid != (pid_t)(-1))
{
gnupg_wait_process (pgmname, pid, 1, NULL);
gnupg_release_process (pid);
}
runner_release (runner);
encfs_handler_cleanup (parm);
return err;
}
/* See be_get_detached_name for a description. Note that the
dispatcher code makes sure that NULL is stored at R_NAME before
calling us. */
gpg_error_t
be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir)
{
char *result;
if (!fname || !*fname)
return gpg_error (GPG_ERR_INV_ARG);
result = strconcat (fname, ".d", NULL);
if (!result)
return gpg_error_from_syserror ();
*r_name = result;
*r_isdir = 1;
return 0;
}
/* Create a new session key and append it as a tuple to the memory
buffer MB.
The EncFS daemon takes a passphrase from stdin and internally
mangles it by means of some KDF from OpenSSL. We want to store a
binary key but we need to make sure that certain characters are not
used because the EncFS utility reads it from stdin and obviously
acts on some of the characters. This we replace CR (in case of an
MSDOS version of EncFS), LF (the delimiter used by EncFS) and Nul
(because it is unlikely to work). We use 32 bytes (256 bit)
because that is sufficient for the largest cipher (AES-256) and in
addition gives enough margin for a possible entropy degradation by
the KDF. */
gpg_error_t
be_encfs_create_new_keys (membuf_t *mb)
{
char *buffer;
int i, j;
/* Allocate a buffer of 32 bytes plus 8 spare bytes we may need to
replace the unwanted values. */
buffer = xtrymalloc_secure (32+8);
if (!buffer)
return gpg_error_from_syserror ();
/* Randomize the buffer. STRONG random should be enough as it is a
good compromise between security and performance. The
anticipated usage of this tool is the quite often creation of new
containers and thus this should not deplete the system's entropy
tool too much. */
gcry_randomize (buffer, 32+8, GCRY_STRONG_RANDOM);
for (i=j=0; i < 32; i++)
{
if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == 0 )
{
/* Replace. */
if (j == 8)
{
/* Need to get more random. */
gcry_randomize (buffer+32, 8, GCRY_STRONG_RANDOM);
j = 0;
}
buffer[i] = buffer[32+j];
j++;
}
}
/* Store the key. */
append_tuple (mb, KEYBLOB_TAG_ENCKEY, buffer, 32);
/* Free the temporary buffer. */
wipememory (buffer, 32+8); /* A failsafe extra wiping. */
xfree (buffer);
return 0;
}
/* Create the container described by the filename FNAME and the keyblob
information in TUPLES. */
gpg_error_t
be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples,
unsigned int *r_id)
{
gpg_error_t err;
int dummy;
char *containername = NULL;
char *mountpoint = NULL;
err = be_encfs_get_detached_name (fname, &containername, &dummy);
if (err)
goto leave;
mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX");
if (!mountpoint)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!gnupg_mkdtemp (mountpoint))
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
"/tmp/.#g13_XXXXXX", gpg_strerror (err));
goto leave;
}
err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint,
tuples, r_id);
/* In any case remove the temporary mount point. */
if (rmdir (mountpoint))
log_error ("error removing temporary mount point '%s': %s\n",
mountpoint, gpg_strerror (gpg_error_from_syserror ()));
leave:
xfree (containername);
xfree (mountpoint);
return err;
}
/* Mount the container described by the filename FNAME and the keyblob
information in TUPLES. On success the runner id is stored at R_ID. */
gpg_error_t
be_encfs_mount_container (ctrl_t ctrl,
const char *fname, const char *mountpoint,
tupledesc_t tuples, unsigned int *r_id)
{
gpg_error_t err;
int dummy;
char *containername = NULL;
if (!mountpoint)
{
log_error ("the encfs backend requires an explicit mountpoint\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = be_encfs_get_detached_name (fname, &containername, &dummy);
if (err)
goto leave;
err = run_encfs_tool (ctrl, ENCFS_CMD_MOUNT, containername, mountpoint,
tuples, r_id);
leave:
xfree (containername);
return err;
}
diff --git a/g13/be-truecrypt.c b/g13/be-truecrypt.c
index e75b936c8..1ce992f91 100644
--- a/g13/be-truecrypt.c
+++ b/g13/be-truecrypt.c
@@ -1,37 +1,37 @@
/* be-truecrypt.c - The Truecrypt based backend
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "be-truecrypt.h"
gpg_error_t
be_truecrypt_create_new_keys (membuf_t *mb)
{
(void)mb;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
diff --git a/g13/call-syshelp.c b/g13/call-syshelp.c
index adffc6eac..8a50c3ff4 100644
--- a/g13/call-syshelp.c
+++ b/g13/call-syshelp.c
@@ -1,631 +1,631 @@
/* call-syshelp.c - Communication with g13-syshelp
* Copyright (C) 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <npth.h>
#include "g13.h"
#include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "g13tuple.h"
#include "keyblob.h"
-#include "membuf.h"
+#include "../common/membuf.h"
#include "create.h"
#include "call-syshelp.h"
/* Local data for this module. A pointer to this is stored in the
CTRL object of each connection. */
struct call_syshelp_s
{
assuan_context_t assctx; /* The Assuan context for the current
g13-syshep connection. */
};
/* Parameter used with the CREATE command. */
struct create_parm_s
{
assuan_context_t ctx;
ctrl_t ctrl;
membuf_t plaintext;
unsigned int expect_plaintext:1;
unsigned int got_plaintext:1;
};
/* Parameter used with the MOUNT command. */
struct mount_parm_s
{
assuan_context_t ctx;
ctrl_t ctrl;
const void *keyblob;
size_t keybloblen;
};
/* Fork off the syshelp tool if this has not already been done. On
success stores the current Assuan context for the syshelp tool at
R_CTX. */
static gpg_error_t
start_syshelp (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
assuan_fd_t no_close_list[3];
int i;
*r_ctx = NULL;
if (ctrl->syshelp_local && (*r_ctx = ctrl->syshelp_local->assctx))
return 0; /* Already set. */
if (opt.verbose)
log_info ("starting a new syshelp\n");
if (!ctrl->syshelp_local)
{
ctrl->syshelp_local = xtrycalloc (1, sizeof *ctrl->syshelp_local);
if (!ctrl->syshelp_local)
return gpg_error_from_syserror ();
}
if (es_fflush (NULL))
{
err = gpg_error_from_syserror ();
log_error ("error flushing pending output: %s\n", gpg_strerror (err));
return err;
}
i = 0;
if (log_get_fd () != -1)
no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
no_close_list[i++] = assuan_fd_from_posix_fd (es_fileno (es_stderr));
no_close_list[i] = ASSUAN_INVALID_FD;
err = assuan_new (&ctx);
if (err)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (err));
return err;
}
/* Call userv to start g13-syshelp. This userv script needs to be
* installed under the name "gnupg-g13-syshelp":
*
* if ( glob service-user root
* )
* reset
* suppress-args
* execute /home/wk/b/gnupg/g13/g13-syshelp -v
* else
* error Nothing to do for this service-user
* fi
* quit
*/
{
const char *argv[4];
argv[0] = "userv";
argv[1] = "root";
argv[2] = "gnupg-g13-syshelp";
argv[3] = NULL;
err = assuan_pipe_connect (ctx, "/usr/bin/userv", argv,
no_close_list, NULL, NULL, 0);
}
if (err)
{
log_error ("can't connect to '%s': %s %s\n",
"g13-syshelp", gpg_strerror (err), gpg_strsource (err));
log_info ("(is userv and its gnupg-g13-syshelp script installed?)\n");
assuan_release (ctx);
return err;
}
*r_ctx = ctrl->syshelp_local->assctx = ctx;
if (DBG_IPC)
log_debug ("connection to g13-syshelp established\n");
return 0;
}
/* Release local resources associated with CTRL. */
void
call_syshelp_release (ctrl_t ctrl)
{
if (!ctrl)
return;
if (ctrl->syshelp_local)
{
assuan_release (ctrl->syshelp_local->assctx);
ctrl->syshelp_local->assctx = NULL;
xfree (ctrl->syshelp_local);
ctrl->syshelp_local = NULL;
}
}
/* Staus callback for call_syshelp_find_device. */
static gpg_error_t
finddevice_status_cb (void *opaque, const char *line)
{
char **r_blockdev = opaque;
char *p;
if ((p = has_leading_keyword (line, "BLOCKDEV")) && *p && !*r_blockdev)
{
*r_blockdev = xtrystrdup (p);
if (!*r_blockdev)
return gpg_error_from_syserror ();
}
return 0;
}
/* Send the FINDDEVICE command to the syshelper. On success the name
* of the block device is stored at R_BLOCKDEV. */
gpg_error_t
call_syshelp_find_device (ctrl_t ctrl, const char *name, char **r_blockdev)
{
gpg_error_t err;
assuan_context_t ctx;
char *line = NULL;
char *blockdev = NULL; /* The result. */
*r_blockdev = NULL;
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
line = xtryasprintf ("FINDDEVICE %s", name);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL,
finddevice_status_cb, &blockdev);
if (err)
goto leave;
if (!blockdev)
{
log_error ("status line for successful FINDDEVICE missing\n");
err = gpg_error (GPG_ERR_UNEXPECTED);
goto leave;
}
*r_blockdev = blockdev;
blockdev = NULL;
leave:
xfree (blockdev);
xfree (line);
return err;
}
static gpg_error_t
getkeyblob_data_cb (void *opaque, const void *data, size_t datalen)
{
membuf_t *mb = opaque;
if (data)
put_membuf (mb, data, datalen);
return 0;
}
/* Send the GTEKEYBLOB command to the syshelper. On success the
* encrypted keyblpob is stored at (R_ENCKEYBLOB,R_ENCKEYBLOBLEN). */
gpg_error_t
call_syshelp_get_keyblob (ctrl_t ctrl,
void **r_enckeyblob, size_t *r_enckeybloblen)
{
gpg_error_t err;
assuan_context_t ctx;
membuf_t mb;
*r_enckeyblob = NULL;
*r_enckeybloblen = 0;
init_membuf (&mb, 512);
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
err = assuan_transact (ctx, "GETKEYBLOB",
getkeyblob_data_cb, &mb,
NULL, NULL, NULL, NULL);
if (err)
goto leave;
*r_enckeyblob = get_membuf (&mb, r_enckeybloblen);
if (!*r_enckeyblob)
err = gpg_error_from_syserror ();
leave:
xfree (get_membuf (&mb, NULL));
return err;
}
/* Send the DEVICE command to the syshelper. FNAME is the name of the
device. */
gpg_error_t
call_syshelp_set_device (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
assuan_context_t ctx;
char *line = NULL;
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
line = xtryasprintf ("DEVICE %s", fname);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
leave:
xfree (line);
return err;
}
static gpg_error_t
create_status_cb (void *opaque, const char *line)
{
struct create_parm_s *parm = opaque;
if (has_leading_keyword (line, "PLAINTEXT_FOLLOWS"))
parm->expect_plaintext = 1;
return 0;
}
static gpg_error_t
create_data_cb (void *opaque, const void *data, size_t datalen)
{
struct create_parm_s *parm = opaque;
gpg_error_t err = 0;
if (!parm->expect_plaintext)
{
log_error ("status line for data missing\n");
err = gpg_error (GPG_ERR_UNEXPECTED);
}
else if (data)
{
put_membuf (&parm->plaintext, data, datalen);
}
else
{
parm->expect_plaintext = 0;
parm->got_plaintext = 1;
}
return err;
}
static gpg_error_t
create_inq_cb (void *opaque, const char *line)
{
struct create_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "ENCKEYBLOB"))
{
void *plaintext;
size_t plaintextlen;
if (!parm->got_plaintext)
err = gpg_error (GPG_ERR_UNEXPECTED);
else if (!(plaintext = get_membuf (&parm->plaintext, &plaintextlen)))
err = gpg_error_from_syserror ();
else
{
void *ciphertext;
size_t ciphertextlen;
log_printhex ("plain", plaintext, plaintextlen);
err = g13_encrypt_keyblob (parm->ctrl,
plaintext, plaintextlen,
&ciphertext, &ciphertextlen);
wipememory (plaintext, plaintextlen);
xfree (plaintext);
if (err)
log_error ("error encrypting keyblob: %s\n", gpg_strerror (err));
else
{
err = assuan_send_data (parm->ctx, ciphertext, ciphertextlen);
xfree (ciphertext);
if (err)
log_error ("sending ciphertext to g13-syshelp failed: %s\n",
gpg_strerror (err));
}
}
}
else
err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/* Run the CREATE command on the current device. CONTTYPES gives the
requested content type for the new container. */
gpg_error_t
call_syshelp_run_create (ctrl_t ctrl, int conttype)
{
gpg_error_t err;
assuan_context_t ctx;
struct create_parm_s parm;
memset (&parm, 0, sizeof parm);
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
/* tty_get ("waiting for debugger"); */
/* tty_kill_prompt (); */
parm.ctx = ctx;
parm.ctrl = ctrl;
init_membuf (&parm.plaintext, 512);
if (conttype == CONTTYPE_DM_CRYPT)
{
err = assuan_transact (ctx, "CREATE dm-crypt",
create_data_cb, &parm,
create_inq_cb, &parm,
create_status_cb, &parm);
}
else
{
log_error ("invalid backend type %d given\n", conttype);
err = GPG_ERR_INTERNAL;
goto leave;
}
leave:
xfree (get_membuf (&parm.plaintext, NULL));
return err;
}
static gpg_error_t
mount_status_cb (void *opaque, const char *line)
{
struct mount_parm_s *parm = opaque;
/* Nothing right now. */
(void)parm;
(void)line;
return 0;
}
/* Inquire callback for MOUNT and RESUME. */
static gpg_error_t
mount_inq_cb (void *opaque, const char *line)
{
struct mount_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYBLOB"))
{
int setconfidential = !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL);
if (setconfidential)
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, parm->keyblob, parm->keybloblen);
if (setconfidential)
assuan_end_confidential (parm->ctx);
if (err)
log_error ("sending keyblob to g13-syshelp failed: %s\n",
gpg_strerror (err));
}
else
err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/*
* Run the MOUNT command on the current device. CONTTYPES gives the
* requested content type for the new container. MOUNTPOINT the
* desired mount point or NULL for default.
*/
gpg_error_t
call_syshelp_run_mount (ctrl_t ctrl, int conttype, const char *mountpoint,
tupledesc_t tuples)
{
gpg_error_t err;
assuan_context_t ctx;
struct mount_parm_s parm;
memset (&parm, 0, sizeof parm);
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
/* tty_get ("waiting for debugger"); */
/* tty_kill_prompt (); */
parm.ctx = ctx;
parm.ctrl = ctrl;
if (conttype == CONTTYPE_DM_CRYPT)
{
ref_tupledesc (tuples);
parm.keyblob = get_tupledesc_data (tuples, &parm.keybloblen);
err = assuan_transact (ctx, "MOUNT dm-crypt",
NULL, NULL,
mount_inq_cb, &parm,
mount_status_cb, &parm);
unref_tupledesc (tuples);
}
else
{
(void)mountpoint; /* Not used. */
log_error ("invalid backend type %d given\n", conttype);
err = GPG_ERR_INTERNAL;
goto leave;
}
leave:
return err;
}
/*
* Run the UMOUNT command on the current device. CONTTYPES gives the
* content type of the container (fixme: Do we really need this?).
*/
gpg_error_t
call_syshelp_run_umount (ctrl_t ctrl, int conttype)
{
gpg_error_t err;
assuan_context_t ctx;
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
if (conttype == CONTTYPE_DM_CRYPT)
{
err = assuan_transact (ctx, "UMOUNT dm-crypt",
NULL, NULL,
NULL, NULL,
NULL, NULL);
}
else
{
log_error ("invalid backend type %d given\n", conttype);
err = GPG_ERR_INTERNAL;
goto leave;
}
leave:
return err;
}
/*
* Run the SUSPEND command on the current device. CONTTYPES gives the
* requested content type for the new container.
*/
gpg_error_t
call_syshelp_run_suspend (ctrl_t ctrl, int conttype)
{
gpg_error_t err;
assuan_context_t ctx;
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
if (conttype == CONTTYPE_DM_CRYPT)
{
err = assuan_transact (ctx, "SUSPEND dm-crypt",
NULL, NULL,
NULL, NULL,
NULL, NULL);
}
else
{
log_error ("invalid backend type %d given\n", conttype);
err = GPG_ERR_INTERNAL;
goto leave;
}
leave:
return err;
}
/* Run the RESUME command on the current device. CONTTYPES gives the
requested content type for the container. */
gpg_error_t
call_syshelp_run_resume (ctrl_t ctrl, int conttype, tupledesc_t tuples)
{
gpg_error_t err;
assuan_context_t ctx;
struct mount_parm_s parm;
memset (&parm, 0, sizeof parm);
err = start_syshelp (ctrl, &ctx);
if (err)
goto leave;
/* tty_get ("waiting for debugger"); */
/* tty_kill_prompt (); */
parm.ctx = ctx;
parm.ctrl = ctrl;
if (conttype == CONTTYPE_DM_CRYPT)
{
ref_tupledesc (tuples);
parm.keyblob = get_tupledesc_data (tuples, &parm.keybloblen);
err = assuan_transact (ctx, "RESUME dm-crypt",
NULL, NULL,
mount_inq_cb, &parm,
NULL, NULL);
unref_tupledesc (tuples);
}
else
{
log_error ("invalid backend type %d given\n", conttype);
err = GPG_ERR_INTERNAL;
goto leave;
}
leave:
return err;
}
diff --git a/g13/create.c b/g13/create.c
index 573039dc2..d55b85931 100644
--- a/g13/create.c
+++ b/g13/create.c
@@ -1,302 +1,302 @@
/* create.c - Create a new crypto container
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "create.h"
#include "keyblob.h"
#include "backend.h"
#include "g13tuple.h"
#include "../common/call-gpg.h"
/* Create a new blob with all the session keys and other meta
information which are to be stored encrypted in the crypto
container header. On success the malloced blob is stored at R_BLOB
and its length at R_BLOBLEN. On error an error code is returned
and (R_BLOB,R_BLOBLEN) are set to (NULL,0).
The format of this blob is a sequence of tag-length-value tuples.
All tuples have this format:
2 byte TAG Big endian unsigned integer (0..65535)
described by the KEYBLOB_TAG_ constants.
2 byte LENGTH Big endian unsigned integer (0..65535)
giving the length of the value.
length bytes VALUE The value described by the tag.
The first tag in a keyblob must be a BLOBVERSION. The other tags
depend on the type of the container as described by the CONTTYPE
tag. See keyblob.h for details. */
static gpg_error_t
create_new_keyblob (ctrl_t ctrl, int is_detached,
void **r_blob, size_t *r_bloblen)
{
gpg_error_t err;
unsigned char twobyte[2];
membuf_t mb;
*r_blob = NULL;
*r_bloblen = 0;
init_membuf_secure (&mb, 512);
append_tuple (&mb, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
twobyte[0] = (ctrl->conttype >> 8);
twobyte[1] = (ctrl->conttype);
append_tuple (&mb, KEYBLOB_TAG_CONTTYPE, twobyte, 2);
if (is_detached)
append_tuple (&mb, KEYBLOB_TAG_DETACHED, NULL, 0);
err = be_create_new_keys (ctrl->conttype, &mb);
if (err)
goto leave;
/* Just for testing. */
append_tuple (&mb, KEYBLOB_TAG_FILLER, "filler", 6);
*r_blob = get_membuf (&mb, r_bloblen);
if (!*r_blob)
{
err = gpg_error_from_syserror ();
*r_bloblen = 0;
}
else
log_debug ("used keyblob size is %zu\n", *r_bloblen);
leave:
xfree (get_membuf (&mb, NULL));
return err;
}
/* Encrypt the keyblob (KEYBLOB,KEYBLOBLEN) and store the result at
(R_ENCBLOB, R_ENCBLOBLEN). Returns 0 on success or an error code.
On error R_EKYBLOB is set to NULL. Depending on the keys set in
CTRL the result is a single OpenPGP binary message, a single
special OpenPGP packet encapsulating a CMS message or a
concatenation of both with the CMS packet being the last. */
gpg_error_t
g13_encrypt_keyblob (ctrl_t ctrl, void *keyblob, size_t keybloblen,
void **r_encblob, size_t *r_encbloblen)
{
gpg_error_t err;
/* FIXME: For now we only implement OpenPGP. */
err = gpg_encrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments,
keyblob, keybloblen,
ctrl->recipients,
r_encblob, r_encbloblen);
return err;
}
/* Write a new file under the name FILENAME with the keyblob and an
appropriate header. This function is called with a lock file in
place and after checking that the filename does not exists. */
static gpg_error_t
write_keyblob (const char *filename,
const void *keyblob, size_t keybloblen)
{
gpg_error_t err;
estream_t fp;
unsigned char packet[32];
size_t headerlen, paddinglen;
fp = es_fopen (filename, "wbx");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error creating new container '%s': %s\n",
filename, gpg_strerror (err));
return err;
}
/* Allow for an least 8 times larger keyblob to accommodate for
future key changes. Round it up to 4096 byte. */
headerlen = ((32 + 8 * keybloblen + 16) + 4095) / 4096 * 4096;
paddinglen = headerlen - 32 - keybloblen;
assert (paddinglen >= 16);
packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = 0;
packet[3] = 0;
packet[4] = 0;
packet[5] = 26;
memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */
packet[16] = 1; /* G13 packet format version. */
packet[17] = 0; /* Reserved. */
packet[18] = 0; /* Reserved. */
packet[19] = 0; /* OS Flag. */
packet[20] = (headerlen >> 24); /* Total length of header. */
packet[21] = (headerlen >> 16);
packet[22] = (headerlen >> 8);
packet[23] = (headerlen);
packet[24] = 1; /* Number of header copies. */
packet[25] = 0; /* Number of header copies at the end. */
packet[26] = 0; /* Reserved. */
packet[27] = 0; /* Reserved. */
packet[28] = 0; /* Reserved. */
packet[29] = 0; /* Reserved. */
packet[30] = 0; /* Reserved. */
packet[31] = 0; /* Reserved. */
if (es_fwrite (packet, 32, 1, fp) != 1)
goto writeerr;
if (es_fwrite (keyblob, keybloblen, 1, fp) != 1)
goto writeerr;
/* Write the padding. */
packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = (paddinglen-6) >> 24;
packet[3] = (paddinglen-6) >> 16;
packet[4] = (paddinglen-6) >> 8;
packet[5] = (paddinglen-6);
memcpy (packet+6, "GnuPG/PAD", 10); /* Packet subtype. */
if (es_fwrite (packet, 16, 1, fp) != 1)
goto writeerr;
memset (packet, 0, 32);
for (paddinglen-=16; paddinglen >= 32; paddinglen -= 32)
if (es_fwrite (packet, 32, 1, fp) != 1)
goto writeerr;
if (paddinglen)
if (es_fwrite (packet, paddinglen, 1, fp) != 1)
goto writeerr;
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
filename, gpg_strerror (err));
remove (filename);
return err;
}
return 0;
writeerr:
err = gpg_error_from_syserror ();
log_error ("error writing header to '%s': %s\n",
filename, gpg_strerror (err));
es_fclose (fp);
remove (filename);
return err;
}
/* Create a new container under the name FILENAME and intialize it
using the current settings. If the file already exists an error is
returned. */
gpg_error_t
g13_create_container (ctrl_t ctrl, const char *filename)
{
gpg_error_t err;
dotlock_t lock;
void *keyblob = NULL;
size_t keybloblen;
void *enckeyblob = NULL;
size_t enckeybloblen;
char *detachedname = NULL;
int detachedisdir;
tupledesc_t tuples = NULL;
unsigned int dummy_rid;
if (!ctrl->recipients)
return gpg_error (GPG_ERR_NO_PUBKEY);
err = be_take_lock_for_create (ctrl, filename, &lock);
if (err)
goto leave;
/* And a possible detached file or directory may not exist either. */
err = be_get_detached_name (ctrl->conttype, filename,
&detachedname, &detachedisdir);
if (err)
goto leave;
if (detachedname)
{
struct stat sb;
if (!stat (detachedname, &sb))
{
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
}
if (ctrl->conttype != CONTTYPE_DM_CRYPT)
{
/* Create a new keyblob. */
err = create_new_keyblob (ctrl, !!detachedname, &keyblob, &keybloblen);
if (err)
goto leave;
/* Encrypt that keyblob. */
err = g13_encrypt_keyblob (ctrl, keyblob, keybloblen,
&enckeyblob, &enckeybloblen);
if (err)
goto leave;
/* Put a copy of the keyblob into a tuple structure. */
err = create_tupledesc (&tuples, keyblob, keybloblen);
if (err)
goto leave;
keyblob = NULL;
/* if (opt.verbose) */
/* dump_keyblob (tuples); */
/* Write out the header, the encrypted keyblob and some padding. */
err = write_keyblob (filename, enckeyblob, enckeybloblen);
if (err)
goto leave;
}
/* Create and append the container. FIXME: We should pass the
estream object in addition to the filename, so that the backend
can append the container to the g13 file. */
err = be_create_container (ctrl, ctrl->conttype, filename, -1, tuples,
&dummy_rid);
leave:
destroy_tupledesc (tuples);
xfree (detachedname);
xfree (enckeyblob);
xfree (keyblob);
dotlock_destroy (lock);
return err;
}
diff --git a/g13/g13-common.c b/g13/g13-common.c
index 8370907fb..35cb13186 100644
--- a/g13/g13-common.c
+++ b/g13/g13-common.c
@@ -1,86 +1,86 @@
/* g13-common.c - Common code for G13 modules
* Copyright (C) 2009, 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "g13-common.h"
#include <gcrypt.h>
#include <assuan.h>
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
/* Global variable to keep an error count. */
int g13_errors_seen = 0;
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM);
}
/* Wrapper around gnupg_init_signals. */
void
g13_init_signals (void)
{
gnupg_init_signals (0, emergency_cleanup);
}
/* Install a regular exit handler to make real sure that the secure
memory gets wiped out. */
void
g13_install_emergency_cleanup (void)
{
if (atexit (emergency_cleanup))
{
log_error ("atexit failed\n");
g13_exit (2);
}
}
/* Use this function instead of exit() in all g13 modules. */
void
g13_exit (int rc)
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : g13_errors_seen? 1 : 0;
exit (rc);
}
diff --git a/g13/g13-syshelp.c b/g13/g13-syshelp.c
index 8b8a4a7d8..bf71ac20f 100644
--- a/g13/g13-syshelp.c
+++ b/g13/g13-syshelp.c
@@ -1,748 +1,748 @@
/* g13-syshelp.c - Helper for disk key management with GnuPG
* Copyright (C) 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
#include <unistd.h>
#include "g13-syshelp.h"
#include <gcrypt.h>
#include <assuan.h>
-#include "i18n.h"
-#include "sysutils.h"
-#include "asshelp.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/asshelp.h"
#include "../common/init.h"
#include "keyblob.h"
enum cmd_and_opt_values {
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRecipient = 'r',
aGPGConfList = 500,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oLogFile,
oNoLogFile,
oAuditLog,
oOutput,
oAgentProgram,
oGpgProgram,
oType,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oStatusFD,
oLoggerFD,
oNoVerbose,
oNoSecmemWarn,
oHomedir,
oDryRun,
oNoDetach,
oNoRandomSeedFile,
oFakedSystemTime
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MOUNT_VALUE , "mount" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick interval used by the idle task. */
#define TIMERTICK_INTERVAL_SEC (1)
/* It is possible that we are currently running under setuid permissions. */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug. */
static const char *debug_level;
static unsigned int debug_value;
/* Local prototypes. */
static void g13_syshelp_deinit_default_ctrl (ctrl_t ctrl);
static void release_tab_items (tab_item_t tab);
static tab_item_t parse_g13tab (const char *username);
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@G13@-syshelp (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40: p = _("Usage: @G13@-syshelp [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @G13@-syshelp [options] [files]\n"
"Helper to perform root-only tasks for g13\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
default: p = NULL; break;
}
return p;
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* if (numok) */
/* opt.debug &= ~(DBG_HASHING_VALUE); */
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
g13_exit(2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
int
main ( int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
gpg_error_t err = 0;
/* const char *fname; */
int may_coredump;
FILE *configfp = NULL;
char *configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int no_more_options = 0;
int default_config =1;
char *logfile = NULL;
/* int debug_wait = 0; */
int use_random_seed = 1;
/* int nodetach = 0; */
/* int nokeysetup = 0; */
struct server_control_s ctrl;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (G13_NAME "-syshelp");
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (G13_NAME "-syshelp", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
/* Take extra care of the random pool. */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
g13_init_signals ();
dotlock_create (NULL, 0); /* Register locking cleanup. */
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* Fixme: We enable verbose mode here because there is currently no
way to do this when starting g13-syshelp. To fix that we should
add a g13-syshelp.conf file in /etc/gnupg. */
opt.verbose = 1;
/* First check whether we have a debug option on the commandline. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
/* Setup malloc hooks. */
{
struct assuan_malloc_hooks malloc_hooks;
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
}
/* Prepare libassuan. */
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
/*assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);*/
setup_libassuan_logging (&opt.debug, NULL);
/* Setup a default control structure for command line mode. */
memset (&ctrl, 0, sizeof ctrl);
g13_syshelp_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
if (default_config )
configname = make_filename (gnupg_sysconfdir (),
G13_NAME"-syshelp.conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if (parse_debug)
log_info (_("NOTE: no default option file '%s'\n"), configname);
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno));
g13_exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname)
log_info (_("reading options from '%s'\n"), configname);
default_config = 0;
}
while (!no_more_options
&& optfile_parse (configfp, configname, &configlineno, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oNoDetach: /*nodetach = 1; */break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: /*debug_wait = pargs.r.ret_int; */break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break;
case oNoRandomSeedFile: use_random_seed = 0; break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the config filename. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
G13_NAME".conf", NULL);
if (log_get_errorcount(0))
g13_exit(2);
/* Now that we have the options parsed we need to update the default
control structure. */
g13_syshelp_init_default_ctrl (&ctrl);
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print any pending secure memory warnings. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Setup the debug flags for all subsystems. */
set_debug ();
/* Install a regular exit handler to make real sure that the secure
memory gets wiped out. */
g13_install_emergency_cleanup ();
/* Terminate if we found any error until now. */
if (log_get_errorcount(0))
g13_exit (2);
/* Set the standard GnuPG random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
/* Get the UID of the caller. */
#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
{
const char *uidstr;
struct passwd *pwd = NULL;
uidstr = getenv ("USERV_UID");
/* Print a quick note if we are not started via userv. */
if (!uidstr)
{
if (getuid ())
{
log_info ("WARNING: Not started via userv\n");
ctrl.fail_all_cmds = 1;
}
ctrl.client.uid = getuid ();
}
else
{
unsigned long myuid;
errno = 0;
myuid = strtoul (uidstr, NULL, 10);
if (myuid == ULONG_MAX && errno)
{
log_info ("WARNING: Started via broken userv: %s\n",
strerror (errno));
ctrl.fail_all_cmds = 1;
ctrl.client.uid = getuid ();
}
else
ctrl.client.uid = (uid_t)myuid;
}
pwd = getpwuid (ctrl.client.uid);
if (!pwd || !*pwd->pw_name)
{
log_info ("WARNING: Name for UID not found: %s\n", strerror (errno));
ctrl.fail_all_cmds = 1;
ctrl.client.uname = xstrdup ("?");
}
else
ctrl.client.uname = xstrdup (pwd->pw_name);
/* Check that the user name does not contain a directory
separator. */
if (strchr (ctrl.client.uname, '/'))
{
log_info ("WARNING: Invalid user name passed\n");
ctrl.fail_all_cmds = 1;
}
}
#else /*!HAVE_PWD_H || !HAVE_GETPWUID*/
log_info ("WARNING: System does not support required syscalls\n");
ctrl.fail_all_cmds = 1;
ctrl.client.uid = getuid ();
ctrl.client.uname = xstrdup ("?");
#endif /*!HAVE_PWD_H || !HAVE_GETPWUID*/
/* Read the table entries for this user. */
if (!ctrl.fail_all_cmds
&& !(ctrl.client.tab = parse_g13tab (ctrl.client.uname)))
ctrl.fail_all_cmds = 1;
/* Start the server. */
err = syshelp_server (&ctrl);
if (err)
log_error ("server exited with error: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
/* Cleanup. */
g13_syshelp_deinit_default_ctrl (&ctrl);
g13_exit (0);
return 8; /*NOTREACHED*/
}
/* Store defaults into the per-connection CTRL object. */
void
g13_syshelp_init_default_ctrl (ctrl_t ctrl)
{
ctrl->conttype = CONTTYPE_DM_CRYPT;
}
/* Release all resources allocated by default in the CTRl object. */
static void
g13_syshelp_deinit_default_ctrl (ctrl_t ctrl)
{
xfree (ctrl->client.uname);
release_tab_items (ctrl->client.tab);
}
/* Release the list of g13tab itejms at TAB. */
static void
release_tab_items (tab_item_t tab)
{
while (tab)
{
tab_item_t next = tab->next;
xfree (tab->mountpoint);
xfree (tab);
tab = next;
}
}
void
g13_syshelp_i_know_what_i_am_doing (void)
{
const char * const yesfile = "Yes-g13-I-know-what-I-am-doing";
char *fname;
fname = make_filename (gnupg_sysconfdir (), yesfile, NULL);
if (access (fname, F_OK))
{
log_info ("*******************************************************\n");
log_info ("* The G13 support for DM-Crypt is new and not matured.\n");
log_info ("* Bugs or improper use may delete all your disks!\n");
log_info ("* To confirm that you are ware of this risk, create\n");
log_info ("* the file '%s'.\n", fname);
log_info ("*******************************************************\n");
exit (1);
}
xfree (fname);
}
/* Parse the /etc/gnupg/g13tab for user USERNAME. Return a table for
the user on success. Return NULL on error and print
diagnostics. */
static tab_item_t
parse_g13tab (const char *username)
{
gpg_error_t err;
int c, n;
char line[512];
char *p;
char *fname;
estream_t fp;
int lnr;
char **words = NULL;
tab_item_t table = NULL;
tab_item_t *tabletail, ti;
fname = make_filename (gnupg_sysconfdir (), G13_NAME"tab", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
tabletail = &table;
err = 0;
lnr = 0;
while (es_fgets (line, DIM(line)-1, fp))
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
/* Eat until end of line. */
while ((c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
continue;
}
line[--n] = 0; /* Chop the LF. */
if (n && line[n-1] == '\r')
line[--n] = 0; /* Chop an optional CR. */
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
/* Parse the line. The format is
* <username> <blockdev> [<label>|"-" [<mountpoint>]]
*/
xfree (words);
words = strtokenize (p, " \t");
if (!words)
{
err = gpg_error_from_syserror ();
break;
}
if (!words[0] || !words[1])
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (GPG_ERR_SYNTAX));
continue;
}
if (!(*words[1] == '/'
|| !strncmp (words[1], "PARTUUID=", 9)
|| !strncmp (words[1], "partuuid=", 9)))
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Invalid block device syntax");
continue;
}
if (words[2])
{
if (strlen (words[2]) > 16 || strchr (words[2], '/'))
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Label too long or invalid syntax");
continue;
}
if (words[3] && *words[3] != '/')
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Invalid mountpoint syntax");
continue;
}
}
if (strcmp (words[0], username))
continue; /* Skip entries for other usernames! */
ti = xtrymalloc (sizeof *ti + strlen (words[1]));
if (!ti)
{
err = gpg_error_from_syserror ();
break;
}
ti->next = NULL;
ti->label = NULL;
ti->mountpoint = NULL;
strcpy (ti->blockdev, *words[1]=='/'? words[1] : words[1]+9);
if (words[2])
{
if (strcmp (words[2], "-")
&& !(ti->label = xtrystrdup (words[2])))
{
err = gpg_error_from_syserror ();
xfree (ti);
break;
}
if (words[3] && !(ti->mountpoint = xtrystrdup (words[3])))
{
err = gpg_error_from_syserror ();
xfree (ti->label);
xfree (ti);
break;
}
}
*tabletail = ti;
tabletail = &ti->next;
}
if (!err && !es_feof (fp))
err = gpg_error_from_syserror ();
if (err)
log_error (_("error reading '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
leave:
xfree (words);
es_fclose (fp);
xfree (fname);
if (err)
{
release_tab_items (table);
return NULL;
}
return table;
}
diff --git a/g13/g13.c b/g13/g13.c
index 0553c85c1..4b925ed59 100644
--- a/g13/g13.c
+++ b/g13/g13.c
@@ -1,1067 +1,1067 @@
/* g13.c - Disk Key management with GnuPG
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <npth.h>
#include "g13.h"
#include <gcrypt.h>
#include <assuan.h>
-#include "i18n.h"
-#include "sysutils.h"
-#include "gc-opt-flags.h"
-#include "asshelp.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/asshelp.h"
#include "../common/init.h"
#include "keyblob.h"
#include "server.h"
#include "runner.h"
#include "create.h"
#include "mount.h"
#include "suspend.h"
#include "mountinfo.h"
#include "backend.h"
#include "call-syshelp.h"
enum cmd_and_opt_values {
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRecipient = 'r',
aGPGConfList = 500,
aGPGConfTest,
aCreate,
aMount,
aUmount,
aSuspend,
aResume,
aServer,
aFindDevice,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oLogFile,
oNoLogFile,
oAuditLog,
oOutput,
oAgentProgram,
oGpgProgram,
oType,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oStatusFD,
oLoggerFD,
oNoVerbose,
oNoSecmemWarn,
oNoGreeting,
oNoTTY,
oNoOptions,
oHomedir,
oWithColons,
oDryRun,
oNoDetach,
oNoRandomSeedFile,
oFakedSystemTime
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aCreate, "create", N_("Create a new file system container")),
ARGPARSE_c (aMount, "mount", N_("Mount a file system container") ),
ARGPARSE_c (aUmount, "umount", N_("Unmount a file system container") ),
ARGPARSE_c (aSuspend, "suspend", N_("Suspend a file system container") ),
ARGPARSE_c (aResume, "resume", N_("Resume a file system container") ),
ARGPARSE_c (aServer, "server", N_("Run in server mode")),
ARGPARSE_c (aFindDevice, "find-device", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oType, "type", N_("|NAME|use container format NAME")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write log output to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_group (303, N_("@\nExamples:\n\n"
" blurb\n"
" blurb\n")),
/* Hidden options. */
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_n (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
/* Command aliases. */
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MOUNT_VALUE , "mount" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick interval used by the idle task. */
#define TIMERTICK_INTERVAL_SEC (1)
/* It is possible that we are currently running under setuid permissions. */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug. */
static const char *debug_level;
static unsigned int debug_value;
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* The thread id of the idle task. */
static npth_t idle_task_thread;
/* The container type as specified on the command line. */
static int cmdline_conttype;
static void set_cmd (enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void start_idle_task (void);
static void join_idle_task (void);
/* Begin NPth wrapper functions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@G13@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40: p = _("Usage: @G13@ [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @G13@ [options] [files]\n"
"Create, mount or unmount an encrypted file system container\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
fprintf (stderr, _("usage: %s [options] "), G13_NAME);
fputs (text, stderr);
putc ('\n', stderr);
g13_exit (2);
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* if (numok) */
/* opt.debug &= ~(DBG_HASHING_VALUE); */
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
g13_exit(2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else
{
log_error (_("conflicting commands\n"));
g13_exit (2);
}
*ret_cmd = cmd;
}
int
main ( int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
gpg_error_t err = 0;
/* const char *fname; */
int may_coredump;
FILE *configfp = NULL;
char *configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int no_more_options = 0;
int default_config =1;
char *logfile = NULL;
int greeting = 0;
int nogreeting = 0;
/* int debug_wait = 0; */
int use_random_seed = 1;
/* int nodetach = 0; */
/* int nokeysetup = 0; */
enum cmd_and_opt_values cmd = 0;
struct server_control_s ctrl;
strlist_t recipients = NULL;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (G13_NAME);
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (G13_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
npth_init ();
/* Take extra care of the random pool. */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
g13_init_signals ();
dotlock_create (NULL, 0); /* Register locking cleanup. */
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* First check whether we have a config file on the commandline. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* Do not remove the args, ignore version. */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes, there is one, so we do not try the default one but
read the config file when it is encountered at the
commandline. */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
/* Setup malloc hooks. */
{
struct assuan_malloc_hooks malloc_hooks;
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
}
/* Prepare libassuan. */
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup a default control structure for command line mode. */
memset (&ctrl, 0, sizeof ctrl);
g13_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
/* Set the default option file */
if (default_config )
configname = make_filename (gnupg_homedir (), G13_NAME".conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if (parse_debug)
log_info (_("Note: no default option file '%s'\n"), configname);
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno));
g13_exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname)
log_info (_("reading options from '%s'\n"), configname);
default_config = 0;
}
while (!no_more_options
&& optfile_parse (configfp, configname, &configlineno, &pargs, opts))
{
switch (pargs.r_opt)
{
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
nogreeting = 1;
/* nokeysetup = 1; */
break;
case aServer:
case aMount:
case aUmount:
case aSuspend:
case aResume:
case aCreate:
case aFindDevice:
set_cmd (&cmd, pargs.r_opt);
break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oNoGreeting: nogreeting = 1; break;
case oNoTTY: break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oNoDetach: /*nodetach = 1; */break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: /*debug_wait = pargs.r.ret_int; */break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
case oNoOptions: break; /* no-options */
case oOptions:
/* Config files may not be nested (silently ignore them). */
if (!configfp)
{
xfree(configname);
configname = xstrdup (pargs.r.ret_str);
goto next_pass;
}
break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oGpgProgram: opt.gpg_program = pargs.r.ret_str; break;
case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break;
case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break;
case oXauthority: opt.xauthority = xstrdup (pargs.r.ret_str); break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oRecipient: /* Store the encryption key. */
add_to_strlist (&recipients, pargs.r.ret_str);
break;
case oType:
if (!strcmp (pargs.r.ret_str, "help"))
{
be_parse_conttype_name (NULL);
g13_exit (0);
}
cmdline_conttype = be_parse_conttype_name (pargs.r.ret_str);
if (!cmdline_conttype)
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
/* XXX Construct GPG arguments. */
{
strlist_t last;
last = append_to_strlist (&opt.gpg_arguments, "-z");
last = append_to_strlist (&last, "0");
last = append_to_strlist (&last, "--trust-model");
last = append_to_strlist (&last, "always");
(void) last;
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the config filename. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
G13_NAME".conf", NULL);
if (log_get_errorcount(0))
g13_exit(2);
/* Now that we have the options parsed we need to update the default
control structure. */
g13_init_default_ctrl (&ctrl);
ctrl.recipients = recipients;
recipients = NULL;
if (nogreeting)
greeting = 0;
if (greeting)
{
fprintf (stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
fprintf (stderr, "%s\n", strusage(15) );
}
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print any pending secure memory warnings. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Setup the debug flags for all subsystems. */
set_debug ();
/* Install emergency cleanup handler. */
g13_install_emergency_cleanup ();
/* Terminate if we found any error until now. */
if (log_get_errorcount(0))
g13_exit (2);
/* Set the standard GnuPG random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
/* Store given filename into FNAME. */
/* fname = argc? *argv : NULL; */
/* Parse all given encryption keys. This does a lookup of the keys
and stops if any of the given keys was not found. */
#if 0 /* Currently not implemented. */
if (!nokeysetup)
{
strlist_t sl;
int failed = 0;
for (sl = ctrl->recipients; sl; sl = sl->next)
if (check_encryption_key ())
failed = 1;
if (failed)
g13_exit (1);
}
#endif /*0*/
/* Dispatch command. */
err = 0;
switch (cmd)
{
case aGPGConfList:
{ /* List options and default values in the GPG Conf format. */
char *config_filename_esc = percent_escape (opt.config_filename, NULL);
printf ("gpgconf-g13.conf:%lu:\"%s\n",
GC_OPT_FLAG_DEFAULT, config_filename_esc);
xfree (config_filename_esc);
printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
}
break;
case aGPGConfTest:
/* This is merely a dummy command to test whether the
configuration file is valid. */
break;
case aServer:
{
start_idle_task ();
ctrl.no_server = 0;
err = g13_server (&ctrl);
if (err)
log_error ("server exited with error: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
g13_request_shutdown ();
}
break;
case aFindDevice:
{
char *blockdev;
if (argc != 1)
wrong_args ("--find-device name");
err = call_syshelp_find_device (&ctrl, argv[0], &blockdev);
if (err)
log_error ("error finding device '%s': %s <%s>\n",
argv[0], gpg_strerror (err), gpg_strsource (err));
else
puts (blockdev);
}
break;
case aCreate: /* Create a new container. */
{
if (argc != 1)
wrong_args ("--create filename");
start_idle_task ();
err = g13_create_container (&ctrl, argv[0]);
if (err)
log_error ("error creating a new container: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
g13_request_shutdown ();
}
break;
case aMount: /* Mount a container. */
{
if (argc != 1 && argc != 2 )
wrong_args ("--mount filename [mountpoint]");
start_idle_task ();
err = g13_mount_container (&ctrl, argv[0], argc == 2?argv[1]:NULL);
if (err)
log_error ("error mounting container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aUmount: /* Unmount a mounted container. */
{
if (argc != 1)
wrong_args ("--umount filename");
err = g13_umount_container (&ctrl, argv[0], NULL);
if (err)
log_error ("error unmounting container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aSuspend: /* Suspend a container. */
{
/* Fixme: Should we add a suspend all container option? */
if (argc != 1)
wrong_args ("--suspend filename");
err = g13_suspend_container (&ctrl, argv[0]);
if (err)
log_error ("error suspending container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aResume: /* Resume a suspended container. */
{
/* Fixme: Should we add a resume all container option? */
if (argc != 1)
wrong_args ("--resume filename");
err = g13_resume_container (&ctrl, argv[0]);
if (err)
log_error ("error resuming container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
break;
}
g13_deinit_default_ctrl (&ctrl);
if (!err)
join_idle_task ();
/* Cleanup. */
g13_exit (0);
return 8; /*NOTREACHED*/
}
/* Store defaults into the per-connection CTRL object. */
void
g13_init_default_ctrl (ctrl_t ctrl)
{
ctrl->conttype = cmdline_conttype? cmdline_conttype : CONTTYPE_ENCFS;
}
/* Release remaining resources allocated in the CTRL object. */
void
g13_deinit_default_ctrl (ctrl_t ctrl)
{
call_syshelp_release (ctrl);
FREE_STRLIST (ctrl->recipients);
}
/* Request a shutdown. This can be used when the process should
* finish instead of running the idle task. */
void
g13_request_shutdown (void)
{
shutdown_pending++;
}
/* This function is called for each signal we catch. It is run in the
main context or the one of a NPth thread and thus it is not
restricted in what it may do. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
log_info ("SIGHUP received - re-reading configuration\n");
/* Fixme: Not yet implemented. */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
mountinfo_dump_all ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %u runners active\n",
runner_get_threads ());
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
g13_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
g13_exit (0);
break;
#endif /*!HAVE_W32_SYSTEM*/
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
/* This ticker function is called about every TIMERTICK_INTERVAL_SEC
seconds. */
static void
handle_tick (void)
{
/* log_debug ("TICK\n"); */
}
/* The idle task. We use a separate thread to do idle stuff and to
catch signals. */
static void *
idle_task (void *dummy_arg)
{
int signo; /* The number of a raised signal is stored here. */
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int ret;
(void)dummy_arg;
/* Create the event to catch the signals. */
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL_SEC;
for (;;)
{
/* The shutdown flag allows us to terminate the idle task. */
if (shutdown_pending)
{
runner_cancel_all ();
if (!runner_get_threads ())
break; /* ready */
}
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL_SEC;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (0, NULL, NULL, NULL, &timeout, npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (0, NULL, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
/* Here one would add processing of file descriptors. */
}
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
return NULL;
}
/* Start the idle task. */
static void
start_idle_task (void)
{
npth_attr_t tattr;
npth_t thread;
sigset_t sigs; /* The set of signals we want to catch. */
int err;
#ifndef HAVE_W32_SYSTEM
/* These signals should always go to the idle task, so they need to
be blocked everywhere else. We assume start_idle_task is called
from the main thread before any other threads are created. */
sigemptyset (&sigs);
sigaddset (&sigs, SIGHUP);
sigaddset (&sigs, SIGUSR1);
sigaddset (&sigs, SIGUSR2);
sigaddset (&sigs, SIGINT);
sigaddset (&sigs, SIGTERM);
npth_sigmask (SIG_BLOCK, &sigs, NULL);
#endif
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
err = npth_create (&thread, &tattr, idle_task, NULL);
if (err)
{
log_fatal ("error starting idle task: %s\n", strerror (err));
return; /*NOTREACHED*/
}
npth_setname_np (thread, "idle-task");
idle_task_thread = thread;
npth_attr_destroy (&tattr);
}
/* Wait for the idle task to finish. */
static void
join_idle_task (void)
{
int err;
/* FIXME: This assumes that a valid pthread_t is non-null. That is
not guaranteed. */
if (idle_task_thread)
{
err = npth_join (idle_task_thread, NULL);
if (err)
log_error ("waiting for idle task thread failed: %s\n",
strerror (err));
}
}
diff --git a/g13/keyblob.c b/g13/keyblob.c
index 81863bb4e..1fb9be73a 100644
--- a/g13/keyblob.c
+++ b/g13/keyblob.c
@@ -1,207 +1,207 @@
/* keyblob.c - Keyblob parser and builder.
* Copyright (C) 2009 Free Software Foundation, Inc.
* Copyright (C) 2015-2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "g13.h"
#include "mount.h"
#include "keyblob.h"
#include "../common/sysutils.h"
-#include "host2net.h"
+#include "../common/host2net.h"
/* Parse the header prefix and return the length of the entire header. */
static gpg_error_t
parse_header (const char *filename,
const unsigned char *packet, size_t packetlen,
size_t *r_headerlen)
{
unsigned int len;
if (packetlen != 32)
return gpg_error (GPG_ERR_BUG);
len = buf32_to_uint (packet+2);
if (packet[0] != (0xc0|61) || len < 26
|| memcmp (packet+6, "GnuPG/G13", 10))
{
log_error ("file '%s' is not valid container\n", filename);
return gpg_error (GPG_ERR_INV_OBJ);
}
if (packet[16] != 1)
{
log_error ("unknown version %u of container '%s'\n",
(unsigned int)packet[16], filename);
return gpg_error (GPG_ERR_INV_OBJ);
}
if (packet[17] || packet[18]
|| packet[26] || packet[27] || packet[28] || packet[29]
|| packet[30] || packet[31])
log_info ("WARNING: unknown meta information in '%s'\n", filename);
if (packet[19])
log_info ("WARNING: OS flag is not supported in '%s'\n", filename);
if (packet[24] > 1 )
log_info ("Note: meta data copies in '%s' are ignored\n", filename);
len = buf32_to_uint (packet+20);
/* Do a basic sanity check on the length. */
if (len < 32 || len > 1024*1024)
{
log_error ("bad length given in container '%s'\n", filename);
return gpg_error (GPG_ERR_INV_OBJ);
}
*r_headerlen = len;
return 0;
}
/* Read the prefix of the keyblob and do some basic parsing. On
success returns an open estream file at R_FP and the length of the
header at R_HEADERLEN. */
static gpg_error_t
read_keyblob_prefix (const char *filename, estream_t *r_fp, size_t *r_headerlen)
{
gpg_error_t err;
estream_t fp;
unsigned char packet[32];
*r_fp = NULL;
fp = es_fopen (filename, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", filename, gpg_strerror (err));
return err;
}
/* Read the header. It is defined as 32 bytes thus we read it in one go. */
if (es_fread (packet, 32, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading the header of '%s': %s\n",
filename, gpg_strerror (err));
es_fclose (fp);
return err;
}
err = parse_header (filename, packet, 32, r_headerlen);
if (err)
es_fclose (fp);
else
*r_fp = fp;
return err;
}
/*
* Test whether the container with name FILENAME is a suitable G13
* container. This function may even be called on a mounted
* container.
*/
gpg_error_t
g13_is_container (ctrl_t ctrl, const char *filename)
{
gpg_error_t err;
estream_t fp = NULL;
size_t dummy;
(void)ctrl;
/* Read just the prefix of the header. */
err = read_keyblob_prefix (filename, &fp, &dummy);
if (!err)
es_fclose (fp);
return err;
}
/*
* Read the keyblob at FILENAME. The caller should have acquired a
* lockfile and checked that the file exists.
*/
gpg_error_t
g13_keyblob_read (const char *filename,
void **r_enckeyblob, size_t *r_enckeybloblen)
{
gpg_error_t err;
estream_t fp = NULL;
size_t headerlen = 0;
size_t msglen;
void *msg = NULL;
*r_enckeyblob = NULL;
*r_enckeybloblen = 0;
err = read_keyblob_prefix (filename, &fp, &headerlen);
if (err)
goto leave;
if (opt.verbose)
log_info ("header length of '%s' is %zu\n", filename, headerlen);
/* Read everything including the padding. We should eventually do a
regular OpenPGP parsing to detect the padding packet and pass
only the actual used OpenPGP data to the engine. This is in
particular required when supporting CMS which will be
encapsulated in an OpenPGP packet. */
assert (headerlen >= 32);
msglen = headerlen - 32;
if (!msglen)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
msg = xtrymalloc (msglen);
if (!msglen)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (es_fread (msg, msglen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading keyblob of '%s': %s\n",
filename, gpg_strerror (err));
goto leave;
}
*r_enckeyblob = msg;
msg = NULL;
*r_enckeybloblen = msglen;
leave:
xfree (msg);
es_fclose (fp);
return err;
}
diff --git a/g13/mount.c b/g13/mount.c
index 7814d5c67..dc415b12d 100644
--- a/g13/mount.c
+++ b/g13/mount.c
@@ -1,265 +1,265 @@
/* mount.c - Mount a crypto container
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "mount.h"
#include "keyblob.h"
#include "backend.h"
#include "g13tuple.h"
#include "mountinfo.h"
#include "runner.h"
-#include "host2net.h"
+#include "../common/host2net.h"
#include "server.h" /*(g13_keyblob_decrypt)*/
#include "../common/sysutils.h"
#include "call-syshelp.h"
/* Mount the container with name FILENAME at MOUNTPOINT. */
gpg_error_t
g13_mount_container (ctrl_t ctrl, const char *filename, const char *mountpoint)
{
gpg_error_t err;
dotlock_t lock;
int needs_syshelp = 0;
void *enckeyblob = NULL;
size_t enckeybloblen;
void *keyblob = NULL;
size_t keybloblen;
tupledesc_t tuples = NULL;
size_t n;
const unsigned char *value;
int conttype;
unsigned int rid;
char *mountpoint_buffer = NULL;
char *blockdev_buffer = NULL;
/* Decide whether we need to use the g13-syshelp. */
err = call_syshelp_find_device (ctrl, filename, &blockdev_buffer);
if (!err)
{
needs_syshelp = 1;
filename = blockdev_buffer;
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_error ("error finding device '%s': %s <%s>\n",
filename, gpg_strerror (err), gpg_strsource (err));
return err;
}
else
{
/* A quick check to see whether we can the container exists. */
if (access (filename, R_OK))
return gpg_error_from_syserror ();
}
if (!mountpoint)
{
mountpoint_buffer = xtrystrdup ("/tmp/g13-XXXXXX");
if (!mountpoint_buffer)
return gpg_error_from_syserror ();
if (!gnupg_mkdtemp (mountpoint_buffer))
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
"/tmp/g13-XXXXXX", gpg_strerror (err));
xfree (mountpoint_buffer);
return err;
}
mountpoint = mountpoint_buffer;
}
err = 0;
if (needs_syshelp)
lock = NULL;
else
{
/* Try to take a lock. */
lock = dotlock_create (filename, 0);
if (!lock)
{
xfree (mountpoint_buffer);
return gpg_error_from_syserror ();
}
if (dotlock_take (lock, 0))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Check again that the file exists. */
if (!needs_syshelp)
{
struct stat sb;
if (stat (filename, &sb))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Read the encrypted keyblob. */
if (needs_syshelp)
{
err = call_syshelp_set_device (ctrl, filename);
if (err)
goto leave;
err = call_syshelp_get_keyblob (ctrl, &enckeyblob, &enckeybloblen);
}
else
err = g13_keyblob_read (filename, &enckeyblob, &enckeybloblen);
if (err)
goto leave;
/* Decrypt that keyblob and store it in a tuple descriptor. */
err = g13_keyblob_decrypt (ctrl, enckeyblob, enckeybloblen,
&keyblob, &keybloblen);
if (err)
goto leave;
xfree (enckeyblob);
enckeyblob = NULL;
err = create_tupledesc (&tuples, keyblob, keybloblen);
if (!err)
keyblob = NULL;
else
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
log_error ("unknown keyblob version\n");
goto leave;
}
if (opt.verbose)
dump_tupledesc (tuples);
value = find_tuple (tuples, KEYBLOB_TAG_CONTTYPE, &n);
if (!value || n != 2)
conttype = 0;
else
conttype = (value[0] << 8 | value[1]);
if (!be_is_supported_conttype (conttype))
{
log_error ("content type %d is not supported\n", conttype);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = be_mount_container (ctrl, conttype, filename, mountpoint, tuples, &rid);
if (err)
;
else if (conttype == CONTTYPE_DM_CRYPT)
g13_request_shutdown ();
else
{
/* Unless this is a DM-CRYPT mount we put it into our mounttable
so that we can manage the mounts ourselves. For dm-crypt we
do not keep a process to monitor he mounts (for now). */
err = mountinfo_add_mount (filename, mountpoint, conttype, rid,
!!mountpoint_buffer);
/* Fixme: What shall we do if this fails? Add a provisional
mountinfo entry first and remove it on error? */
if (!err)
{
char *tmp = percent_plus_escape (mountpoint);
if (!tmp)
err = gpg_error_from_syserror ();
else
{
g13_status (ctrl, STATUS_MOUNTPOINT, tmp, NULL);
xfree (tmp);
}
}
}
leave:
destroy_tupledesc (tuples);
xfree (keyblob);
xfree (enckeyblob);
dotlock_destroy (lock);
xfree (mountpoint_buffer);
xfree (blockdev_buffer);
return err;
}
/* Unmount the container with name FILENAME or the one mounted at
MOUNTPOINT. If both are given the FILENAME takes precedence. */
gpg_error_t
g13_umount_container (ctrl_t ctrl, const char *filename, const char *mountpoint)
{
gpg_error_t err;
char *blockdev;
if (!filename && !mountpoint)
return gpg_error (GPG_ERR_ENOENT);
/* Decide whether we need to use the g13-syshelp. */
err = call_syshelp_find_device (ctrl, filename, &blockdev);
if (!err)
{
/* Need to employ the syshelper to umount the file system. */
/* FIXME: We should get the CONTTYPE from the blockdev. */
err = be_umount_container (ctrl, CONTTYPE_DM_CRYPT, blockdev);
if (!err)
{
/* if (conttype == CONTTYPE_DM_CRYPT) */
g13_request_shutdown ();
}
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_error ("error finding device '%s': %s <%s>\n",
filename, gpg_strerror (err), gpg_strsource (err));
}
else
{
/* Not in g13tab - kill the runner process for this mount. */
unsigned int rid;
runner_t runner;
err = mountinfo_find_mount (filename, mountpoint, &rid);
if (err)
return err;
runner = runner_find_by_rid (rid);
if (!runner)
{
log_error ("runner %u not found\n", rid);
return gpg_error (GPG_ERR_NOT_FOUND);
}
runner_cancel (runner);
runner_release (runner);
}
xfree (blockdev);
return err;
}
diff --git a/g13/mountinfo.c b/g13/mountinfo.c
index 26eca0cd0..ed898b836 100644
--- a/g13/mountinfo.c
+++ b/g13/mountinfo.c
@@ -1,198 +1,198 @@
/* mountinfo.c - Track infos about mounts
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "mountinfo.h"
#include "keyblob.h"
#include "g13tuple.h"
/* The object to keep track of mount information. */
struct mounttable_s
{
int in_use; /* The slot is in use. */
char *container; /* Name of the container. */
char *mountpoint; /* Name of the mounttype. */
int conttype; /* Type of the container. */
unsigned int rid; /* Identifier of the runner task. */
struct {
unsigned int remove:1; /* True if the mountpoint shall be removed
on umount. */
} flags;
};
/* The allocated table of mounts and its size. */
static mtab_t mounttable;
size_t mounttable_size;
/* Add CONTAINER,MOUNTPOINT,CONTTYPE,RID to the mounttable. */
gpg_error_t
mountinfo_add_mount (const char *container, const char *mountpoint,
int conttype, unsigned int rid, int remove_flag)
{
size_t idx;
mtab_t m;
for (idx=0; idx < mounttable_size; idx++)
if (!mounttable[idx].in_use)
break;
if (!(idx < mounttable_size))
{
size_t nslots = mounttable_size;
mounttable_size += 10;
m = xtrycalloc (mounttable_size, sizeof *mounttable);
if (!m)
return gpg_error_from_syserror ();
if (mounttable)
{
for (idx=0; idx < nslots; idx++)
m[idx] = mounttable[idx];
xfree (mounttable);
}
mounttable = m;
m = mounttable + nslots;
assert (!m->in_use);
}
else
m = mounttable + idx;
m->container = xtrystrdup (container);
if (!m->container)
return gpg_error_from_syserror ();
m->mountpoint = xtrystrdup (mountpoint);
if (!m->mountpoint)
{
xfree (m->container);
m->container = NULL;
return gpg_error_from_syserror ();
}
m->conttype = conttype;
m->rid = rid;
m->flags.remove = !!remove_flag;
m->in_use = 1;
return 0;
}
/* Remove a mount info. Either the CONTAINER, the MOUNTPOINT or the
RID must be given. The first argument given is used. */
gpg_error_t
mountinfo_del_mount (const char *container, const char *mountpoint,
unsigned int rid)
{
gpg_error_t err;
size_t idx;
mtab_t m;
/* If a container or mountpint is givem search the RID via the
standard find function. */
if (container || mountpoint)
{
err = mountinfo_find_mount (container, mountpoint, &rid);
if (err)
return err;
}
/* Find via RID and delete. */
for (idx=0, m = mounttable; idx < mounttable_size; idx++, m++)
if (m->in_use && m->rid == rid)
{
if (m->flags.remove && m->mountpoint)
{
/* FIXME: This does not always work because the umount may
not have completed yet. We should add the mountpoints
to an idle queue and retry a remove. */
if (rmdir (m->mountpoint))
log_error ("error removing mount point '%s': %s\n",
m->mountpoint,
gpg_strerror (gpg_error_from_syserror ()));
}
m->in_use = 0;
xfree (m->container);
m->container = NULL;
xfree (m->mountpoint);
m->mountpoint = NULL;
return 0;
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Find a mount and return its rid at R_RID. If CONTAINER is given,
the search is done by the container name, if it is not given the
search is done by MOUNTPOINT. */
gpg_error_t
mountinfo_find_mount (const char *container, const char *mountpoint,
unsigned int *r_rid)
{
size_t idx;
mtab_t m;
if (container)
{
for (idx=0, m = mounttable; idx < mounttable_size; idx++, m++)
if (m->in_use && !strcmp (m->container, container))
break;
}
else if (mountpoint)
{
for (idx=0, m = mounttable; idx < mounttable_size; idx++, m++)
if (m->in_use && !strcmp (m->mountpoint, mountpoint))
break;
}
else
idx = mounttable_size;
if (!(idx < mounttable_size))
return gpg_error (GPG_ERR_NOT_FOUND);
*r_rid = m->rid;
return 0;
}
/* Dump all info to the log stream. */
void
mountinfo_dump_all (void)
{
size_t idx;
mtab_t m;
for (idx=0, m = mounttable; idx < mounttable_size; idx++, m++)
if (m->in_use)
log_info ("mtab[%d] %s on %s type %d rid %u%s\n",
(int)idx, m->container, m->mountpoint, m->conttype, m->rid,
m->flags.remove?" [remove]":"");
}
diff --git a/g13/runner.c b/g13/runner.c
index af2e836d8..138269d21 100644
--- a/g13/runner.c
+++ b/g13/runner.c
@@ -1,539 +1,539 @@
/* runner.c - Run and watch the backend engines
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <npth.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
#include "runner.h"
#include "../common/exechelp.h"
#include "mountinfo.h"
/* The runner object. */
struct runner_s
{
char *name; /* The name of this runner. */
unsigned int identifier; /* The runner identifier. */
int spawned; /* True if runner_spawn has been called. */
npth_t thread; /* The TID of the runner thread. */
runner_t next_running; /* Builds a list of all running threads. */
int canceled; /* Set if a cancel has already been send once. */
int cancel_flag; /* If set the thread should terminate itself. */
/* We use a reference counter to know when it is safe to remove the
object. Lacking an explicit ref function this counter will take
only these two values:
1 = Thread not running or only the thread is still running.
2 = Thread is running and someone is holding a reference. */
int refcount;
pid_t pid; /* PID of the backend's process (the engine). */
int in_fd; /* File descriptors to read from the engine. */
int out_fd; /* File descriptors to write to the engine. */
engine_handler_fnc_t handler; /* The handler functions. */
engine_handler_cleanup_fnc_t handler_cleanup;
void *handler_data; /* Private data of HANDLER and HANDLER_CLEANUP. */
/* Instead of IN_FD we use an estream. Note that the runner thread
may close the stream and set status_fp to NULL at any time. Thus
it won't be a good idea to use it while the runner thread is
running. */
estream_t status_fp;
};
/* The head of the list of all running threads. */
static runner_t running_threads;
/* Write NBYTES of BUF to file descriptor FD. */
static int
writen (int fd, const void *buf, size_t nbytes)
{
size_t nleft = nbytes;
int nwritten;
while (nleft > 0)
{
nwritten = npth_write (fd, buf, nleft);
if (nwritten < 0)
{
if (errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
buf = (const char*)buf + nwritten;
}
return 0;
}
static int
check_already_spawned (runner_t runner, const char *funcname)
{
if (runner->spawned)
{
log_error ("BUG: runner already spawned - ignoring call to %s\n",
funcname);
return 1;
}
else
return 0;
}
/* Return the number of active threads. */
unsigned int
runner_get_threads (void)
{
unsigned int n = 0;
runner_t r;
for (r = running_threads; r; r = r->next_running)
n++;
return n;
}
/* The public release function. */
void
runner_release (runner_t runner)
{
gpg_error_t err;
if (!runner)
return;
if (!--runner->refcount)
return;
err = mountinfo_del_mount (NULL, NULL, runner->identifier);
if (err)
log_error ("failed to remove mount with rid %u from mtab: %s\n",
runner->identifier, gpg_strerror (err));
es_fclose (runner->status_fp);
if (runner->in_fd != -1)
close (runner->in_fd);
if (runner->out_fd != -1)
close (runner->out_fd);
/* Fixme: close the process. */
/* Tell the engine to release its data. */
if (runner->handler_cleanup)
runner->handler_cleanup (runner->handler_data);
if (runner->pid != (pid_t)(-1))
{
/* The process has not been cleaned up - do it now. */
gnupg_kill_process (runner->pid);
/* (Actually we should use the program name and not the
arbitrary NAME of the runner object. However it does not
matter because that information is only used for
diagnostics.) */
gnupg_wait_process (runner->name, runner->pid, 1, NULL);
gnupg_release_process (runner->pid);
}
xfree (runner->name);
xfree (runner);
}
/* Create a new runner context. On success a new runner object is
stored at R_RUNNER. On failure NULL is stored at this address and
an error code returned. */
gpg_error_t
runner_new (runner_t *r_runner, const char *name)
{
static unsigned int namecounter; /* Global name counter. */
char *namebuffer;
runner_t runner, r;
*r_runner = NULL;
runner = xtrycalloc (1, sizeof *runner);
if (!runner)
return gpg_error_from_syserror ();
/* Bump up the namecounter. In case we ever had an overflow we
check that this number is currently not in use. The algorithm is
a bit lame but should be sufficient because such an wrap is not
very likely: Assuming that we do a mount 10 times a second, then
we would overwrap on a 32 bit system after 13 years. */
do
{
namecounter++;
for (r = running_threads; r; r = r->next_running)
if (r->identifier == namecounter)
break;
}
while (r);
runner->identifier = namecounter;
runner->name = namebuffer = xtryasprintf ("%s-%d", name, namecounter);
if (!runner->name)
{
xfree (runner);
return gpg_error_from_syserror ();
}
runner->refcount = 1;
runner->pid = (pid_t)(-1);
runner->in_fd = -1;
runner->out_fd = -1;
*r_runner = runner;
return 0;
}
/* Return the identifier of RUNNER. */
unsigned int
runner_get_rid (runner_t runner)
{
return runner->identifier;
}
/* Find a runner by its rid. Returns the runner object. The caller
must release the runner object. */
runner_t
runner_find_by_rid (unsigned int rid)
{
runner_t r;
for (r = running_threads; r; r = r->next_running)
if (r->identifier == rid)
{
r->refcount++;
return r;
}
return NULL;
}
/* A runner usually maintains two file descriptors to control the
backend engine. This function is used to set these file
descriptors. The function takes ownership of these file
descriptors. IN_FD will be used to read from engine and OUT_FD to
send data to the engine. */
void
runner_set_fds (runner_t runner, int in_fd, int out_fd)
{
if (check_already_spawned (runner, "runner_set_fds"))
return;
if (runner->in_fd != -1)
close (runner->in_fd);
if (runner->out_fd != -1)
close (runner->out_fd);
runner->in_fd = in_fd;
runner->out_fd = out_fd;
}
/* Set the PID of the backend engine. After this call the engine is
owned by the runner object. */
void
runner_set_pid (runner_t runner, pid_t pid)
{
if (check_already_spawned (runner, "runner_set_fds"))
return;
runner->pid = pid;
}
/* Register the engine handler fucntions HANDLER and HANDLER_CLEANUP
and its private HANDLER_DATA with RUNNER. */
void
runner_set_handler (runner_t runner,
engine_handler_fnc_t handler,
engine_handler_cleanup_fnc_t handler_cleanup,
void *handler_data)
{
if (check_already_spawned (runner, "runner_set_handler"))
return;
runner->handler = handler;
runner->handler_cleanup = handler_cleanup;
runner->handler_data = handler_data;
}
/* The thread spawned by runner_spawn. */
static void *
runner_thread (void *arg)
{
runner_t runner = arg;
gpg_error_t err = 0;
log_debug ("starting runner thread\n");
/* If a status_fp is available, the thread's main task is to read
from that stream and invoke the backend's handler function. This
is done on a line by line base and the line length is limited to
a reasonable value (about 1000 characters). Other work will
continue either due to an EOF of the stream or by demand of the
engine. */
if (runner->status_fp)
{
int c, cont_line;
unsigned int pos;
char buffer[1024];
estream_t fp = runner->status_fp;
pos = 0;
cont_line = 0;
while (!err && !runner->cancel_flag && (c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (opt.verbose)
log_info ("%s%s: %s\n",
runner->name, cont_line? "(cont)":"", buffer);
/* We handle only complete lines and ignore any stuff we
possibly had to truncate. That is - at least for the
encfs engine - not an issue because our changes to
the tool make sure that only relatively short prompt
lines are of interest. */
if (!cont_line && runner->handler)
err = runner->handler (runner->handler_data,
runner, buffer);
pos = 0;
cont_line = (c != '\n');
}
}
if (!err && runner->cancel_flag)
log_debug ("runner thread noticed cancel flag\n");
else
log_debug ("runner thread saw EOF\n");
if (pos)
{
buffer[pos] = 0;
if (opt.verbose)
log_info ("%s%s: %s\n",
runner->name, cont_line? "(cont)":"", buffer);
if (!cont_line && !err && runner->handler)
err = runner->handler (runner->handler_data,
runner, buffer);
}
if (!err && es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading from %s: %s\n",
runner->name, gpg_strerror (err));
}
runner->status_fp = NULL;
es_fclose (fp);
log_debug ("runner thread closed status fp\n");
}
/* Now wait for the process to finish. */
if (!err && runner->pid != (pid_t)(-1))
{
int exitcode;
log_debug ("runner thread waiting ...\n");
err = gnupg_wait_process (runner->name, runner->pid, 1, &exitcode);
gnupg_release_process (runner->pid);
runner->pid = (pid_t)(-1);
if (err)
log_error ("running '%s' failed (exitcode=%d): %s\n",
runner->name, exitcode, gpg_strerror (err));
log_debug ("runner thread waiting finished\n");
}
/* Get rid of the runner object (note: it is refcounted). */
log_debug ("runner thread releasing runner ...\n");
{
runner_t r, rprev;
for (r = running_threads, rprev = NULL; r; rprev = r, r = r->next_running)
if (r == runner)
{
if (!rprev)
running_threads = r->next_running;
else
rprev->next_running = r->next_running;
r->next_running = NULL;
break;
}
}
runner_release (runner);
log_debug ("runner thread runner released\n");
return NULL;
}
/* Spawn a new thread to let RUNNER work as a coprocess. */
gpg_error_t
runner_spawn (runner_t runner)
{
gpg_error_t err;
npth_attr_t tattr;
npth_t thread;
int ret;
if (check_already_spawned (runner, "runner_spawn"))
return gpg_error (GPG_ERR_BUG);
/* In case we have an input fd, open it as an estream so that the
Pth scheduling will work. The stdio functions don't work with
Pth because they don't call the pth counterparts of read and
write unless linker tricks are used. */
if (runner->in_fd != -1)
{
estream_t fp;
fp = es_fdopen (runner->in_fd, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't fdopen pipe for reading: %s\n", gpg_strerror (err));
return err;
}
runner->status_fp = fp;
runner->in_fd = -1; /* Now owned by status_fp. */
}
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ret = npth_create (&thread, &tattr, runner_thread, runner);
if (ret)
{
err = gpg_error_from_errno (ret);
log_error ("error spawning runner thread: %s\n", gpg_strerror (err));
return err;
}
npth_setname_np (thread, runner->name);
/* The scheduler has not yet kicked in, thus we can safely set the
spawned flag and the tid. */
runner->spawned = 1;
runner->thread = thread;
runner->next_running = running_threads;
running_threads = runner;
npth_attr_destroy (&tattr);
/* The runner thread is now runnable. */
return 0;
}
/* Cancel a running thread. */
void
runner_cancel (runner_t runner)
{
/* Warning: runner_cancel_all has knowledge of this code. */
if (runner->spawned)
{
runner->canceled = 1; /* Mark that we canceled this one already. */
/* FIXME: This does only work if the thread emits status lines. We
need to change the thread to wait on an event. */
runner->cancel_flag = 1;
/* For now we use the brutal way and kill the process. */
gnupg_kill_process (runner->pid);
}
}
/* Cancel all runner threads. */
void
runner_cancel_all (void)
{
runner_t r;
do
{
for (r = running_threads; r; r = r->next_running)
if (r->spawned && !r->canceled)
{
runner_cancel (r);
break;
}
}
while (r);
}
/* Send a line of data down to the engine. This line may not contain
a binary Nul or a LF character. This function is used by the
engine's handler. */
gpg_error_t
runner_send_line (runner_t runner, const void *data, size_t datalen)
{
gpg_error_t err = 0;
if (!runner->spawned)
{
log_error ("BUG: runner for %s not spawned\n", runner->name);
err = gpg_error (GPG_ERR_INTERNAL);
}
else if (runner->out_fd == -1)
{
log_error ("no output file descriptor for runner %s\n", runner->name);
err = gpg_error (GPG_ERR_EBADF);
}
else if (data && datalen)
{
if (memchr (data, '\n', datalen))
{
log_error ("LF detected in response data\n");
err = gpg_error (GPG_ERR_BUG);
}
else if (memchr (data, 0, datalen))
{
log_error ("Nul detected in response data\n");
err = gpg_error (GPG_ERR_BUG);
}
else if (writen (runner->out_fd, data, datalen))
err = gpg_error_from_syserror ();
}
if (!err)
if (writen (runner->out_fd, "\n", 1))
err = gpg_error_from_syserror ();
return err;
}
diff --git a/g13/server.c b/g13/server.c
index e3cb313a2..bbe42d4f6 100644
--- a/g13/server.c
+++ b/g13/server.c
@@ -1,798 +1,798 @@
/* server.c - The G13 Assuan server
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "g13.h"
#include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
#include "server.h"
#include "create.h"
#include "mount.h"
#include "suspend.h"
#include "../common/server-help.h"
#include "../common/call-gpg.h"
/* The filepointer for status message used in non-server mode */
static FILE *statusfp;
/* Local data for this server module. A pointer to this is stored in
the CTRL object of each connection. */
struct server_local_s
{
/* The Assuan context we are working on. */
assuan_context_t assuan_ctx;
char *containername; /* Malloced active containername. */
};
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/*
Helper functions.
*/
/* Set an error and a description. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* The handler for Assuan OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
(void)ctrl;
if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (opt.session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (opt.session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
err = session_env_setenv (opt.session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
err = session_env_setenv (opt.session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
xfree (opt.lc_ctype);
opt.lc_ctype = xtrystrdup (value);
if (!opt.lc_ctype)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "lc-messages"))
{
xfree (opt.lc_messages);
opt.lc_messages = xtrystrdup (value);
if (!opt.lc_messages)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
; /* We always allow it. */
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* The handler for an Assuan RESET command. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
FREE_STRLIST (ctrl->recipients);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_open[] =
"OPEN [<options>] <filename>\n"
"\n"
"Open the container FILENAME. FILENAME must be percent-plus\n"
"escaped. A quick check to see whether this is a suitable G13\n"
"container file is done. However no cryptographic check or any\n"
"other check is done. This command is used to define the target for\n"
"further commands. The filename is reset with the RESET command,\n"
"another OPEN or the CREATE command.";
static gpg_error_t
cmd_open (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
/* In any case reset the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Do a basic check. */
err = g13_is_container (ctrl, line);
if (err)
goto leave;
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
leave:
return leave_cmd (ctx, err);
}
static const char hlp_mount[] =
"MOUNT [options] [<mountpoint>]\n"
"\n"
"Mount the currently open file onto MOUNTPOINT. If MOUNTPOINT is not\n"
"given the system picks an unused mountpoint. MOUNTPOINT must\n"
"be percent-plus escaped to allow for arbitrary names.";
static gpg_error_t
cmd_mount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
if (!ctrl->server_local->containername)
{
err = gpg_error (GPG_ERR_MISSING_ACTION);
goto leave;
}
/* Perform the mount. */
err = g13_mount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_umount[] =
"UMOUNT [options] [<mountpoint>]\n"
"\n"
"Unmount the currently open file or the one opened at MOUNTPOINT.\n"
"MOUNTPOINT must be percent-plus escaped. On success the mountpoint\n"
"is returned via a \"MOUNTPOINT\" status line.";
static gpg_error_t
cmd_umount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Perform the unmount. */
err = g13_umount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_suspend[] =
"SUSPEND\n"
"\n"
"Suspend the currently set device.";
static gpg_error_t
cmd_suspend (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_suspend_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_resume[] =
"RESUME\n"
"\n"
"Resume the currently set device.";
static gpg_error_t
cmd_resume (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_resume_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_recipient[] =
"RECIPIENT <userID>\n"
"\n"
"Add USERID to the list of recipients to be used for the next CREATE\n"
"command. All recipient commands are cumulative until a RESET or an\n"
"successful create command.";
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
line = skip_options (line);
if (!add_to_strlist_try (&ctrl->recipients, line))
err = gpg_error_from_syserror ();
return leave_cmd (ctx, err);
}
static const char hlp_signer[] =
"SIGNER <userID>\n"
"\n"
"Not yet implemented.";
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
(void)ctrl;
(void)line;
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return leave_cmd (ctx, err);
}
static const char hlp_create[] =
"CREATE [options] <filename>\n"
"\n"
"Create a new container. On success the OPEN command is \n"
"implictly done for the new container.";
static gpg_error_t
cmd_create (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *pend;
size_t len;
/* First we close the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Create container. */
err = g13_create_container (ctrl, line);
if (!err)
{
FREE_STRLIST (ctrl->recipients);
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" cmd_has_option CMD OPT\n"
" - Return OK if the command CMD implements the option OPT.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
if (!strcmp (line, "version"))
{
const char *s = PACKAGE_VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
err = gpg_error (GPG_ERR_GENERAL);
}
}
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
/* Return true if the command CMD implements the option CMDOPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
(void)cmd;
(void)cmdopt;
return 0;
}
/* Tell the Assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "OPEN", cmd_open, hlp_open },
{ "MOUNT", cmd_mount, hlp_mount},
{ "UMOUNT", cmd_umount, hlp_umount },
{ "SUSPEND", cmd_suspend, hlp_suspend },
{ "RESUME", cmd_resume, hlp_resume },
{ "RECIPIENT", cmd_recipient, hlp_recipient },
{ "SIGNER", cmd_signer, hlp_signer },
{ "CREATE", cmd_create, hlp_create },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETINFO", cmd_getinfo,hlp_getinfo },
{ NULL }
};
gpg_error_t err;
int i;
for (i=0; table[i].name; i++)
{
err = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (err)
return err;
}
return 0;
}
/* Startup the server. DEFAULT_RECPLIST is the list of recipients as
set from the command line or config file. We only require those
marked as encrypt-to. */
gpg_error_t
g13_server (ctrl_t ctrl)
{
gpg_error_t err;
assuan_fd_t filedes[2];
assuan_context_t ctx = NULL;
static const char hello[] = ("GNU Privacy Guard's G13 server "
PACKAGE_VERSION " ready");
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FIELDES in this case. */
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
err = assuan_new (&ctx);
if (err)
{
log_error ("failed to allocate an Assuan context: %s\n",
gpg_strerror (err));
goto leave;
}
err = assuan_init_pipe_server (ctx, filedes);
if (err)
{
log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
goto leave;
}
err = register_commands (ctx);
if (err)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror (err));
goto leave;
}
assuan_set_pointer (ctx, ctrl);
if (opt.verbose || opt.debug)
{
char *tmp;
tmp = xtryasprintf ("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename,
hello);
if (tmp)
{
assuan_set_hello_line (ctx, tmp);
xfree (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->assuan_ctx = ctx;
while ( !(err = assuan_accept (ctx)) )
{
err = assuan_process (ctx);
if (err)
log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
}
if (err == -1)
err = 0;
else
log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
leave:
reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
if (ctrl->server_local)
{
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
assuan_release (ctx);
return err;
}
/* Send a status line with status ID NO. The arguments are a list of
strings terminated by a NULL argument. */
gpg_error_t
g13_status (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (ctrl->no_server && ctrl->status_fd == -1)
; /* No status wanted. */
else if (ctrl->no_server)
{
if (!statusfp)
{
if (ctrl->status_fd == 1)
statusfp = stdout;
else if (ctrl->status_fd == 2)
statusfp = stderr;
else
statusfp = fdopen (ctrl->status_fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
ctrl->status_fd, strerror(errno));
}
}
fputs ("[GNUPG:] ", statusfp);
fputs (get_status_string (no), statusfp);
while ( (text = va_arg (arg_ptr, const char*) ))
{
putc ( ' ', statusfp );
for (; *text; text++)
{
if (*text == '\n')
fputs ( "\\n", statusfp );
else if (*text == '\r')
fputs ( "\\r", statusfp );
else
putc ( *(const byte *)text, statusfp );
}
}
putc ('\n', statusfp);
fflush (statusfp);
}
else
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
char buf[950], *p;
size_t n;
p = buf;
n = 0;
while ( (text = va_arg (arg_ptr, const char *)) )
{
if (n)
{
*p++ = ' ';
n++;
}
for ( ; *text && n < DIM (buf)-2; n++)
*p++ = *text++;
}
*p = 0;
err = assuan_write_status (ctx, get_status_string (no), buf);
}
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about Pinentry events. Returns an gpg
error code. */
gpg_error_t
g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
if (!ctrl || !ctrl->server_local)
return 0;
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/*
* Decrypt the keyblob (ENCKEYBLOB,ENCKEYBLOBLEN) and store the result
* at (R_KEYBLOB, R_KEYBLOBLEN). Returns 0 on success or an error
* code. On error R_KEYBLOB is set to NULL.
*
* This actually does not belong here but for that simple wrapper it
* does not make sense to add another source file. Note that we do
* not want to have this in keyblob.c, because that code is also used
* by the syshelp.
*/
gpg_error_t
g13_keyblob_decrypt (ctrl_t ctrl, const void *enckeyblob, size_t enckeybloblen,
void **r_keyblob, size_t *r_keybloblen)
{
gpg_error_t err;
/* FIXME: For now we only implement OpenPGP. */
err = gpg_decrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments,
enckeyblob, enckeybloblen,
r_keyblob, r_keybloblen);
return err;
}
diff --git a/g13/sh-blockdev.c b/g13/sh-blockdev.c
index 6c12dde04..a477a6716 100644
--- a/g13/sh-blockdev.c
+++ b/g13/sh-blockdev.c
@@ -1,152 +1,152 @@
/* sh-blockdev.c - Block device functions for g13-syshelp
* Copyright (C) 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include "g13-syshelp.h"
#include <assuan.h>
-#include "i18n.h"
-#include "exectool.h"
+#include "../common/i18n.h"
+#include "../common/exectool.h"
#include "keyblob.h"
#ifndef HAVE_STRTOULL
# error building this tool requires strtoull(3)
#endif
#ifndef ULLONG_MAX
# error ULLONG_MAX missing
#endif
/* Return the size measured in the number of 512 byte sectors for the
block device NAME. */
gpg_error_t
sh_blockdev_getsz (const char *name, unsigned long long *r_nblocks)
{
gpg_error_t err;
const char *argv[3];
char *result;
*r_nblocks = 0;
argv[0] = "--getsz";
argv[1] = name;
argv[2] = NULL;
err = gnupg_exec_tool ("/sbin/blockdev", argv, NULL, &result, NULL);
if (!err)
{
gpg_err_set_errno (0);
*r_nblocks = strtoull (result, NULL, 10);
if (*r_nblocks == ULLONG_MAX && errno)
{
err = gpg_error_from_syserror ();
*r_nblocks = 0;
}
xfree (result);
}
return err;
}
/* Return 0 if the device NAME looks like an empty partition. */
gpg_error_t
sh_is_empty_partition (const char *name)
{
gpg_error_t err;
const char *argv[6];
char *buffer;
estream_t fp;
char *p;
size_t nread;
argv[0] = "-o";
argv[1] = "value";
argv[2] = "-s";
argv[3] = "UUID";
argv[4] = name;
argv[5] = NULL;
err = gnupg_exec_tool ("/sbin/blkid", argv, NULL, &buffer, NULL);
if (err)
return gpg_error (GPG_ERR_FALSE);
if (*buffer)
{
/* There seems to be an UUID - thus we have a file system. */
xfree (buffer);
return gpg_error (GPG_ERR_FALSE);
}
xfree (buffer);
argv[0] = "-o";
argv[1] = "value";
argv[2] = "-s";
argv[3] = "PARTUUID";
argv[4] = name;
argv[5] = NULL;
err = gnupg_exec_tool ("/sbin/blkid", argv, NULL, &buffer, NULL);
if (err)
return gpg_error (GPG_ERR_FALSE);
if (!*buffer)
{
/* If there is no PARTUUID we assume that name has already a
mapped partition. */
xfree (buffer);
return gpg_error (GPG_ERR_FALSE);
}
xfree (buffer);
/* As a safeguard we require that the first 32k of a partition are
all zero before we assume the partition is empty. */
buffer = xtrymalloc (32 * 1024);
if (!buffer)
return gpg_error_from_syserror ();
fp = es_fopen (name, "rb,samethread");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", name, gpg_strerror (err));
xfree (buffer);
return gpg_error (GPG_ERR_FALSE);
}
if (es_read (fp, buffer, 32 * 1024, &nread))
err = gpg_error_from_syserror ();
else if (nread != 32 *1024)
err = gpg_error (GPG_ERR_TOO_SHORT);
else
err = 0;
es_fclose (fp);
if (err)
{
log_error ("error reading the first 32 KiB from '%s': %s\n",
name, gpg_strerror (err));
xfree (buffer);
return err;
}
for (p=buffer; nread && !*p; nread--, p++)
;
xfree (buffer);
if (nread)
return gpg_error (GPG_ERR_FALSE); /* No all zeroes. */
return 0;
}
diff --git a/g13/sh-cmd.c b/g13/sh-cmd.c
index 523ec56f6..b57369d9c 100644
--- a/g13/sh-cmd.c
+++ b/g13/sh-cmd.c
@@ -1,937 +1,937 @@
/* sh-cmd.c - The Assuan server for g13-syshelp
* Copyright (C) 2015 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "g13-syshelp.h"
#include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keyblob.h"
/* Local data for this server module. A pointer to this is stored in
the CTRL object of each connection. */
struct server_local_s
{
/* The Assuan context we are working on. */
assuan_context_t assuan_ctx;
/* The malloced name of the device. */
char *devicename;
/* A stream open for read of the device set by the DEVICE command or
NULL if no DEVICE command has been used. */
estream_t devicefp;
};
/* Local prototypes. */
/*
Helper functions.
*/
/* Set an error and a description. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
#define set_error_fail_cmd() set_error (GPG_ERR_NOT_INITIALIZED, \
"not called via userv or unknown user")
/* Skip over options. Blanks after the options are also removed. */
static char *
skip_options (const char *line)
{
while (spacep (line))
line++;
while ( *line == '-' && line[1] == '-' )
{
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
}
return (char*)line;
}
/* Check whether the option NAME appears in LINE. */
/* static int */
/* has_option (const char *line, const char *name) */
/* { */
/* const char *s; */
/* int n = strlen (name); */
/* s = strstr (line, name); */
/* if (s && s >= skip_options (line)) */
/* return 0; */
/* return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); */
/* } */
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* The handler for Assuan OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
(void)ctrl;
(void)key;
(void)value;
if (ctrl->fail_all_cmds)
err = set_error_fail_cmd ();
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* The handler for an Assuan RESET command. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
xfree (ctrl->server_local->devicename);
ctrl->server_local->devicename = NULL;
es_fclose (ctrl->server_local->devicefp);
ctrl->server_local->devicefp = NULL;
ctrl->devti = NULL;
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_finddevice[] =
"FINDDEVICE <name>\n"
"\n"
"Find the device matching NAME. NAME be any identifier from\n"
"g13tab permissible for the user. The corresponding block\n"
"device is returned using a status line.";
static gpg_error_t
cmd_finddevice (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
tab_item_t ti;
const char *s;
const char *name;
name = skip_options (line);
/* Are we allowed to use the given device? We check several names:
* 1. The full block device
* 2. The label
* 3. The final part of the block device if NAME does not have a slash.
* 4. The mountpoint
*/
for (ti=ctrl->client.tab; ti; ti = ti->next)
if (!strcmp (name, ti->blockdev))
break;
if (!ti)
{
for (ti=ctrl->client.tab; ti; ti = ti->next)
if (ti->label && !strcmp (name, ti->label))
break;
}
if (!ti && !strchr (name, '/'))
{
for (ti=ctrl->client.tab; ti; ti = ti->next)
{
s = strrchr (ti->blockdev, '/');
if (s && s[1] && !strcmp (name, s+1))
break;
}
}
if (!ti)
{
for (ti=ctrl->client.tab; ti; ti = ti->next)
if (ti->mountpoint && !strcmp (name, ti->mountpoint))
break;
}
if (!ti)
{
err = set_error (GPG_ERR_NOT_FOUND, "device not configured for user");
goto leave;
}
/* Check whether we have permissions to open the device. */
{
estream_t fp = es_fopen (ti->blockdev, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n",
ti->blockdev, gpg_strerror (err));
goto leave;
}
es_fclose (fp);
}
err = g13_status (ctrl, STATUS_BLOCKDEV, ti->blockdev, NULL);
if (err)
return err;
leave:
return leave_cmd (ctx, err);
}
static const char hlp_device[] =
"DEVICE <name>\n"
"\n"
"Set the device used by further commands.\n"
"A device name or a PARTUUID string may be used.\n"
"Access to that device (by the g13 system) is locked\n"
"until a new DEVICE command or end of this process\n";
static gpg_error_t
cmd_device (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
tab_item_t ti;
estream_t fp = NULL;
line = skip_options (line);
/* # warning hardwired to /dev/sdb1 ! */
/* if (strcmp (line, "/dev/sdb1")) */
/* { */
/* err = gpg_error (GPG_ERR_ENOENT); */
/* goto leave; */
/* } */
/* Always close an open device stream of this session. */
xfree (ctrl->server_local->devicename);
ctrl->server_local->devicename = NULL;
es_fclose (ctrl->server_local->devicefp);
ctrl->server_local->devicefp = NULL;
/* Are we allowed to use the given device? */
for (ti=ctrl->client.tab; ti; ti = ti->next)
if (!strcmp (line, ti->blockdev))
break;
if (!ti)
{
err = set_error (GPG_ERR_EACCES, "device not configured for user");
goto leave;
}
ctrl->server_local->devicename = xtrystrdup (line);
if (!ctrl->server_local->devicename)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Check whether we have permissions to open the device and keep an
FD open. */
fp = es_fopen (ctrl->server_local->devicename, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n",
ctrl->server_local->devicename, gpg_strerror (err));
goto leave;
}
es_fclose (ctrl->server_local->devicefp);
ctrl->server_local->devicefp = fp;
fp = NULL;
ctrl->devti = ti;
/* Fixme: Take some kind of lock. */
leave:
es_fclose (fp);
if (err)
{
xfree (ctrl->server_local->devicename);
ctrl->server_local->devicename = NULL;
ctrl->devti = NULL;
}
return leave_cmd (ctx, err);
}
static const char hlp_create[] =
"CREATE <type>\n"
"\n"
"Create a new encrypted partition on the current device.\n"
"<type> must be \"dm-crypt\" for now.";
static gpg_error_t
cmd_create (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
estream_t fp = NULL;
line = skip_options (line);
if (strcmp (line, "dm-crypt"))
{
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
goto leave;
}
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_is_empty_partition (ctrl->server_local->devicename);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_FALSE)
err = gpg_error (GPG_ERR_CONFLICT);
err = assuan_set_error (ctx, err, "Partition is not empty");
goto leave;
}
/* We need a writeable stream to create the container. */
fp = es_fopen (ctrl->server_local->devicename, "r+b");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n",
ctrl->server_local->devicename, gpg_strerror (err));
goto leave;
}
if (es_setvbuf (fp, NULL, _IONBF, 0))
{
err = gpg_error_from_syserror ();
log_error ("error setting '%s' to _IONBF: %s\n",
ctrl->server_local->devicename, gpg_strerror (err));
goto leave;
}
err = sh_dmcrypt_create_container (ctrl,
ctrl->server_local->devicename,
fp);
if (es_fclose (fp))
{
gpg_error_t err2 = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
ctrl->server_local->devicename, gpg_strerror (err2));
if (!err)
err = err2;
}
fp = NULL;
leave:
es_fclose (fp);
return leave_cmd (ctx, err);
}
static const char hlp_getkeyblob[] =
"GETKEYBLOB\n"
"\n"
"Return the encrypted keyblob of the current device.";
static gpg_error_t
cmd_getkeyblob (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
void *enckeyblob = NULL;
size_t enckeybloblen;
line = skip_options (line);
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_is_empty_partition (ctrl->server_local->devicename);
if (!err)
{
err = gpg_error (GPG_ERR_ENODEV);
assuan_set_error (ctx, err, "Partition is empty");
goto leave;
}
err = 0;
err = g13_keyblob_read (ctrl->server_local->devicename,
&enckeyblob, &enckeybloblen);
if (err)
goto leave;
err = assuan_send_data (ctx, enckeyblob, enckeybloblen);
if (!err)
err = assuan_send_data (ctx, NULL, 0); /* Flush */
leave:
xfree (enckeyblob);
return leave_cmd (ctx, err);
}
static const char hlp_mount[] =
"MOUNT <type>\n"
"\n"
"Mount an encrypted partition on the current device.\n"
"<type> must be \"dm-crypt\" for now.";
static gpg_error_t
cmd_mount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *keyblob = NULL;
size_t keybloblen;
tupledesc_t tuples = NULL;
line = skip_options (line);
if (strcmp (line, "dm-crypt"))
{
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
goto leave;
}
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_is_empty_partition (ctrl->server_local->devicename);
if (!err)
{
err = gpg_error (GPG_ERR_ENODEV);
assuan_set_error (ctx, err, "Partition is empty");
goto leave;
}
err = 0;
/* We expect that the client already decrypted the keyblob.
* Eventually we should move reading of the keyblob to here and ask
* the client to decrypt it. */
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYBLOB",
&keyblob, &keybloblen, 4 * 1024);
assuan_end_confidential (ctx);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
err = create_tupledesc (&tuples, keyblob, keybloblen);
if (!err)
keyblob = NULL;
else
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
log_error ("unknown keyblob version received\n");
goto leave;
}
err = sh_dmcrypt_mount_container (ctrl,
ctrl->server_local->devicename,
tuples);
leave:
destroy_tupledesc (tuples);
return leave_cmd (ctx, err);
}
static const char hlp_umount[] =
"UMOUNT <type>\n"
"\n"
"Unmount an encrypted partition and wipe the key.\n"
"<type> must be \"dm-crypt\" for now.";
static gpg_error_t
cmd_umount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
line = skip_options (line);
if (strcmp (line, "dm-crypt"))
{
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
goto leave;
}
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_dmcrypt_umount_container (ctrl, ctrl->server_local->devicename);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_suspend[] =
"SUSPEND <type>\n"
"\n"
"Suspend an encrypted partition and wipe the key.\n"
"<type> must be \"dm-crypt\" for now.";
static gpg_error_t
cmd_suspend (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
line = skip_options (line);
if (strcmp (line, "dm-crypt"))
{
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
goto leave;
}
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_is_empty_partition (ctrl->server_local->devicename);
if (!err)
{
err = gpg_error (GPG_ERR_ENODEV);
assuan_set_error (ctx, err, "Partition is empty");
goto leave;
}
err = 0;
err = sh_dmcrypt_suspend_container (ctrl, ctrl->server_local->devicename);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_resume[] =
"RESUME <type>\n"
"\n"
"Resume an encrypted partition and set the key.\n"
"<type> must be \"dm-crypt\" for now.";
static gpg_error_t
cmd_resume (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *keyblob = NULL;
size_t keybloblen;
tupledesc_t tuples = NULL;
line = skip_options (line);
if (strcmp (line, "dm-crypt"))
{
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
goto leave;
}
if (!ctrl->server_local->devicename
|| !ctrl->server_local->devicefp
|| !ctrl->devti)
{
err = set_error (GPG_ERR_ENOENT, "No device has been set");
goto leave;
}
err = sh_is_empty_partition (ctrl->server_local->devicename);
if (!err)
{
err = gpg_error (GPG_ERR_ENODEV);
assuan_set_error (ctx, err, "Partition is empty");
goto leave;
}
err = 0;
/* We expect that the client already decrypted the keyblob.
* Eventually we should move reading of the keyblob to here and ask
* the client to decrypt it. */
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYBLOB",
&keyblob, &keybloblen, 4 * 1024);
assuan_end_confidential (ctx);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
err = create_tupledesc (&tuples, keyblob, keybloblen);
if (!err)
keyblob = NULL;
else
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
log_error ("unknown keyblob version received\n");
goto leave;
}
err = sh_dmcrypt_resume_container (ctrl,
ctrl->server_local->devicename,
tuples);
leave:
destroy_tupledesc (tuples);
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" showtab - Show the table for the user.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *buf;
if (!strcmp (line, "version"))
{
const char *s = PACKAGE_VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "getsz", 5))
{
unsigned long long nblocks;
err = sh_blockdev_getsz (line+6, &nblocks);
if (!err)
log_debug ("getsz=%llu\n", nblocks);
}
else if (!strcmp (line, "showtab"))
{
tab_item_t ti;
for (ti=ctrl->client.tab; !err && ti; ti = ti->next)
{
buf = es_bsprintf ("%s %s%s %s %s%s\n",
ctrl->client.uname,
*ti->blockdev=='/'? "":"partuuid=",
ti->blockdev,
ti->label? ti->label : "-",
ti->mountpoint? " ":"",
ti->mountpoint? ti->mountpoint:"");
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = assuan_send_data (ctx, buf, strlen (buf));
if (!err)
err = assuan_send_data (ctx, NULL, 0); /* Flush */
}
xfree (buf);
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
/* This command handler is used for all commands if this process has
not been started as expected. */
static gpg_error_t
fail_command (assuan_context_t ctx, char *line)
{
gpg_error_t err;
const char *name = assuan_get_command_name (ctx);
(void)line;
if (!name)
name = "?";
err = set_error_fail_cmd ();
log_error ("command '%s' failed: %s\n", name, gpg_strerror (err));
return err;
}
/* Tell the Assuan library about our commands. */
static int
register_commands (assuan_context_t ctx, int fail_all)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "FINDDEVICE", cmd_finddevice, hlp_finddevice },
{ "DEVICE", cmd_device, hlp_device },
{ "CREATE", cmd_create, hlp_create },
{ "GETKEYBLOB", cmd_getkeyblob, hlp_getkeyblob },
{ "MOUNT", cmd_mount, hlp_mount },
{ "UMOUNT", cmd_umount, hlp_umount },
{ "SUSPEND", cmd_suspend,hlp_suspend},
{ "RESUME", cmd_resume, hlp_resume },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ NULL }
};
gpg_error_t err;
int i;
for (i=0; table[i].name; i++)
{
err = assuan_register_command (ctx, table[i].name,
fail_all ? fail_command : table[i].handler,
table[i].help);
if (err)
return err;
}
return 0;
}
/* Startup the server. */
gpg_error_t
syshelp_server (ctrl_t ctrl)
{
gpg_error_t err;
assuan_fd_t filedes[2];
assuan_context_t ctx = NULL;
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FILEDES in this case. */
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
err = assuan_new (&ctx);
if (err)
{
log_error ("failed to allocate an Assuan context: %s\n",
gpg_strerror (err));
goto leave;
}
err = assuan_init_pipe_server (ctx, filedes);
if (err)
{
log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
goto leave;
}
err = register_commands (ctx, 0 /*FIXME:ctrl->fail_all_cmds*/);
if (err)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror (err));
goto leave;
}
assuan_set_pointer (ctx, ctrl);
{
char *tmp = xtryasprintf ("G13-syshelp %s ready to serve requests "
"from %lu(%s)",
PACKAGE_VERSION,
(unsigned long)ctrl->client.uid,
ctrl->client.uname);
if (tmp)
{
assuan_set_hello_line (ctx, tmp);
xfree (tmp);
}
}
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->assuan_ctx = ctx;
while ( !(err = assuan_accept (ctx)) )
{
err = assuan_process (ctx);
if (err)
log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
}
if (err == -1)
err = 0;
else
log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
leave:
reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
if (ctrl->server_local)
{
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
assuan_release (ctx);
return err;
}
gpg_error_t
sh_encrypt_keyblob (ctrl_t ctrl, const void *keyblob, size_t keybloblen,
char **r_enckeyblob, size_t *r_enckeybloblen)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
gpg_error_t err;
unsigned char *enckeyblob;
size_t enckeybloblen;
*r_enckeyblob = NULL;
/* Send the plaintext. */
err = g13_status (ctrl, STATUS_PLAINTEXT_FOLLOWS, NULL);
if (err)
return err;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, keyblob, keybloblen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
assuan_end_confidential (ctx);
if (!err)
err = assuan_write_line (ctx, "END");
if (err)
{
log_error (_("error sending data: %s\n"), gpg_strerror (err));
return err;
}
/* Inquire the ciphertext. */
err = assuan_inquire (ctx, "ENCKEYBLOB",
&enckeyblob, &enckeybloblen, 16 * 1024);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
return err;
}
*r_enckeyblob = enckeyblob;
*r_enckeybloblen = enckeybloblen;
return 0;
}
/* Send a status line with status ID NO. The arguments are a list of
strings terminated by a NULL argument. */
gpg_error_t
g13_status (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (1)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
char buf[950], *p;
size_t n;
p = buf;
n = 0;
while ( (text = va_arg (arg_ptr, const char *)) )
{
if (n)
{
*p++ = ' ';
n++;
}
for ( ; *text && n < DIM (buf)-2; n++)
*p++ = *text++;
}
*p = 0;
err = assuan_write_status (ctx, get_status_string (no), buf);
}
va_end (arg_ptr);
return err;
}
diff --git a/g13/sh-dmcrypt.c b/g13/sh-dmcrypt.c
index bbeab65a2..f7ec79758 100644
--- a/g13/sh-dmcrypt.c
+++ b/g13/sh-dmcrypt.c
@@ -1,1036 +1,1036 @@
/* sh-dmcrypt.c - The DM-Crypt part for g13-syshelp
* Copyright (C) 2015, 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#ifdef HAVE_STAT
# include <sys/stat.h>
#endif
#include <unistd.h>
#include "g13-syshelp.h"
#include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "g13tuple.h"
-#include "exectool.h"
+#include "../common/exectool.h"
#include "keyblob.h"
/* The standard disk block size (logical). */
#define SECTOR_SIZE 512
/* The physical block size used by modern devices. */
#define PHY_SECTOR_SIZE (SECTOR_SIZE*8) /* 4 KiB */
/* The length of the crypto setup area in sectors. 16 KiB is a nice
multiple of a modern block size and should be sufficient for all
kind of extra public key encryption packets. */
#define SETUP_AREA_SECTORS 32 /* 16 KiB */
/* The number of header block copies stored at the begin and end of
the device. */
#define HEADER_SETUP_AREA_COPIES 2
#define FOOTER_SETUP_AREA_COPIES 2
/* The length in blocks of the space we put at the start and at the
end of the device. This space is used to store N copies of the
setup area for the actual encrypted container in between. */
#define HEADER_SECTORS (SETUP_AREA_SECTORS * HEADER_SETUP_AREA_COPIES)
#define FOOTER_SECTORS (SETUP_AREA_SECTORS * FOOTER_SETUP_AREA_COPIES)
/* Minimim size of the encrypted space in blocks. This is more or
less an arbitrary value. */
#define MIN_ENCRYPTED_SPACE 32
/* Some consistency checks for the above constants. */
#if (PHY_SECTOR_SIZE % SECTOR_SIZE)
# error the physical secotor size should be a multiple of 512
#endif
#if ((SETUP_AREA_SECTORS*SECTOR_SIZE) % PHY_SECTOR_SIZE)
# error The setup area size should be a multiple of the phy. sector size.
#endif
/*
* Check whether the block device DEVNAME is used by device mapper.
* If EXPECT_BUSY is set no error message is printed if the device is
* busy. Returns: 0 if the device is good and not yet used by DM.
*/
static gpg_error_t
check_blockdev (const char *devname, int expect_busy)
{
gpg_error_t err;
struct stat sb;
unsigned int devmajor, devminor;
char *result = NULL;
char **lines = NULL;
char **fields = NULL;
int lno, count;
if (stat (devname, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error stating '%s': %s\n", devname, gpg_strerror (err));
return err;
}
if (!S_ISBLK (sb.st_mode))
{
err = gpg_error (GPG_ERR_ENOTBLK);
log_error ("can't use '%s': %s\n", devname, gpg_strerror (err));
return err;
}
devmajor = major (sb.st_rdev);
devminor = minor (sb.st_rdev);
{
const char *argv[2];
argv[0] = "deps";
argv[1] = NULL;
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running '%s' to search for '%s': %s\n",
"dmsetup deps", devname, gpg_strerror (err));
goto leave;
}
lines = strsplit (result, '\n', 0, NULL);
if (!lines)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (lines[0] && !strcmp (lines[0], "No devices found"))
;
else
{
for (lno=0; lines[lno]; lno++)
{
unsigned int xmajor, xminor;
if (!*lines[lno])
continue;
xfree (fields);
fields = strsplit (lines[lno], ':', 0, &count);
if (!fields)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (count < 3
|| sscanf (fields[2], " (%u,%u)", &xmajor, &xminor) != 2)
{
log_error ("error running '%s' to search for '%s': %s\n",
"dmsetup deps", devname, "unexpected output");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (xmajor == devmajor && xminor == devminor)
{
if (!expect_busy)
log_error ("device '%s' (%u:%u)"
" already in use by device mapper\n",
devname, devmajor, devminor);
err = gpg_error (GPG_ERR_EBUSY);
goto leave;
}
}
}
leave:
xfree (fields);
xfree (lines);
xfree (result);
return err;
}
/* Return a malloced buffer with the prefix of the setup area. This
is the data written right before the encrypted keyblob. Return NULL
on error and sets ERRNO. */
static void *
mk_setup_area_prefix (size_t *r_length)
{
unsigned char *packet;
size_t setuparealen;
packet = xtrymalloc (32);
if (!packet)
return NULL;
*r_length = 32;
setuparealen = SETUP_AREA_SECTORS * SECTOR_SIZE;
packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = 0;
packet[3] = 0;
packet[4] = 0;
packet[5] = 26;
memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */
packet[16] = 1; /* G13 packet format version. */
packet[17] = 0; /* Reserved. */
packet[18] = 0; /* Reserved. */
packet[19] = 1; /* OS Flag = Linux */
packet[20] = (setuparealen >> 24); /* Total length of header. */
packet[21] = (setuparealen >> 16);
packet[22] = (setuparealen >> 8);
packet[23] = (setuparealen);
packet[24] = HEADER_SETUP_AREA_COPIES;
packet[25] = FOOTER_SETUP_AREA_COPIES;
packet[26] = 0; /* Reserved. */
packet[27] = 0; /* Reserved. */
packet[28] = 0; /* Reserved. */
packet[29] = 0; /* Reserved. */
packet[30] = 0; /* Reserved. */
packet[31] = 0; /* Reserved. */
return packet;
}
/* Create a new g13 styloe DM-Crypt container on devoce DEVNAME. */
gpg_error_t
sh_dmcrypt_create_container (ctrl_t ctrl, const char *devname, estream_t devfp)
{
gpg_error_t err;
char *header_space;
size_t header_space_size, header_space_used;
size_t paddinglen;
char *targetname = NULL;
size_t nread;
char *p;
char hexkey[16*2+1];
char *table = NULL;
unsigned long long nblocks;
char *result = NULL;
unsigned char twobyte[2];
membuf_t keyblob;
void *keyblob_buf = NULL;
size_t keyblob_len;
size_t n;
const char *s;
unsigned char *packet;
int copy;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
header_space_size = SETUP_AREA_SECTORS * SECTOR_SIZE;
header_space = xtrymalloc (header_space_size);
if (!header_space)
return gpg_error_from_syserror ();
/* Start building the keyblob. */
init_membuf (&keyblob, 512);
append_tuple (&keyblob, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
n = CONTTYPE_DM_CRYPT;
twobyte[0] = (n >> 8);
twobyte[1] = n;
append_tuple (&keyblob, KEYBLOB_TAG_CONTTYPE, twobyte, 2);
{
gnupg_isotime_t tbuf;
gnupg_get_isotime (tbuf);
append_tuple (&keyblob, KEYBLOB_TAG_CREATED, tbuf, strlen (tbuf));
}
/* Rewind device stream. */
if (es_fseeko (devfp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to begin of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
/* Extra check that the device is empty. */
if (es_read (devfp, header_space, header_space_size, &nread))
err = gpg_error_from_syserror ();
else if (nread != header_space_size)
err = gpg_error (GPG_ERR_TOO_SHORT);
else
err = 0;
if (err)
{
log_error ("error reading header space of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
for (p=header_space; nread && !*p; nread--, p++)
;
if (nread)
{
log_error ("header space of '%s' already used - use %s to override\n",
devname, "--force");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Check that the device is not used by device mapper. */
err = check_blockdev (devname, 0);
if (err)
goto leave;
/* Compute the number of blocks. */
err = sh_blockdev_getsz (devname, &nblocks);
if (err)
{
log_error ("error getting size of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS)
{
log_error ("device '%s' is too small (min=%d blocks)\n",
devname,
HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS);
err = gpg_error (GPG_ERR_TOO_SHORT);
goto leave;
}
append_tuple_uint (&keyblob, KEYBLOB_TAG_CONT_NSEC, nblocks);
nblocks -= HEADER_SECTORS + FOOTER_SECTORS;
append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_NSEC, nblocks);
append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_OFF, HEADER_SECTORS);
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname = strconcat ("g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Create the key. */
{
char key[16];
gcry_randomize (key, sizeof key, GCRY_STRONG_RANDOM);
append_tuple (&keyblob, KEYBLOB_TAG_ENCKEY, key, sizeof key);
bin2hex (key, 16, hexkey);
wipememory (key, 16);
/* Add a 2*(4+16) byte filler to conceal the fact that we use
AES-128. If we ever want to switch to 256 bit we can resize
that filler to keep the keyblob at the same size. */
append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
}
/* Build dmcrypt table. */
s = "aes-cbc-essiv:sha256";
append_tuple (&keyblob, KEYBLOB_TAG_ALGOSTR, s, strlen (s));
table = es_bsprintf ("0 %llu crypt %s %s 0 %s %d",
nblocks, s, hexkey, devname, HEADER_SECTORS);
if (!table)
{
err = gpg_error_from_syserror ();
goto leave;
}
wipememory (hexkey, sizeof hexkey);
/* Add a copy of the setup area prefix to the keyblob. */
p = mk_setup_area_prefix (&n);
if (!p)
{
err = gpg_error_from_syserror ();
goto leave;
}
append_tuple (&keyblob, KEYBLOB_TAG_HDRCOPY, p, n);
assert (n < header_space_size);
memcpy (header_space, p, n);
header_space_used = n;
/* Turn the keyblob into a buffer and callback to encrypt it. */
keyblob_buf = get_membuf (&keyblob, &keyblob_len);
if (!keyblob_buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = sh_encrypt_keyblob (ctrl, keyblob_buf, keyblob_len, &p, &n);
if (err)
{
log_error ("encrypting the keyblob failed: %s\n", gpg_strerror (err));
goto leave;
}
log_debug ("plain setuparea=%p %zu bytes\n", keyblob_buf, keyblob_len);
wipememory (keyblob_buf, keyblob_len);
xfree (keyblob_buf);
keyblob_buf = NULL;
log_debug ("encry setuparea=%p %zu bytes\n", p, n);
if (n >= header_space_size || (header_space_used + n) >= header_space_size)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("setup area would overflow: %s\n", gpg_strerror (err));
goto leave;
}
memcpy (header_space + header_space_used, p, n);
header_space_used += n;
/* Write the padding. */
packet = header_space + header_space_used;
paddinglen = header_space_size - header_space_used;
if (paddinglen < 16)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("setup area too short for padding: %s\n", gpg_strerror (err));
goto leave;
}
packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = (paddinglen-6) >> 24;
packet[3] = (paddinglen-6) >> 16;
packet[4] = (paddinglen-6) >> 8;
packet[5] = (paddinglen-6);
packet += 6;
paddinglen -= 6;
header_space_used += 6;
for ( ;paddinglen >= 10;
paddinglen -= 10, packet += 10, header_space_used += 10)
memcpy (packet, "GnuPG/PAD", 10);
for ( ;paddinglen; paddinglen--, packet++, header_space_used++)
*packet = 0;
if (header_space_used != header_space_size)
BUG ();
/* Create the container. */
{
const char *argv[3];
argv[0] = "create";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup create %s\"\n", targetname);
log_debug (" with table='%s'\"\n", table);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL);
}
if (err)
{
log_error ("error running dmsetup for '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
/* Write the setup area. */
if (es_fseeko (devfp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to begin of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
for (copy = 0; copy < HEADER_SETUP_AREA_COPIES; copy++)
{
size_t nwritten;
if (es_write (devfp, header_space, header_space_size, &nwritten))
{
err = gpg_error_from_syserror ();
break;
}
else if (nwritten != header_space_size)
{
err = gpg_error (GPG_ERR_TOO_SHORT);
break;
}
}
if (err)
{
log_error ("error writing header space copy %d of '%s': %s\n",
copy, devname, gpg_strerror (err));
goto leave;
}
if (es_fseeko (devfp,
(- header_space_size * FOOTER_SETUP_AREA_COPIES), SEEK_END))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to end of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
for (copy = 0; copy < FOOTER_SETUP_AREA_COPIES; copy++)
{
size_t nwritten;
if (es_write (devfp, header_space, header_space_size, &nwritten))
{
err = gpg_error_from_syserror ();
break;
}
else if (nwritten != header_space_size)
{
err = gpg_error (GPG_ERR_TOO_SHORT);
break;
}
}
if (!err && es_fflush (devfp))
err = gpg_error_from_syserror ();
if (err)
{
log_error ("error writing footer space copy %d of '%s': %s\n",
copy, devname, gpg_strerror (err));
goto leave;
}
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
if (keyblob_buf)
{
wipememory (keyblob_buf, keyblob_len);
xfree (keyblob_buf);
}
xfree (get_membuf (&keyblob, NULL));
xfree (targetname);
xfree (result);
xfree (header_space);
return err;
}
/* Mount a DM-Crypt container on device DEVNAME taking keys and other
* meta data from KEYBLOB. */
gpg_error_t
sh_dmcrypt_mount_container (ctrl_t ctrl, const char *devname,
tupledesc_t keyblob)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char hexkey[16*2+1];
char *table = NULL;
unsigned long long nblocks, nblocks2;
char *result = NULL;
size_t n;
const char *s;
const char *algostr;
size_t algostrlen;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is not yet used by device mapper. */
err = check_blockdev (devname, 0);
if (err)
goto leave;
/* Compute the number of blocks and compare them to the value
provided as meta data. */
err = sh_blockdev_getsz (devname, &nblocks);
if (err)
{
log_error ("error getting size of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
err = find_tuple_uint (keyblob, KEYBLOB_TAG_CONT_NSEC, &nblocks2);
if (err)
{
log_error ("error getting size from keyblob: %s\n", gpg_strerror (err));
goto leave;
}
if (nblocks != nblocks2)
{
log_error ("inconsistent size of container: expected==%llu got=%llu\n",
nblocks2, nblocks);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS)
{
log_error ("device '%s' is too small (min=%d blocks)\n",
devname,
HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS);
err = gpg_error (GPG_ERR_TOO_SHORT);
goto leave;
}
nblocks -= HEADER_SECTORS + FOOTER_SECTORS;
err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_NSEC, &nblocks2);
if (err)
{
log_error ("error getting enc size from keyblob: %s\n",
gpg_strerror (err));
goto leave;
}
if (nblocks != nblocks2)
{
log_error ("inconsistent size of enc data: expected==%llu got=%llu\n",
nblocks2, nblocks);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Check that the offset is consistent. */
err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_OFF, &nblocks2);
if (err)
{
log_error ("error getting enc offset from keyblob: %s\n",
gpg_strerror (err));
goto leave;
}
if (nblocks2 != HEADER_SECTORS)
{
log_error ("inconsistent offset of enc data: expected==%llu got=%d\n",
nblocks2, HEADER_SECTORS);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Get the algorithm string. */
algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen);
if (!algostr || algostrlen > 100)
{
log_error ("algo string not found in keyblob or too long\n");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Get the key. */
s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n);
if (!s || n != 16)
{
if (!s)
log_error ("no key found in keyblob\n");
else
log_error ("unexpected size of key (%zu)\n", n);
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
bin2hex (s, 16, hexkey);
/* Build dmcrypt table. */
table = es_bsprintf ("0 %llu crypt %.*s %s 0 %s %d",
nblocks, (int)algostrlen, algostr,
hexkey, devname, HEADER_SECTORS);
wipememory (hexkey, sizeof hexkey);
if (!table)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Load the table. */
{
const char *argv[3];
argv[0] = "create";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup create %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL);
}
if (err)
{
log_error ("error running dmsetup for '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
/* Mount if a mountpoint has been given. */
if (ctrl->devti->mountpoint)
{
const char *argv[3];
argv[0] = targetname_abs;
argv[1] = ctrl->devti->mountpoint;
argv[2] = NULL;
log_debug ("now running \"mount %s %s\"\n",
targetname_abs, ctrl->devti->mountpoint);
err = gnupg_exec_tool ("/bin/mount", argv, NULL, &result, NULL);
if (err)
{
log_error ("error running mount: %s\n", gpg_strerror (err));
goto leave;
}
if (result && *result) /* (We should not see output to stdout). */
log_info ("WARNING: mount returned data on stdout! (%s)\n", result);
}
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
xfree (targetname_abs);
xfree (result);
return err;
}
/* Unmount a DM-Crypt container on device DEVNAME and wipe the keys. */
gpg_error_t
sh_dmcrypt_umount_container (ctrl_t ctrl, const char *devname)
{
gpg_error_t err;
char *targetname_abs = NULL;
char *result = NULL;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Fixme: Check that this is really a g13 partition. */
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Run the regular umount command. */
{
const char *argv[2];
argv[0] = targetname_abs;
argv[1] = NULL;
log_debug ("now running \"umount %s\"\n", targetname_abs);
err = gnupg_exec_tool ("/bin/umount", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running umount: %s\n", gpg_strerror (err));
if (1)
{
/* Try to show some info about processes using the partition. */
const char *argv[3];
argv[0] = "-mv";
argv[1] = targetname_abs;
argv[2] = NULL;
gnupg_exec_tool ("/bin/fuser", argv, NULL, &result, NULL);
}
goto leave;
}
if (result && *result) /* (We should not see output to stdout). */
log_info ("WARNING: umount returned data on stdout! (%s)\n", result);
xfree (result);
result = NULL;
/* Run the dmsetup remove command. */
{
const char *argv[3];
argv[0] = "remove";
argv[1] = targetname_abs;
argv[2] = NULL;
log_debug ("now running \"dmsetup remove %s\"\n", targetname_abs);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup remove %s\": %s\n",
targetname_abs, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
xfree (targetname_abs);
xfree (result);
return err;
}
/* Suspend a DM-Crypt container on device DEVNAME and wipe the keys. */
gpg_error_t
sh_dmcrypt_suspend_container (ctrl_t ctrl, const char *devname)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char *result = NULL;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Fixme: Check that this is really a g13 partition. */
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Send the suspend command. */
{
const char *argv[3];
argv[0] = "suspend";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup suspend %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup suspend %s\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
/* Send the wipe key command. */
{
const char *argv[5];
argv[0] = "message";
argv[1] = targetname;
argv[2] = "0";
argv[3] = "key wipe";
argv[4] = NULL;
log_debug ("now running \"dmsetup message %s 0 key wipe\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup message %s 0 key wipe\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
xfree (targetname_abs);
xfree (result);
return err;
}
/* Resume a DM-Crypt container on device DEVNAME taking keys and other
* meta data from KEYBLOB. */
gpg_error_t
sh_dmcrypt_resume_container (ctrl_t ctrl, const char *devname,
tupledesc_t keyblob)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char hexkey[8+16*2+1]; /* 8 is used to prepend "key set ". */
char *table = NULL;
char *result = NULL;
size_t n;
const char *s;
const char *algostr;
size_t algostrlen;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Get the algorithm string. */
algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen);
if (!algostr || algostrlen > 100)
{
log_error ("algo string not found in keyblob or too long\n");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Get the key. */
s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n);
if (!s || n != 16)
{
if (!s)
log_error ("no key found in keyblob\n");
else
log_error ("unexpected size of key (%zu)\n", n);
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
strcpy (hexkey, "key set ");
bin2hex (s, 16, hexkey+8);
/* Send the key */
{
const char *argv[4];
argv[0] = "message";
argv[1] = targetname;
argv[2] = "0";
argv[3] = NULL;
log_debug ("now running \"dmsetup message %s 0 [key set]\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, hexkey, &result, NULL);
}
wipememory (hexkey, sizeof hexkey);
if (err)
{
log_error ("error running \"dmsetup message %s 0 [key set]\": %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
/* Send the resume command. */
{
const char *argv[3];
argv[0] = "resume";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup resume %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup resume %s\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
xfree (targetname_abs);
xfree (result);
return err;
}
diff --git a/g13/suspend.c b/g13/suspend.c
index 7bdf738c8..0aa20f01c 100644
--- a/g13/suspend.c
+++ b/g13/suspend.c
@@ -1,144 +1,144 @@
/* suspend.c - Suspend/Resume a crypto container
* Copyright (C) 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "g13.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "suspend.h"
#include "keyblob.h"
#include "backend.h"
#include "g13tuple.h"
#include "server.h" /*(g13_keyblob_decrypt)*/
/* Suspend the container with name FILENAME. */
gpg_error_t
g13_suspend_container (ctrl_t ctrl, const char *filename)
{
gpg_error_t err;
int needs_syshelp;
/* A quick check to see whether the container exists. */
if (access (filename, R_OK))
return gpg_error_from_syserror ();
/* Decide whether we need to use the g13-syshelp because we can't
use lock files for them. This is most likely the case for device
files; thus we test for this. FIXME: The correct solution would
be to call g13-syshelp to match the file against the g13tab. */
needs_syshelp = !strncmp (filename, "/dev/", 5);
if (!needs_syshelp)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
err = be_suspend_container (ctrl, CONTTYPE_DM_CRYPT, filename);
return err;
}
/* Resume the container with name FILENAME. */
gpg_error_t
g13_resume_container (ctrl_t ctrl, const char *filename)
{
gpg_error_t err;
int needs_syshelp;
void *enckeyblob = NULL;
size_t enckeybloblen;
void *keyblob = NULL;
size_t keybloblen;
tupledesc_t tuples = NULL;
size_t n;
const unsigned char *value;
int conttype;
char *mountpoint_buffer = NULL;
/* A quick check to see whether the container exists. */
if (access (filename, R_OK))
return gpg_error_from_syserror ();
/* Decide whether we need to use the g13-syshelp because we can't
use lock files for them. This is most likely the case for device
files; thus we test for this. FIXME: The correct solution would
be to call g13-syshelp to match the file against the g13tab. */
needs_syshelp = !strncmp (filename, "/dev/", 5);
if (!needs_syshelp)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Read the encrypted keyblob. */
/* Fixme: Should we move this to syshelp for dm-crypt or do we
assume that the encrypted device is world readable? */
err = g13_keyblob_read (filename, &enckeyblob, &enckeybloblen);
if (err)
goto leave;
/* Decrypt that keyblob and store it in a tuple descriptor. */
err = g13_keyblob_decrypt (ctrl, enckeyblob, enckeybloblen,
&keyblob, &keybloblen);
if (err)
goto leave;
xfree (enckeyblob);
enckeyblob = NULL;
err = create_tupledesc (&tuples, keyblob, keybloblen);
if (!err)
keyblob = NULL;
else
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
log_error ("unknown keyblob version\n");
goto leave;
}
if (opt.verbose)
dump_tupledesc (tuples);
value = find_tuple (tuples, KEYBLOB_TAG_CONTTYPE, &n);
if (!value || n != 2)
conttype = 0;
else
conttype = (value[0] << 8 | value[1]);
if (!be_is_supported_conttype (conttype))
{
log_error ("content type %d is not supported\n", conttype);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = be_resume_container (ctrl, conttype, filename, tuples);
leave:
destroy_tupledesc (tuples);
xfree (keyblob);
xfree (enckeyblob);
xfree (mountpoint_buffer);
return err;
}
diff --git a/g13/t-g13tuple.c b/g13/t-g13tuple.c
index bbd98988e..2809d23f7 100644
--- a/g13/t-g13tuple.c
+++ b/g13/t-g13tuple.c
@@ -1,223 +1,223 @@
/* t-g13tuple.c - Module test for g13tuple.c
* Copyright (C) 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
-#include "util.h"
+#include "../common/util.h"
#include "keyblob.h"
#include "g13tuple.h"
#define PGM "t-g13tuple"
static int verbose;
static int debug;
static int errcount;
/* Test for the functions append_tuple_uint and find_tuple_unit. */
static void
test_tuple_uint (void)
{
static struct {
int tag;
int len;
char *data;
unsigned long long val;
gpg_err_code_t ec;
} tv[] = {
{ 1, 0, "", 0, GPG_ERR_ERANGE },
{ 2, 1, "\x00", 0, 0},
{ 3, 1, "\x7f", 127ull, 0},
{ 4, 1, "\x80", 0, GPG_ERR_ERANGE },
{ 5, 1, "\x81", 0, GPG_ERR_ERANGE },
{ 6, 2, "\x80\x01", 0, GPG_ERR_ERANGE },
{ 7, 2, "\x00\x80", 128ull, 0 },
{ 8, 1, "\x01", 1, 0 },
{ 9, 1, "\x40", 64, 0 },
{ 10, 2, "\x40\x00", 16384, 0 },
{ 11, 8, "\x7f\xff\xff\xff\xff\xff\xff\xff", 0x7fffffffffffffffull, 0 },
{ 12, 9, "\x00\xff\xff\xff\xff\xff\xff\xff\xff", 0xffffffffffffffffull, 0},
{ 13, 9, "\x01\xff\xff\xff\xff\xff\xff\xff\xff", 0, GPG_ERR_ERANGE }
};
int tidx;
gpg_error_t err;
membuf_t mb, mb2;
void *p;
const void *s;
size_t n;
tupledesc_t tuples;
tupledesc_t tuples2;
unsigned long long value;
int i;
init_membuf (&mb, 512);
init_membuf (&mb2, 512);
append_tuple (&mb, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
append_tuple (&mb2, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
for (tidx=0; tidx < DIM (tv); tidx++)
{
append_tuple (&mb, tv[tidx].tag, tv[tidx].data, tv[tidx].len);
if (!tv[tidx].ec)
append_tuple_uint (&mb2, tv[tidx].tag, tv[tidx].val);
}
p = get_membuf (&mb, &n);
if (!p)
{
err = gpg_error_from_syserror ();
fprintf (stderr, PGM ":%s: get_membuf failed: %s\n",
__func__, gpg_strerror (err));
exit (1);
}
err = create_tupledesc (&tuples, p, n);
if (err)
{
fprintf (stderr, PGM ":%s: create_tupledesc failed: %s\n",
__func__, gpg_strerror (err));
exit (1);
}
p = get_membuf (&mb2, &n);
if (!p)
{
err = gpg_error_from_syserror ();
fprintf (stderr, PGM ":%s: get_membuf failed: %s\n",
__func__, gpg_strerror (err));
exit (1);
}
err = create_tupledesc (&tuples2, p, n);
if (err)
{
fprintf (stderr, PGM ":%s: create_tupledesc failed: %s\n",
__func__, gpg_strerror (err));
exit (1);
}
for (tidx=0; tidx < DIM (tv); tidx++)
{
err = find_tuple_uint (tuples, tv[tidx].tag, &value);
if (tv[tidx].ec != gpg_err_code (err))
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong error returned; "
"expected(%s) got(%s)\n",
__func__, tidx,
gpg_strerror (tv[tidx].ec), gpg_strerror (err));
errcount++;
}
else if (!err && tv[tidx].val != value)
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong value returned; "
"expected(%llx) got(%llx)\n",
__func__, tidx, tv[tidx].val, value);
errcount++;
}
err = find_tuple_uint (tuples2, tv[tidx].tag, &value);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
if (!tv[tidx].ec)
{
fprintf (stderr, PGM ":%s:tidx=%d: find_tuple failed: %s\n",
__func__, tidx, gpg_strerror (err));
errcount++;
}
}
else if (tv[tidx].ec != gpg_err_code (err))
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong error returned (2); "
"expected(%s) got(%s)\n",
__func__, tidx,
gpg_strerror (tv[tidx].ec), gpg_strerror (err));
errcount++;
}
else if (!err && tv[tidx].val != value)
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong value returned (2); "
"expected(%llx) got(%llx)\n",
__func__, tidx, tv[tidx].val, value);
errcount++;
}
s = find_tuple (tuples2, tv[tidx].tag, &n);
if (!s)
;
else if (tv[tidx].len != n)
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong string length returned; "
"expected(%d) got(%zu)\n",
__func__, tidx, tv[tidx].len, n);
errcount++;
}
else if (memcmp (tv[tidx].data, s, n))
{
fprintf (stderr, PGM ":%s:tidx=%d: wrong string returned:",
__func__, tidx);
for (i=0; i < n; i++)
fprintf (stderr, " %02x", ((unsigned char*)s)[i]);
fputc ('\n', stderr);
errcount++;
}
}
destroy_tupledesc (tuples);
destroy_tupledesc (tuples2);
}
int
main (int argc, char **argv)
{
int last_argc = -1;
gpgrt_init ();
if (argc)
{ argc--; argv++; }
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
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);
}
}
test_tuple_uint ();
return !!errcount;
}
diff --git a/kbx/Makefile.am b/kbx/Makefile.am
index fe7da1bb8..8fca24afb 100644
--- a/kbx/Makefile.am
+++ b/kbx/Makefile.am
@@ -1,67 +1,67 @@
# Keybox Makefile
# Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = mkerrors
-AM_CPPFLAGS = -I$(top_srcdir)/common
+AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
noinst_LIBRARIES = libkeybox.a libkeybox509.a
bin_PROGRAMS = kbxutil
if HAVE_W32CE_SYSTEM
extra_libs = $(LIBASSUAN_LIBS)
else
extra_libs =
endif
common_sources = \
keybox.h keybox-defs.h keybox-search-desc.h \
keybox-util.c \
keybox-init.c \
keybox-blob.c \
keybox-file.c \
keybox-search.c \
keybox-update.c \
keybox-openpgp.c \
keybox-dump.c
libkeybox_a_SOURCES = $(common_sources)
libkeybox509_a_SOURCES = $(common_sources)
libkeybox_a_CFLAGS = $(AM_CFLAGS)
libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
# We need W32SOCKLIBS because the init subsystem code in libcommon
# requires it - although we don't actually need it. It is easier
# to do it this way.
kbxutil_SOURCES = kbxutil.c $(common_sources)
kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
kbxutil_LDADD = ../common/libcommon.a \
$(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \
$(NETLIBS)
$(PROGRAMS) : ../common/libcommon.a
diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c
index dd8477c69..609429884 100644
--- a/kbx/kbxutil.c
+++ b/kbx/kbxutil.c
@@ -1,625 +1,625 @@
/* kbxutil.c - The Keybox utility
* Copyright (C) 2000, 2001, 2004, 2007, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <gpg-error.h>
#include "../common/logging.h"
#include "../common/argparse.h"
#include "../common/stringhelp.h"
#include "../common/utf8conv.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keybox-defs.h"
#include "../common/init.h"
#include <gcrypt.h>
enum cmd_and_opt_values {
aNull = 0,
oArmor = 'a',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oVerbose = 'v',
aNoSuchCmd = 500, /* force other values not to be a letter */
aFindByFpr,
aFindByKid,
aFindByUid,
aStats,
aImportOpenPGP,
aFindDups,
aCut,
oDebug,
oDebugAll,
oNoArmor,
oFrom,
oTo,
aTest
};
static ARGPARSE_OPTS opts[] = {
{ 300, NULL, 0, N_("@Commands:\n ") },
/* { aFindByFpr, "find-by-fpr", 0, "|FPR| find key using it's fingerprnt" }, */
/* { aFindByKid, "find-by-kid", 0, "|KID| find key using it's keyid" }, */
/* { aFindByUid, "find-by-uid", 0, "|NAME| find key by user name" }, */
{ aStats, "stats", 0, "show key statistics" },
{ aImportOpenPGP, "import-openpgp", 0, "import OpenPGP keyblocks"},
{ aFindDups, "find-dups", 0, "find duplicates" },
{ aCut, "cut", 0, "export records" },
{ 301, NULL, 0, N_("@\nOptions:\n ") },
{ oFrom, "from", 4, "|N|first record to export" },
{ oTo, "to", 4, "|N|last record to export" },
/* { oArmor, "armor", 0, N_("create ascii armored output")}, */
/* { oArmor, "armour", 0, "@" }, */
/* { oOutput, "output", 2, N_("use as output file")}, */
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("be somewhat more quiet") },
{ oDryRun, "dry-run", 0, N_("do not make any changes") },
{ oDebug, "debug" ,4|16, N_("set debugging flags")},
{ oDebugAll, "debug-all" ,0, N_("enable full debugging")},
{0} /* end of list */
};
void myexit (int rc);
int keybox_errors_seen = 0;
static const char *
my_strusage( int level )
{
const char *p;
switch( level ) {
case 11: p = "kbxutil (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p =
_("Usage: kbxutil [options] [files] (-h for help)");
break;
case 41: p =
_("Syntax: kbxutil [options] [files]\n"
"List, export, import Keybox data\n");
break;
default: p = NULL;
}
return p;
}
/* Used by gcry for logging */
static void
my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
{
(void)dummy;
/* Map the log levels. */
switch (level)
{
case GCRY_LOG_CONT: level = GPGRT_LOG_CONT; break;
case GCRY_LOG_INFO: level = GPGRT_LOG_INFO; break;
case GCRY_LOG_WARN: level = GPGRT_LOG_WARN; break;
case GCRY_LOG_ERROR:level = GPGRT_LOG_ERROR; break;
case GCRY_LOG_FATAL:level = GPGRT_LOG_FATAL; break;
case GCRY_LOG_BUG: level = GPGRT_LOG_BUG; break;
case GCRY_LOG_DEBUG:level = GPGRT_LOG_DEBUG; break;
default: level = GPGRT_LOG_ERROR; break;
}
log_logv (level, fmt, arg_ptr);
}
/* static void */
/* wrong_args( const char *text ) */
/* { */
/* log_error("usage: kbxutil %s\n", text); */
/* myexit ( 1 ); */
/* } */
#if 0
static int
hextobyte( const byte *s )
{
int c;
if( *s >= '0' && *s <= '9' )
c = 16 * (*s - '0');
else if( *s >= 'A' && *s <= 'F' )
c = 16 * (10 + *s - 'A');
else if( *s >= 'a' && *s <= 'f' )
c = 16 * (10 + *s - 'a');
else
return -1;
s++;
if( *s >= '0' && *s <= '9' )
c += *s - '0';
else if( *s >= 'A' && *s <= 'F' )
c += 10 + *s - 'A';
else if( *s >= 'a' && *s <= 'f' )
c += 10 + *s - 'a';
else
return -1;
return c;
}
#endif
#if 0
static char *
format_fingerprint ( const char *s )
{
int i, c;
byte fpr[20];
for (i=0; i < 20 && *s; ) {
if ( *s == ' ' || *s == '\t' ) {
s++;
continue;
}
c = hextobyte(s);
if (c == -1) {
return NULL;
}
fpr[i++] = c;
s += 2;
}
return gcry_xstrdup ( fpr );
}
#endif
#if 0
static int
format_keyid ( const char *s, u32 *kid )
{
char helpbuf[9];
switch ( strlen ( s ) ) {
case 8:
kid[0] = 0;
kid[1] = strtoul( s, NULL, 16 );
return 10;
case 16:
mem2str( helpbuf, s, 9 );
kid[0] = strtoul( helpbuf, NULL, 16 );
kid[1] = strtoul( s+8, NULL, 16 );
return 11;
}
return 0; /* error */
}
#endif
static char *
read_file (const char *fname, size_t *r_length)
{
FILE *fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xtrymalloc (bufsize);
else
buf = xtryrealloc (buf, bufsize);
if (!buf)
log_fatal ("can't allocate buffer: %s\n", strerror (errno));
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (fileno(fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
log_fatal ("can't allocate buffer: %s\n", strerror (errno));
if (fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
fclose (fp);
xfree (buf);
return NULL;
}
fclose (fp);
}
*r_length = buflen;
return buf;
}
static void
dump_fpr (const unsigned char *buffer, size_t len)
{
int i;
for (i=0; i < len; i++, buffer++)
{
if (len == 20)
{
if (i == 10)
putchar (' ');
printf (" %02X%02X", buffer[0], buffer[1]);
i++; buffer++;
}
else
{
if (i && !(i % 8))
putchar (' ');
printf (" %02X", buffer[0]);
}
}
}
static void
dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image)
{
printf ("pub %2d %02X%02X%02X%02X",
info->primary.algo,
info->primary.keyid[4], info->primary.keyid[5],
info->primary.keyid[6], info->primary.keyid[7] );
dump_fpr (info->primary.fpr, info->primary.fprlen);
putchar ('\n');
if (info->nsubkeys)
{
struct _keybox_openpgp_key_info *k;
k = &info->subkeys;
do
{
printf ("sub %2d %02X%02X%02X%02X",
k->algo,
k->keyid[4], k->keyid[5],
k->keyid[6], k->keyid[7] );
dump_fpr (k->fpr, k->fprlen);
putchar ('\n');
k = k->next;
}
while (k);
}
if (info->nuids)
{
struct _keybox_openpgp_uid_info *u;
u = &info->uids;
do
{
printf ("uid\t\t%.*s\n", (int)u->len, image + u->off);
u = u->next;
}
while (u);
}
}
static void
import_openpgp (const char *filename, int dryrun)
{
gpg_error_t err;
char *buffer;
size_t buflen, nparsed;
unsigned char *p;
struct _keybox_openpgp_info info;
KEYBOXBLOB blob;
buffer = read_file (filename, &buflen);
if (!buffer)
return;
p = (unsigned char *)buffer;
for (;;)
{
err = _keybox_parse_openpgp (p, buflen, &nparsed, &info);
assert (nparsed <= buflen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
break;
if (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM)
{
/* This is likely a v3 key packet with a non-RSA
algorithm. These are keys from very early versions
of GnuPG (pre-OpenPGP). */
}
else
{
fflush (stdout);
log_info ("%s: failed to parse OpenPGP keyblock: %s\n",
filename, gpg_strerror (err));
}
}
else
{
if (dryrun)
dump_openpgp_key (&info, p);
else
{
err = _keybox_create_openpgp_blob (&blob, &info, p, nparsed,
NULL, 0);
if (err)
{
fflush (stdout);
log_error ("%s: failed to create OpenPGP keyblock: %s\n",
filename, gpg_strerror (err));
}
else
{
err = _keybox_write_blob (blob, stdout);
_keybox_release_blob (blob);
if (err)
{
fflush (stdout);
log_error ("%s: failed to write OpenPGP keyblock: %s\n",
filename, gpg_strerror (err));
}
}
}
_keybox_destroy_openpgp_info (&info);
}
p += nparsed;
buflen -= nparsed;
}
xfree (buffer);
}
int
main( int argc, char **argv )
{
ARGPARSE_ARGS pargs;
enum cmd_and_opt_values cmd = 0;
unsigned long from = 0, to = ULONG_MAX;
int dry_run = 0;
early_system_init ();
set_strusage( my_strusage );
gcry_control (GCRYCTL_DISABLE_SECMEM);
log_set_prefix ("kbxutil", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_set_log_handler (my_gcry_logger, NULL);
/*create_dotlock(NULL); register locking cleanup */
/* We need to use the gcry malloc function because jnlib uses them. */
keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
while (arg_parse( &pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose:
/*opt.verbose++;*/
/*gcry_control( GCRYCTL_SET_VERBOSITY, (int)opt.verbose );*/
break;
case oDebug:
/*opt.debug |= pargs.r.ret_ulong; */
break;
case oDebugAll:
/*opt.debug = ~0;*/
break;
case aFindByFpr:
case aFindByKid:
case aFindByUid:
case aStats:
case aImportOpenPGP:
case aFindDups:
case aCut:
cmd = pargs.r_opt;
break;
case oFrom: from = pargs.r.ret_ulong; break;
case oTo: to = pargs.r.ret_ulong; break;
case oDryRun: dry_run = 1; break;
default:
pargs.err = 2;
break;
}
}
if (to < from)
log_error ("record number of \"--to\" is lower than \"--from\" one\n");
if (log_get_errorcount(0) )
myexit(2);
if (!cmd)
{ /* Default is to list a KBX file */
if (!argc)
_keybox_dump_file (NULL, 0, stdout);
else
{
for (; argc; argc--, argv++)
_keybox_dump_file (*argv, 0, stdout);
}
}
else if (cmd == aStats )
{
if (!argc)
_keybox_dump_file (NULL, 1, stdout);
else
{
for (; argc; argc--, argv++)
_keybox_dump_file (*argv, 1, stdout);
}
}
else if (cmd == aFindDups )
{
if (!argc)
_keybox_dump_find_dups (NULL, 0, stdout);
else
{
for (; argc; argc--, argv++)
_keybox_dump_find_dups (*argv, 0, stdout);
}
}
else if (cmd == aCut )
{
if (!argc)
_keybox_dump_cut_records (NULL, from, to, stdout);
else
{
for (; argc; argc--, argv++)
_keybox_dump_cut_records (*argv, from, to, stdout);
}
}
else if (cmd == aImportOpenPGP)
{
if (!argc)
import_openpgp ("-", dry_run);
else
{
for (; argc; argc--, argv++)
import_openpgp (*argv, dry_run);
}
}
#if 0
else if ( cmd == aFindByFpr )
{
char *fpr;
if ( argc != 2 )
wrong_args ("kbxfile foingerprint");
fpr = format_fingerprint ( argv[1] );
if ( !fpr )
log_error ("invalid formatted fingerprint\n");
else
{
kbxfile_search_by_fpr ( argv[0], fpr );
gcry_free ( fpr );
}
}
else if ( cmd == aFindByKid )
{
u32 kid[2];
int mode;
if ( argc != 2 )
wrong_args ("kbxfile short-or-long-keyid");
mode = format_keyid ( argv[1], kid );
if ( !mode )
log_error ("invalid formatted keyID\n");
else
{
kbxfile_search_by_kid ( argv[0], kid, mode );
}
}
else if ( cmd == aFindByUid )
{
if ( argc != 2 )
wrong_args ("kbxfile userID");
kbxfile_search_by_uid ( argv[0], argv[1] );
}
#endif
else
log_error ("unsupported action\n");
myexit(0);
return 8; /*NEVER REACHED*/
}
void
myexit( int rc )
{
/* if( opt.debug & DBG_MEMSTAT_VALUE ) {*/
/* gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); */
/* gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); */
/* }*/
/* if( opt.debug ) */
/* gcry_control( GCRYCTL_DUMP_SECMEM_STATS ); */
rc = rc? rc : log_get_errorcount(0)? 2 :
keybox_errors_seen? 1 : 0;
exit(rc );
}
diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c
index 0e8f63a91..d24f117ab 100644
--- a/kbx/keybox-dump.c
+++ b/kbx/keybox-dump.c
@@ -1,805 +1,805 @@
/* keybox-dump.c - Debug helpers
* Copyright (C) 2001, 2003 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "keybox-defs.h"
#include <gcrypt.h>
-#include "host2net.h"
+#include "../common/host2net.h"
/* Argg, we can't include ../common/util.h */
char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
#define get32(a) buf32_to_ulong ((a))
#define get16(a) buf16_to_ulong ((a))
void
print_string (FILE *fp, const byte *p, size_t n, int delim)
{
for ( ; n; n--, p++ )
{
if (*p < 0x20 || (*p >= 0x7f && *p < 0xa0) || *p == delim)
{
putc('\\', fp);
if( *p == '\n' )
putc('n', fp);
else if( *p == '\r' )
putc('r', fp);
else if( *p == '\f' )
putc('f', fp);
else if( *p == '\v' )
putc('v', fp);
else if( *p == '\b' )
putc('b', fp);
else if( !*p )
putc('0', fp);
else
fprintf(fp, "x%02x", *p );
}
else
putc(*p, fp);
}
}
static int
print_checksum (const byte *buffer, size_t length, size_t unhashed, FILE *fp)
{
const byte *p;
int i;
int hashlen;
unsigned char digest[20];
fprintf (fp, "Checksum: ");
if (unhashed && unhashed < 20)
{
fputs ("[specified unhashed sized too short]\n", fp);
return 0;
}
if (!unhashed)
{
unhashed = 16;
hashlen = 16;
}
else
hashlen = 20;
if (length < 5+unhashed)
{
fputs ("[blob too short for a checksum]\n", fp);
return 0;
}
p = buffer + length - hashlen;
for (i=0; i < hashlen; p++, i++)
fprintf (fp, "%02x", *p);
if (hashlen == 16) /* Compatibility method. */
{
gcry_md_hash_buffer (GCRY_MD_MD5, digest, buffer, length - 16);
if (!memcmp (buffer + length - 16, digest, 16))
fputs (" [valid]\n", fp);
else
fputs (" [bad]\n", fp);
}
else
{
gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer, length - unhashed);
if (!memcmp (buffer + length - hashlen, digest, hashlen))
fputs (" [valid]\n", fp);
else
fputs (" [bad]\n", fp);
}
return 0;
}
static int
dump_header_blob (const byte *buffer, size_t length, FILE *fp)
{
unsigned long n;
if (length < 32)
{
fprintf (fp, "[blob too short]\n");
return -1;
}
fprintf (fp, "Version: %d\n", buffer[5]);
n = get16 (buffer + 6);
fprintf( fp, "Flags: %04lX", n);
if (n)
{
int any = 0;
fputs (" (", fp);
if ((n & 2))
{
if (any)
putc (',', fp);
fputs ("openpgp", fp);
any++;
}
putc (')', fp);
}
putc ('\n', fp);
if ( memcmp (buffer+8, "KBXf", 4))
fprintf (fp, "[Error: invalid magic number]\n");
n = get32 (buffer+16);
fprintf( fp, "created-at: %lu\n", n );
n = get32 (buffer+20);
fprintf( fp, "last-maint: %lu\n", n );
return 0;
}
/* Dump one block to FP */
int
_keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
{
const byte *buffer;
size_t length;
int type, i;
ulong n, nkeys, keyinfolen;
ulong nuids, uidinfolen;
ulong nsigs, siginfolen;
ulong rawdata_off, rawdata_len;
ulong nserial;
ulong unhashed;
const byte *p;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 32)
{
fprintf (fp, "[blob too short]\n");
return -1;
}
n = get32( buffer );
if (n > length)
fprintf (fp, "[blob larger than length - output truncated]\n");
else
length = n; /* ignore the rest */
fprintf (fp, "Length: %lu\n", n );
type = buffer[4];
switch (type)
{
case KEYBOX_BLOBTYPE_EMPTY:
fprintf (fp, "Type: Empty\n");
return 0;
case KEYBOX_BLOBTYPE_HEADER:
fprintf (fp, "Type: Header\n");
return dump_header_blob (buffer, length, fp);
case KEYBOX_BLOBTYPE_PGP:
fprintf (fp, "Type: OpenPGP\n");
break;
case KEYBOX_BLOBTYPE_X509:
fprintf (fp, "Type: X.509\n");
break;
default:
fprintf (fp, "Type: %d\n", type);
fprintf (fp, "[can't dump this blob type]\n");
return 0;
}
fprintf (fp, "Version: %d\n", buffer[5]);
if (length < 40)
{
fprintf (fp, "[blob too short]\n");
return -1;
}
n = get16 (buffer + 6);
fprintf( fp, "Blob-Flags: %04lX", n);
if (n)
{
int any = 0;
fputs (" (", fp);
if ((n & 1))
{
fputs ("secret", fp);
any++;
}
if ((n & 2))
{
if (any)
putc (',', fp);
fputs ("ephemeral", fp);
any++;
}
putc (')', fp);
}
putc ('\n', fp);
rawdata_off = get32 (buffer + 8);
rawdata_len = get32 (buffer + 12);
fprintf( fp, "Data-Offset: %lu\n", rawdata_off );
fprintf( fp, "Data-Length: %lu\n", rawdata_len );
if (rawdata_off > length || rawdata_len > length
|| rawdata_off+rawdata_len > length
|| rawdata_len + 4 > length
|| rawdata_off+rawdata_len + 4 > length)
fprintf (fp, "[Error: raw data larger than blob]\n");
unhashed = length - rawdata_off - rawdata_len;
fprintf (fp, "Unhashed: %lu\n", unhashed);
nkeys = get16 (buffer + 16);
fprintf (fp, "Key-Count: %lu\n", nkeys );
if (!nkeys)
fprintf (fp, "[Error: no keys]\n");
if (nkeys > 1 && type == KEYBOX_BLOBTYPE_X509)
fprintf (fp, "[Error: only one key allowed for X509]\n");
keyinfolen = get16 (buffer + 18 );
fprintf (fp, "Key-Info-Length: %lu\n", keyinfolen);
/* fixme: check bounds */
p = buffer + 20;
for (n=0; n < nkeys; n++, p += keyinfolen)
{
ulong kidoff, kflags;
fprintf (fp, "Key-Fpr[%lu]: ", n );
for (i=0; i < 20; i++ )
fprintf (fp, "%02X", p[i]);
kidoff = get32 (p + 20);
fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff );
fprintf (fp, "Key-Kid[%lu]: ", n );
/* fixme: check bounds */
for (i=0; i < 8; i++ )
fprintf (fp, "%02X", buffer[kidoff+i] );
kflags = get16 (p + 24 );
fprintf( fp, "\nKey-Flags[%lu]: %04lX\n", n, kflags);
}
/* serial number */
fputs ("Serial-No: ", fp);
nserial = get16 (p);
p += 2;
if (!nserial)
fputs ("none", fp);
else
{
for (; nserial; nserial--, p++)
fprintf (fp, "%02X", *p);
}
putc ('\n', fp);
/* user IDs */
nuids = get16 (p);
fprintf (fp, "Uid-Count: %lu\n", nuids );
uidinfolen = get16 (p + 2);
fprintf (fp, "Uid-Info-Length: %lu\n", uidinfolen);
/* fixme: check bounds */
p += 4;
for (n=0; n < nuids; n++, p += uidinfolen)
{
ulong uidoff, uidlen, uflags;
uidoff = get32( p );
uidlen = get32( p+4 );
if (type == KEYBOX_BLOBTYPE_X509 && !n)
{
fprintf (fp, "Issuer-Off: %lu\n", uidoff );
fprintf (fp, "Issuer-Len: %lu\n", uidlen );
fprintf (fp, "Issuer: \"");
}
else if (type == KEYBOX_BLOBTYPE_X509 && n == 1)
{
fprintf (fp, "Subject-Off: %lu\n", uidoff );
fprintf (fp, "Subject-Len: %lu\n", uidlen );
fprintf (fp, "Subject: \"");
}
else
{
fprintf (fp, "Uid-Off[%lu]: %lu\n", n, uidoff );
fprintf (fp, "Uid-Len[%lu]: %lu\n", n, uidlen );
fprintf (fp, "Uid[%lu]: \"", n );
}
print_string (fp, buffer+uidoff, uidlen, '\"');
fputs ("\"\n", fp);
uflags = get16 (p + 8);
if (type == KEYBOX_BLOBTYPE_X509 && !n)
{
fprintf (fp, "Issuer-Flags: %04lX\n", uflags );
fprintf (fp, "Issuer-Validity: %d\n", p[10] );
}
else if (type == KEYBOX_BLOBTYPE_X509 && n == 1)
{
fprintf (fp, "Subject-Flags: %04lX\n", uflags );
fprintf (fp, "Subject-Validity: %d\n", p[10] );
}
else
{
fprintf (fp, "Uid-Flags[%lu]: %04lX\n", n, uflags );
fprintf (fp, "Uid-Validity[%lu]: %d\n", n, p[10] );
}
}
nsigs = get16 (p);
fprintf (fp, "Sig-Count: %lu\n", nsigs );
siginfolen = get16 (p + 2);
fprintf (fp, "Sig-Info-Length: %lu\n", siginfolen );
/* fixme: check bounds */
p += 4;
{
int in_range = 0;
ulong first = 0;
for (n=0; n < nsigs; n++, p += siginfolen)
{
ulong sflags;
sflags = get32 (p);
if (!in_range && !sflags)
{
in_range = 1;
first = n;
continue;
}
if (in_range && !sflags)
continue;
if (in_range)
{
fprintf (fp, "Sig-Expire[%lu-%lu]: [not checked]\n", first, n-1);
in_range = 0;
}
fprintf (fp, "Sig-Expire[%lu]: ", n );
if (!sflags)
fputs ("[not checked]", fp);
else if (sflags == 1 )
fputs ("[missing key]", fp);
else if (sflags == 2 )
fputs ("[bad signature]", fp);
else if (sflags < 0x10000000)
fprintf (fp, "[bad flag %0lx]", sflags);
else if (sflags == (ulong)(-1))
fputs ("[good - does not expire]", fp );
else
fprintf (fp, "[good - expires at %lu]", sflags);
putc ('\n', fp );
}
if (in_range)
fprintf (fp, "Sig-Expire[%lu-%lu]: [not checked]\n", first, n-1);
}
fprintf (fp, "Ownertrust: %d\n", p[0] );
fprintf (fp, "All-Validity: %d\n", p[1] );
p += 4;
n = get32 (p);
p += 4;
fprintf (fp, "Recheck-After: %lu\n", n );
n = get32 (p );
p += 4;
fprintf( fp, "Latest-Timestamp: %lu\n", n );
n = get32 (p );
p += 4;
fprintf (fp, "Created-At: %lu\n", n );
n = get32 (p );
fprintf (fp, "Reserved-Space: %lu\n", n );
if (n >= 4 && unhashed >= 24)
{
n = get32 ( buffer + length - unhashed);
fprintf (fp, "Storage-Flags: %08lx\n", n );
}
print_checksum (buffer, length, unhashed, fp);
return 0;
}
/* Compute the SHA-1 checksum of the rawdata in BLOB and put it into
DIGEST. */
static int
hash_blob_rawdata (KEYBOXBLOB blob, unsigned char *digest)
{
const unsigned char *buffer;
size_t n, length;
int type;
ulong rawdata_off, rawdata_len;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 32)
return -1;
n = get32 (buffer);
if (n < length)
length = n; /* Blob larger than length in header - ignore the rest. */
type = buffer[4];
switch (type)
{
case KEYBOX_BLOBTYPE_PGP:
case KEYBOX_BLOBTYPE_X509:
break;
case KEYBOX_BLOBTYPE_EMPTY:
case KEYBOX_BLOBTYPE_HEADER:
default:
memset (digest, 0, 20);
return 0;
}
if (length < 40)
return -1;
rawdata_off = get32 (buffer + 8);
rawdata_len = get32 (buffer + 12);
if (rawdata_off > length || rawdata_len > length
|| rawdata_off+rawdata_off > length)
return -1; /* Out of bounds. */
gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer+rawdata_off, rawdata_len);
return 0;
}
struct file_stats_s
{
unsigned long too_short_blobs;
unsigned long too_large_blobs;
unsigned long total_blob_count;
unsigned long empty_blob_count;
unsigned long header_blob_count;
unsigned long pgp_blob_count;
unsigned long x509_blob_count;
unsigned long unknown_blob_count;
unsigned long non_flagged;
unsigned long secret_flagged;
unsigned long ephemeral_flagged;
unsigned long skipped_long_blobs;
};
static int
update_stats (KEYBOXBLOB blob, struct file_stats_s *s)
{
const unsigned char *buffer;
size_t length;
int type;
unsigned long n;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 32)
{
s->too_short_blobs++;
return -1;
}
n = get32( buffer );
if (n > length)
s->too_large_blobs++;
else
length = n; /* ignore the rest */
s->total_blob_count++;
type = buffer[4];
switch (type)
{
case KEYBOX_BLOBTYPE_EMPTY:
s->empty_blob_count++;
return 0;
case KEYBOX_BLOBTYPE_HEADER:
s->header_blob_count++;
return 0;
case KEYBOX_BLOBTYPE_PGP:
s->pgp_blob_count++;
break;
case KEYBOX_BLOBTYPE_X509:
s->x509_blob_count++;
break;
default:
s->unknown_blob_count++;
return 0;
}
if (length < 40)
{
s->too_short_blobs++;
return -1;
}
n = get16 (buffer + 6);
if (n)
{
if ((n & 1))
s->secret_flagged++;
if ((n & 2))
s->ephemeral_flagged++;
}
else
s->non_flagged++;
return 0;
}
static FILE *
open_file (const char **filename, FILE *outfp)
{
FILE *fp;
if (!*filename)
{
*filename = "-";
fp = stdin;
}
else
fp = fopen (*filename, "rb");
if (!fp)
{
int save_errno = errno;
fprintf (outfp, "can't open '%s': %s\n", *filename, strerror(errno));
gpg_err_set_errno (save_errno);
}
return fp;
}
int
_keybox_dump_file (const char *filename, int stats_only, FILE *outfp)
{
FILE *fp;
KEYBOXBLOB blob;
int rc;
unsigned long count = 0;
struct file_stats_s stats;
memset (&stats, 0, sizeof stats);
if (!(fp = open_file (&filename, outfp)))
return gpg_error_from_syserror ();
for (;;)
{
rc = _keybox_read_blob (&blob, fp);
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
if (stats_only)
stats.skipped_long_blobs++;
else
{
fprintf (outfp, "BEGIN-RECORD: %lu\n", count );
fprintf (outfp, "# Record too large\nEND-RECORD\n");
}
count++;
continue;
}
if (rc)
break;
if (stats_only)
{
update_stats (blob, &stats);
}
else
{
fprintf (outfp, "BEGIN-RECORD: %lu\n", count );
_keybox_dump_blob (blob, outfp);
fprintf (outfp, "END-RECORD\n");
}
_keybox_release_blob (blob);
count++;
}
if (rc == -1)
rc = 0;
if (rc)
fprintf (outfp, "# error reading '%s': %s\n", filename, gpg_strerror (rc));
if (fp != stdin)
fclose (fp);
if (stats_only)
{
fprintf (outfp,
"Total number of blobs: %8lu\n"
" header: %8lu\n"
" empty: %8lu\n"
" openpgp: %8lu\n"
" x509: %8lu\n"
" non flagged: %8lu\n"
" secret flagged: %8lu\n"
" ephemeral flagged: %8lu\n",
stats.total_blob_count,
stats.header_blob_count,
stats.empty_blob_count,
stats.pgp_blob_count,
stats.x509_blob_count,
stats.non_flagged,
stats.secret_flagged,
stats.ephemeral_flagged);
if (stats.skipped_long_blobs)
fprintf (outfp, " skipped long blobs: %8lu\n",
stats.skipped_long_blobs);
if (stats.unknown_blob_count)
fprintf (outfp, " unknown blob types: %8lu\n",
stats.unknown_blob_count);
if (stats.too_short_blobs)
fprintf (outfp, " too short blobs: %8lu (error)\n",
stats.too_short_blobs);
if (stats.too_large_blobs)
fprintf (outfp, " too large blobs: %8lu (error)\n",
stats.too_large_blobs);
}
return rc;
}
struct dupitem_s
{
unsigned long recno;
unsigned char digest[20];
};
static int
cmp_dupitems (const void *arg_a, const void *arg_b)
{
struct dupitem_s *a = (struct dupitem_s *)arg_a;
struct dupitem_s *b = (struct dupitem_s *)arg_b;
return memcmp (a->digest, b->digest, 20);
}
int
_keybox_dump_find_dups (const char *filename, int print_them, FILE *outfp)
{
FILE *fp;
KEYBOXBLOB blob;
int rc;
unsigned long recno = 0;
unsigned char zerodigest[20];
struct dupitem_s *dupitems;
size_t dupitems_size, dupitems_count, lastn, n;
char fprbuf[3*20+1];
(void)print_them;
memset (zerodigest, 0, sizeof zerodigest);
if (!(fp = open_file (&filename, outfp)))
return gpg_error_from_syserror ();
dupitems_size = 1000;
dupitems = malloc (dupitems_size * sizeof *dupitems);
if (!dupitems)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
fprintf (outfp, "error allocating array for '%s': %s\n",
filename, strerror(errno));
return tmperr;
}
dupitems_count = 0;
while ( !(rc = _keybox_read_blob (&blob, fp)) )
{
unsigned char digest[20];
if (hash_blob_rawdata (blob, digest))
fprintf (outfp, "error in blob %ld of '%s'\n", recno, filename);
else if (memcmp (digest, zerodigest, 20))
{
if (dupitems_count >= dupitems_size)
{
struct dupitem_s *tmp;
dupitems_size += 1000;
tmp = realloc (dupitems, dupitems_size * sizeof *dupitems);
if (!tmp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
fprintf (outfp, "error reallocating array for '%s': %s\n",
filename, strerror(errno));
free (dupitems);
return tmperr;
}
dupitems = tmp;
}
dupitems[dupitems_count].recno = recno;
memcpy (dupitems[dupitems_count].digest, digest, 20);
dupitems_count++;
}
_keybox_release_blob (blob);
recno++;
}
if (rc == -1)
rc = 0;
if (rc)
fprintf (outfp, "error reading '%s': %s\n", filename, gpg_strerror (rc));
if (fp != stdin)
fclose (fp);
qsort (dupitems, dupitems_count, sizeof *dupitems, cmp_dupitems);
for (lastn=0, n=1; n < dupitems_count; lastn=n, n++)
{
if (!memcmp (dupitems[lastn].digest, dupitems[n].digest, 20))
{
bin2hexcolon (dupitems[lastn].digest, 20, fprbuf);
fprintf (outfp, "fpr=%s recno=%lu", fprbuf, dupitems[lastn].recno);
do
fprintf (outfp, " %lu", dupitems[n].recno);
while (++n < dupitems_count
&& !memcmp (dupitems[lastn].digest, dupitems[n].digest, 20));
putc ('\n', outfp);
n--;
}
}
free (dupitems);
return rc;
}
/* Print records with record numbers FROM to TO to OUTFP. */
int
_keybox_dump_cut_records (const char *filename, unsigned long from,
unsigned long to, FILE *outfp)
{
FILE *fp;
KEYBOXBLOB blob;
int rc;
unsigned long recno = 0;
if (!(fp = open_file (&filename, stderr)))
return gpg_error_from_syserror ();
while ( !(rc = _keybox_read_blob (&blob, fp)) )
{
if (recno > to)
break; /* Ready. */
if (recno >= from)
{
if ((rc = _keybox_write_blob (blob, outfp)))
{
fprintf (stderr, "error writing output: %s\n",
gpg_strerror (rc));
goto leave;
}
}
_keybox_release_blob (blob);
recno++;
}
if (rc == -1)
rc = 0;
if (rc)
fprintf (stderr, "error reading '%s': %s\n", filename, gpg_strerror (rc));
leave:
if (fp != stdin)
fclose (fp);
return rc;
}
diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c
index 6885e059f..d82c2cb75 100644
--- a/kbx/keybox-openpgp.c
+++ b/kbx/keybox-openpgp.c
@@ -1,522 +1,522 @@
/* keybox-openpgp.c - OpenPGP key parsing
* Copyright (C) 2001, 2003, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* This is a simple OpenPGP parser suitable for all OpenPGP key
material. It just provides the functionality required to build and
parse an KBX OpenPGP key blob. Thus it is not a complete parser.
However it is self-contained and optimized for fast in-memory
parsing. Note that we don't support old ElGamal v3 keys
anymore. */
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "keybox-defs.h"
#include <gcrypt.h>
#include "../common/openpgpdefs.h"
-#include "host2net.h"
+#include "../common/host2net.h"
/* Assume a valid OpenPGP packet at the address pointed to by BUFBTR
which has a maximum length as stored at BUFLEN. Return the header
information of that packet and advance the pointer stored at BUFPTR
to the next packet; also adjust the length stored at BUFLEN to
match the remaining bytes. If there are no more packets, store NULL
at BUFPTR. Return an non-zero error code on failure or the
following data on success:
R_DATAPKT = Pointer to the begin of the packet data.
R_DATALEN = Length of this data. This has already been checked to fit
into the buffer.
R_PKTTYPE = The packet type.
R_NTOTAL = The total number of bytes of this packet
Note that these values are only updated on success.
*/
static gpg_error_t
next_packet (unsigned char const **bufptr, size_t *buflen,
unsigned char const **r_data, size_t *r_datalen, int *r_pkttype,
size_t *r_ntotal)
{
const unsigned char *buf = *bufptr;
size_t len = *buflen;
int c, ctb, pkttype;
unsigned long pktlen;
if (!len)
return gpg_error (GPG_ERR_NO_DATA);
ctb = *buf++; len--;
if ( !(ctb & 0x80) )
return gpg_error (GPG_ERR_INV_PACKET); /* Invalid CTB. */
if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */
{
pkttype = (ctb & 0x3f);
if (!len)
return gpg_error (GPG_ERR_INV_PACKET); /* No 1st length byte. */
c = *buf++; len--;
if (pkttype == PKT_COMPRESSED)
return gpg_error (GPG_ERR_UNEXPECTED); /* ... packet in a keyblock. */
if ( c < 192 )
pktlen = c;
else if ( c < 224 )
{
pktlen = (c - 192) * 256;
if (!len)
return gpg_error (GPG_ERR_INV_PACKET); /* No 2nd length byte. */
c = *buf++; len--;
pktlen += c + 192;
}
else if (c == 255)
{
if (len <4 )
return gpg_error (GPG_ERR_INV_PACKET); /* No length bytes. */
pktlen = buf32_to_ulong (buf);
buf += 4;
len -= 4;
}
else /* Partial length encoding is not allowed for key packets. */
return gpg_error (GPG_ERR_UNEXPECTED);
}
else /* Old style CTB. */
{
int lenbytes;
pktlen = 0;
pkttype = (ctb>>2)&0xf;
lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3));
if (!lenbytes) /* Not allowed in key packets. */
return gpg_error (GPG_ERR_UNEXPECTED);
if (len < lenbytes)
return gpg_error (GPG_ERR_INV_PACKET); /* Not enough length bytes. */
for (; lenbytes; lenbytes--)
{
pktlen <<= 8;
pktlen |= *buf++; len--;
}
}
/* Do some basic sanity check. */
switch (pkttype)
{
case PKT_SIGNATURE:
case PKT_SECRET_KEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_MARKER:
case PKT_RING_TRUST:
case PKT_USER_ID:
case PKT_PUBLIC_SUBKEY:
case PKT_OLD_COMMENT:
case PKT_ATTRIBUTE:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
break; /* Okay these are allowed packets. */
default:
return gpg_error (GPG_ERR_UNEXPECTED);
}
if (pkttype == 63 && pktlen == 0xFFFFFFFF)
/* Sometimes the decompressing layer enters an error state in
which it simply outputs 0xff for every byte read. If we have a
stream of 0xff bytes, then it will be detected as a new format
packet with type 63 and a 4-byte encoded length that is 4G-1.
Since packets with type 63 are private and we use them as a
control packet, which won't be 4 GB, we reject such packets as
invalid. */
return gpg_error (GPG_ERR_INV_PACKET);
if (pktlen > len)
return gpg_error (GPG_ERR_INV_PACKET); /* Packet length header too long. */
*r_data = buf;
*r_datalen = pktlen;
*r_pkttype = pkttype;
*r_ntotal = (buf - *bufptr) + pktlen;
*bufptr = buf + pktlen;
*buflen = len - pktlen;
if (!*buflen)
*bufptr = NULL;
return 0;
}
/* Parse a key packet and store the information in KI. */
static gpg_error_t
parse_key (const unsigned char *data, size_t datalen,
struct _keybox_openpgp_key_info *ki)
{
gpg_error_t err;
const unsigned char *data_start = data;
int i, version, algorithm;
size_t n;
int npkey;
unsigned char hashbuffer[768];
const unsigned char *mpi_n = NULL;
size_t mpi_n_len = 0, mpi_e_len = 0;
gcry_md_hd_t md;
int is_ecc = 0;
if (datalen < 5)
return gpg_error (GPG_ERR_INV_PACKET);
version = *data++; datalen--;
if (version < 2 || version > 4 )
return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */
/*timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));*/
data +=4; datalen -=4;
if (version < 4)
{
if (datalen < 2)
return gpg_error (GPG_ERR_INV_PACKET);
data +=2; datalen -= 2;
}
if (!datalen)
return gpg_error (GPG_ERR_INV_PACKET);
algorithm = *data++; datalen--;
switch (algorithm)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S:
npkey = 2;
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
npkey = 3;
break;
case PUBKEY_ALGO_DSA:
npkey = 4;
break;
case PUBKEY_ALGO_ECDH:
npkey = 3;
is_ecc = 1;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA:
npkey = 2;
is_ecc = 1;
break;
default: /* Unknown algorithm. */
return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
}
ki->algo = algorithm;
for (i=0; i < npkey; i++ )
{
unsigned int nbits, nbytes;
if (datalen < 2)
return gpg_error (GPG_ERR_INV_PACKET);
if (is_ecc && (i == 0 || i == 2))
{
nbytes = data[0];
if (nbytes < 2 || nbytes > 254)
return gpg_error (GPG_ERR_INV_PACKET);
nbytes++; /* The size byte itself. */
if (datalen < nbytes)
return gpg_error (GPG_ERR_INV_PACKET);
}
else
{
nbits = ((data[0]<<8)|(data[1]));
data += 2;
datalen -= 2;
nbytes = (nbits+7) / 8;
if (datalen < nbytes)
return gpg_error (GPG_ERR_INV_PACKET);
/* For use by v3 fingerprint calculation we need to know the RSA
modulus and exponent. */
if (i==0)
{
mpi_n = data;
mpi_n_len = nbytes;
}
else if (i==1)
mpi_e_len = nbytes;
}
data += nbytes; datalen -= nbytes;
}
n = data - data_start;
if (version < 4)
{
/* We do not support any other algorithm than RSA in v3
packets. */
if (algorithm < 1 || algorithm > 3)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
err = gcry_md_open (&md, GCRY_MD_MD5, 0);
if (err)
return err; /* Oops */
gcry_md_write (md, mpi_n, mpi_n_len);
gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len);
memcpy (ki->fpr, gcry_md_read (md, 0), 16);
gcry_md_close (md);
ki->fprlen = 16;
if (mpi_n_len < 8)
{
/* Moduli less than 64 bit are out of the specs scope. Zero
them out because this is what gpg does too. */
memset (ki->keyid, 0, 8);
}
else
memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8);
}
else
{
/* Its a pitty that we need to prefix the buffer with the tag
and a length header: We can't simply pass it to the fast
hashing function for that reason. It might be a good idea to
have a scatter-gather enabled hash function. What we do here
is to use a static buffer if this one is large enough and
only use the regular hash functions if this buffer is not
large enough. */
if ( 3 + n < sizeof hashbuffer )
{
hashbuffer[0] = 0x99; /* CTB */
hashbuffer[1] = (n >> 8); /* 2 byte length header. */
hashbuffer[2] = n;
memcpy (hashbuffer + 3, data_start, n);
gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n);
}
else
{
err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (err)
return err; /* Oops */
gcry_md_putc (md, 0x99 ); /* CTB */
gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */
gcry_md_putc (md, n );
gcry_md_write (md, data_start, n);
memcpy (ki->fpr, gcry_md_read (md, 0), 20);
gcry_md_close (md);
}
ki->fprlen = 20;
memcpy (ki->keyid, ki->fpr+12, 8);
}
return 0;
}
/* The caller must pass the address of an INFO structure which will
get filled on success with information pertaining to the OpenPGP
keyblock IMAGE of length IMAGELEN. Note that a caller does only
need to release this INFO structure if the function returns
success. If NPARSED is not NULL the actual number of bytes parsed
will be stored at this address. */
gpg_error_t
_keybox_parse_openpgp (const unsigned char *image, size_t imagelen,
size_t *nparsed, keybox_openpgp_info_t info)
{
gpg_error_t err = 0;
const unsigned char *image_start, *data;
size_t n, datalen;
int pkttype;
int first = 1;
int read_error = 0;
struct _keybox_openpgp_key_info *k, **ktail = NULL;
struct _keybox_openpgp_uid_info *u, **utail = NULL;
memset (info, 0, sizeof *info);
if (nparsed)
*nparsed = 0;
image_start = image;
while (image)
{
err = next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n);
if (err)
{
read_error = 1;
break;
}
if (first)
{
if (pkttype == PKT_PUBLIC_KEY)
;
else if (pkttype == PKT_SECRET_KEY)
info->is_secret = 1;
else
{
err = gpg_error (GPG_ERR_UNEXPECTED);
if (nparsed)
*nparsed += n;
break;
}
first = 0;
}
else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
break; /* Next keyblock encountered - ready. */
if (nparsed)
*nparsed += n;
if (pkttype == PKT_SIGNATURE)
{
/* For now we only count the total number of signatures. */
info->nsigs++;
}
else if (pkttype == PKT_USER_ID)
{
info->nuids++;
if (info->nuids == 1)
{
info->uids.off = data - image_start;
info->uids.len = datalen;
utail = &info->uids.next;
}
else
{
u = xtrycalloc (1, sizeof *u);
if (!u)
{
err = gpg_error_from_syserror ();
break;
}
u->off = data - image_start;
u->len = datalen;
*utail = u;
utail = &u->next;
}
}
else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
{
err = parse_key (data, datalen, &info->primary);
if (err)
break;
}
else if( pkttype == PKT_PUBLIC_SUBKEY && datalen && *data == '#' )
{
/* Early versions of GnuPG used old PGP comment packets;
* luckily all those comments are prefixed by a hash
* sign - ignore these packets. */
}
else if (pkttype == PKT_PUBLIC_SUBKEY || pkttype == PKT_SECRET_SUBKEY)
{
info->nsubkeys++;
if (info->nsubkeys == 1)
{
err = parse_key (data, datalen, &info->subkeys);
if (err)
{
info->nsubkeys--;
/* We ignore subkeys with unknown algorithms. */
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM)
err = 0;
if (err)
break;
}
else
ktail = &info->subkeys.next;
}
else
{
k = xtrycalloc (1, sizeof *k);
if (!k)
{
err = gpg_error_from_syserror ();
break;
}
err = parse_key (data, datalen, k);
if (err)
{
xfree (k);
info->nsubkeys--;
/* We ignore subkeys with unknown algorithms. */
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM)
err = 0;
if (err)
break;
}
else
{
*ktail = k;
ktail = &k->next;
}
}
}
}
if (err)
{
_keybox_destroy_openpgp_info (info);
if (!read_error)
{
/* Packet parsing worked, thus we should be able to skip the
rest of the keyblock. */
while (image)
{
if (next_packet (&image, &imagelen,
&data, &datalen, &pkttype, &n) )
break; /* Another error - stop here. */
if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
break; /* Next keyblock encountered - ready. */
if (nparsed)
*nparsed += n;
}
}
}
return err;
}
/* Release any malloced data in INFO but not INFO itself! */
void
_keybox_destroy_openpgp_info (keybox_openpgp_info_t info)
{
struct _keybox_openpgp_key_info *k, *k2;
struct _keybox_openpgp_uid_info *u, *u2;
assert (!info->primary.next);
for (k=info->subkeys.next; k; k = k2)
{
k2 = k->next;
xfree (k);
}
for (u=info->uids.next; u; u = u2)
{
u2 = u->next;
xfree (u);
}
}
diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c
index ec5aad128..cc114c64c 100644
--- a/kbx/keybox-search.c
+++ b/kbx/keybox-search.c
@@ -1,1227 +1,1227 @@
/* keybox-search.c - Search operations
* Copyright (C) 2001, 2002, 2003, 2004, 2012,
* 2013 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "keybox-defs.h"
#include <gcrypt.h>
-#include "host2net.h"
-#include "mbox-util.h"
+#include "../common/host2net.h"
+#include "../common/mbox-util.h"
#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))
struct sn_array_s {
int snlen;
unsigned char *sn;
};
#define get32(a) buf32_to_ulong ((a))
#define get16(a) buf16_to_ulong ((a))
static inline unsigned int
blob_get_blob_flags (KEYBOXBLOB blob)
{
const unsigned char *buffer;
size_t length;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 8)
return 0; /* oops */
return get16 (buffer + 6);
}
/* Return the first keyid from the blob. Returns true if
available. */
static int
blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid)
{
const unsigned char *buffer;
size_t length, nkeys, keyinfolen;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 48)
return 0; /* blob too short */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18);
if (!nkeys || keyinfolen < 28)
return 0; /* invalid blob */
kid[0] = get32 (buffer + 32);
kid[1] = get32 (buffer + 36);
return 1;
}
/* Return information on the flag WHAT within the blob BUFFER,LENGTH.
Return the offset and the length (in bytes) of the flag in
FLAGOFF,FLAG_SIZE. */
gpg_err_code_t
_keybox_get_flag_location (const unsigned char *buffer, size_t length,
int what, size_t *flag_off, size_t *flag_size)
{
size_t pos;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
size_t nsigs, siginfolen, siginfooff;
switch (what)
{
case KEYBOX_FLAG_BLOB:
if (length < 8)
return GPG_ERR_INV_OBJ;
*flag_off = 6;
*flag_size = 2;
break;
case KEYBOX_FLAG_OWNERTRUST:
case KEYBOX_FLAG_VALIDITY:
case KEYBOX_FLAG_CREATED_AT:
case KEYBOX_FLAG_SIG_INFO:
if (length < 20)
return GPG_ERR_INV_OBJ;
/* Key info. */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return GPG_ERR_INV_OBJ;
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* Serial number. */
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* User IDs. */
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 )
return GPG_ERR_INV_OBJ;
pos += uidinfolen*nuids;
if (pos+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
/* Signature info. */
siginfooff = pos;
nsigs = get16 (buffer + pos); pos += 2;
siginfolen = get16 (buffer + pos); pos += 2;
if (siginfolen < 4 )
return GPG_ERR_INV_OBJ;
pos += siginfolen*nsigs;
if (pos+1+1+2+4+4+4+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
*flag_size = 1;
*flag_off = pos;
switch (what)
{
case KEYBOX_FLAG_VALIDITY:
*flag_off += 1;
break;
case KEYBOX_FLAG_CREATED_AT:
*flag_size = 4;
*flag_off += 1+2+4+4+4;
break;
case KEYBOX_FLAG_SIG_INFO:
*flag_size = siginfolen * nsigs;
*flag_off = siginfooff;
break;
default:
break;
}
break;
default:
return GPG_ERR_INV_FLAG;
}
return 0;
}
/* Return one of the flags WHAT in VALUE from the blob BUFFER of
LENGTH bytes. Return 0 on success or an raw error code. */
static gpg_err_code_t
get_flag_from_image (const unsigned char *buffer, size_t length,
int what, unsigned int *value)
{
gpg_err_code_t ec;
size_t pos, size;
*value = 0;
ec = _keybox_get_flag_location (buffer, length, what, &pos, &size);
if (!ec)
switch (size)
{
case 1: *value = buffer[pos]; break;
case 2: *value = get16 (buffer + pos); break;
case 4: *value = get32 (buffer + pos); break;
default: ec = GPG_ERR_BUG; break;
}
return ec;
}
static int
blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
off = pos + 2;
if (off+nserial > length)
return 0; /* out of bounds */
return nserial == snlen && !memcmp (buffer+off, sn, snlen);
}
/* Returns 0 if not found or the number of the key which was found.
For X.509 this is always 1, for OpenPGP this is 1 for the primary
key and 2 and more for the subkeys. */
static int
blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20;
if (pos + keyinfolen*nkeys > length)
return 0; /* out of bounds */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (!memcmp (buffer + off, fpr, 20))
return idx+1; /* found */
}
return 0; /* not found */
}
static int
blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr,
int fproff, int fprlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20;
if (pos + keyinfolen*nkeys > length)
return 0; /* out of bounds */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (!memcmp (buffer + off + fproff, fpr, fprlen))
return idx+1; /* found */
}
return 0; /* not found */
}
static int
blob_cmp_name (KEYBOXBLOB blob, int idx,
const char *name, size_t namelen, int substr, int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (idx < 0)
{ /* Compare all names. Note that for X.509 we start with index 1
so to skip the issuer at index 0. */
for (idx = !!x509; idx < nuids; idx++)
{
size_t mypos = pos;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if (off+len > length)
return 0; /* error: better stop here out of bounds */
if (len < 1)
continue; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
}
else
{
if (idx > nuids)
return 0; /* no user ID with that idx */
pos += idx*uidinfolen;
off = get32 (buffer+pos);
len = get32 (buffer+pos+4);
if (off+len > length)
return 0; /* out of bounds */
if (len < 1)
return 0; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
/* Compare all email addresses of the subject. With SUBSTR given as
True a substring search is done in the mail address. The X509 flag
indicated whether the search is done on an X.509 blob. */
static int
blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr,
int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
int idx;
/* fixme: this code is common to blob_cmp_mail */
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (namelen < 1)
return 0;
/* Note that for X.509 we start at index 1 because index 0 is used
for the issuer name. */
for (idx=!!x509 ;idx < nuids; idx++)
{
size_t mypos = pos;
size_t mylen;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if (off+len > length)
return 0; /* error: better stop here - out of bounds */
if (x509)
{
if (len < 2 || buffer[off] != '<')
continue; /* empty name or trailing 0 not stored */
len--; /* one back */
if ( len < 3 || buffer[off+len] != '>')
continue; /* not a proper email address */
off++;
len--;
}
else /* OpenPGP. */
{
/* We need to forward to the mailbox part. */
mypos = off;
mylen = len;
for ( ; len && buffer[off] != '<'; len--, off++)
;
if (len < 2 || buffer[off] != '<')
{
/* Mailbox not explicitly given or too short. Restore
OFF and LEN and check whether the entire string
resembles a mailbox without the angle brackets. */
off = mypos;
len = mylen;
if (!is_valid_mailbox_mem (buffer+off, len))
continue; /* Not a mail address. */
}
else /* Seems to be standard user id with mail address. */
{
off++; /* Point to first char of the mail address. */
len--;
/* Search closing '>'. */
for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++)
;
if (!len || buffer[mypos] != '>' || off == mypos)
continue; /* Not a proper mail address. */
len = mypos - off;
}
}
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !ascii_memcasecmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
#ifdef KEYBOX_WITH_X509
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
We don't have the keygrips as meta data, thus we need to parse the
certificate. Fixme: We might want to return proper error codes
instead of failing a search for invalid certificates etc. */
static int
blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
int rc;
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
ksba_sexp_t p = NULL;
gcry_sexp_t s_pkey;
unsigned char array[20];
unsigned char *rcp;
size_t n;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* Too short. */
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if (cert_off+cert_len > length)
return 0; /* Too short. */
rc = ksba_reader_new (&reader);
if (rc)
return 0; /* Problem with ksba. */
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
goto failed;
rc = ksba_cert_new (&cert);
if (rc)
goto failed;
rc = ksba_cert_read_der (cert, reader);
if (rc)
goto failed;
p = ksba_cert_get_public_key (cert);
if (!p)
goto failed;
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
goto failed;
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
if (rc)
{
gcry_sexp_release (s_pkey);
goto failed;
}
rcp = gcry_pk_get_keygrip (s_pkey, array);
gcry_sexp_release (s_pkey);
if (!rcp)
goto failed; /* Can't calculate keygrip. */
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return !memcmp (array, grip, 20);
failed:
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/*
The has_foo functions are used as helpers for search
*/
static inline int
has_short_kid (KEYBOXBLOB blob, u32 lkid)
{
unsigned char buf[4];
buf[0] = lkid >> 24;
buf[1] = lkid >> 16;
buf[2] = lkid >> 8;
buf[3] = lkid;
return blob_cmp_fpr_part (blob, buf, 16, 4);
}
static inline int
has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid)
{
unsigned char buf[8];
buf[0] = mkid >> 24;
buf[1] = mkid >> 16;
buf[2] = mkid >> 8;
buf[3] = mkid;
buf[4] = lkid >> 24;
buf[5] = lkid >> 16;
buf[6] = lkid >> 8;
buf[7] = lkid;
return blob_cmp_fpr_part (blob, buf, 12, 8);
}
static inline int
has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr)
{
return blob_cmp_fpr (blob, fpr);
}
static inline int
has_keygrip (KEYBOXBLOB blob, const unsigned char *grip)
{
#ifdef KEYBOX_WITH_X509
if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509)
return blob_x509_has_grip (blob, grip);
#else
(void)blob;
(void)grip;
#endif
return 0;
}
static inline int
has_issuer (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1);
}
static inline int
has_issuer_sn (KEYBOXBLOB blob, const char *name,
const unsigned char *sn, int snlen)
{
size_t namelen;
return_val_if_fail (name, 0);
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return (blob_cmp_sn (blob, sn, snlen)
&& blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1));
}
static inline int
has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
return blob_cmp_sn (blob, sn, snlen);
}
static inline int
has_subject (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1);
}
static inline int
has_username (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, -1 /* all subject/user names */, name,
namelen, substr, (btype == KEYBOX_BLOBTYPE_X509));
}
static inline int
has_mail (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<')
name++; /* Hack to remove the leading '<' for gpg. */
namelen = strlen (name);
if (namelen && name[namelen-1] == '>')
namelen--;
return blob_cmp_mail (blob, name, namelen, substr,
(btype == KEYBOX_BLOBTYPE_X509));
}
static void
release_sn_array (struct sn_array_s *array, size_t size)
{
size_t n;
for (n=0; n < size; n++)
xfree (array[n].sn);
xfree (array);
}
/*
The search API
*/
gpg_error_t
keybox_search_reset (KEYBOX_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
if (hd->fp)
{
fclose (hd->fp);
hd->fp = NULL;
}
hd->error = 0;
hd->eof = 0;
return 0;
}
/* Note: When in ephemeral mode the search function does visit all
blobs but in standard mode, blobs flagged as ephemeral are ignored.
If WANT_BLOBTYPE is not 0 only blobs of this type are considered.
The value at R_SKIPPED is updated by the number of skipped long
records (counts PGP and X.509). */
gpg_error_t
keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
keybox_blobtype_t want_blobtype,
size_t *r_descindex, unsigned long *r_skipped)
{
gpg_error_t rc;
size_t n;
int need_words, any_skip;
KEYBOXBLOB blob = NULL;
struct sn_array_s *sn_array = NULL;
int pk_no, uid_no;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
/* clear last found result */
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
if (hd->error)
return hd->error; /* still in error state */
if (hd->eof)
return -1; /* still EOF */
/* figure out what information we need */
need_words = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_WORDS:
need_words = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keybox_search_reset (hd);
break;
default:
break;
}
if (desc[n].skipfnc)
any_skip = 1;
if (desc[n].snlen == -1 && !sn_array)
{
sn_array = xtrycalloc (ndesc, sizeof *sn_array);
if (!sn_array)
return (hd->error = gpg_error_from_syserror ());
}
}
(void)need_words; /* Not yet implemented. */
if (!hd->fp)
{
hd->fp = fopen (hd->kb->fname, "rb");
if (!hd->fp)
{
hd->error = gpg_error_from_syserror ();
xfree (sn_array);
return hd->error;
}
}
/* Kludge: We need to convert an SN given as hexstring to its binary
representation - in some cases we are not able to store it in the
search descriptor, because due to the way we use it, it is not
possible to free allocated memory. */
if (sn_array)
{
const unsigned char *s;
int i, odd;
size_t snlen;
for (n=0; n < ndesc; n++)
{
if (!desc[n].sn)
;
else if (desc[n].snlen == -1)
{
unsigned char *sn;
s = desc[n].sn;
for (i=0; *s && *s != '/'; s++, i++)
;
odd = (i & 1);
snlen = (i+1)/2;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
sn = sn_array[n].sn;
s = desc[n].sn;
if (odd)
{
*sn++ = xtoi_1 (s);
s++;
}
for (; *s && *s != '/'; s += 2)
*sn++ = xtoi_2 (s);
}
else
{
const unsigned char *sn;
sn = desc[n].sn;
snlen = desc[n].snlen;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
memcpy (sn_array[n].sn, sn, snlen);
}
}
}
pk_no = uid_no = 0;
for (;;)
{
unsigned int blobflags;
int blobtype;
_keybox_release_blob (blob); blob = NULL;
rc = _keybox_read_blob (&blob, hd->fp);
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
++*r_skipped;
continue; /* Skip too large records. */
}
if (rc)
break;
blobtype = blob_get_type (blob);
if (blobtype == KEYBOX_BLOBTYPE_HEADER)
continue;
if (want_blobtype && blobtype != want_blobtype)
continue;
blobflags = blob_get_blob_flags (blob);
if (!hd->ephemeral && (blobflags & 2))
continue; /* Not in ephemeral mode but blob is flagged ephemeral. */
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
break;
case KEYDB_SEARCH_MODE_EXACT:
uid_no = has_username (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAIL:
uid_no = has_mail (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILSUB:
uid_no = has_mail (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_SUBSTR:
uid_no = has_username (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
/* not yet implemented */
break;
case KEYDB_SEARCH_MODE_ISSUER:
if (has_issuer (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (has_issuer_sn (blob, desc[n].u.name,
sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SN:
if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SUBJECT:
if (has_subject (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
pk_no = has_short_kid (blob, desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_FPR:
case KEYDB_SEARCH_MODE_FPR20:
pk_no = has_fingerprint (blob, desc[n].u.fpr);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
if (has_keygrip (blob, desc[n].u.grip))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
goto found;
break;
default:
rc = gpg_error (GPG_ERR_INV_VALUE);
goto found;
}
}
continue;
found:
/* Record which DESC we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(r_descindex)
*r_descindex = n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
u32 kid[2];
if (desc[n].skipfnc
&& blob_get_first_keyid (blob, kid)
&& desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no))
break;
}
if (n == ndesc)
break; /* got it */
}
if (!rc)
{
hd->found.blob = blob;
hd->found.pk_no = pk_no;
hd->found.uid_no = uid_no;
}
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
_keybox_release_blob (blob);
hd->eof = 1;
}
else
{
_keybox_release_blob (blob);
hd->error = rc;
}
if (sn_array)
release_sn_array (sn_array, ndesc);
return rc;
}
/*
Functions to return a certificate or a keyblock. To be used after
a successful search operation.
*/
/* Return the last found keyblock. Returns 0 on success and stores a
new iobuf at R_IOBUF and a signature status vector at R_SIGSTATUS
in that case. R_UID_NO and R_PK_NO are used to retun the number of
the key or user id which was matched the search criteria; if not
known they are set to 0. */
gpg_error_t
keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_pk_no, int *r_uid_no, u32 **r_sigstatus)
{
gpg_error_t err;
const unsigned char *buffer, *p;
size_t length;
size_t image_off, image_len;
size_t siginfo_off, siginfo_len;
u32 *sigstatus, n, n_sigs, sigilen;
*r_iobuf = NULL;
*r_sigstatus = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
image_off = get32 (buffer+8);
image_len = get32 (buffer+12);
if (image_off+image_len > length)
return gpg_error (GPG_ERR_TOO_SHORT);
err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO,
&siginfo_off, &siginfo_len);
if (err)
return err;
n_sigs = get16 (buffer + siginfo_off);
sigilen = get16 (buffer + siginfo_off + 2);
p = buffer + siginfo_off + 4;
sigstatus = xtrymalloc ((1+n_sigs) * sizeof *sigstatus);
if (!sigstatus)
return gpg_error_from_syserror ();
sigstatus[0] = n_sigs;
for (n=1; n <= n_sigs; n++, p += sigilen)
sigstatus[n] = get32 (p);
*r_pk_no = hd->found.pk_no;
*r_uid_no = hd->found.uid_no;
*r_sigstatus = sigstatus;
*r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len);
return 0;
}
#ifdef KEYBOX_WITH_X509
/*
Return the last found cert. Caller must free it.
*/
int
keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert)
{
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
int rc;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if (cert_off+cert_len > length)
return gpg_error (GPG_ERR_TOO_SHORT);
rc = ksba_reader_new (&reader);
if (rc)
return rc;
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
{
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
rc = ksba_cert_new (&cert);
if (rc)
{
ksba_reader_release (reader);
return rc;
}
rc = ksba_cert_read_der (cert, reader);
if (rc)
{
ksba_cert_release (cert);
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
*r_cert = cert;
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/* Return the flags named WHAT at the address of VALUE. IDX is used
only for certain flags and should be 0 if not required. */
int
keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value)
{
const unsigned char *buffer;
size_t length;
gpg_err_code_t ec;
(void)idx; /* Not yet used. */
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
ec = get_flag_from_image (buffer, length, what, value);
return ec? gpg_error (ec):0;
}
off_t
keybox_offset (KEYBOX_HANDLE hd)
{
if (!hd->fp)
return 0;
return ftello (hd->fp);
}
gpg_error_t
keybox_seek (KEYBOX_HANDLE hd, off_t offset)
{
int err;
if (hd->error)
return hd->error; /* still in error state */
if (! hd->fp)
{
if (offset == 0)
/* No need to open the file. An unopened file is effectively at
offset 0. */
return 0;
hd->fp = fopen (hd->kb->fname, "rb");
if (!hd->fp)
{
hd->error = gpg_error_from_syserror ();
return hd->error;
}
}
err = fseeko (hd->fp, offset, SEEK_SET);
hd->error = gpg_error_from_errno (err);
return hd->error;
}
diff --git a/kbx/keybox-util.c b/kbx/keybox-util.c
index 486753c0d..b71335b7d 100644
--- a/kbx/keybox-util.c
+++ b/kbx/keybox-util.c
@@ -1,155 +1,155 @@
/* keybox-util.c - Utility functions for Keybox
* Copyright (C) 2001 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_DOSISH_SYSTEM
# define WIN32_LEAN_AND_MEAN /* We only need the OS core stuff. */
# include <windows.h>
#endif
#include "keybox-defs.h"
-#include "utilproto.h"
+#include "../common/utilproto.h"
static void *(*alloc_func)(size_t n) = malloc;
static void *(*realloc_func)(void *p, size_t n) = realloc;
static void (*free_func)(void*) = free;
void
keybox_set_malloc_hooks ( void *(*new_alloc_func)(size_t n),
void *(*new_realloc_func)(void *p, size_t n),
void (*new_free_func)(void*) )
{
alloc_func = new_alloc_func;
realloc_func = new_realloc_func;
free_func = new_free_func;
}
void *
_keybox_malloc (size_t n)
{
return alloc_func (n);
}
void *
_keybox_realloc (void *a, size_t n)
{
return realloc_func (a, n);
}
void *
_keybox_calloc (size_t n, size_t m)
{
void *p = _keybox_malloc (n*m);
if (p)
memset (p, 0, n* m);
return p;
}
void
_keybox_free (void *p)
{
if (p)
free_func (p);
}
/* Store the two malloced temporary file names used for keybox updates
of file FILENAME at R_BAKNAME and R_TMPNAME. On error an error
code is returned and NULL stored at R_BAKNAME and R_TMPNAME. If
FOR_KEYRING is true the returned names match those used by GnuPG's
keyring code. */
gpg_error_t
keybox_tmp_names (const char *filename, int for_keyring,
char **r_bakname, char **r_tmpname)
{
gpg_error_t err;
char *bak_name, *tmp_name;
*r_bakname = NULL;
*r_tmpname = NULL;
# ifdef USE_ONLY_8DOT3
/* Here is another Windoze bug?:
* you can't rename("pubring.kbx.tmp", "pubring.kbx");
* but rename("pubring.kbx.tmp", "pubring.aaa");
* works. So we replace ".kbx" by ".kb_" or ".k__". Note that we
* can't use ".bak" and ".tmp", because these suffixes are used by
* gpg's keyrings and would lead to a sharing violation or data
* corruption. If the name does not end in ".kbx" we assume working
* on a modern file system and append the suffix. */
{
const char *ext = for_keyring? EXTSEP_S GPGEXT_GPG : EXTSEP_S "kbx";
const char *b_ext = for_keyring? EXTSEP_S "bak" : EXTSEP_S "kb_";
const char *t_ext = for_keyring? EXTSEP_S "tmp" : EXTSEP_S "k__";
int repl;
if (strlen (ext) != 4 || strlen (b_ext) != 4)
BUG ();
repl = (strlen (filename) > 4
&& !strcmp (filename + strlen (filename) - 4, ext));
bak_name = xtrymalloc (strlen (filename) + (repl?0:4) + 1);
if (!bak_name)
return gpg_error_from_syserror ();
strcpy (bak_name, filename);
strcpy (bak_name + strlen (filename) - (repl?4:0), b_ext);
tmp_name = xtrymalloc (strlen (filename) + (repl?0:4) + 1);
if (!tmp_name)
{
err = gpg_error_from_syserror ();
xfree (bak_name);
return err;
}
strcpy (tmp_name, filename);
strcpy (tmp_name + strlen (filename) - (repl?4:0), t_ext);
}
# else /* Posix file names */
(void)for_keyring;
bak_name = xtrymalloc (strlen (filename) + 2);
if (!bak_name)
return gpg_error_from_syserror ();
strcpy (stpcpy (bak_name, filename), "~");
tmp_name = xtrymalloc (strlen (filename) + 5);
if (!tmp_name)
{
err = gpg_error_from_syserror ();
xfree (bak_name);
return err;
}
strcpy (stpcpy (tmp_name,filename), EXTSEP_S "tmp");
# endif /* Posix filename */
*r_bakname = bak_name;
*r_tmpname = tmp_name;
return 0;
}
gpg_error_t
keybox_file_rename (const char *oldname, const char *newname,
int *block_signals)
{
return gnupg_rename_file (oldname, newname, block_signals);
}
diff --git a/scd/Makefile.am b/scd/Makefile.am
index db096f6e0..cbd1f9f0f 100644
--- a/scd/Makefile.am
+++ b/scd/Makefile.am
@@ -1,51 +1,51 @@
# Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = ChangeLog-2011 scdaemon-w32info.rc
libexec_PROGRAMS = scdaemon
-AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBUSB_CPPFLAGS)
+AM_CPPFLAGS = $(LIBUSB_CPPFLAGS)
include $(top_srcdir)/am/cmacros.am
if HAVE_W32_SYSTEM
resource_objs += scdaemon-w32info.o
endif
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
$(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS)
card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c
scdaemon_SOURCES = \
scdaemon.c scdaemon.h \
command.c \
atr.c atr.h \
apdu.c apdu.h \
ccid-driver.c ccid-driver.h \
iso7816.c iso7816.h \
app.c app-common.h app-help.c $(card_apps)
scdaemon_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(LIBUSB_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(DL_LIBS) $(NETLIBS) $(LIBICONV) $(resource_objs)
diff --git a/scd/apdu.c b/scd/apdu.c
index c8ce68473..147bf7382 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -1,3264 +1,3264 @@
/* apdu.c - ISO 7816 APDU functions and low level I/O
* Copyright (C) 2003, 2004, 2008, 2009, 2010,
* 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* NOTE: This module is also used by other software, thus the use of
the macro USE_NPTH is mandatory. For GnuPG this macro is
guaranteed to be defined true. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#ifdef USE_NPTH
# include <unistd.h>
# include <fcntl.h>
# include <npth.h>
#endif
/* If requested include the definitions for the remote APDU protocol
code. */
#ifdef USE_G10CODE_RAPDU
#include "rapdu.h"
#endif /*USE_G10CODE_RAPDU*/
#if defined(GNUPG_SCD_MAIN_HEADER)
#include GNUPG_SCD_MAIN_HEADER
#elif GNUPG_MAJOR_VERSION == 1
/* This is used with GnuPG version < 1.9. The code has been source
copied from the current GnuPG >= 1.9 and is maintained over
there. */
-#include "options.h"
+#include "../common/options.h"
#include "errors.h"
#include "memory.h"
-#include "util.h"
-#include "i18n.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
#include "dynload.h"
#include "cardglue.h"
#else /* GNUPG_MAJOR_VERSION != 1 */
#include "scdaemon.h"
-#include "exechelp.h"
+#include "../common/exechelp.h"
#endif /* GNUPG_MAJOR_VERSION != 1 */
-#include "host2net.h"
+#include "../common/host2net.h"
#include "iso7816.h"
#include "apdu.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
struct dev_list {
struct ccid_dev_table *ccid_table;
const char *portstr;
int idx;
int idx_max;
};
#define MAX_READER 4 /* Number of readers we support concurrently. */
#if defined(_WIN32) || defined(__CYGWIN__)
#define DLSTDCALL __stdcall
#else
#define DLSTDCALL
#endif
#if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__)
typedef unsigned int pcsc_dword_t;
#else
typedef unsigned long pcsc_dword_t;
#endif
/* A structure to collect information pertaining to one reader
slot. */
struct reader_table_s {
int used; /* True if slot is used. */
unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */
/* Function pointers intialized to the various backends. */
int (*connect_card)(int);
int (*disconnect_card)(int);
int (*close_reader)(int);
int (*reset_reader)(int);
int (*get_status_reader)(int, unsigned int *, int);
int (*send_apdu_reader)(int,unsigned char *,size_t,
unsigned char *, size_t *, pininfo_t *);
int (*check_pinpad)(int, int, pininfo_t *);
void (*dump_status_reader)(int);
int (*set_progress_cb)(int, gcry_handler_progress_t, void*);
int (*pinpad_verify)(int, int, int, int, int, pininfo_t *);
int (*pinpad_modify)(int, int, int, int, int, pininfo_t *);
struct {
ccid_driver_t handle;
} ccid;
struct {
long context;
long card;
pcsc_dword_t protocol;
pcsc_dword_t verify_ioctl;
pcsc_dword_t modify_ioctl;
int pinmin;
int pinmax;
} pcsc;
#ifdef USE_G10CODE_RAPDU
struct {
rapdu_t handle;
} rapdu;
#endif /*USE_G10CODE_RAPDU*/
char *rdrname; /* Name of the connected reader or NULL if unknown. */
unsigned int is_t0:1; /* True if we know that we are running T=0. */
unsigned int is_spr532:1; /* True if we know that the reader is a SPR532. */
unsigned int pinpad_varlen_supported:1; /* True if we know that the reader
supports variable length pinpad
input. */
unsigned int require_get_status:1;
unsigned char atr[33];
size_t atrlen; /* A zero length indicates that the ATR has
not yet been read; i.e. the card is not
ready for use. */
#ifdef USE_NPTH
npth_mutex_t lock;
#endif
};
typedef struct reader_table_s *reader_table_t;
/* A global table to keep track of active readers. */
static struct reader_table_s reader_table[MAX_READER];
#ifdef USE_NPTH
static npth_mutex_t reader_table_lock;
#endif
/* PC/SC constants and function pointer. */
#define PCSC_SCOPE_USER 0
#define PCSC_SCOPE_TERMINAL 1
#define PCSC_SCOPE_SYSTEM 2
#define PCSC_SCOPE_GLOBAL 3
#define PCSC_PROTOCOL_T0 1
#define PCSC_PROTOCOL_T1 2
#ifdef HAVE_W32_SYSTEM
# define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */
#else
# define PCSC_PROTOCOL_RAW 4
#endif
#define PCSC_SHARE_EXCLUSIVE 1
#define PCSC_SHARE_SHARED 2
#define PCSC_SHARE_DIRECT 3
#define PCSC_LEAVE_CARD 0
#define PCSC_RESET_CARD 1
#define PCSC_UNPOWER_CARD 2
#define PCSC_EJECT_CARD 3
#ifdef HAVE_W32_SYSTEM
# define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */
# define PCSC_ABSENT 0x0001 /* Card is absent. */
# define PCSC_PRESENT 0x0002 /* Card is present. */
# define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0004 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */
#else
# define PCSC_UNKNOWN 0x0001
# define PCSC_ABSENT 0x0002 /* Card is absent. */
# define PCSC_PRESENT 0x0004 /* Card is present. */
# define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0010 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */
#endif
#define PCSC_STATE_UNAWARE 0x0000 /* Want status. */
#define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */
#define PCSC_STATE_CHANGED 0x0002 /* State has changed. */
#define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */
#define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */
#define PCSC_STATE_EMPTY 0x0010 /* Card removed. */
#define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */
#define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */
#define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */
#define PCSC_STATE_INUSE 0x0100 /* Shared mode. */
#define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */
#ifdef HAVE_W32_SYSTEM
# define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */
#endif
/* Some PC/SC error codes. */
#define PCSC_E_CANCELLED 0x80100002
#define PCSC_E_CANT_DISPOSE 0x8010000E
#define PCSC_E_INSUFFICIENT_BUFFER 0x80100008
#define PCSC_E_INVALID_ATR 0x80100015
#define PCSC_E_INVALID_HANDLE 0x80100003
#define PCSC_E_INVALID_PARAMETER 0x80100004
#define PCSC_E_INVALID_TARGET 0x80100005
#define PCSC_E_INVALID_VALUE 0x80100011
#define PCSC_E_NO_MEMORY 0x80100006
#define PCSC_E_UNKNOWN_READER 0x80100009
#define PCSC_E_TIMEOUT 0x8010000A
#define PCSC_E_SHARING_VIOLATION 0x8010000B
#define PCSC_E_NO_SMARTCARD 0x8010000C
#define PCSC_E_UNKNOWN_CARD 0x8010000D
#define PCSC_E_PROTO_MISMATCH 0x8010000F
#define PCSC_E_NOT_READY 0x80100010
#define PCSC_E_SYSTEM_CANCELLED 0x80100012
#define PCSC_E_NOT_TRANSACTED 0x80100016
#define PCSC_E_READER_UNAVAILABLE 0x80100017
#define PCSC_E_NO_SERVICE 0x8010001D
#define PCSC_E_SERVICE_STOPPED 0x8010001E
#define PCSC_W_REMOVED_CARD 0x80100069
/* Fix pcsc-lite ABI incompatibilty. */
#ifndef SCARD_CTL_CODE
#ifdef _WIN32
#include <winioctl.h>
#define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#else
#define SCARD_CTL_CODE(code) (0x42000000 + (code))
#endif
#endif
#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
#define FEATURE_VERIFY_PIN_DIRECT 0x06
#define FEATURE_MODIFY_PIN_DIRECT 0x07
#define FEATURE_GET_TLV_PROPERTIES 0x12
#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2
#define PCSCv2_PART10_PROPERTY_bTimeOut2 3
#define PCSCv2_PART10_PROPERTY_bMinPINSize 6
#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7
#define PCSCv2_PART10_PROPERTY_wIdVendor 11
#define PCSCv2_PART10_PROPERTY_wIdProduct 12
/* The PC/SC error is defined as a long as per specs. Due to left
shifts bit 31 will get sign extended. We use this mask to fix
it. */
#define PCSC_ERR_MASK(a) ((a) & 0xffffffff)
struct pcsc_io_request_s
{
unsigned long protocol;
unsigned long pci_len;
};
typedef struct pcsc_io_request_s *pcsc_io_request_t;
#ifdef __APPLE__
#pragma pack(1)
#endif
struct pcsc_readerstate_s
{
const char *reader;
void *user_data;
pcsc_dword_t current_state;
pcsc_dword_t event_state;
pcsc_dword_t atrlen;
unsigned char atr[33];
};
#ifdef __APPLE__
#pragma pack()
#endif
typedef struct pcsc_readerstate_s *pcsc_readerstate_t;
long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope,
const void *reserved1,
const void *reserved2,
long *r_context);
long (* DLSTDCALL pcsc_release_context) (long context);
long (* DLSTDCALL pcsc_list_readers) (long context,
const char *groups,
char *readers, pcsc_dword_t*readerslen);
long (* DLSTDCALL pcsc_get_status_change) (long context,
pcsc_dword_t timeout,
pcsc_readerstate_t readerstates,
pcsc_dword_t nreaderstates);
long (* DLSTDCALL pcsc_connect) (long context,
const char *reader,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
long *r_card,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_reconnect) (long card,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
pcsc_dword_t initialization,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_disconnect) (long card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_status) (long card,
char *reader, pcsc_dword_t *readerlen,
pcsc_dword_t *r_state,
pcsc_dword_t *r_protocol,
unsigned char *atr, pcsc_dword_t *atrlen);
long (* DLSTDCALL pcsc_begin_transaction) (long card);
long (* DLSTDCALL pcsc_end_transaction) (long card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_transmit) (long card,
const pcsc_io_request_t send_pci,
const unsigned char *send_buffer,
pcsc_dword_t send_len,
pcsc_io_request_t recv_pci,
unsigned char *recv_buffer,
pcsc_dword_t *recv_len);
long (* DLSTDCALL pcsc_set_timeout) (long context,
pcsc_dword_t timeout);
long (* DLSTDCALL pcsc_control) (long card,
pcsc_dword_t control_code,
const void *send_buffer,
pcsc_dword_t send_len,
void *recv_buffer,
pcsc_dword_t recv_len,
pcsc_dword_t *bytes_returned);
/* Prototypes. */
static int pcsc_vendor_specific_init (int slot);
static int pcsc_get_status (int slot, unsigned int *status, int on_wire);
static int reset_pcsc_reader (int slot);
static int apdu_get_status_internal (int slot, int hang, unsigned int *status,
int on_wire);
static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo);
static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
/*
Helper
*/
static int
lock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_lock (&reader_table[slot].lock);
if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static int
trylock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_trylock (&reader_table[slot].lock);
if (err == EBUSY)
return SW_HOST_BUSY;
else if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static void
unlock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_unlock (&reader_table[slot].lock);
if (err)
log_error ("failed to release apdu lock: %s\n", strerror (errno));
#endif /*USE_NPTH*/
}
/* Find an unused reader slot for PORTSTR and put it into the reader
table. Return -1 on error or the index into the reader table.
Acquire slot's lock on successful return. Caller needs to unlock it. */
static int
new_reader_slot (void)
{
int i, reader = -1;
for (i=0; i < MAX_READER; i++)
if (!reader_table[i].used)
{
reader = i;
reader_table[reader].used = 1;
break;
}
if (reader == -1)
{
log_error ("new_reader_slot: out of slots\n");
return -1;
}
if (lock_slot (reader))
{
reader_table[reader].used = 0;
return -1;
}
reader_table[reader].connect_card = NULL;
reader_table[reader].disconnect_card = NULL;
reader_table[reader].close_reader = NULL;
reader_table[reader].reset_reader = NULL;
reader_table[reader].get_status_reader = NULL;
reader_table[reader].send_apdu_reader = NULL;
reader_table[reader].check_pinpad = check_pcsc_pinpad;
reader_table[reader].dump_status_reader = NULL;
reader_table[reader].set_progress_cb = NULL;
reader_table[reader].pinpad_verify = pcsc_pinpad_verify;
reader_table[reader].pinpad_modify = pcsc_pinpad_modify;
reader_table[reader].is_t0 = 1;
reader_table[reader].is_spr532 = 0;
reader_table[reader].pinpad_varlen_supported = 0;
reader_table[reader].require_get_status = 1;
reader_table[reader].pcsc.verify_ioctl = 0;
reader_table[reader].pcsc.modify_ioctl = 0;
reader_table[reader].pcsc.pinmin = -1;
reader_table[reader].pcsc.pinmax = -1;
return reader;
}
static void
dump_reader_status (int slot)
{
if (!opt.verbose)
return;
if (reader_table[slot].dump_status_reader)
reader_table[slot].dump_status_reader (slot);
if (reader_table[slot].atrlen)
{
log_info ("slot %d: ATR=", slot);
log_printhex ("", reader_table[slot].atr, reader_table[slot].atrlen);
}
}
static const char *
host_sw_string (long err)
{
switch (err)
{
case 0: return "okay";
case SW_HOST_OUT_OF_CORE: return "out of core";
case SW_HOST_INV_VALUE: return "invalid value";
case SW_HOST_NO_DRIVER: return "no driver";
case SW_HOST_NOT_SUPPORTED: return "not supported";
case SW_HOST_LOCKING_FAILED: return "locking failed";
case SW_HOST_BUSY: return "busy";
case SW_HOST_NO_CARD: return "no card";
case SW_HOST_CARD_INACTIVE: return "card inactive";
case SW_HOST_CARD_IO_ERROR: return "card I/O error";
case SW_HOST_GENERAL_ERROR: return "general error";
case SW_HOST_NO_READER: return "no reader";
case SW_HOST_ABORTED: return "aborted";
case SW_HOST_NO_PINPAD: return "no pinpad";
case SW_HOST_ALREADY_CONNECTED: return "already connected";
default: return "unknown host status error";
}
}
const char *
apdu_strerror (int rc)
{
switch (rc)
{
case SW_EOF_REACHED : return "eof reached";
case SW_EEPROM_FAILURE : return "eeprom failure";
case SW_WRONG_LENGTH : return "wrong length";
case SW_CHV_WRONG : return "CHV wrong";
case SW_CHV_BLOCKED : return "CHV blocked";
case SW_REF_DATA_INV : return "referenced data invalidated";
case SW_USE_CONDITIONS : return "use conditions not satisfied";
case SW_BAD_PARAMETER : return "bad parameter";
case SW_NOT_SUPPORTED : return "not supported";
case SW_FILE_NOT_FOUND : return "file not found";
case SW_RECORD_NOT_FOUND:return "record not found";
case SW_REF_NOT_FOUND : return "reference not found";
case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file";
case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure.";
case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1";
case SW_BAD_LC : return "Lc inconsistent with P0,P1";
case SW_BAD_P0_P1 : return "bad P0,P1";
case SW_INS_NOT_SUP : return "instruction not supported";
case SW_CLA_NOT_SUP : return "class not supported";
case SW_SUCCESS : return "success";
default:
if ((rc & ~0x00ff) == SW_MORE_DATA)
return "more data available";
if ( (rc & 0x10000) )
return host_sw_string (rc);
return "unknown status error";
}
}
/*
PC/SC Interface
*/
static const char *
pcsc_error_string (long err)
{
const char *s;
if (!err)
return "okay";
if ((err & 0x80100000) != 0x80100000)
return "invalid PC/SC error code";
err &= 0xffff;
switch (err)
{
case 0x0002: s = "cancelled"; break;
case 0x000e: s = "can't dispose"; break;
case 0x0008: s = "insufficient buffer"; break;
case 0x0015: s = "invalid ATR"; break;
case 0x0003: s = "invalid handle"; break;
case 0x0004: s = "invalid parameter"; break;
case 0x0005: s = "invalid target"; break;
case 0x0011: s = "invalid value"; break;
case 0x0006: s = "no memory"; break;
case 0x0013: s = "comm error"; break;
case 0x0001: s = "internal error"; break;
case 0x0014: s = "unknown error"; break;
case 0x0007: s = "waited too long"; break;
case 0x0009: s = "unknown reader"; break;
case 0x000a: s = "timeout"; break;
case 0x000b: s = "sharing violation"; break;
case 0x000c: s = "no smartcard"; break;
case 0x000d: s = "unknown card"; break;
case 0x000f: s = "proto mismatch"; break;
case 0x0010: s = "not ready"; break;
case 0x0012: s = "system cancelled"; break;
case 0x0016: s = "not transacted"; break;
case 0x0017: s = "reader unavailable"; break;
case 0x0065: s = "unsupported card"; break;
case 0x0066: s = "unresponsive card"; break;
case 0x0067: s = "unpowered card"; break;
case 0x0068: s = "reset card"; break;
case 0x0069: s = "removed card"; break;
case 0x006a: s = "inserted card"; break;
case 0x001f: s = "unsupported feature"; break;
case 0x0019: s = "PCI too small"; break;
case 0x001a: s = "reader unsupported"; break;
case 0x001b: s = "duplicate reader"; break;
case 0x001c: s = "card unsupported"; break;
case 0x001d: s = "no service"; break;
case 0x001e: s = "service stopped"; break;
default: s = "unknown PC/SC error code"; break;
}
return s;
}
/* Map PC/SC error codes to our special host status words. */
static int
pcsc_error_to_sw (long ec)
{
int rc;
switch ( PCSC_ERR_MASK (ec) )
{
case 0: rc = 0; break;
case PCSC_E_CANCELLED: rc = SW_HOST_ABORTED; break;
case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break;
case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case PCSC_E_NO_SERVICE:
case PCSC_E_SERVICE_STOPPED:
case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break;
case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break;
case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break;
case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break;
case PCSC_E_INVALID_TARGET:
case PCSC_E_INVALID_VALUE:
case PCSC_E_INVALID_HANDLE:
case PCSC_E_INVALID_PARAMETER:
case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static void
dump_pcsc_reader_status (int slot)
{
if (reader_table[slot].pcsc.card)
{
log_info ("reader slot %d: active protocol:", slot);
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0))
log_printf (" T0");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
log_printf (" T1");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW))
log_printf (" raw");
log_printf ("\n");
}
else
log_info ("reader slot %d: not connected\n", slot);
}
static int
pcsc_get_status (int slot, unsigned int *status, int on_wire)
{
long err;
struct pcsc_readerstate_s rdrstates[1];
(void)on_wire;
memset (rdrstates, 0, sizeof *rdrstates);
rdrstates[0].reader = reader_table[slot].rdrname;
rdrstates[0].current_state = PCSC_STATE_UNAWARE;
err = pcsc_get_status_change (reader_table[slot].pcsc.context,
0,
rdrstates, 1);
if (err == PCSC_E_TIMEOUT)
err = 0; /* Timeout is no error error here. */
if (err)
{
log_error ("pcsc_get_status_change failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
/* log_debug */
/* ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", */
/* (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", */
/* (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", */
/* (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", */
/* (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", */
/* (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", */
/* (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", */
/* (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", */
/* (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", */
/* (rdrstates[0].event_state & PCSC_STATE_INUSE)? " unuse":"", */
/* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */
*status = 0;
if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) )
{
*status |= APDU_CARD_PRESENT;
if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) )
*status |= APDU_CARD_ACTIVE;
}
#ifndef HAVE_W32_SYSTEM
/* We indicate a useful card if it is not in use by another
application. This is because we only use exclusive access
mode. */
if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)
&& !(rdrstates[0].event_state & PCSC_STATE_INUSE) )
*status |= APDU_CARD_USABLE;
#else
/* Some winscard drivers may set EXCLUSIVE and INUSE at the same
time when we are the only user (SCM SCR335) under Windows. */
if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
*status |= APDU_CARD_USABLE;
#endif
return 0;
}
/* Send the APDU of length APDULEN to SLOT and return a maximum of
*BUFLEN data in BUFFER, the actual returned size will be stored at
BUFLEN. Returns: A status word. */
static int
pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
struct pcsc_io_request_s send_pci;
pcsc_dword_t recv_len;
(void)pininfo;
if (!reader_table[slot].atrlen
&& (err = reset_pcsc_reader (slot)))
return err;
if (DBG_CARD_IO)
log_printhex (" PCSC_data:", apdu, apdulen);
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
send_pci.protocol = PCSC_PROTOCOL_T1;
else
send_pci.protocol = PCSC_PROTOCOL_T0;
send_pci.pci_len = sizeof send_pci;
recv_len = *buflen;
err = pcsc_transmit (reader_table[slot].pcsc.card,
&send_pci, apdu, apdulen,
NULL, buffer, &recv_len);
*buflen = recv_len;
if (err)
log_error ("pcsc_transmit failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
/* Do some control with the value of IOCTL_CODE to the card inserted
to SLOT. Input buffer is specified by CNTLBUF of length LEN.
Output buffer is specified by BUFFER of length *BUFLEN, and the
actual output size will be stored at BUFLEN. Returns: A status word.
This routine is used for PIN pad input support. */
static int
control_pcsc (int slot, pcsc_dword_t ioctl_code,
const unsigned char *cntlbuf, size_t len,
unsigned char *buffer, pcsc_dword_t *buflen)
{
long err;
err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code,
cntlbuf, len, buffer, buflen? *buflen:0, buflen);
if (err)
{
log_error ("pcsc_control failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
return 0;
}
static int
close_pcsc_reader (int slot)
{
pcsc_release_context (reader_table[slot].pcsc.context);
return 0;
}
/* Connect a PC/SC card. */
static int
connect_pcsc_card (int slot)
{
long err;
assert (slot >= 0 && slot < MAX_READER);
if (reader_table[slot].pcsc.card)
return SW_HOST_ALREADY_CONNECTED;
reader_table[slot].atrlen = 0;
reader_table[slot].is_t0 = 0;
err = pcsc_connect (reader_table[slot].pcsc.context,
reader_table[slot].rdrname,
PCSC_SHARE_EXCLUSIVE,
PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1,
&reader_table[slot].pcsc.card,
&reader_table[slot].pcsc.protocol);
if (err)
{
reader_table[slot].pcsc.card = 0;
if (err != PCSC_E_NO_SMARTCARD)
log_error ("pcsc_connect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
}
else
{
char reader[250];
pcsc_dword_t readerlen, atrlen;
pcsc_dword_t card_state, card_protocol;
pcsc_vendor_specific_init (slot);
atrlen = DIM (reader_table[0].atr);
readerlen = sizeof reader -1 ;
err = pcsc_status (reader_table[slot].pcsc.card,
reader, &readerlen,
&card_state, &card_protocol,
reader_table[slot].atr, &atrlen);
if (err)
log_error ("pcsc_status failed: %s (0x%lx) %lu\n",
pcsc_error_string (err), err, (long unsigned int)readerlen);
else
{
if (atrlen > DIM (reader_table[0].atr))
log_bug ("ATR returned by pcsc_status is too large\n");
reader_table[slot].atrlen = atrlen;
reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
}
}
dump_reader_status (slot);
return pcsc_error_to_sw (err);
}
static int
disconnect_pcsc_card (int slot)
{
long err;
assert (slot >= 0 && slot < MAX_READER);
if (!reader_table[slot].pcsc.card)
return 0;
err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD);
if (err)
{
log_error ("pcsc_disconnect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return SW_HOST_CARD_IO_ERROR;
}
reader_table[slot].pcsc.card = 0;
return 0;
}
/* Send an PC/SC reset command and return a status word on error or 0
on success. */
static int
reset_pcsc_reader (int slot)
{
int sw;
sw = disconnect_pcsc_card (slot);
if (!sw)
sw = connect_pcsc_card (slot);
return sw;
}
/* Examine reader specific parameters and initialize. This is mostly
for pinpad input. Called at opening the connection to the reader. */
static int
pcsc_vendor_specific_init (int slot)
{
unsigned char buf[256];
pcsc_dword_t len;
int sw;
int vendor = 0;
int product = 0;
pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1;
unsigned char *p;
len = sizeof (buf);
sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n",
sw);
return SW_NOT_SUPPORTED;
}
else
{
p = buf;
while (p < buf + len)
{
unsigned char code = *p++;
int l = *p++;
unsigned int v = 0;
if (l == 1)
v = p[0];
else if (l == 2)
v = buf16_to_uint (p);
else if (l == 4)
v = buf32_to_uint (p);
if (code == FEATURE_VERIFY_PIN_DIRECT)
reader_table[slot].pcsc.verify_ioctl = v;
else if (code == FEATURE_MODIFY_PIN_DIRECT)
reader_table[slot].pcsc.modify_ioctl = v;
else if (code == FEATURE_GET_TLV_PROPERTIES)
get_tlv_ioctl = v;
if (DBG_CARD_IO)
log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v);
p += l;
}
}
if (get_tlv_ioctl == (pcsc_dword_t)-1)
{
/*
* For system which doesn't support GET_TLV_PROPERTIES,
* we put some heuristics here.
*/
if (reader_table[slot].rdrname)
{
if (strstr (reader_table[slot].rdrname, "SPRx32"))
{
reader_table[slot].is_spr532 = 1;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "ST-2xxx"))
{
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "cyberJack")
|| strstr (reader_table[slot].rdrname, "DIGIPASS")
|| strstr (reader_table[slot].rdrname, "Gnuk")
|| strstr (reader_table[slot].rdrname, "KAAN"))
reader_table[slot].pinpad_varlen_supported = 1;
}
return 0;
}
len = sizeof (buf);
sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw);
return SW_NOT_SUPPORTED;
}
p = buf;
while (p < buf + len)
{
unsigned char tag = *p++;
int l = *p++;
unsigned int v = 0;
/* Umm... here is little endian, while the encoding above is big. */
if (l == 1)
v = p[0];
else if (l == 2)
v = (((unsigned int)p[1] << 8) | p[0]);
else if (l == 4)
v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize)
reader_table[slot].pcsc.pinmin = v;
else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize)
reader_table[slot].pcsc.pinmax = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor)
vendor = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct)
product = v;
if (DBG_CARD_IO)
log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v);
p += l;
}
if (vendor == VENDOR_VEGA && product == VEGA_ALPHA)
{
/*
* Please read the comment of ccid_vendor_specific_init in
* ccid-driver.c.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
cmd, sizeof (cmd), NULL, 0);
if (sw)
return SW_NOT_SUPPORTED;
}
else if (vendor == VENDOR_SCM && product == SCM_SPR532) /* SCM SPR532 */
{
reader_table[slot].is_spr532 = 1;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == 0x046a)
{
/* Cherry ST-2xxx (product == 0x003e) supports TPDU level
* exchange. Other products which only support short APDU level
* exchange only work with shorter keys like RSA 1024.
*/
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == 0x0c4b /* Tested with Reiner cyberJack GO */
|| vendor == 0x1a44 /* Tested with Vasco DIGIPASS 920 */
|| vendor == 0x234b /* Tested with FSIJ Gnuk Token */
|| vendor == 0x0d46 /* Tested with KAAN Advanced??? */)
reader_table[slot].pinpad_varlen_supported = 1;
return 0;
}
/* Open the PC/SC reader without using the wrapper. Returns -1 on
error or a slot number for the reader. */
static int
open_pcsc_reader (const char *portstr)
{
long err;
int slot;
char *list = NULL;
char *rdrname = NULL;
pcsc_dword_t nreader;
char *p;
slot = new_reader_slot ();
if (slot == -1)
return -1;
/* Fixme: Allocating a context for each slot is not required. One
global context should be sufficient. */
err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL,
&reader_table[slot].pcsc.context);
if (err)
{
log_error ("pcsc_establish_context failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1;
}
err = pcsc_list_readers (reader_table[slot].pcsc.context,
NULL, NULL, &nreader);
if (!err)
{
list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */
if (!list)
{
log_error ("error allocating memory for reader list\n");
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1 /*SW_HOST_OUT_OF_CORE*/;
}
err = pcsc_list_readers (reader_table[slot].pcsc.context,
NULL, list, &nreader);
}
if (err)
{
log_error ("pcsc_list_readers failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
xfree (list);
unlock_slot (slot);
return -1;
}
p = list;
while (nreader)
{
if (!*p && !p[1])
break;
log_info ("detected reader '%s'\n", p);
if (nreader < (strlen (p)+1))
{
log_error ("invalid response from pcsc_list_readers\n");
break;
}
if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr)))
rdrname = p;
nreader -= strlen (p)+1;
p += strlen (p) + 1;
}
if (!rdrname)
rdrname = list;
reader_table[slot].rdrname = xtrystrdup (rdrname);
if (!reader_table[slot].rdrname)
{
log_error ("error allocating memory for reader name\n");
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1;
}
xfree (list);
list = NULL;
reader_table[slot].pcsc.card = 0;
reader_table[slot].atrlen = 0;
reader_table[slot].connect_card = connect_pcsc_card;
reader_table[slot].disconnect_card = disconnect_pcsc_card;
reader_table[slot].close_reader = close_pcsc_reader;
reader_table[slot].reset_reader = reset_pcsc_reader;
reader_table[slot].get_status_reader = pcsc_get_status;
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
/* Check whether the reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. */
static int
check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
{
int r;
if (reader_table[slot].pcsc.pinmin >= 0)
pininfo->minlen = reader_table[slot].pcsc.pinmin;
if (reader_table[slot].pcsc.pinmax >= 0)
pininfo->maxlen = reader_table[slot].pcsc.pinmax;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0)
|| (command == ISO7816_CHANGE_REFERENCE_DATA
&& reader_table[slot].pcsc.modify_ioctl != 0))
r = 0; /* Success */
else
r = SW_NOT_SUPPORTED;
if (DBG_CARD_IO)
log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n",
(unsigned int)command, r);
if (reader_table[slot].pinpad_varlen_supported)
pininfo->fixedlen = 0;
return r;
}
#define PIN_VERIFY_STRUCTURE_SIZE 24
static int
pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_verify;
int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen;
/*
* The result buffer is only expected to have two-byte result on
* return. However, some implementation uses this buffer for lower
* layer too and it assumes that there is enough space for lower
* layer communication. Such an implementation fails for TPDU
* readers with "insufficient buffer", as it needs header and
* trailer. Six is the number for header + result + trailer (TPDU).
*/
unsigned char result[6];
pcsc_dword_t resultlen = 6;
int no_lc;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_verify = xtrymalloc (len);
if (!pin_verify)
return SW_HOST_OUT_OF_CORE;
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
pin_verify[0] = 0x00; /* bTimeOut */
pin_verify[1] = 0x00; /* bTimeOut2 */
pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_verify[4] = 0x00; /* bmPINLengthFormat */
pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_verify[7] |= 0x01; /* Max size reached. */
pin_verify[8] = 0x01; /* bNumberMessage: One message */
pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
pin_verify[11] = 0x00; /* bMsgIndex */
pin_verify[12] = 0x00; /* bTeoPrologue[0] */
pin_verify[13] = 0x00; /* bTeoPrologue[1] */
pin_verify[14] = pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
pin_verify[15] = pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
pin_verify[16] = 0x00; /* ulDataLength */
pin_verify[17] = 0x00; /* ulDataLength */
pin_verify[18] = 0x00; /* ulDataLength */
pin_verify[19] = class; /* abData[0] */
pin_verify[20] = ins; /* abData[1] */
pin_verify[21] = p0; /* abData[2] */
pin_verify[22] = p1; /* abData[3] */
pin_verify[23] = pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_verify[24], 0xff, pininfo->fixedlen);
else if (no_lc)
len--;
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl,
pin_verify, len, result, &resultlen);
xfree (pin_verify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#define PIN_MODIFY_STRUCTURE_SIZE 29
static int
pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_modify;
int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen;
unsigned char result[6]; /* See the comment at pinpad_verify. */
pcsc_dword_t resultlen = 6;
int no_lc;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_modify = xtrymalloc (len);
if (!pin_modify)
return SW_HOST_OUT_OF_CORE;
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
pin_modify[0] = 0x00; /* bTimeOut */
pin_modify[1] = 0x00; /* bTimeOut2 */
pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_modify[4] = 0x00; /* bmPINLengthFormat */
pin_modify[5] = 0x00; /* bInsertionOffsetOld */
pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */
pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_modify[9] = (p0 == 0 ? 0x03 : 0x01);
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_modify[10] |= 0x01; /* Max size reached. */
pin_modify[11] = 0x03; /* bNumberMessage: Three messages */
pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
pin_modify[14] = 0x00; /* bMsgIndex1 */
pin_modify[15] = 0x01; /* bMsgIndex2 */
pin_modify[16] = 0x02; /* bMsgIndex3 */
pin_modify[17] = 0x00; /* bTeoPrologue[0] */
pin_modify[18] = 0x00; /* bTeoPrologue[1] */
pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
pin_modify[20] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
pin_modify[21] = 0x00; /* ulDataLength */
pin_modify[22] = 0x00; /* ulDataLength */
pin_modify[23] = 0x00; /* ulDataLength */
pin_modify[24] = class; /* abData[0] */
pin_modify[25] = ins; /* abData[1] */
pin_modify[26] = p0; /* abData[2] */
pin_modify[27] = p1; /* abData[3] */
pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen);
else if (no_lc)
len--;
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, (int)pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl,
pin_modify, len, result, &resultlen);
xfree (pin_modify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#ifdef HAVE_LIBUSB
/*
Internal CCID driver interface.
*/
static void
dump_ccid_reader_status (int slot)
{
log_info ("reader slot %d: using ccid driver\n", slot);
}
static int
close_ccid_reader (int slot)
{
ccid_close_reader (reader_table[slot].ccid.handle);
return 0;
}
static int
reset_ccid_reader (int slot)
{
int err;
reader_table_t slotp = reader_table + slot;
unsigned char atr[33];
size_t atrlen;
err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen);
if (err)
return err;
/* If the reset was successful, update the ATR. */
assert (sizeof slotp->atr >= sizeof atr);
slotp->atrlen = atrlen;
memcpy (slotp->atr, atr, atrlen);
dump_reader_status (slot);
return 0;
}
static int
set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
reader_table_t slotp = reader_table + slot;
return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg);
}
static int
get_status_ccid (int slot, unsigned int *status, int on_wire)
{
int rc;
int bits;
rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire);
if (rc)
return rc;
if (bits == 0)
*status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE);
else if (bits == 1)
*status = APDU_CARD_PRESENT;
else
*status = 0;
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: Internal CCID driver error code. */
static int
send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
size_t maxbuflen;
/* If we don't have an ATR, we need to reset the reader first. */
if (!reader_table[slot].atrlen
&& (err = reset_ccid_reader (slot)))
return err;
if (DBG_CARD_IO)
log_printhex (" raw apdu:", apdu, apdulen);
maxbuflen = *buflen;
if (pininfo)
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, apdulen, pininfo,
buffer, maxbuflen, buflen);
else
err = ccid_transceive (reader_table[slot].ccid.handle,
apdu, apdulen,
buffer, maxbuflen, buflen);
if (err)
log_error ("ccid_transceive failed: (0x%lx)\n",
err);
return err;
}
/* Check whether the CCID reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
static int
check_ccid_pinpad (int slot, int command, pininfo_t *pininfo)
{
unsigned char apdu[] = { 0, 0, 0, 0x81 };
apdu[1] = command;
return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu,
sizeof apdu, pininfo, NULL, 0, NULL);
}
static int
ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
unsigned char apdu[4];
int err, sw;
unsigned char result[2];
size_t resultlen = 2;
apdu[0] = class;
apdu[1] = ins;
apdu[2] = p0;
apdu[3] = p1;
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, sizeof apdu, pininfo,
result, 2, &resultlen);
if (err)
return err;
if (resultlen < 2)
return SW_HOST_INCOMPLETE_CARD_RESPONSE;
sw = (result[resultlen-2] << 8) | result[resultlen-1];
return sw;
}
/* Open the reader and try to read an ATR. */
static int
open_ccid_reader (struct dev_list *dl)
{
int err;
int slot;
int require_get_status;
reader_table_t slotp;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
err = ccid_open_reader (dl->portstr, dl->idx, dl->ccid_table,
&slotp->ccid.handle, &slotp->rdrname);
if (err)
{
slotp->used = 0;
unlock_slot (slot);
return -1;
}
err = ccid_get_atr (slotp->ccid.handle,
slotp->atr, sizeof slotp->atr, &slotp->atrlen);
if (err)
{
slotp->atrlen = 0;
err = 0;
}
require_get_status = ccid_require_get_status (slotp->ccid.handle);
reader_table[slot].close_reader = close_ccid_reader;
reader_table[slot].reset_reader = reset_ccid_reader;
reader_table[slot].get_status_reader = get_status_ccid;
reader_table[slot].send_apdu_reader = send_apdu_ccid;
reader_table[slot].check_pinpad = check_ccid_pinpad;
reader_table[slot].dump_status_reader = dump_ccid_reader_status;
reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader;
reader_table[slot].pinpad_verify = ccid_pinpad_operation;
reader_table[slot].pinpad_modify = ccid_pinpad_operation;
/* Our CCID reader code does not support T=0 at all, thus reset the
flag. */
reader_table[slot].is_t0 = 0;
reader_table[slot].require_get_status = require_get_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
#endif /* HAVE_LIBUSB */
#ifdef USE_G10CODE_RAPDU
/*
The Remote APDU Interface.
This uses the Remote APDU protocol to contact a reader.
The port number is actually an index into the list of ports as
returned via the protocol.
*/
static int
rapdu_status_to_sw (int status)
{
int rc;
switch (status)
{
case RAPDU_STATUS_SUCCESS: rc = 0; break;
case RAPDU_STATUS_INVCMD:
case RAPDU_STATUS_INVPROT:
case RAPDU_STATUS_INVSEQ:
case RAPDU_STATUS_INVCOOKIE:
case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break;
case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break;
case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static int
close_rapdu_reader (int slot)
{
rapdu_release (reader_table[slot].rapdu.handle);
return 0;
}
static int
reset_rapdu_reader (int slot)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slotp = reader_table + slot;
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_error ("sending rapdu command RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command RESET failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
my_rapdu_get_status (int slot, unsigned int *status, int on_wire)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
int oldslot;
(void)on_wire;
slotp = reader_table + slot;
oldslot = rapdu_set_reader (slotp->rapdu.handle, slot);
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS);
rapdu_set_reader (slotp->rapdu.handle, oldslot);
if (err)
{
log_error ("sending rapdu command GET_STATUS failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command GET_STATUS failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
*status = msg->data[0];
rapdu_msg_release (msg);
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: APDU error code. */
static int
my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
size_t maxlen = *buflen;
slotp = reader_table + slot;
*buflen = 0;
if (DBG_CARD_IO)
log_printhex (" APDU_data:", apdu, apdulen);
if (apdulen < 4)
{
log_error ("rapdu_send_apdu: APDU is too short\n");
return SW_HOST_INV_VALUE;
}
err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen);
if (err)
{
log_error ("sending rapdu command APDU failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command APDU failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > maxlen)
{
log_error ("rapdu response apdu too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
*buflen = msg->datalen;
memcpy (buffer, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
open_rapdu_reader (int portno,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
int err;
int slot;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
slotp->rapdu.handle = rapdu_new ();
if (!slotp->rapdu.handle)
{
slotp->used = 0;
unlock_slot (slot);
return -1;
}
rapdu_set_reader (slotp->rapdu.handle, portno);
rapdu_set_iofunc (slotp->rapdu.handle,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
rapdu_set_cookie (slotp->rapdu.handle, cookie, length);
/* First try to get the current ATR, but if the card is inactive
issue a reset instead. */
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR);
if (err == RAPDU_STATUS_NEEDRESET)
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_info ("sending rapdu command GET_ATR/RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_info ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
log_info ("rapdu command GET ATR failed: %s\n",
rapdu_strerror (msg->cmd));
goto failure;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
goto failure;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
reader_table[slot].close_reader = close_rapdu_reader;
reader_table[slot].reset_reader = reset_rapdu_reader;
reader_table[slot].get_status_reader = my_rapdu_get_status;
reader_table[slot].send_apdu_reader = my_rapdu_send_apdu;
reader_table[slot].check_pinpad = NULL;
reader_table[slot].dump_status_reader = NULL;
reader_table[slot].pinpad_verify = NULL;
reader_table[slot].pinpad_modify = NULL;
dump_reader_status (slot);
rapdu_msg_release (msg);
unlock_slot (slot);
return slot;
failure:
rapdu_msg_release (msg);
rapdu_release (slotp->rapdu.handle);
slotp->used = 0;
unlock_slot (slot);
return -1;
}
#endif /*USE_G10CODE_RAPDU*/
/*
Driver Access
*/
gpg_error_t
apdu_dev_list_start (const char *portstr, struct dev_list **l_p)
{
struct dev_list *dl = xtrymalloc (sizeof (struct dev_list));
*l_p = NULL;
if (!dl)
return gpg_error_from_syserror ();
dl->portstr = portstr;
dl->idx = 0;
npth_mutex_lock (&reader_table_lock);
#ifdef HAVE_LIBUSB
if (opt.disable_ccid)
{
dl->ccid_table = NULL;
dl->idx_max = 1;
}
else
{
gpg_error_t err;
err = ccid_dev_scan (&dl->idx_max, &dl->ccid_table);
if (err)
return err;
if (dl->idx_max == 0)
{
/* If a CCID reader specification has been given, the user does
not want a fallback to other drivers. */
if (portstr && strlen (portstr) > 5 && portstr[4] == ':')
{
if (DBG_READER)
log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n");
xfree (dl);
npth_mutex_unlock (&reader_table_lock);
return gpg_error (GPG_ERR_ENODEV);
}
else
dl->idx_max = 1;
}
}
#else
dl->ccid_table = NULL;
dl->idx_max = 1;
#endif /* HAVE_LIBUSB */
*l_p = dl;
return 0;
}
void
apdu_dev_list_finish (struct dev_list *dl)
{
#ifdef HAVE_LIBUSB
if (dl->ccid_table)
ccid_dev_scan_finish (dl->ccid_table, dl->idx_max);
#endif
xfree (dl);
npth_mutex_unlock (&reader_table_lock);
}
/* Open the reader and return an internal slot number or -1 on
error. If PORTSTR is NULL we default to a suitable port (for ctAPI:
the first USB reader. For PC/SC the first listed reader). */
static int
apdu_open_one_reader (const char *portstr)
{
static int pcsc_api_loaded;
int slot;
if (DBG_READER)
log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr);
/* Lets try the PC/SC API */
if (!pcsc_api_loaded)
{
void *handle;
handle = dlopen (opt.pcsc_driver, RTLD_LAZY);
if (!handle)
{
log_error ("apdu_open_reader: failed to open driver '%s': %s\n",
opt.pcsc_driver, dlerror ());
return -1;
}
pcsc_establish_context = dlsym (handle, "SCardEstablishContext");
pcsc_release_context = dlsym (handle, "SCardReleaseContext");
pcsc_list_readers = dlsym (handle, "SCardListReaders");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_list_readers)
pcsc_list_readers = dlsym (handle, "SCardListReadersA");
#endif
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_get_status_change)
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA");
#endif
pcsc_connect = dlsym (handle, "SCardConnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_connect)
pcsc_connect = dlsym (handle, "SCardConnectA");
#endif
pcsc_reconnect = dlsym (handle, "SCardReconnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_reconnect)
pcsc_reconnect = dlsym (handle, "SCardReconnectA");
#endif
pcsc_disconnect = dlsym (handle, "SCardDisconnect");
pcsc_status = dlsym (handle, "SCardStatus");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_status)
pcsc_status = dlsym (handle, "SCardStatusA");
#endif
pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction");
pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
pcsc_transmit = dlsym (handle, "SCardTransmit");
pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
pcsc_control = dlsym (handle, "SCardControl");
if (!pcsc_establish_context
|| !pcsc_release_context
|| !pcsc_list_readers
|| !pcsc_get_status_change
|| !pcsc_connect
|| !pcsc_reconnect
|| !pcsc_disconnect
|| !pcsc_status
|| !pcsc_begin_transaction
|| !pcsc_end_transaction
|| !pcsc_transmit
|| !pcsc_control
/* || !pcsc_set_timeout */)
{
/* Note that set_timeout is currently not used and also not
available under Windows. */
log_error ("apdu_open_reader: invalid PC/SC driver "
"(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
!!pcsc_establish_context,
!!pcsc_release_context,
!!pcsc_list_readers,
!!pcsc_get_status_change,
!!pcsc_connect,
!!pcsc_reconnect,
!!pcsc_disconnect,
!!pcsc_status,
!!pcsc_begin_transaction,
!!pcsc_end_transaction,
!!pcsc_transmit,
!!pcsc_set_timeout,
!!pcsc_control );
dlclose (handle);
return -1;
}
pcsc_api_loaded = 1;
}
slot = open_pcsc_reader (portstr);
if (DBG_READER)
log_debug ("leave: apdu_open_reader => slot=%d [pc/sc]\n", slot);
return slot;
}
int
apdu_open_reader (struct dev_list *dl, int app_empty)
{
int slot;
#ifdef HAVE_LIBUSB
if (dl->ccid_table)
{ /* CCID readers. */
int readerno;
/* See whether we want to use the reader ID string or a reader
number. A readerno of -1 indicates that the reader ID string is
to be used. */
if (dl->portstr && strchr (dl->portstr, ':'))
readerno = -1; /* We want to use the readerid. */
else if (dl->portstr)
{
readerno = atoi (dl->portstr);
if (readerno < 0)
{
return -1;
}
}
else
readerno = 0; /* Default. */
if (readerno > 0)
{ /* Use single, the specific reader. */
if (readerno >= dl->idx_max)
return -1;
dl->idx = readerno;
dl->portstr = NULL;
slot = open_ccid_reader (dl);
dl->idx = dl->idx_max;
if (slot >= 0)
return slot;
else
return -1;
}
while (dl->idx < dl->idx_max)
{
unsigned int bai = ccid_get_BAI (dl->idx, dl->ccid_table);
if (DBG_READER)
log_debug ("apdu_open_reader: BAI=%x\n", bai);
/* Check identity by BAI against already opened HANDLEs. */
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used
&& reader_table[slot].ccid.handle
&& ccid_compare_BAI (reader_table[slot].ccid.handle, bai))
break;
if (slot == MAX_READER)
{ /* Found a new device. */
if (DBG_READER)
log_debug ("apdu_open_reader: new device=%x\n", bai);
slot = open_ccid_reader (dl);
dl->idx++;
if (slot >= 0)
return slot;
else
{
/* Skip this reader. */
log_error ("ccid open error: skip\n");
continue;
}
}
else
dl->idx++;
}
/* Not found. Try one for PC/SC, only when it's the initial scan. */
if (app_empty && dl->idx == dl->idx_max)
{
dl->idx++;
slot = apdu_open_one_reader (dl->portstr);
}
else
slot = -1;
}
else
#endif
{ /* PC/SC readers. */
if (app_empty && dl->idx == 0)
{
dl->idx++;
slot = apdu_open_one_reader (dl->portstr);
}
else
slot = -1;
}
return slot;
}
/* Open an remote reader and return an internal slot number or -1 on
error. This function is an alternative to apdu_open_reader and used
with remote readers only. Note that the supplied CLOSEFNC will
only be called once and the slot will not be valid afther this.
If PORTSTR is NULL we default to the first available port.
*/
int
apdu_open_remote_reader (const char *portstr,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
#ifdef USE_G10CODE_RAPDU
return open_rapdu_reader (portstr? atoi (portstr) : 0,
cookie, length,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
#else
(void)portstr;
(void)cookie;
(void)length;
(void)readfnc;
(void)readfnc_value;
(void)writefnc;
(void)writefnc_value;
(void)closefnc;
(void)closefnc_value;
#ifdef _WIN32
errno = ENOENT;
#else
errno = ENOSYS;
#endif
return -1;
#endif
}
int
apdu_close_reader (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_close_reader: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
sw = apdu_disconnect (slot);
if (sw)
{
/*
* When the reader/token was removed it might come here.
* It should go through to call CLOSE_READER even if we got an error.
*/
if (DBG_READER)
log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw);
}
if (reader_table[slot].close_reader)
{
sw = reader_table[slot].close_reader (slot);
reader_table[slot].used = 0;
if (DBG_READER)
log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw);
return sw;
}
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n");
return SW_HOST_NOT_SUPPORTED;
}
/* Function suitable for a cleanup function to close all reader. It
should not be used if the reader will be opened again. The reason
for implementing this to properly close USB devices so that they
will startup the next time without error. */
void
apdu_prepare_exit (void)
{
static int sentinel;
int slot;
if (!sentinel)
{
sentinel = 1;
npth_mutex_lock (&reader_table_lock);
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used)
{
apdu_disconnect (slot);
if (reader_table[slot].close_reader)
reader_table[slot].close_reader (slot);
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
}
npth_mutex_unlock (&reader_table_lock);
sentinel = 0;
}
}
/* Enumerate all readers and return information on whether this reader
is in use. The caller should start with SLOT set to 0 and
increment it with each call until an error is returned. */
int
apdu_enum_reader (int slot, int *used)
{
if (slot < 0 || slot >= MAX_READER)
return SW_HOST_NO_DRIVER;
*used = reader_table[slot].used;
return 0;
}
/* Connect a card. This is used to power up the card and make sure
that an ATR is available. Depending on the reader backend it may
return an error for an inactive card or if no card is available.
Return -1 on error. Return 1 if reader requires get_status to
watch card removal. Return 0 if it's a token (always with a card),
or it supports INTERRUPT endpoint to watch card removal.
*/
int
apdu_connect (int slot)
{
int sw = 0;
unsigned int status = 0;
if (DBG_READER)
log_debug ("enter: apdu_connect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n");
return -1;
}
/* Only if the access method provides a connect function we use it.
If not, we expect that the card has been implicitly connected by
apdu_open_reader. */
if (reader_table[slot].connect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].connect_card (slot);
unlock_slot (slot);
}
}
/* We need to call apdu_get_status_internal, so that the last-status
machinery gets setup properly even if a card is inserted while
scdaemon is fired up and apdu_get_status has not yet been called.
Without that we would force a reset of the card with the next
call to apdu_get_status. */
if (!sw)
sw = apdu_get_status_internal (slot, 1, &status, 1);
if (sw)
;
else if (!(status & APDU_CARD_PRESENT))
sw = SW_HOST_NO_CARD;
else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE))
sw = SW_HOST_CARD_INACTIVE;
if (sw == SW_HOST_CARD_INACTIVE)
{
/* Try power it up again. */
sw = apdu_reset (slot);
}
if (DBG_READER)
log_debug ("leave: apdu_connect => sw=0x%x\n", sw);
if (sw)
return -1;
return reader_table[slot].require_get_status;
}
int
apdu_disconnect (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_disconnect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if (reader_table[slot].disconnect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].disconnect_card (slot);
unlock_slot (slot);
}
}
else
sw = 0;
if (DBG_READER)
log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw);
return sw;
}
/* Set the progress callback of SLOT to CB and its args to CB_ARG. If
CB is NULL the progress callback is removed. */
int
apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
int sw;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].set_progress_cb)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg);
unlock_slot (slot);
}
}
else
sw = 0;
return sw;
}
/* Do a reset for the card in reader at SLOT. */
int
apdu_reset (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_reset: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if ((sw = lock_slot (slot)))
{
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw);
return sw;
}
if (reader_table[slot].reset_reader)
sw = reader_table[slot].reset_reader (slot);
unlock_slot (slot);
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x\n", sw);
return sw;
}
/* Return the ATR or NULL if none is available. On success the length
of the ATR is stored at ATRLEN. The caller must free the returned
value. */
unsigned char *
apdu_get_atr (int slot, size_t *atrlen)
{
unsigned char *buf;
if (DBG_READER)
log_debug ("enter: apdu_get_atr: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (bad slot)\n");
return NULL;
}
if (!reader_table[slot].atrlen)
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (no ATR)\n");
return NULL;
}
buf = xtrymalloc (reader_table[slot].atrlen);
if (!buf)
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (out of core)\n");
return NULL;
}
memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
*atrlen = reader_table[slot].atrlen;
if (DBG_READER)
log_debug ("leave: apdu_get_atr => atrlen=%zu\n", *atrlen);
return buf;
}
/* Retrieve the status for SLOT. The function does only wait for the
card to become available if HANG is set to true. On success the
bits in STATUS will be set to
APDU_CARD_USABLE (bit 0) = card present and usable
APDU_CARD_PRESENT (bit 1) = card present
APDU_CARD_ACTIVE (bit 2) = card active
(bit 3) = card access locked [not yet implemented]
For most applications, testing bit 0 is sufficient.
*/
static int
apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire)
{
int sw;
unsigned int s = 0;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if ((sw = hang? lock_slot (slot) : trylock_slot (slot)))
return sw;
if (reader_table[slot].get_status_reader)
sw = reader_table[slot].get_status_reader (slot, &s, on_wire);
unlock_slot (slot);
if (sw)
{
if (on_wire)
reader_table[slot].atrlen = 0;
s = 0;
}
if (status)
*status = s;
return sw;
}
/* See above for a description. */
int
apdu_get_status (int slot, int hang, unsigned int *status)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang);
sw = apdu_get_status_internal (slot, hang, status, 0);
if (DBG_READER)
{
if (status)
log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n",
sw, *status);
else
log_debug ("leave: apdu_get_status => sw=0x%x\n", sw);
}
return sw;
}
/* Check whether the reader supports the ISO command code COMMAND on
the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
int
apdu_check_pinpad (int slot, int command, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (opt.enable_pinpad_varlen)
pininfo->fixedlen = 0;
if (reader_table[slot].check_pinpad)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].check_pinpad (slot, command, pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_verify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_modify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
/* Dispatcher for the actual send_apdu function. Note, that this
function should be called in locked state. */
static int
send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].send_apdu_reader)
return reader_table[slot].send_apdu_reader (slot,
apdu, apdulen,
buffer, buflen,
pininfo);
else
return SW_HOST_NOT_SUPPORTED;
}
/* Core APDU tranceiver function. Parameters are described at
apdu_send_le with the exception of PININFO which indicates pinpad
related operations if not NULL. If EXTENDED_MODE is not 0
command chaining or extended length will be used according to these
values:
n < 0 := Use command chaining with the data part limited to -n
in each chunk. If -1 is used a default value is used.
n == 0 := No extended mode or command chaining.
n == 1 := Use extended length for input and output without a
length limit.
n > 1 := Use extended length with up to N bytes.
*/
static int
send_le (int slot, int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen,
pininfo_t *pininfo, int extended_mode)
{
#define SHORT_RESULT_BUFFER_SIZE 258
/* We allocate 8 extra bytes as a safety margin towards a driver bug. */
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+1];
unsigned char *apdu_buffer = NULL;
size_t apdu_buffer_size;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* We need a long here due to PC/SC. */
int did_exact_length_hack = 0;
int use_chaining = 0;
int use_extended_length = 0;
int lc_chunk;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (DBG_CARD_IO)
log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n",
class, ins, p0, p1, lc, le, extended_mode);
if (lc != -1 && (lc > 255 || lc < 0))
{
/* Data does not fit into an APDU. What we do now depends on
the EXTENDED_MODE parameter. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (extended_mode > 0)
use_extended_length = 1;
else if (extended_mode < 0)
{
/* Send APDU using chaining mode. */
if (lc > 16384)
return SW_WRONG_LENGTH; /* Sanity check. */
if ((class&0xf0) != 0)
return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */
use_chaining = extended_mode == -1? 255 : -extended_mode;
use_chaining &= 0xff;
}
else
return SW_HOST_INV_VALUE;
}
else if (lc == -1 && extended_mode > 0)
use_extended_length = 1;
if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0))
{
/* Expected Data does not fit into an APDU. What we do now
depends on the EXTENDED_MODE parameter. Note that a check
for command chaining does not make sense because we are
looking at Le. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (use_extended_length)
; /* We are already using extended length. */
else if (extended_mode > 0)
use_extended_length = 1;
else
return SW_HOST_INV_VALUE;
}
if ((!data && lc != -1) || (data && lc == -1))
return SW_HOST_INV_VALUE;
if (use_extended_length)
{
if (reader_table[slot].is_t0)
return SW_HOST_NOT_SUPPORTED;
/* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */
apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2;
apdu_buffer = xtrymalloc (apdu_buffer_size + 10);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
}
if (use_extended_length && (le > 256 || le < 0))
{
/* Two more bytes are needed for status bytes. */
result_buffer_size = le < 0? 4096 : (le + 2);
result_buffer = xtrymalloc (result_buffer_size);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = lock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
do
{
if (use_extended_length)
{
use_chaining = 0;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc > 0)
{
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((lc >> 8) & 0xff);
apdu[apdulen++] = (lc & 0xff);
memcpy (apdu+apdulen, data, lc);
data += lc;
apdulen += lc;
}
if (le != -1)
{
if (lc <= 0)
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((le >> 8) & 0xff);
apdu[apdulen++] = (le & 0xff);
}
}
else
{
apdulen = 0;
apdu[apdulen] = class;
if (use_chaining && lc > 255)
{
apdu[apdulen] |= 0x10;
assert (use_chaining < 256);
lc_chunk = use_chaining;
lc -= use_chaining;
}
else
{
use_chaining = 0;
lc_chunk = lc;
}
apdulen++;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc_chunk != -1)
{
apdu[apdulen++] = lc_chunk;
memcpy (apdu+apdulen, data, lc_chunk);
data += lc_chunk;
apdulen += lc_chunk;
/* T=0 does not allow the use of Lc together with Le;
thus disable Le in this case. */
if (reader_table[slot].is_t0)
le = -1;
}
if (le != -1 && !use_chaining)
apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */
}
exact_length_hack:
/* As a safeguard don't pass any garbage to the driver. */
assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
if (rc || resultlen < 2)
{
log_info ("apdu_send_simple(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (apdu_buffer);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (!use_extended_length
&& !did_exact_length_hack && SW_EXACT_LENGTH_P (sw))
{
apdu[apdulen-1] = (sw & 0x00ff);
did_exact_length_hack = 1;
goto exact_length_hack;
}
}
while (use_chaining && sw == SW_SUCCESS);
if (apdu_buffer)
{
xfree (apdu_buffer);
apdu_buffer = NULL;
}
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
{
if (retbuf)
{
*retbuf = xtrymalloc (resultlen? resultlen : 1);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
else if ((sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_simple(%d): %d more bytes available\n",
slot, len);
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_simple(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_simple(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen);
if (tmp)
*retbuf = tmp;
}
}
unlock_slot (slot);
xfree (result_buffer);
if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
log_printhex (" dump: ", *retbuf, *retbuflen);
return sw;
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
for LC won't sent this field and the data field; in this case DATA
must also be passed as NULL. If EXTENDED_MODE is not 0 command
chaining or extended length will be used; see send_le for details.
The return value is the status word or -1 for an invalid SLOT or
other non card related error. If RETBUF is not NULL, it will
receive an allocated buffer with the returned data. The length of
that data will be put into *RETBUFLEN. The caller is responsible
for releasing the buffer even in case of errors. */
int
apdu_send_le(int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1,
lc, data, le,
retbuf, retbuflen,
NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. If RETBUF is not NULL, it will receive an
allocated buffer with the returned data. The length of that data
will be put into *RETBUFLEN. The caller is responsible for
releasing the buffer even in case of errors. */
int
apdu_send (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1, lc, data, 256,
retbuf, retbuflen, NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. No data will be returned. */
int
apdu_send_simple (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data)
{
return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL,
extended_mode);
}
/* This is a more generic version of the apdu sending routine. It
takes an already formatted APDU in APDUDATA or length APDUDATALEN
and returns with an APDU including the status word. With
HANDLE_MORE set to true this function will handle the MORE DATA
status and return all APDUs concatenated with one status word at
the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed
with a max. result data length of EXTENDED_LENGTH bytes. The
function does not return a regular status word but 0 on success.
If the slot is locked, the function returns immediately with an
error. */
int
apdu_send_direct (int slot, size_t extended_length,
const unsigned char *apdudata, size_t apdudatalen,
int handle_more,
unsigned char **retbuf, size_t *retbuflen)
{
#define SHORT_RESULT_BUFFER_SIZE 258
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+10];
unsigned char *apdu_buffer = NULL;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* we need a long here due to PC/SC. */
int class;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (apdudatalen > 65535)
return SW_HOST_INV_VALUE;
if (apdudatalen > sizeof short_apdu_buffer - 5)
{
apdu_buffer = xtrymalloc (apdudatalen + 5);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu = short_apdu_buffer;
}
apdulen = apdudatalen;
memcpy (apdu, apdudata, apdudatalen);
class = apdulen? *apdu : 0;
if (extended_length >= 256 && extended_length <= 65536)
{
result_buffer_size = extended_length;
result_buffer = xtrymalloc (result_buffer_size + 10);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = trylock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
xfree (apdu_buffer);
apdu_buffer = NULL;
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if (handle_more && (sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize + 2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_direct(%d): %d more bytes available\n",
slot, len);
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize + 2);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_direct(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen + 2);
if (tmp)
*retbuf = tmp;
}
}
else
{
if (retbuf)
{
*retbuf = xtrymalloc ((resultlen? resultlen : 1)+2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
unlock_slot (slot);
xfree (result_buffer);
/* Append the status word. Note that we reserved the two extra
bytes while allocating the buffer. */
if (retbuf)
{
(*retbuf)[(*retbuflen)++] = (sw >> 8);
(*retbuf)[(*retbuflen)++] = sw;
}
if (DBG_CARD_IO && retbuf)
log_printhex (" dump: ", *retbuf, *retbuflen);
return 0;
}
const char *
apdu_get_reader_name (int slot)
{
return reader_table[slot].rdrname;
}
gpg_error_t
apdu_init (void)
{
#ifdef USE_NPTH
gpg_error_t err;
int i;
if (npth_mutex_init (&reader_table_lock, NULL))
goto leave;
for (i = 0; i < MAX_READER; i++)
if (npth_mutex_init (&reader_table[i].lock, NULL))
goto leave;
/* All done well. */
return 0;
leave:
err = gpg_error_from_syserror ();
log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err));
return err;
#endif /*USE_NPTH*/
return 0;
}
diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c
index f7ecc4bd2..bea285687 100644
--- a/scd/app-dinsig.c
+++ b/scd/app-dinsig.c
@@ -1,573 +1,573 @@
/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application.
* Copyright (C) 2002, 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* The German signature law and its bylaw (SigG and SigV) is currently
used with an interface specification described in DIN V 66291-1.
The AID to be used is: 'D27600006601'.
The file IDs for certificates utilize the generic format:
Cxyz
C being the hex digit 'C' (12).
x being the service indicator:
'0' := SigG conform digital signature.
'1' := entity authentication.
'2' := key encipherment.
'3' := data encipherment.
'4' := key agreement.
other values are reserved for future use.
y being the security environment number using '0' for cards
not supporting a SE number.
z being the certificate type:
'0' := C.CH (base certificate of card holder) or C.ICC.
'1' .. '7' := C.CH (business or professional certificate
of card holder.
'8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA).
'E' := C.RCA (self certified certificate of the Root-CA).
'F' := reserved.
The file IDs used by default are:
'1F00' EF.SSD (security service descriptor). [o,o]
'2F02' EF.GDO (global data objects) [m,m]
'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte.
Read and update after user authentication. [o,o]
'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size
of keys. [m (unless a 'C00E' is present),m]
'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size
of keys. [o,o]
'C00n' EF.C.CH.DS (digital signature certificate of card holder)
with n := 0 .. 7. Size is 2k or size of cert. Read and
update allowed after user authentication. [m,m]
'C00m' EF.C.CA.DS (digital signature certificate of CA)
with m := 8 .. E. Size is 1k or size of cert. Read always
allowed, update after user authentication. [o,o]
'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m]
'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m]
'D000' EF.DM (display message) [-,m]
The letters in brackets indicate optional or mandatory files: The
first for card terminals under full control and the second for
"business" card terminals.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
+#include "../common/tlv.h"
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
char hexkeygrip[41];
size_t len, certoff;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
int fid;
(void)flags;
/* Return the certificate of the card holder. */
fid = 0xC000;
len = app_help_read_length_of_cert (app->slot, fid, &certoff);
if (!len)
return 0; /* Card has not been personalized. */
sprintf (ct_buf, "%d", 101);
sprintf (id_buf, "DINSIG.%04X", fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
/* Now we need to read the certificate, so that we can get the
public key out of it. */
err = iso7816_read_binary (app->slot, certoff, len-certoff, &der, &derlen);
if (err)
{
log_info ("error reading entire certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return 0;
}
err = ksba_cert_new (&cert);
if (err)
{
xfree (der);
return err;
}
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der); der = NULL;
if (err)
{
log_error ("failed to parse the certificate at FID 0x%04X: %s\n",
fid, gpg_strerror (err));
ksba_cert_release (cert);
return err;
}
err = app_help_get_keygrip_string (cert, hexkeygrip);
if (err)
{
log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid);
ksba_cert_release (cert);
return gpg_error (GPG_ERR_CARD);
}
ksba_cert_release (cert);
sprintf (id_buf, "DINSIG.%04X", fid);
send_status_info (ctrl, "KEYPAIRINFO",
hexkeygrip, 40,
id_buf, strlen (id_buf),
NULL, (size_t)0);
return 0;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN.
FIXME: This needs some cleanups and caching with do_learn_status.
*/
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int fid;
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
*cert = NULL;
*certlen = 0;
if (strncmp (certid, "DINSIG.", 7) )
return gpg_error (GPG_ERR_INV_ID);
certid += 7;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
if (fid != 0xC000 )
return gpg_error (GPG_ERR_NOT_FOUND);
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = iso7816_select_file (app->slot, fid, 0);
if (err)
{
log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
const char *s;
int rc;
pininfo_t pininfo;
if ( app->did_chv1 && !app->force_chv1 )
return 0; /* No need to verify it again. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = 6;
pininfo.maxlen = 8;
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
{
rc = pincb (pincb_arg,
_("||Please enter your PIN at the reader's pinpad"),
NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app->slot, 0x81, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else /* No Pinpad. */
{
char *pinvalue;
rc = pincb (pincb_arg, "PIN", &pinvalue);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
/* We require the PIN to be at least 6 and at max 8 bytes.
According to the specs, this should all be ASCII. */
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
log_error ("Non-numeric digits found in PIN\n");
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) < pininfo.minlen)
{
log_error ("PIN is too short; minimum length is %d\n",
pininfo.minlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
else if (strlen (pinvalue) > pininfo.maxlen)
{
log_error ("PIN is too large; maximum length is %d\n",
pininfo.maxlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
if (gpg_err_code (rc) == GPG_ERR_INV_VALUE)
{
/* We assume that ISO 9564-1 encoding is used and we failed
because the first nibble we passed was 3 and not 2. DIN
says something about looking up such an encoding in the
SSD but I was not able to find any tag relevant to
this. */
char paddedpin[8];
int i, ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < sizeof paddedpin && *s)
paddedpin[i++] = (((*s - '0') << 4) | 0x0f);
while (i < sizeof paddedpin)
paddedpin[i++] = 0xff;
rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin);
}
xfree (pinvalue);
}
if (rc)
{
log_error ("verify PIN failed\n");
return rc;
}
app->did_chv1 = 1;
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha256_prefix[19] = /* OID is 2.16.840.1.101.3.4.2.1 */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
int rc;
int fid;
unsigned char data[19+32]; /* Must be large enough for a SHA-256 digest
+ the largest OID _prefix above. */
int datalen;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen != 20 && indatalen != 16 && indatalen != 32
&& indatalen != (15+20) && indatalen != (19+32))
return gpg_error (GPG_ERR_INV_VALUE);
/* Check that the provided ID is vaid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (strncmp (keyidstr, "DINSIG.", 7) )
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 7;
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
if (fid != 0xC000)
return gpg_error (GPG_ERR_NOT_FOUND);
/* Prepare the DER object from INDATA. */
datalen = 35;
if (indatalen == 15+20)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else if (indatalen == 19+32)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
datalen = indatalen;
if (hashalgo == GCRY_MD_SHA256 && !memcmp (indata, sha256_prefix, 19))
;
else if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha256_prefix, 19))
{
/* Fixme: This is a kludge. A better solution is not to use
SHA1 as default but use an autodetection. However this
needs changes in all app-*.c */
datalen = indatalen;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else
{
int len = 15;
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, len);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, len);
else if (hashalgo == GCRY_MD_SHA256)
{
len = 19;
datalen = len + indatalen;
memcpy (data, sha256_prefix, len);
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+len, indata, indatalen);
}
rc = verify_pin (app, pincb, pincb_arg);
if (!rc)
rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0,
outdata, outdatalen);
return rc;
}
#if 0
#warning test function - works but may brick your card
/* Handle the PASSWD command. CHVNOSTR is currently ignored; we
always use VHV0. RESET_MODE is not yet implemented. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *pinvalue;
const char *oldpin;
size_t oldpinlen;
if ((flags & APP_CHANGE_FLAG_RESET))
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would fail
if the Nullpin is still set. */
oldpin = "\0\0\0\0\0";
oldpinlen = 6;
}
else
{
err = verify_pin (app, pincb, pincb_arg);
if (err)
return err;
oldpin = NULL;
oldpinlen = 0;
}
/* TRANSLATORS: Do not translate the "|*|" prefixes but
keep it at the start of the string. We need this elsewhere
to get some infos on the string. */
err = pincb (pincb_arg, _("|N|Initial New PIN"), &pinvalue);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
return err;
}
err = iso7816_change_reference_data (app->slot, 0x81,
oldpin, oldpinlen,
pinvalue, strlen (pinvalue));
xfree (pinvalue);
return err;
}
#endif /*0*/
/* Select the DINSIG application on the card in SLOT. This function
must be used before any other DINSIG application functions. */
gpg_error_t
app_select_dinsig (app_t app)
{
static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
int slot = app->slot;
int rc;
rc = iso7816_select_application (slot, aid, sizeof aid, 0);
if (!rc)
{
app->apptype = "DINSIG";
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = NULL;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = NULL;
app->fnc.change_pin = NULL /*do_change_pin*/;
app->fnc.check_pin = NULL;
app->force_chv1 = 1;
}
return rc;
}
diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c
index c2771719d..510beb550 100644
--- a/scd/app-geldkarte.c
+++ b/scd/app-geldkarte.c
@@ -1,409 +1,409 @@
/* app-geldkarte.c - The German Geldkarte application
* Copyright (C) 2004 g10 Code GmbH
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* This is a read-only application to quickly dump information of a
German Geldkarte (debit card for small amounts). We only support
newer Geldkarte (with the AID DF_BOERSE_NEU) issued since 2000 or
even earlier.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <ctype.h>
#include "scdaemon.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
+#include "../common/tlv.h"
/* Object with application (i.e. Geldkarte) specific data. */
struct app_local_s
{
char kblz[2+1+4+1];
const char *banktype;
char *cardno;
char expires[7+1];
char validfrom[10+1];
char *country;
char currency[3+1];
unsigned int currency_mult100;
unsigned char chipid;
unsigned char osvers;
int balance;
int maxamount;
int maxamount1;
};
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
xfree (app->app_local->cardno);
xfree (app->app_local->country);
xfree (app->app_local);
app->app_local = NULL;
}
}
static gpg_error_t
send_one_string (ctrl_t ctrl, const char *name, const char *string)
{
if (!name || !string)
return 0;
send_status_info (ctrl, name, string, strlen (string), NULL, 0);
return 0;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
struct app_local_s *ld = app->app_local;
char numbuf[100];
if (!strcmp (name, "X-KBLZ"))
err = send_one_string (ctrl, name, ld->kblz);
else if (!strcmp (name, "X-BANKINFO"))
err = send_one_string (ctrl, name, ld->banktype);
else if (!strcmp (name, "X-CARDNO"))
err = send_one_string (ctrl, name, ld->cardno);
else if (!strcmp (name, "X-EXPIRES"))
err = send_one_string (ctrl, name, ld->expires);
else if (!strcmp (name, "X-VALIDFROM"))
err = send_one_string (ctrl, name, ld->validfrom);
else if (!strcmp (name, "X-COUNTRY"))
err = send_one_string (ctrl, name, ld->country);
else if (!strcmp (name, "X-CURRENCY"))
err = send_one_string (ctrl, name, ld->currency);
else if (!strcmp (name, "X-ZKACHIPID"))
{
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-OSVERSION"))
{
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-BALANCE"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->balance / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-MAXAMOUNT"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->maxamount / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-MAXAMOUNT1"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->maxamount1 / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
static const char *names[] = {
"X-KBLZ",
"X-BANKINFO",
"X-CARDNO",
"X-EXPIRES",
"X-VALIDFROM",
"X-COUNTRY",
"X-CURRENCY",
"X-ZKACHIPID",
"X-OSVERSION",
"X-BALANCE",
"X-MAXAMOUNT",
"X-MAXAMOUNT1",
NULL
};
gpg_error_t err = 0;
int idx;
(void)flags;
for (idx=0; names[idx] && !err; idx++)
err = do_getattr (app, ctrl, names[idx]);
return err;
}
static char *
copy_bcd (const unsigned char *string, size_t length)
{
const unsigned char *s;
size_t n;
size_t needed;
char *buffer, *dst;
if (!length)
return xtrystrdup ("");
/* Skip leading zeroes. */
for (; length && !*string; length--, string++)
;
s = string;
n = length;
needed = 0;
for (; n ; n--, s++)
{
if (!needed && !(*s & 0xf0))
; /* Skip the leading zero in the first nibble. */
else
{
if ( ((*s >> 4) & 0x0f) > 9 )
{
errno = EINVAL;
return NULL;
}
needed++;
}
if ( n == 1 && (*s & 0x0f) > 9 )
; /* Ignore the last digit if it has the sign. */
else
{
needed++;
if ( (*s & 0x0f) > 9 )
{
errno = EINVAL;
return NULL;
}
}
}
if (!needed) /* If it is all zero, print a "0". */
needed++;
buffer = dst = xtrymalloc (needed+1);
if (!buffer)
return NULL;
s = string;
n = length;
needed = 0;
for (; n ; n--, s++)
{
if (!needed && !(*s & 0xf0))
; /* Skip the leading zero in the first nibble. */
else
{
*dst++ = '0' + ((*s >> 4) & 0x0f);
needed++;
}
if ( n == 1 && (*s & 0x0f) > 9 )
; /* Ignore the last digit if it has the sign. */
else
{
*dst++ = '0' + (*s & 0x0f);
needed++;
}
}
if (!needed)
*dst++ = '0';
*dst = 0;
return buffer;
}
/* Convert the BCD number at STING of LENGTH into an integer and store
that at RESULT. Return 0 on success. */
static gpg_error_t
bcd_to_int (const unsigned char *string, size_t length, int *result)
{
char *tmp;
tmp = copy_bcd (string, length);
if (!tmp)
return gpg_error (GPG_ERR_BAD_DATA);
*result = strtol (tmp, NULL, 10);
xfree (tmp);
return 0;
}
/* Select the Geldkarte application. */
gpg_error_t
app_select_geldkarte (app_t app)
{
static char const aid[] =
{ 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
gpg_error_t err;
int slot = app->slot;
unsigned char *result = NULL;
size_t resultlen;
struct app_local_s *ld;
const char *banktype;
err = iso7816_select_application (slot, aid, sizeof aid, 0);
if (err)
goto leave;
/* Read the first record of EF_ID (SFI=0x17). We require this
record to be at least 24 bytes with the first byte 0x67 and a
correct filler byte. */
err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
if (err)
goto leave; /* Oops - not a Geldkarte. */
if (resultlen < 24 || *result != 0x67 || result[22])
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* The short Bankleitzahl consists of 3 bytes at offset 1. */
switch (result[1])
{
case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
case 0x25: banktype = "Sparkasse"; break;
case 0x26:
case 0x29: banktype = "Genossenschaftsbank"; break;
default:
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave; /* Probably not a Geldkarte. */
}
app->apptype = "GELDKARTE";
app->fnc.deinit = do_deinit;
/* If we don't have a serialno yet construct it from the EF_ID. */
if (!app->serialno)
{
app->serialno = xtrymalloc (10);
if (!app->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->serialno, result, 10);
app->serialnolen = 10;
err = app_munge_serialno (app);
if (err)
goto leave;
}
app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X",
result[1], result[2], result[3]);
ld->banktype = banktype;
ld->cardno = copy_bcd (result+4, 5);
if (!ld->cardno)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X",
result[10], result[11]);
snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
result[12], result[13], result[14]);
ld->country = copy_bcd (result+15, 2);
if (!ld->country)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
isascii (result[17])? result[17]:' ',
isascii (result[18])? result[18]:' ',
isascii (result[19])? result[19]:' ');
ld->currency_mult100 = (result[20] == 0x01? 1:
result[20] == 0x02? 10:
result[20] == 0x04? 100:
result[20] == 0x08? 1000:
result[20] == 0x10? 10000:
result[20] == 0x20? 100000:0);
ld->chipid = result[21];
ld->osvers = result[23];
/* Read the first record of EF_BETRAG (SFI=0x18). */
xfree (result);
err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
if (err)
goto leave; /* It does not make sense to continue. */
if (resultlen < 12)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
err = bcd_to_int (result+0, 3, &ld->balance);
if (!err)
err = bcd_to_int (result+3, 3, &ld->maxamount);
if (!err)
err = bcd_to_int (result+6, 3, &ld->maxamount1);
/* The next 3 bytes are the maximum amount chargable without using a
MAC. This is usually 0. */
if (err)
goto leave;
/* Setup the rest of the methods. */
app->fnc.learn_status = do_learn_status;
app->fnc.getattr = do_getattr;
leave:
xfree (result);
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app-help.c b/scd/app-help.c
index 85bcc6646..842a73d5a 100644
--- a/scd/app-help.c
+++ b/scd/app-help.c
@@ -1,180 +1,180 @@
/* app-help.c - Application helper functions
* Copyright (C) 2004, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "scdaemon.h"
#include "app-common.h"
#include "iso7816.h"
-#include "tlv.h"
+#include "../common/tlv.h"
/* Count the number of bits, assuming the A represents an unsigned big
integer of length LEN bytes. If A is NULL a length of 0 is
returned. */
unsigned int
app_help_count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
if (!a)
return 0;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
/* Return the KEYGRIP for the certificate CERT as an hex encoded
string in the user provided buffer HEXKEYGRIP which must be of at
least 41 bytes. */
gpg_error_t
app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
ksba_sexp_t p;
size_t n;
unsigned char array[20];
p = ksba_cert_get_public_key (cert);
if (!p)
return gpg_error (GPG_ERR_BUG);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
xfree (p);
if (err)
return err; /* Can't parse that S-expression. */
if (!gcry_pk_get_keygrip (s_pkey, array))
{
gcry_sexp_release (s_pkey);
return gpg_error (GPG_ERR_GENERAL); /* Failed to calculate the keygrip.*/
}
gcry_sexp_release (s_pkey);
bin2hex (array, 20, hexkeygrip);
return 0;
}
/* Given the SLOT and the File ID FID, return the length of the
certificate contained in that file. Returns 0 if the file does not
exists or does not contain a certificate. If R_CERTOFF is not
NULL, the length the header will be stored at this address; thus to
parse the X.509 certificate a read should start at that offset.
On success the file is still selected.
*/
size_t
app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t resultlen, objlen, hdrlen;
err = iso7816_select_file (slot, fid, 0);
if (err)
{
log_info ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return 0;
}
err = iso7816_read_binary (slot, 0, 32, &buffer, &buflen);
if (err)
{
log_info ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return 0;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
xfree (buffer);
return 0;
}
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
{
log_info ("error parsing certificate in FID 0x%04X: %s\n",
fid, gpg_strerror (err));
xfree (buffer);
return 0;
}
/* All certificates should commence with a SEQUENCE except for the
special ROOT CA which are enclosed in a SET. */
if ( !(class == CLASS_UNIVERSAL && constructed
&& (tag == TAG_SEQUENCE || tag == TAG_SET)))
{
log_info ("data at FID 0x%04X does not look like a certificate\n", fid);
return 0;
}
resultlen = objlen + hdrlen;
if (r_certoff)
{
/* The callers want the offset to the actual certificate. */
*r_certoff = hdrlen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
return 0;
if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a
userCertificate container. Assume the following sequence
is the certificate. */
*r_certoff += hdrlen + objlen;
if (*r_certoff > resultlen)
{
*r_certoff = 0;
return 0; /* That should never happen. */
}
}
else
*r_certoff = 0;
}
return resultlen;
}
diff --git a/scd/app-nks.c b/scd/app-nks.c
index 98dd5884f..9e720f0b0 100644
--- a/scd/app-nks.c
+++ b/scd/app-nks.c
@@ -1,1429 +1,1429 @@
/* app-nks.c - The Telesec NKS card application.
* Copyright (C) 2004, 2007, 2008, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* Notes:
- We are now targeting TCOS 3 cards and it may happen that there is
a regression towards TCOS 2 cards. Please report.
- The TKS3 AUT key is not used. It seems that it is only useful for
the internal authentication command and not accessible by other
applications. The key itself is in the encryption class but the
corresponding certificate has only the digitalSignature
capability.
- If required, we automagically switch between the NKS application
and the SigG application. This avoids to use the DINSIG
application which is somewhat limited, has no support for Secure
Messaging as required by TCOS 3 and has no way to change the PIN
or even set the NullPIN.
- We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer
cards. This is because the NKS application has moved to DF02 with
TCOS 3 and thus we better use a DF independent tag.
- We use only the global PINs for the NKS application.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
+#include "../common/tlv.h"
#include "apdu.h"
-#include "host2net.h"
+#include "../common/host2net.h"
static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
static struct
{
int is_sigg; /* Valid for SigG application. */
int fid; /* File ID. */
int nks_ver; /* 0 for NKS version 2, 3 for version 3. */
int certtype; /* Type of certificate or 0 if it is not a certificate. */
int iskeypair; /* If true has the FID of the corresponding certificate. */
int issignkey; /* True if file is a key usable for signing. */
int isenckey; /* True if file is a key usable for decryption. */
unsigned char kid; /* Corresponding key references. */
} filelist[] = {
{ 0, 0x4531, 0, 0, 0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */
{ 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
{ 0, 0x4331, 0, 100 },
{ 0, 0x4332, 0, 100 },
{ 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
{ 0, 0x45B1, 0, 0, 0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */
{ 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
{ 0, 0x43B1, 0, 100 },
{ 0, 0x43B2, 0, 100 },
/* The authentication key is not used. */
/* { 0, 0x4571, 3, 0, 0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */
/* { 0, 0xC500, 3, 101 }, /\* EF_C.NKS.AUT *\/ */
{ 0, 0x45B2, 3, 0, 0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */
{ 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */
{ 1, 0x4531, 3, 0, 0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG */
{ 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */
{ 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */
{ 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */
{ 0, 0 }
};
/* Object with application (i.e. NKS) specific data. */
struct app_local_s {
int nks_version; /* NKS version. */
int sigg_active; /* True if switched to the SigG application. */
int sigg_msig_checked;/* True if we checked for a mass signature card. */
int sigg_is_msig; /* True if this is a mass signature card. */
int need_app_select; /* Need to re-select the application. */
};
static gpg_error_t switch_application (app_t app, int enable_sigg);
/* Release local data. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
xfree (app->app_local);
app->app_local = NULL;
}
}
static int
all_zero_p (void *buffer, size_t length)
{
char *p;
for (p=buffer; length; length--, p++)
if (*p)
return 0;
return 1;
}
/* Read the file with FID, assume it contains a public key and return
its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */
static gpg_error_t
keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
{
gpg_error_t err;
unsigned char grip[20];
unsigned char *buffer[2];
size_t buflen[2];
gcry_sexp_t sexp;
int i;
int offset[2] = { 0, 0 };
err = iso7816_select_file (app->slot, fid, 0);
if (err)
return err;
err = iso7816_read_record (app->slot, 1, 1, 0, &buffer[0], &buflen[0]);
if (err)
return err;
err = iso7816_read_record (app->slot, 2, 1, 0, &buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if (app->app_local->nks_version < 3)
{
/* Old versions of NKS store the values in a TLV encoded format.
We need to do some checks. */
for (i=0; i < 2; i++)
{
/* Check that the value appears like an integer encoded as
Simple-TLV. We don't check the tag because the tests cards I
have use 1 for both, the modulus and the exponent - the
example in the documentation gives 2 for the exponent. */
if (buflen[i] < 3)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (buffer[i][1] != buflen[i]-2 )
err = gpg_error (GPG_ERR_INV_OBJ);
else
offset[i] = 2;
}
}
else
{
/* Remove leading zeroes to get a correct keygrip. Take care of
negative numbers. We should also fix it the same way in
libgcrypt but we can't yet rely on it yet. */
for (i=0; i < 2; i++)
{
while (buflen[i]-offset[i] > 1
&& !buffer[i][offset[i]]
&& !(buffer[i][offset[i]+1] & 0x80))
offset[i]++;
}
}
/* Check whether negative values are not prefixed with a zero and
fix that. */
for (i=0; i < 2; i++)
{
if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80))
{
unsigned char *newbuf;
size_t newlen;
newlen = 1 + buflen[i] - offset[i];
newbuf = xtrymalloc (newlen);
if (!newlen)
{
xfree (buffer[0]);
xfree (buffer[1]);
return gpg_error_from_syserror ();
}
newbuf[0] = 0;
memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]);
xfree (buffer[i]);
buffer[i] = newbuf;
buflen[i] = newlen;
offset[i] = 0;
}
}
if (!err)
err = gcry_sexp_build (&sexp, NULL,
"(public-key (rsa (n %b) (e %b)))",
(int)buflen[0]-offset[0], buffer[0]+offset[0],
(int)buflen[1]-offset[1], buffer[1]+offset[1]);
xfree (buffer[0]);
xfree (buffer[1]);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
{
err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by
libgcrypt. */
}
else
{
bin2hex (grip, 20, r_gripstr);
}
gcry_sexp_release (sexp);
return err;
}
/* TCOS responds to a verify with empty data (i.e. without the Lc
byte) with the status of the PIN. PWID is the PIN ID, If SIGG is
true, the application is switched into SigG mode.
Returns:
-1 = Error retrieving the data,
-2 = No such PIN,
-3 = PIN blocked,
-4 = NullPIN activ,
n >= 0 = Number of verification attempts left. */
static int
get_chv_status (app_t app, int sigg, int pwid)
{
unsigned char *result = NULL;
size_t resultlen;
char command[4];
int rc;
if (switch_application (app, sigg))
return sigg? -2 : -1; /* No such PIN / General error. */
command[0] = 0x00;
command[1] = 0x20;
command[2] = 0x00;
command[3] = pwid;
if (apdu_send_direct (app->slot, 0, (unsigned char *)command,
4, 0, &result, &resultlen))
rc = -1; /* Error. */
else if (resultlen < 2)
rc = -1; /* Error. */
else
{
unsigned int sw = buf16_to_uint (result+resultlen-2);
if (sw == 0x6a88)
rc = -2; /* No such PIN. */
else if (sw == 0x6983)
rc = -3; /* PIN is blocked. */
else if (sw == 0x6985)
rc = -4; /* NullPIN is activ. */
else if ((sw & 0xfff0) == 0x63C0)
rc = (sw & 0x000f); /* PIN has N tries left. */
else
rc = -1; /* Other error. */
}
xfree (result);
return rc;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int special;
} table[] = {
{ "$AUTHKEYID", 1 },
{ "NKS-VERSION", 2 },
{ "CHV-STATUS", 3 },
{ NULL, 0 }
};
gpg_error_t err = 0;
int idx;
char buffer[100];
err = switch_application (app, 0);
if (err)
return err;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
switch (table[idx].special)
{
case 1: /* $AUTHKEYID */
{
/* NetKey 3.0 cards define an authentication key but according
to the specs this key is only usable for encryption and not
signing. it might work anyway but it has not yet been
tested - fixme. Thus for now we use the NKS signature key
for authentication. */
char const tmp[] = "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 2: /* NKS-VERSION */
snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
break;
case 3: /* CHV-STATUS */
{
/* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the
two global passwords followed by the two SigG passwords.
For the values, see the function get_chv_status. */
int tmp[4];
/* We use a helper array so that we can control that there is
no superfluous application switch. Note that PW2.CH.SIG
really has the identifier 0x83 and not 0x82 as one would
expect. */
tmp[0] = get_chv_status (app, 0, 0x00);
tmp[1] = get_chv_status (app, 0, 0x01);
tmp[2] = get_chv_status (app, 1, 0x81);
tmp[3] = get_chv_status (app, 1, 0x83);
snprintf (buffer, sizeof buffer,
"%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
}
break;
default:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
return err;
}
static void
do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags, int is_sigg)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
int i;
const char *tag;
if (is_sigg)
tag = "SIGG";
else if (app->app_local->nks_version < 3)
tag = "DF01";
else
tag = "NKS3";
/* Output information about all useful objects in the NKS application. */
for (i=0; filelist[i].fid; i++)
{
if (filelist[i].nks_ver > app->app_local->nks_version)
continue;
if (!!filelist[i].is_sigg != !!is_sigg)
continue;
if (filelist[i].certtype && !(flags &1))
{
size_t len;
len = app_help_read_length_of_cert (app->slot,
filelist[i].fid, NULL);
if (len)
{
/* FIXME: We should store the length in the application's
context so that a following readcert does only need to
read that many bytes. */
snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype);
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
else if (filelist[i].iskeypair)
{
char gripstr[40+1];
err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr);
if (err)
log_error ("can't get keygrip from FID 0x%04X: %s\n",
filelist[i].fid, gpg_strerror (err));
else
{
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
}
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
err = switch_application (app, 0);
if (err)
return err;
do_learn_status_core (app, ctrl, flags, 0);
err = switch_application (app, 1);
if (err)
return 0; /* Silently ignore if we can't switch to SigG. */
do_learn_status_core (app, ctrl, flags, 1);
return 0;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int i, fid;
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
int is_sigg = 0;
*cert = NULL;
*certlen = 0;
if (!strncmp (certid, "NKS-NKS3.", 9))
;
else if (!strncmp (certid, "NKS-DF01.", 9))
;
else if (!strncmp (certid, "NKS-SIGG.", 9))
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
certid += 9;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
for (i=0; filelist[i].fid; i++)
if ((filelist[i].certtype || filelist[i].iskeypair)
&& filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
/* If the requested objects is a plain public key, redirect it to
the corresponding certificate. The whole system is a bit messy
because we sometime use the key directly or let the caller
retrieve the key from the certificate. The rationale for
that is to support not-yet stored certificates. */
if (filelist[i].iskeypair)
fid = filelist[i].iskeypair;
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = iso7816_select_file (app->slot, fid, 0);
if (err)
{
log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/* Handle the READKEY command. On success a canonical encoded
S-expression with the public key will get stored at PK and its
length at PKLEN; the caller must release that buffer. On error PK
and PKLEN are not changed and an error code is returned. As of now
this function is only useful for the internal authentication key.
Other keys are automagically retrieved via by means of the
certificate parsing code in commands.c:cmd_readkey. For internal
use PK and PKLEN may be NULL to just check for an existing key. */
static gpg_error_t
do_readkey (app_t app, int advanced, const char *keyid,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
unsigned char *buffer[2];
size_t buflen[2];
unsigned short path[1] = { 0x4500 };
if (advanced)
return GPG_ERR_NOT_SUPPORTED;
/* We use a generic name to retrieve PK.AUT.IFD-SPK. */
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else /* Return the error code expected by cmd_readkey. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* Access the KEYD file which is always in the master directory. */
err = iso7816_select_path (app->slot, path, DIM (path));
if (err)
return err;
/* Due to the above select we need to re-select our application. */
app->app_local->need_app_select = 1;
/* Get the two records. */
err = iso7816_read_record (app->slot, 5, 1, 0, &buffer[0], &buflen[0]);
if (err)
return err;
if (all_zero_p (buffer[0], buflen[0]))
{
xfree (buffer[0]);
return gpg_error (GPG_ERR_NOT_FOUND);
}
err = iso7816_read_record (app->slot, 6, 1, 0, &buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if (pk && pklen)
{
*pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0],
buffer[1], buflen[1],
pklen);
if (!*pk)
err = gpg_error_from_syserror ();
}
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
/* Handle the WRITEKEY command for NKS. This function expects a
canonical encoded S-expression with the public key in KEYDATA and
its length in KEYDATALEN. The only supported KEYID is
"$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of
FLAGS indicates whether an existing key shall get overwritten.
PINCB and PINCB_ARG are the usual arguments for the pinentry
callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
unsigned int nbits;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else
return gpg_error (GPG_ERR_INV_ID);
if (!force && !do_readkey (app, 0, keyid, NULL, NULL))
return gpg_error (GPG_ERR_EEXIST);
/* Parse the S-expression. */
err = get_rsa_pk_from_canon_sexp (keydata, keydatalen,
&rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len);
if (err)
goto leave;
/* Check that the parameters match the requirements. */
nbits = app_help_count_bits (rsa_n, rsa_n_len);
if (nbits != 1024)
{
log_error (_("RSA modulus missing or not of size %d bits\n"), 1024);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
nbits = app_help_count_bits (rsa_e, rsa_e_len);
if (nbits < 2 || nbits > 32)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
32);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* /\* Store them. *\/ */
/* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */
/* if (err) */
/* goto leave; */
/* Send the MSE:Store_Public_Key. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
/* mse = xtrymalloc (1000); */
/* mse[0] = 0x80; /\* Algorithm reference. *\/ */
/* mse[1] = 1; */
/* mse[2] = 0x17; */
/* mse[3] = 0x84; /\* Private key reference. *\/ */
/* mse[4] = 1; */
/* mse[5] = 0x77; */
/* mse[6] = 0x7F; /\* Public key parameter. *\/ */
/* mse[7] = 0x49; */
/* mse[8] = 0x81; */
/* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */
/* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */
/* mse[11] = 0x81; */
/* mse[12] = rsa_n_len; */
/* memcpy (mse+12, rsa_n, rsa_n_len); */
/* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */
/* mse[12] = rsa_e_len; */
/* memcpy (mse+12, rsa_e, rsa_e_len); */
/* err = iso7816_manage_security_env (app->slot, 0x81, 0xB6, */
/* mse, sizeof mse); */
leave:
return err;
}
static gpg_error_t
basic_pin_checks (const char *pinvalue, int minlen, int maxlen)
{
if (strlen (pinvalue) < minlen)
{
log_error ("PIN is too short; minimum length is %d\n", minlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) > maxlen)
{
log_error ("PIN is too large; maximum length is %d\n", maxlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
return 0;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app, int pwid, const char *desc,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
pininfo_t pininfo;
int rc;
if (!desc)
desc = "PIN";
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = 6;
pininfo.maxlen = 16;
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
{
rc = pincb (pincb_arg, desc, NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app->slot, pwid, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
char *pinvalue;
rc = pincb (pincb_arg, desc, &pinvalue);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen);
if (rc)
{
xfree (pinvalue);
return rc;
}
rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
{
if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS )
log_error (_("the NullPIN has not yet been changed\n"));
else
log_error ("verify PIN failed\n");
return rc;
}
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
int rc, i;
int is_sigg = 0;
int fid;
unsigned char kid;
unsigned char data[83]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix. */
size_t datalen;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
switch (indatalen)
{
case 16: case 20: case 35: case 47: case 51: case 67: case 83: break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (is_sigg && app->app_local->sigg_is_msig)
{
log_info ("mass signature cards are not allowed\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].issignkey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
/* Prepare the DER object from INDATA. */
if (app->app_local->nks_version > 2 && (indatalen == 35
|| indatalen == 47
|| indatalen == 51
|| indatalen == 67
|| indatalen == 83))
{
/* The caller send data matching the length of the ASN.1 encoded
hash for SHA-{1,224,256,384,512}. Assume that is okay. */
assert (indatalen <= sizeof data);
memcpy (data, indata, indatalen);
datalen = indatalen;
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. This is for TCOS 2. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
datalen = 35;
}
else if (indatalen == 20)
{
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
datalen = 35;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
/* Send an MSE for PSO:Computer_Signature. */
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 2; /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check. */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
rc = iso7816_manage_security_env (app->slot, 0x41, 0xB6,
mse, sizeof mse);
}
/* Verify using PW1.CH. */
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Compute the signature. */
if (!rc)
rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0,
outdata, outdatalen);
return rc;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
int rc, i;
int is_sigg = 0;
int fid;
int kid;
(void)r_info;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].isenckey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
rc = iso7816_manage_security_env (app->slot, 0x41, 0xB8,
mse, sizeof mse);
}
else
{
static const unsigned char mse[] =
{
0x80, 1, 0x10, /* Select algorithm RSA. */
0x84, 1, 0x81 /* Select local secret key 1 for decryption. */
};
rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8,
mse, sizeof mse);
}
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Note that we need to use extended length APDUs for TCOS 3 cards.
Command chaining does not work. */
if (!rc)
rc = iso7816_decipher (app->slot, app->app_local->nks_version > 2? 1:0,
indata, indatalen, 0, 0x81,
outdata, outdatalen);
return rc;
}
/* Parse a password ID string. Returns NULL on error or a string
suitable as passphrase prompt on success. On success stores the
reference value for the password at R_PWID and a flag indicating
that the SigG application is to be used at R_SIGG. If NEW_MODE is
true, the returned description is suitable for a new Password.
Supported values for PWIDSTR are:
PW1.CH - Global password 1
PW2.CH - Global password 2
PW1.CH.SIG - SigG password 1
PW2.CH.SIG - SigG password 2
*/
static const char *
parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid)
{
const char *desc;
if (!pwidstr)
desc = NULL;
else if (!strcmp (pwidstr, "PW1.CH"))
{
*r_sigg = 0;
*r_pwid = 0x00;
/* TRANSLATORS: Do not translate the "|*|" prefixes but keep
them verbatim at the start of the string. */
desc = (new_mode
? _("|N|Please enter a new PIN for the standard keys.")
: _("||Please enter the PIN for the standard keys."));
}
else if (!strcmp (pwidstr, "PW2.CH"))
{
*r_pwid = 0x01;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the standard keys.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the standard keys."));
}
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
{
*r_pwid = 0x81;
*r_sigg = 1;
desc = (new_mode
? _("|N|Please enter a new PIN for the key to create "
"qualified signatures.")
: _("||Please enter the PIN for the key to create "
"qualified signatures."));
}
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
{
*r_pwid = 0x83; /* Yes, that is 83 and not 82. */
*r_sigg = 1;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the key to create qualified signatures.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the key to create qualified signatures."));
}
else
{
*r_pwid = 0; /* Only to avoid gcc warning in calling function. */
desc = NULL; /* Error. */
}
return desc;
}
/* Handle the PASSWD command. See parse_pwidstr() for allowed values
for CHVNOSTR. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *newpin = NULL;
char *oldpin = NULL;
size_t newpinlen;
size_t oldpinlen;
int is_sigg;
const char *newdesc;
int pwid;
pininfo_t pininfo;
(void)ctrl;
/* The minimum length is enforced by TCOS, the maximum length is
just a reasonable value. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.minlen = 6;
pininfo.maxlen = 16;
newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid);
if (!newdesc)
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would
fail if the Nullpin is still set. */
oldpin = xtrycalloc (1, 6);
if (!oldpin)
{
err = gpg_error_from_syserror ();
goto leave;
}
oldpinlen = 6;
}
else
{
const char *desc;
int dummy1, dummy2;
if ((flags & APP_CHANGE_FLAG_RESET))
{
/* Reset mode: Ask for the alternate PIN. */
const char *altpwidstr;
if (!strcmp (pwidstr, "PW1.CH"))
altpwidstr = "PW2.CH";
else if (!strcmp (pwidstr, "PW2.CH"))
altpwidstr = "PW1.CH";
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
altpwidstr = "PW2.CH.SIG";
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
altpwidstr = "PW1.CH.SIG";
else
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2);
}
else
{
/* Regular change mode: Ask for the old PIN. */
desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2);
}
err = pincb (pincb_arg, desc, &oldpin);
if (err)
{
log_error ("error getting old PIN: %s\n", gpg_strerror (err));
goto leave;
}
oldpinlen = strlen (oldpin);
err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
}
err = pincb (pincb_arg, newdesc, &newpin);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
goto leave;
}
newpinlen = strlen (newpin);
err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *data;
size_t datalen = oldpinlen + newpinlen;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (data, oldpin, oldpinlen);
memcpy (data+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app->slot, pwid,
data, datalen);
wipememory (data, datalen);
xfree (data);
}
else
err = iso7816_change_reference_data (app->slot, pwid,
oldpin, oldpinlen,
newpin, newpinlen);
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
static gpg_error_t
do_check_pin (app_t app, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int pwid;
int is_sigg;
const char *desc;
desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid);
if (!desc)
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
return verify_pin (app, pwid, desc, pincb, pincb_arg);
}
/* Return the version of the NKS application. */
static int
get_nks_version (int slot)
{
unsigned char *result = NULL;
size_t resultlen;
int type;
if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0,
&result, &resultlen))
return 2; /* NKS 2 does not support this command. */
/* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00
vv tt ccccccccccccccccc aa bb cc vvvvvvvvvvv xx
vendor (Philips) -+ | | | | | | |
chip type -----------+ | | | | | |
chip id ----------------+ | | | | |
card type (3 - tcos 3) -------------------+ | | | |
OS version of card type ---------------------+ | | |
OS release of card type ------------------------+ | |
OS vendor internal version ------------------------+ |
RFU -----------------------------------------------------------+
*/
if (resultlen < 16)
type = 0; /* Invalid data returned. */
else
type = result[8];
xfree (result);
return type;
}
/* If ENABLE_SIGG is true switch to the SigG application if not yet
active. If false switch to the NKS application if not yet active.
Returns 0 on success. */
static gpg_error_t
switch_application (app_t app, int enable_sigg)
{
gpg_error_t err;
if (((app->app_local->sigg_active && enable_sigg)
|| (!app->app_local->sigg_active && !enable_sigg))
&& !app->app_local->need_app_select)
return 0; /* Already switched. */
log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS");
if (enable_sigg)
err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0);
else
err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0);
if (!err && enable_sigg && app->app_local->nks_version >= 3
&& !app->app_local->sigg_msig_checked)
{
/* Check whether this card is a mass signature card. */
unsigned char *buffer;
size_t buflen;
const unsigned char *tmpl;
size_t tmpllen;
app->app_local->sigg_msig_checked = 1;
app->app_local->sigg_is_msig = 1;
err = iso7816_select_file (app->slot, 0x5349, 0);
if (!err)
err = iso7816_read_record (app->slot, 1, 1, 0, &buffer, &buflen);
if (!err)
{
tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen);
if (tmpl && tmpllen == 12
&& !memcmp (tmpl,
"\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83",
12))
app->app_local->sigg_is_msig = 0;
xfree (buffer);
}
if (app->app_local->sigg_is_msig)
log_info ("This is a mass signature card\n");
}
if (!err)
{
app->app_local->need_app_select = 0;
app->app_local->sigg_active = enable_sigg;
}
else
log_error ("app-nks: error switching to %s: %s\n",
enable_sigg? "SigG":"NKS", gpg_strerror (err));
return err;
}
/* Select the NKS application. */
gpg_error_t
app_select_nks (app_t app)
{
int slot = app->slot;
int rc;
rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0);
if (!rc)
{
app->apptype = "NKS";
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->app_local->nks_version = get_nks_version (slot);
if (opt.verbose)
log_info ("Detected NKS version: %d\n", app->app_local->nks_version);
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.writekey = do_writekey;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index af81dce65..66b235d81 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -1,5105 +1,5105 @@
/* app-openpgp.c - The OpenPGP card application.
* Copyright (C) 2003, 2004, 2005, 2007, 2008,
* 2009, 2013, 2014, 2015 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* Some notes:
CHV means Card Holder Verification and is nothing else than a PIN
or password. That term seems to have been used originally with GSM
cards. Version v2 of the specs changes the term to the clearer
term PW for password. We use the terms here interchangeable
because we do not want to change existing strings i18n wise.
Version 2 of the specs also drops the separate PW2 which was
required in v1 due to ISO requirements. It is now possible to have
one physical PW but two reference to it so that they can be
individually be verified (e.g. to implement a forced verification
for one key). Thus you will noticed the use of PW2 with the verify
command but not with change_reference_data because the latter
operates directly on the physical PW.
The Reset Code (RC) as implemented by v2 cards uses the same error
counter as the PW2 of v1 cards. By default no RC is set and thus
that error counter is set to 0. After setting the RC the error
counter will be initialized to 3.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#if GNUPG_MAJOR_VERSION == 1
/* This is used with GnuPG version < 1.9. The code has been source
copied from the current GnuPG >= 1.9 and is maintained over
there. */
#include "options.h"
#include "errors.h"
#include "memory.h"
#include "cardglue.h"
#else /* GNUPG_MAJOR_VERSION != 1 */
#include "scdaemon.h"
#endif /* GNUPG_MAJOR_VERSION != 1 */
-#include "util.h"
-#include "i18n.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
-#include "host2net.h"
-#include "openpgpdefs.h"
+#include "../common/tlv.h"
+#include "../common/host2net.h"
+#include "../common/openpgpdefs.h"
/* A table describing the DOs of the card. */
static struct {
int tag;
int constructed;
int get_from; /* Constructed DO with this DO or 0 for direct access. */
int binary:1;
int dont_cache:1;
int flush_on_error:1;
int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of
this data object if it is used in 1.1
and later versions of the card. This
does not work with composite DO and
is currently only useful for the CHV
status bytes. */
int try_extlen:1; /* Large object; try to use an extended
length APDU. */
char *desc;
} data_objects[] = {
{ 0x005E, 0, 0, 1, 0, 0, 0, 0, "Login Data" },
{ 0x5F50, 0, 0, 0, 0, 0, 0, 0, "URL" },
{ 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" },
{ 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"},
{ 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" },
{ 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" },
{ 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Sex" },
{ 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" },
{ 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" },
{ 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" },
{ 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" },
{ 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" },
{ 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" },
{ 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" },
{ 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" },
{ 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" },
{ 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" },
{ 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" },
{ 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" },
{ 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" },
{ 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" },
{ 0x0101, 0, 0, 0, 0, 0, 0, 0, "Private DO 1"},
{ 0x0102, 0, 0, 0, 0, 0, 0, 0, "Private DO 2"},
{ 0x0103, 0, 0, 0, 0, 0, 0, 0, "Private DO 3"},
{ 0x0104, 0, 0, 0, 0, 0, 0, 0, "Private DO 4"},
{ 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"},
/* V3.0 */
{ 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"},
{ 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"},
{ 0 }
};
/* Type of keys. */
typedef enum
{
KEY_TYPE_ECC,
KEY_TYPE_RSA,
}
key_type_t;
/* The format of RSA private keys. */
typedef enum
{
RSA_UNKNOWN_FMT,
RSA_STD,
RSA_STD_N,
RSA_CRT,
RSA_CRT_N
}
rsa_key_format_t;
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application (i.e. OpenPGP card) specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Keep track of the public keys. */
struct
{
int read_done; /* True if we have at least tried to read them. */
unsigned char *key; /* This is a malloced buffer with a canonical
encoded S-expression encoding a public
key. Might be NULL if key is not
available. */
size_t keylen; /* The length of the above S-expression. This
is usually only required for cross checks
because the length of an S-expression is
implicitly available. */
} pk[3];
unsigned char status_indicator; /* The card status indicator. */
unsigned int manufacturer:16; /* Manufacturer ID from the s/n. */
/* Keep track of the ISO card capabilities. */
struct
{
unsigned int cmd_chaining:1; /* Command chaining is supported. */
unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */
} cardcap;
/* Keep track of extended card capabilities. */
struct
{
unsigned int is_v2:1; /* This is a v2.0 compatible card. */
unsigned int sm_supported:1; /* Secure Messaging is supported. */
unsigned int get_challenge:1;
unsigned int key_import:1;
unsigned int change_force_chv:1;
unsigned int private_dos:1;
unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */
unsigned int has_decrypt:1; /* Support symmetric decryption. */
unsigned int has_button:1;
unsigned int sm_algo:2; /* Symmetric crypto algo for SM. */
unsigned int max_certlen_3:16;
unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */
} extcap;
/* Flags used to control the application. */
struct
{
unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */
unsigned int def_chv2:1; /* Use 123456 for CHV2. */
} flags;
/* Pinpad request specified on card. */
struct
{
unsigned int specified:1;
int fixedlen_user;
int fixedlen_admin;
} pinpad;
struct
{
key_type_t key_type;
union {
struct {
unsigned int n_bits; /* Size of the modulus in bits. The rest
of this strucuire is only valid if
this is not 0. */
unsigned int e_bits; /* Size of the public exponent in bits. */
rsa_key_format_t format;
} rsa;
struct {
const char *curve;
int flags;
} ecc;
};
} keyattr[3];
};
#define ECC_FLAG_DJB_TWEAK (1 << 0)
#define ECC_FLAG_PUBKEY (1 << 1)
/***** Local prototypes *****/
static unsigned long convert_sig_counter_value (const unsigned char *value,
size_t valuelen);
static unsigned long get_sig_counter (app_t app);
static gpg_error_t do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
static void parse_algorithm_attribute (app_t app, int keyno);
static gpg_error_t change_keyattr_from_string
(app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *value, size_t valuelen);
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
int i;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
for (i=0; i < DIM (app->app_local->pk); i++)
{
xfree (app->app_local->pk[i].key);
app->app_local->pk[i].read_done = 0;
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
from the cache. With GET_IMMEDIATE passed as true, the cache is
bypassed. With TRY_EXTLEN extended lengths APDUs are use if
supported by the card. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate, int try_extlen)
{
gpg_error_t err;
int i;
unsigned char *p;
size_t len;
struct cache_s *c;
int exmode;
*result = NULL;
*resultlen = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error (gpg_err_code_from_errno (errno));
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
if (try_extlen && app->app_local->cardcap.ext_lc_le)
exmode = app->app_local->extcap.max_certlen_3;
else
exmode = 0;
err = iso7816_get_data (app->slot, exmode, tag, &p, &len);
if (err)
return err;
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
memcpy (c->data, p, len);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove DO at TAG from the cache. */
static void
flush_cache_item (app_t app, int tag)
{
struct cache_s *c, *cprev;
int i;
if (!app->app_local)
return;
for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next)
if (c->tag == tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
/* Try again if we have an outer tag. */
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag && data_objects[i].get_from
&& data_objects[i].get_from != tag)
flush_cache_item (app, data_objects[i].get_from);
}
/* Flush all entries from the cache which might be out of sync after
an error. */
static void
flush_cache_after_error (app_t app)
{
int i;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].flush_on_error)
flush_cache_item (app, data_objects[i].tag);
}
/* Flush the entire cache. */
static void
flush_cache (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
app->app_local->cache = NULL;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
buffer with its content in RESULT and NBYTES. The return value is
NULL if not found or a pointer which must be used to release the
buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_rc)
{
int rc, i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
int dummyrc;
int exmode;
if (!r_rc)
r_rc = &dummyrc;
*result = NULL;
*nbytes = 0;
*r_rc = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11)
{
exmode = 0;
rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen);
if (rc)
{
*r_rc = rc;
return NULL;
}
*result = buffer;
*nbytes = buflen;
return buffer;
}
value = NULL;
rc = -1;
if (data_objects[i].tag && data_objects[i].get_from)
{
rc = get_cached_data (app, data_objects[i].get_from,
&buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
const unsigned char *s;
s = find_tlv_unchecked (buffer, buflen, tag, &valuelen);
if (!s)
value = NULL; /* not found */
else if (valuelen > buflen - (s - buffer))
{
log_error ("warning: constructed DO too short\n");
value = NULL;
xfree (buffer); buffer = NULL;
}
else
value = buffer + (s - buffer);
}
}
if (!value) /* Not in a constructed DO, try simple. */
{
rc = get_cached_data (app, tag, &buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
value = buffer;
valuelen = buflen;
}
}
if (!rc)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_rc = rc;
return NULL;
}
static void
dump_all_do (int slot)
{
int rc, i, j;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
if (data_objects[i].get_from)
continue;
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen);
if (gpg_err_code (rc) == GPG_ERR_NO_OBJ)
;
else if (rc)
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (rc));
else
{
if (data_objects[i].binary)
{
log_info ("DO '%s': ", data_objects[i].desc);
log_printhex ("", buffer, buflen);
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[i].desc,
(int)buflen, buffer); /* FIXME: sanitize */
if (data_objects[i].constructed)
{
for (j=0; data_objects[j].tag; j++)
{
const unsigned char *value;
size_t valuelen;
if (j==i || data_objects[i].tag != data_objects[j].get_from)
continue;
value = find_tlv_unchecked (buffer, buflen,
data_objects[j].tag, &valuelen);
if (!value)
; /* not found */
else if (valuelen > buflen - (value - buffer))
log_error ("warning: constructed DO too short\n");
else
{
if (data_objects[j].binary)
{
log_info ("DO '%s': ", data_objects[j].desc);
if (valuelen > 200)
log_info ("[%u]\n", (unsigned int)valuelen);
else
log_printhex ("", value, valuelen);
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[j].desc,
(int)valuelen, value); /* FIXME: sanitize */
}
}
}
}
xfree (buffer); buffer = NULL;
}
}
/* Count the number of bits, assuming the A represents an unsigned big
integer of length LEN bytes. */
static unsigned int
count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
/* GnuPG makes special use of the login-data DO, this function parses
the login data to store the flags for later use. It may be called
at any time and should be called after changing the login-data DO.
Everything up to a LF is considered a mailbox or account name. If
the first LF is followed by DC4 (0x14) control sequence are
expected up to the next LF. Control sequences are separated by FS
(0x18) and consist of key=value pairs. There are two keys defined:
F=<flags>
Where FLAGS is a plain hexadecimal number representing flag values.
The lsb is here the rightmost bit. Defined flags bits are:
Bit 0 = CHV1 and CHV2 are not syncronized
Bit 1 = CHV2 has been set to the default PIN of "123456"
(this implies that bit 0 is also set).
P=<pinpad-request>
Where PINPAD_REQUEST is in the format of: <n> or <n>,<m>.
N for user PIN, M for admin PIN. If M is missing it means M=N.
0 means to force not to use pinpad.
*/
static void
parse_login_data (app_t app)
{
unsigned char *buffer, *p;
size_t buflen, len;
void *relptr;
/* Set defaults. */
app->app_local->flags.no_sync = 0;
app->app_local->flags.def_chv2 = 0;
app->app_local->pinpad.specified = 0;
app->app_local->pinpad.fixedlen_user = -1;
app->app_local->pinpad.fixedlen_admin = -1;
/* Read the DO. */
relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL);
if (!relptr)
return; /* Ooops. */
for (; buflen; buflen--, buffer++)
if (*buffer == '\n')
break;
if (buflen < 2 || buffer[1] != '\x14')
{
xfree (relptr);
return; /* No control sequences. */
}
buflen--;
buffer++;
do
{
buflen--;
buffer++;
if (buflen > 1 && *buffer == 'F' && buffer[1] == '=')
{
/* Flags control sequence found. */
int lastdig = 0;
/* For now we are only interested in the last digit, so skip
any leading digits but bail out on invalid characters. */
for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--)
lastdig = xtoi_1 (p);
buffer = p;
buflen = len;
if (len && !(*p == '\n' || *p == '\x18'))
goto next; /* Invalid characters in field. */
app->app_local->flags.no_sync = !!(lastdig & 1);
app->app_local->flags.def_chv2 = (lastdig & 3) == 3;
}
else if (buflen > 1 && *buffer == 'P' && buffer[1] == '=')
{
/* Pinpad request control sequence found. */
buffer += 2;
buflen -= 2;
if (buflen)
{
if (digitp (buffer))
{
char *q;
int n, m;
n = strtol (buffer, &q, 10);
if (q >= (char *)buffer + buflen
|| *q == '\x18' || *q == '\n')
m = n;
else
{
if (*q++ != ',' || !digitp (q))
goto next;
m = strtol (q, &q, 10);
}
if (buflen < ((unsigned char *)q - buffer))
break;
buflen -= ((unsigned char *)q - buffer);
buffer = q;
if (buflen && !(*buffer == '\n' || *buffer == '\x18'))
goto next;
app->app_local->pinpad.specified = 1;
app->app_local->pinpad.fixedlen_user = n;
app->app_local->pinpad.fixedlen_admin = m;
}
}
}
next:
/* Skip to FS (0x18) or LF (\n). */
for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--)
buffer++;
}
while (buflen && *buffer != '\n');
xfree (relptr);
}
#define MAX_ARGS_STORE_FPR 3
/* Note, that FPR must be at least 20 bytes. */
static gpg_error_t
store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr,
int algo, ...)
{
unsigned int n, nbits;
unsigned char *buffer, *p;
int tag, tag2;
int rc;
const unsigned char *m[MAX_ARGS_STORE_FPR];
size_t mlen[MAX_ARGS_STORE_FPR];
va_list ap;
int argc;
int i;
n = 6; /* key packet version, 4-byte timestamps, and algorithm */
if (algo == PUBKEY_ALGO_ECDH)
argc = 3;
else
argc = 2;
va_start (ap, algo);
for (i = 0; i < argc; i++)
{
m[i] = va_arg (ap, const unsigned char *);
mlen[i] = va_arg (ap, size_t);
if (algo == PUBKEY_ALGO_RSA || i == 1)
n += 2;
n += mlen[i];
}
va_end (ap);
p = buffer = xtrymalloc (3 + n);
if (!buffer)
return gpg_error_from_syserror ();
*p++ = 0x99; /* ctb */
*p++ = n >> 8; /* 2 byte length header */
*p++ = n;
*p++ = 4; /* key packet version */
*p++ = timestamp >> 24;
*p++ = timestamp >> 16;
*p++ = timestamp >> 8;
*p++ = timestamp;
*p++ = algo;
for (i = 0; i < argc; i++)
{
if (algo == PUBKEY_ALGO_RSA || i == 1)
{
nbits = count_bits (m[i], mlen[i]);
*p++ = nbits >> 8;
*p++ = nbits;
}
memcpy (p, m[i], mlen[i]);
p += mlen[i];
}
gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
xfree (buffer);
tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber;
flush_cache_item (app, 0xC5);
tag2 = 0xCE + keynumber;
flush_cache_item (app, 0xCD);
rc = iso7816_put_data (app->slot, 0, tag, fpr, 20);
if (rc)
log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc));
if (!rc && app->card_version > 0x0100)
{
unsigned char buf[4];
buf[0] = timestamp >> 24;
buf[1] = timestamp >> 16;
buf[2] = timestamp >> 8;
buf[3] = timestamp;
rc = iso7816_put_data (app->slot, 0, tag2, buf, 4);
if (rc)
log_error (_("failed to store the creation date: %s\n"),
gpg_strerror (rc));
}
return rc;
}
static void
send_fpr_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *fpr)
{
int i;
char buf[41];
char numbuf[25];
for (i=0; i < 20 && !fpr[i]; i++)
;
if (i==20)
return; /* All zero. */
bin2hex (fpr, 20, buf);
if (number == -1)
*numbuf = 0; /* Don't print the key number */
else
sprintf (numbuf, "%d", number);
send_status_info (ctrl, keyword,
numbuf, (size_t)strlen(numbuf),
buf, (size_t)strlen (buf), NULL, 0);
}
static void
send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *stamp)
{
char numbuf1[50], numbuf2[50];
unsigned long value;
value = buf32_to_ulong (stamp);
if (!value)
return;
sprintf (numbuf1, "%d", number);
sprintf (numbuf2, "%lu", value);
send_status_info (ctrl, keyword,
numbuf1, (size_t)strlen(numbuf1),
numbuf2, (size_t)strlen(numbuf2), NULL, 0);
}
static void
send_key_data (ctrl_t ctrl, const char *name,
const unsigned char *a, size_t alen)
{
char *buffer, *buf;
size_t buflen;
buffer = buf = bin2hex (a, alen, NULL);
if (!buffer)
{
log_error ("memory allocation error in send_key_data\n");
return;
}
buflen = strlen (buffer);
/* 768 is the hexified size for the modulus of an 3072 bit key. We
use extra chunks to transmit larger data (i.e for 4096 bit). */
for ( ;buflen > 768; buflen -= 768, buf += 768)
send_status_info (ctrl, "KEY-DATA",
"-", 1,
buf, 768,
NULL, 0);
send_status_info (ctrl, "KEY-DATA",
name, (size_t)strlen(name),
buf, buflen,
NULL, 0);
xfree (buffer);
}
static void
send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno)
{
char buffer[200];
assert (keyno >=0 && keyno < DIM(app->app_local->keyattr));
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d",
keyno+1,
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
{
snprintf (buffer, sizeof buffer, "%d %d %s",
keyno+1,
keyno==1? PUBKEY_ALGO_ECDH :
(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)?
PUBKEY_ALGO_EDDSA : PUBKEY_ALGO_ECDSA,
app->app_local->keyattr[keyno].ecc.curve);
}
else
snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1);
send_status_direct (ctrl, keyword, buffer);
}
#define RSA_SMALL_SIZE_KEY 1952
#define RSA_SMALL_SIZE_OP 2048
static int
determine_rsa_response (app_t app, int keyno)
{
int size;
size = 2 + 3 /* header */
+ 4 /* tag+len */ + app->app_local->keyattr[keyno].rsa.n_bits/8
+ 2 /* tag+len */ + app->app_local->keyattr[keyno].rsa.e_bits/8;
return size;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "DISP-NAME", 0x005B },
{ "LOGIN-DATA", 0x005E },
{ "DISP-LANG", 0x5F2D },
{ "DISP-SEX", 0x5F35 },
{ "PUBKEY-URL", 0x5F50 },
{ "KEY-FPR", 0x00C5, 3 },
{ "KEY-TIME", 0x00CD, 4 },
{ "KEY-ATTR", 0x0000, -5 },
{ "CA-FPR", 0x00C6, 3 },
{ "CHV-STATUS", 0x00C4, 1 },
{ "SIG-COUNTER", 0x0093, 2 },
{ "SERIALNO", 0x004F, -1 },
{ "AID", 0x004F },
{ "EXTCAP", 0x0000, -2 },
{ "PRIVATE-DO-1", 0x0101 },
{ "PRIVATE-DO-2", 0x0102 },
{ "PRIVATE-DO-3", 0x0103 },
{ "PRIVATE-DO-4", 0x0104 },
{ "$AUTHKEYID", 0x0000, -3 },
{ "$DISPSERIALNO",0x0000, -4 },
{ NULL, 0 }
};
int idx, i, rc;
void *relptr;
unsigned char *value;
size_t valuelen;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].special == -1)
{
/* The serial number is very special. We could have used the
AID DO to retrieve it. The AID DO is available anyway but
not hex formatted. */
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
return 0;
}
if (table[idx].special == -2)
{
char tmp[110];
snprintf (tmp, sizeof tmp,
"gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d "
"sm=%d si=%u dec=%d bt=%d",
app->app_local->extcap.get_challenge,
app->app_local->extcap.key_import,
app->app_local->extcap.change_force_chv,
app->app_local->extcap.private_dos,
app->app_local->extcap.max_certlen_3,
app->app_local->extcap.algo_attr_change,
(app->app_local->extcap.sm_supported
? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES :
(app->app_local->extcap.sm_algo == 1?
CIPHER_ALGO_AES : CIPHER_ALGO_AES256))
: 0),
app->app_local->status_indicator,
app->app_local->extcap.has_decrypt,
app->app_local->extcap.has_button);
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -3)
{
char const tmp[] = "OPENPGP.3";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -4)
{
char *serial = app_get_serialno (app);
if (serial)
{
if (strlen (serial) > 16+12)
{
send_status_info (ctrl, table[idx].name, serial+16, 12, NULL, 0);
xfree (serial);
return 0;
}
xfree (serial);
}
return gpg_error (GPG_ERR_INV_NAME);
}
if (table[idx].special == -5)
{
for (i=0; i < 3; i++)
send_key_attr (ctrl, app, table[idx].name, i);
return 0;
}
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc);
if (relptr)
{
if (table[idx].special == 1)
{
char numbuf[7*23];
for (i=0,*numbuf=0; i < valuelen && i < 7; i++)
sprintf (numbuf+strlen (numbuf), " %d", value[i]);
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 2)
{
char numbuf[50];
sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen));
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 3)
{
if (valuelen >= 60)
for (i=0; i < 3; i++)
send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20);
}
else if (table[idx].special == 4)
{
if (valuelen >= 12)
for (i=0; i < 3; i++)
send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4);
}
else
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
return rc;
}
/* Return the DISP-NAME without any padding characters. Caller must
* free the result. If not found or empty NULL is returned. */
static char *
get_disp_name (app_t app)
{
int rc;
void *relptr;
unsigned char *value;
size_t valuelen;
char *string;
char *p, *given;
char *result;
relptr = get_one_do (app, 0x005B, &value, &valuelen, &rc);
if (!relptr)
return NULL;
string = xtrymalloc (valuelen + 1);
if (!string)
{
xfree (relptr);
return NULL;
}
memcpy (string, value, valuelen);
string[valuelen] = 0;
xfree (relptr);
/* Swap surname and given name. */
given = strstr (string, "<<");
for (p = string; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
result = strconcat (given, " ", string, NULL);
}
else
{
result = string;
string = NULL;
}
xfree (string);
return result;
}
/* Return the pretty formatted serialnumber. On error NULL is
* returned. */
static char *
get_disp_serialno (app_t app)
{
char *serial = app_get_serialno (app);
/* For our OpenPGP cards we do not want to show the entire serial
* number but a nicely reformatted actual serial number. */
if (serial && strlen (serial) > 16+12)
{
memmove (serial, serial+16, 4);
serial[4] = ' ';
/* memmove (serial+5, serial+20, 4); */
/* serial[9] = ' '; */
/* memmove (serial+10, serial+24, 4); */
/* serial[14] = 0; */
memmove (serial+5, serial+20, 8);
serial[13] = 0;
}
return serial;
}
/* Return the number of remaining tries for the standard or the admin
* pw. Returns -1 on card error. */
static int
get_remaining_tries (app_t app, int adminpw)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return -1;
}
remaining = value[adminpw? 6 : 4];
xfree (relptr);
return remaining;
}
/* Retrieve the fingerprint from the card inserted in SLOT and write
the according hex representation to FPR. Caller must have provide
a buffer at FPR of least 41 bytes. Returns 0 on success or an
error code. */
#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
retrieve_fpr_from_card (app_t app, int keyno, char *fpr)
{
gpg_error_t err = 0;
void *relptr;
unsigned char *value;
size_t valuelen;
assert (keyno >=0 && keyno <= 2);
relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL);
if (relptr && valuelen >= 60)
bin2hex (value+keyno*20, 20, fpr);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (relptr);
return err;
}
#endif /*GNUPG_MAJOR_VERSION > 1*/
/* Retrieve the public key material for the RSA key, whose fingerprint
is FPR, from gpg output, which can be read through the stream FP.
The RSA modulus will be stored at the address of M and MLEN, the
public exponent at E and ELEN. Returns zero on success, an error
code on failure. Caller must release the allocated buffers at M
and E if the function returns success. */
#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
retrieve_key_material (FILE *fp, const char *hexkeyid,
const unsigned char **m, size_t *mlen,
const unsigned char **e, size_t *elen)
{
gcry_error_t err = 0;
char *line = NULL; /* read_line() buffer. */
size_t line_size = 0; /* Helper for for read_line. */
int found_key = 0; /* Helper to find a matching key. */
unsigned char *m_new = NULL;
unsigned char *e_new = NULL;
size_t m_new_n = 0;
size_t e_new_n = 0;
/* Loop over all records until we have found the subkey
corresponding to the fingerprint. Inm general the first record
should be the pub record, but we don't rely on that. Given that
we only need to look at one key, it is sufficient to compare the
keyid so that we don't need to look at "fpr" records. */
for (;;)
{
char *p;
char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
int nfields;
size_t max_length;
gcry_mpi_t mpi;
int i;
max_length = 4096;
i = read_line (fp, &line, &line_size, &max_length);
if (!i)
break; /* EOF. */
if (i < 0)
{
err = gpg_error_from_syserror ();
goto leave; /* Error. */
}
if (!max_length)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave; /* Line truncated - we better stop processing. */
}
/* Parse the line into fields. */
for (nfields=0, p=line; p && nfields < DIM (fields); nfields++)
{
fields[nfields] = p;
p = strchr (p, ':');
if (p)
*(p++) = 0;
}
if (!nfields)
continue; /* No fields at all - skip line. */
if (!found_key)
{
if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
&& nfields > 4 && !strcmp (fields[4], hexkeyid))
found_key = 1;
continue;
}
if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
break; /* Next key - stop. */
if ( strcmp (fields[0], "pkd") )
continue; /* Not a key data record. */
if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1
|| (!i && m_new) || (i && e_new))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave; /* Error: Invalid key data record or not an RSA key. */
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL);
if (err)
mpi = NULL;
else if (!i)
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi);
else
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi);
gcry_mpi_release (mpi);
if (err)
goto leave;
}
if (m_new && e_new)
{
*m = m_new;
*mlen = m_new_n;
m_new = NULL;
*e = e_new;
*elen = e_new_n;
e_new = NULL;
}
else
err = gpg_error (GPG_ERR_GENERAL);
leave:
xfree (m_new);
xfree (e_new);
xfree (line);
return err;
}
#endif /*GNUPG_MAJOR_VERSION > 1*/
static gpg_error_t
rsa_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
size_t mlen, elen;
unsigned char *mbuf = NULL, *ebuf = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
return gpg_error (GPG_ERR_CARD);
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
return gpg_error (GPG_ERR_CARD);
}
if (ctrl)
{
send_key_data (ctrl, "n", m, mlen);
send_key_data (ctrl, "e", e, elen);
}
for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* strip leading zeroes */
;
if (ctrl)
{
unsigned char fprbuf[20];
err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
m, mlen, e, elen);
if (err)
return err;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Determine KDF hash algorithm and KEK encryption algorithm by CURVE. */
static const unsigned char*
ecdh_params (const char *curve)
{
unsigned int nbits;
openpgp_curve_to_oid (curve, &nbits);
/* See RFC-6637 for those constants.
0x03: Number of bytes
0x01: Version for this parameter format
KDF algo
KEK algo
*/
if (nbits <= 256)
return (const unsigned char*)"\x03\x01\x08\x07";
else if (nbits <= 384)
return (const unsigned char*)"\x03\x01\x09\x08";
else
return (const unsigned char*)"\x03\x01\x0a\x09";
}
static gpg_error_t
ecc_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
unsigned char *qbuf = NULL;
const unsigned char *ecc_q;
size_t ecc_q_len;
gcry_mpi_t oid = NULL;
int n;
const char *curve;
const char *oidstr;
const unsigned char *oidbuf;
size_t oid_len;
int algo;
const char *format;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_q_len);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
return gpg_error (GPG_ERR_CARD);
}
curve = app->app_local->keyattr[keyno].ecc.curve;
oidstr = openpgp_curve_to_oid (curve, NULL);
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
return err;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
qbuf = xtrymalloc (ecc_q_len + 1);
if (!qbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
{ /* Prepend 0x40 prefix. */
*qbuf = 0x40;
memcpy (qbuf+1, ecc_q, ecc_q_len);
ecc_q_len++;
}
else
memcpy (qbuf, ecc_q, ecc_q_len);
if (ctrl)
{
send_key_data (ctrl, "q", qbuf, ecc_q_len);
send_key_data (ctrl, "curve", oidbuf, oid_len);
}
if (keyno == 1)
{
if (ctrl)
send_key_data (ctrl, "kdf/kek", ecdh_params (curve), (size_t)4);
algo = PUBKEY_ALGO_ECDH;
}
else
{
if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
algo = PUBKEY_ALGO_EDDSA;
else
algo = PUBKEY_ALGO_ECDSA;
}
if (ctrl)
{
unsigned char fprbuf[20];
err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len,
qbuf, ecc_q_len, ecdh_params (curve), (size_t)4);
if (err)
goto leave;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
if (!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
format = "(public-key(ecc(curve%s)(q%b)))";
else if (keyno == 1)
format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))";
else
format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))";
err = gcry_sexp_build (r_sexp, NULL, format,
app->app_local->keyattr[keyno].ecc.curve,
(int)ecc_q_len, qbuf);
leave:
gcry_mpi_release (oid);
xfree (qbuf);
return err;
}
/* Parse tag-length-value data for public key in BUFFER of BUFLEN
length. Key of KEYNO in APP is updated with an S-expression of
public key. When CTRL is not NULL, fingerprint is computed with
CREATED_AT, and fingerprint is written to the card, and key data
and fingerprint are send back to the client side.
*/
static gpg_error_t
read_public_key (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *buffer, size_t buflen)
{
gpg_error_t err;
const unsigned char *data;
size_t datalen;
gcry_sexp_t s_pkey = NULL;
data = find_tlv (buffer, buflen, 0x7F49, &datalen);
if (!data)
{
log_error (_("response does not contain the public key data\n"));
return gpg_error (GPG_ERR_CARD);
}
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
err = rsa_read_pubkey (app, ctrl, created_at, keyno,
data, datalen, &s_pkey);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
err = ecc_read_pubkey (app, ctrl, created_at, keyno,
data, datalen, &s_pkey);
else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if (!err)
{
unsigned char *keybuf;
size_t len;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!data)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
return err;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
}
return err;
}
/* Get the public key for KEYNO and store it as an S-expresion with
the APP handle. On error that field gets cleared. If we already
know about the public key we will just return. Note that this does
not mean a key is available; this is solely indicated by the
presence of the app->app_local->pk[KEYNO].key field.
Note that GnuPG 1.x does not need this and it would be too time
consuming to send it just for the fun of it. However, given that we
use the same code in gpg 1.4, we can't use the gcry S-expresion
here but need to open encode it. */
#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
get_public_key (app_t app, int keyno)
{
gpg_error_t err = 0;
unsigned char *buffer;
const unsigned char *m, *e;
size_t buflen;
size_t mlen = 0;
size_t elen = 0;
char *keybuf = NULL;
gcry_sexp_t s_pkey;
size_t len;
if (keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* Already cached? */
if (app->app_local->pk[keyno].read_done)
return 0;
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
m = e = NULL; /* (avoid cc warning) */
if (app->card_version > 0x0100)
{
int exmode, le_value;
/* We may simply read the public key out of these cards. */
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY)
{
exmode = 1; /* Use extended length. */
le_value = determine_rsa_response (app, keyno);
}
else
{
exmode = 0;
le_value = 256; /* Use legacy value. */
}
err = iso7816_read_public_key (app->slot, exmode,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
log_error (_("reading public key failed: %s\n"), gpg_strerror (err));
goto leave;
}
err = read_public_key (app, NULL, 0U, keyno, buffer, buflen);
}
else
{
/* Due to a design problem in v1.0 cards we can't get the public
key out of these cards without doing a verify on CHV3.
Clearly that is not an option and thus we try to locate the
key using an external helper.
The helper we use here is gpg itself, which should know about
the key in any case. */
char fpr[41];
char *hexkeyid;
char *command = NULL;
FILE *fp;
int ret;
buffer = NULL; /* We don't need buffer. */
err = retrieve_fpr_from_card (app, keyno, fpr);
if (err)
{
log_error ("error while retrieving fpr from card: %s\n",
gpg_strerror (err));
goto leave;
}
hexkeyid = fpr + 24;
ret = gpgrt_asprintf
(&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr);
if (ret < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = popen (command, "r");
xfree (command);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("running gpg failed: %s\n", gpg_strerror (err));
goto leave;
}
err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
pclose (fp);
if (err)
{
log_error ("error while retrieving key material through pipe: %s\n",
gpg_strerror (err));
goto leave;
}
err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, m, (int)elen, e);
if (err)
goto leave;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!keybuf)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
goto leave;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = (unsigned char*)keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
}
leave:
/* Set a flag to indicate that we tried to read the key. */
app->app_local->pk[keyno].read_done = 1;
xfree (buffer);
return err;
}
#endif /* GNUPG_MAJOR_VERSION > 1 */
/* Send the KEYPAIRINFO back. KEY needs to be in the range [1,3].
This is used by the LEARN command. */
static gpg_error_t
send_keypair_info (app_t app, ctrl_t ctrl, int key)
{
int keyno = key - 1;
gpg_error_t err = 0;
/* Note that GnuPG 1.x does not need this and it would be too time
consuming to send it just for the fun of it. */
#if GNUPG_MAJOR_VERSION > 1
unsigned char grip[20];
char gripstr[41];
char idbuf[50];
err = get_public_key (app, keyno);
if (err)
goto leave;
assert (keyno >= 0 && keyno <= 2);
if (!app->app_local->pk[keyno].key)
goto leave; /* No such key - ignore. */
err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key,
app->app_local->pk[keyno].keylen,
grip);
if (err)
goto leave;
bin2hex (grip, 20, gripstr);
sprintf (idbuf, "OPENPGP.%d", keyno+1);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
idbuf, strlen (idbuf),
NULL, (size_t)0);
leave:
#endif /* GNUPG_MAJOR_VERSION > 1 */
return err;
}
/* Handle the LEARN command for OpenPGP. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
(void)flags;
do_getattr (app, ctrl, "EXTCAP");
do_getattr (app, ctrl, "DISP-NAME");
do_getattr (app, ctrl, "DISP-LANG");
do_getattr (app, ctrl, "DISP-SEX");
do_getattr (app, ctrl, "PUBKEY-URL");
do_getattr (app, ctrl, "LOGIN-DATA");
do_getattr (app, ctrl, "KEY-FPR");
if (app->card_version > 0x0100)
do_getattr (app, ctrl, "KEY-TIME");
do_getattr (app, ctrl, "CA-FPR");
do_getattr (app, ctrl, "CHV-STATUS");
do_getattr (app, ctrl, "SIG-COUNTER");
if (app->app_local->extcap.private_dos)
{
do_getattr (app, ctrl, "PRIVATE-DO-1");
do_getattr (app, ctrl, "PRIVATE-DO-2");
if (app->did_chv2)
do_getattr (app, ctrl, "PRIVATE-DO-3");
if (app->did_chv3)
do_getattr (app, ctrl, "PRIVATE-DO-4");
}
send_keypair_info (app, ctrl, 1);
send_keypair_info (app, ctrl, 2);
send_keypair_info (app, ctrl, 3);
/* Note: We do not send the Cardholder Certificate, because that is
relatively long and for OpenPGP applications not really needed. */
return 0;
}
/* Handle the READKEY command for OpenPGP. On success a canonical
encoded S-expression with the public key will get stored at PK and
its length (for assertions) at PKLEN; the caller must release that
buffer. On error PK and PKLEN are not changed and an error code is
returned. */
static gpg_error_t
do_readkey (app_t app, int advanced, const char *keyid,
unsigned char **pk, size_t *pklen)
{
#if GNUPG_MAJOR_VERSION > 1
gpg_error_t err;
int keyno;
unsigned char *buf;
if (!strcmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!strcmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!strcmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = get_public_key (app, keyno);
if (err)
return err;
buf = app->app_local->pk[keyno].key;
if (!buf)
return gpg_error (GPG_ERR_NO_PUBKEY);
if (advanced)
{
gcry_sexp_t s_key;
err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0);
if (err)
return err;
*pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0);
*pk = xtrymalloc (*pklen);
if (!*pk)
{
err = gpg_error_from_syserror ();
*pklen = 0;
return err;
}
gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen);
gcry_sexp_release (s_key);
/* Decrement for trailing '\0' */
*pklen = *pklen - 1;
}
else
{
*pklen = app->app_local->pk[keyno].keylen;
*pk = xtrymalloc (*pklen);
if (!*pk)
{
err = gpg_error_from_syserror ();
*pklen = 0;
return err;
}
memcpy (*pk, buf, *pklen);
}
return 0;
#else
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Read the standard certificate of an OpenPGP v2 card. It is
returned in a freshly allocated buffer with that address stored at
CERT and the length of the certificate stored at CERTLEN. CERTID
needs to be set to "OPENPGP.3". */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
#if GNUPG_MAJOR_VERSION > 1
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
*cert = NULL;
*certlen = 0;
if (strcmp (certid, "OPENPGP.3"))
return gpg_error (GPG_ERR_INV_ID);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_FOUND);
relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL);
if (!relptr)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!buflen)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (!(*cert = xtrymalloc (buflen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*cert, buffer, buflen);
*certlen = buflen;
err = 0;
}
xfree (relptr);
return err;
#else
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Decide if we use the pinpad of the reader for PIN input according
to the user preference on the card, and the capability of the
reader. This routine is only called when the reader has pinpad.
Returns 0 if we use pinpad, 1 otherwise. */
static int
check_pinpad_request (app_t app, pininfo_t *pininfo, int admin_pin)
{
if (app->app_local->pinpad.specified == 0) /* No preference on card. */
{
if (pininfo->fixedlen == 0) /* Reader has varlen capability. */
return 0; /* Then, use pinpad. */
else
/*
* Reader has limited capability, and it may not match PIN of
* the card.
*/
return 1;
}
if (admin_pin)
pininfo->fixedlen = app->app_local->pinpad.fixedlen_admin;
else
pininfo->fixedlen = app->app_local->pinpad.fixedlen_user;
if (pininfo->fixedlen == 0 /* User requests disable pinpad. */
|| pininfo->fixedlen < pininfo->minlen
|| pininfo->fixedlen > pininfo->maxlen
/* Reader doesn't have the capability to input a PIN which
* length is FIXEDLEN. */)
return 1;
return 0;
}
/* Return a string with information about the card for use in a
* prompt. Returns NULL on memory failure. */
static char *
get_prompt_info (app_t app, int chvno, unsigned long sigcount, int remaining)
{
char *serial, *disp_name, *rembuf, *tmpbuf, *result;
serial = get_disp_serialno (app);
if (!serial)
return NULL;
disp_name = get_disp_name (app);
if (chvno == 1)
{
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s%%0A"
"Counter\x1f: %lu"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
sigcount,
"");
}
else
{
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
"");
}
xfree (disp_name);
xfree (serial);
if (remaining != -1)
{
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (!rembuf)
{
xfree (result);
return NULL;
}
tmpbuf = strconcat (result, "%0A%0A", rembuf, NULL);
xfree (rembuf);
if (!tmpbuf)
{
xfree (result);
return NULL;
}
xfree (result);
result = tmpbuf;
}
return result;
}
/* Verify a CHV either using the pinentry or if possible by
using a pinpad. PINCB and PINCB_ARG describe the usual callback
for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only
used with CHV1. PINVALUE is the address of a pointer which will
receive a newly allocated block with the actual PIN (this is useful
in case that PIN shall be used for another verify operation). The
caller needs to free this value. If the function returns with
success and NULL is stored at PINVALUE, the caller should take this
as an indication that the pinpad has been used.
*/
static gpg_error_t
verify_a_chv (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
int chvno, unsigned long sigcount, char **pinvalue)
{
int rc = 0;
char *prompt_buffer = NULL;
const char *prompt;
pininfo_t pininfo;
int minlen = 6;
int remaining;
log_assert (chvno == 1 || chvno == 2);
*pinvalue = NULL;
remaining = get_remaining_tries (app, 0);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (chvno == 2 && app->app_local->flags.def_chv2)
{
/* Special case for def_chv2 mechanism. */
if (opt.verbose)
log_info (_("using default PIN as %s\n"), "CHV2");
rc = iso7816_verify (app->slot, 0x82, "123456", 6);
if (rc)
{
/* Verification of CHV2 with the default PIN failed,
although the card pretends to have the default PIN set as
CHV2. We better disable the def_chv2 flag now. */
log_info (_("failed to use default PIN as %s: %s"
" - disabling further default use\n"),
"CHV2", gpg_strerror (rc));
app->app_local->flags.def_chv2 = 0;
}
return rc;
}
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
{
const char *firstline = _("||Please unlock the card");
char *infoblock = get_prompt_info (app, chvno, sigcount,
remaining < 3? remaining : -1);
prompt_buffer = strconcat (firstline, "%0A%0A", infoblock, NULL);
if (prompt_buffer)
prompt = prompt_buffer;
else
prompt = firstline; /* ENOMEM fallback. */
xfree (infoblock);
}
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 0))
{
/* The reader supports the verify command through the pinpad.
Note that the pincb appends a text to the prompt telling the
user to use the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app->slot, 0x80+chvno, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
log_assert (!*pinvalue);
}
else
{
/* The reader has no pinpad or we don't want to use it. */
rc = pincb (pincb_arg, prompt, pinvalue);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (*pinvalue) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
xfree (*pinvalue);
*pinvalue = NULL;
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = iso7816_verify (app->slot, 0x80+chvno,
*pinvalue, strlen (*pinvalue));
}
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc));
xfree (*pinvalue);
*pinvalue = NULL;
flush_cache_after_error (app);
}
return rc;
}
/* Verify CHV2 if required. Depending on the configuration of the
card CHV1 will also be verified. */
static gpg_error_t
verify_chv2 (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc;
char *pinvalue;
if (app->did_chv2)
return 0; /* We already verified CHV2. */
rc = verify_a_chv (app, pincb, pincb_arg, 2, 0, &pinvalue);
if (rc)
return rc;
app->did_chv2 = 1;
if (!app->did_chv1 && !app->force_chv1 && pinvalue)
{
/* For convenience we verify CHV1 here too. We do this only if
the card is not configured to require a verification before
each CHV1 controlled operation (force_chv1) and if we are not
using the pinpad (PINVALUE == NULL). */
rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc));
flush_cache_after_error (app);
}
else
app->did_chv1 = 1;
}
xfree (pinvalue);
return rc;
}
/* Build the prompt to enter the Admin PIN. The prompt depends on the
current sdtate of the card. */
static gpg_error_t
build_enter_admin_pin_prompt (app_t app, char **r_prompt)
{
int remaining;
char *prompt;
char *infoblock;
*r_prompt = NULL;
remaining = get_remaining_tries (app, 1);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (!remaining)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_BAD_PIN);
}
log_info (ngettext("%d Admin PIN attempt remaining before card"
" is permanently locked\n",
"%d Admin PIN attempts remaining before card"
" is permanently locked\n",
remaining), remaining);
infoblock = get_prompt_info (app, 3, 0, remaining < 3? remaining : -1);
/* TRANSLATORS: Do not translate the "|A|" prefix but keep it at
the start of the string. Use %0A (single percent) for a linefeed. */
prompt = strconcat (_("|A|Please enter the Admin PIN"),
"%0A%0A", infoblock, NULL);
xfree (infoblock);
if (!prompt)
return gpg_error_from_syserror ();
*r_prompt = prompt;
return 0;
}
/* Verify CHV3 if required. */
static gpg_error_t
verify_chv3 (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
#if GNUPG_MAJOR_VERSION != 1
if (!opt.allow_admin)
{
log_info (_("access to admin commands is not configured\n"));
return gpg_error (GPG_ERR_EACCES);
}
#endif
if (!app->did_chv3)
{
pininfo_t pininfo;
int minlen = 8;
char *prompt;
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
rc = build_enter_admin_pin_prompt (app, &prompt);
if (rc)
return rc;
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 1))
{
/* The reader supports the verify command through the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app->slot, 0x83, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else
{
char *pinvalue;
rc = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (pinvalue) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), 3, minlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc));
flush_cache_after_error (app);
return rc;
}
app->did_chv3 = 1;
}
return rc;
}
/* Handle the SETATTR operation. All arguments are already basically
checked. */
static gpg_error_t
do_setattr (app_t app, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t rc;
int idx;
static struct {
const char *name;
int tag;
int need_chv;
int special;
unsigned int need_v2:1;
} table[] = {
{ "DISP-NAME", 0x005B, 3 },
{ "LOGIN-DATA", 0x005E, 3, 2 },
{ "DISP-LANG", 0x5F2D, 3 },
{ "DISP-SEX", 0x5F35, 3 },
{ "PUBKEY-URL", 0x5F50, 3 },
{ "CHV-STATUS-1", 0x00C4, 3, 1 },
{ "CA-FPR-1", 0x00CA, 3 },
{ "CA-FPR-2", 0x00CB, 3 },
{ "CA-FPR-3", 0x00CC, 3 },
{ "PRIVATE-DO-1", 0x0101, 2 },
{ "PRIVATE-DO-2", 0x0102, 3 },
{ "PRIVATE-DO-3", 0x0103, 2 },
{ "PRIVATE-DO-4", 0x0104, 3 },
{ "CERT-3", 0x7F21, 3, 0, 1 },
{ "SM-KEY-ENC", 0x00D1, 3, 0, 1 },
{ "SM-KEY-MAC", 0x00D2, 3, 0, 1 },
{ "KEY-ATTR", 0, 0, 3, 1 },
{ "AESKEY", 0x00D5, 3, 0, 1 },
{ NULL, 0 }
};
int exmode;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].need_v2 && !app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */
if (table[idx].special == 3)
return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen);
switch (table[idx].need_chv)
{
case 2:
rc = verify_chv2 (app, pincb, pincb_arg);
break;
case 3:
rc = verify_chv3 (app, pincb, pincb_arg);
break;
default:
rc = 0;
}
if (rc)
return rc;
/* Flush the cache before writing it, so that the next get operation
will reread the data from the card and thus get synced in case of
errors (e.g. data truncated by the card). */
flush_cache_item (app, table[idx].tag);
if (app->app_local->cardcap.ext_lc_le && valuelen > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && valuelen > 254)
exmode = -254; /* Command chaining with max. 254 bytes. */
else
exmode = 0;
rc = iso7816_put_data (app->slot, exmode, table[idx].tag, value, valuelen);
if (rc)
log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc));
if (table[idx].special == 1)
app->force_chv1 = (valuelen && *value == 0);
else if (table[idx].special == 2)
parse_login_data (app);
return rc;
}
/* Handle the WRITECERT command for OpenPGP. This rites the standard
certifciate to the card; CERTID needs to be set to "OPENPGP.3".
PINCB and PINCB_ARG are the usual arguments for the pinentry
callback. */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *certdata, size_t certdatalen)
{
(void)ctrl;
#if GNUPG_MAJOR_VERSION > 1
if (strcmp (certidstr, "OPENPGP.3"))
return gpg_error (GPG_ERR_INV_ID);
if (!certdata || !certdatalen)
return gpg_error (GPG_ERR_INV_ARG);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (certdatalen > app->app_local->extcap.max_certlen_3)
return gpg_error (GPG_ERR_TOO_LARGE);
return do_setattr (app, "CERT-3", pincb, pincb_arg, certdata, certdatalen);
#else
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Handle the PASSWD command. The following combinations are
possible:
Flags CHVNO Vers. Description
RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2
RESET 1 2 Verify PW3 and set a new PW1.
RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2.
RESET 2 2 Verify PW3 and set a new Reset Code.
RESET 3 any Returns GPG_ERR_INV_ID.
- 1 1 Verify CHV2 and set a new CHV1 and CHV2.
- 1 2 Verify PW1 and set a new PW1.
- 2 1 Verify CHV2 and set a new CHV1 and CHV2.
- 2 2 Verify Reset Code and set a new PW1.
- 3 any Verify CHV3/PW3 and set a new CHV3/PW3.
*/
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
int chvno = atoi (chvnostr);
char *resetcode = NULL;
char *oldpinvalue = NULL;
char *pinvalue = NULL;
int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET);
int set_resetcode = 0;
pininfo_t pininfo;
int use_pinpad = 0;
int minlen = 6;
(void)ctrl;
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
if (reset_mode && chvno == 3)
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (reset_mode || chvno == 3)
{
/* We always require that the PIN is entered. */
app->did_chv3 = 0;
rc = verify_chv3 (app, pincb, pincb_arg);
if (rc)
goto leave;
}
else if (chvno == 1 || chvno == 2)
{
/* On a v1.x card CHV1 and CVH2 should always have the same
value, thus we enforce it here. */
int save_force = app->force_chv1;
app->force_chv1 = 0;
app->did_chv1 = 0;
app->did_chv2 = 0;
rc = verify_chv2 (app, pincb, pincb_arg);
app->force_chv1 = save_force;
if (rc)
goto leave;
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
}
else
{
/* Version 2 cards. */
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot,
ISO7816_CHANGE_REFERENCE_DATA, &pininfo)
&& !check_pinpad_request (app, &pininfo, chvno == 3))
use_pinpad = 1;
if (reset_mode)
{
/* To reset a PIN the Admin PIN is required. */
use_pinpad = 0;
app->did_chv3 = 0;
rc = verify_chv3 (app, pincb, pincb_arg);
if (rc)
goto leave;
if (chvno == 2)
set_resetcode = 1;
}
else if (chvno == 1 || chvno == 3)
{
if (!use_pinpad)
{
char *promptbuf = NULL;
const char *prompt;
if (chvno == 3)
{
minlen = 8;
rc = build_enter_admin_pin_prompt (app, &promptbuf);
if (rc)
goto leave;
prompt = promptbuf;
}
else
prompt = _("||Please enter the PIN");
rc = pincb (pincb_arg, prompt, &oldpinvalue);
xfree (promptbuf);
promptbuf = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (oldpinvalue) < minlen)
{
log_info (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
}
else if (chvno == 2)
{
/* There is no PW2 for v2 cards. We use this condition to
allow a PW reset using the Reset Code. */
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
use_pinpad = 0;
minlen = 8;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
rc = gpg_error (GPG_ERR_CARD);
goto leave;
}
remaining = value[5];
xfree (relptr);
if (!remaining)
{
log_error (_("Reset Code not or not anymore available\n"));
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
rc = pincb (pincb_arg,
_("||Please enter the Reset Code for the card"),
&resetcode);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (resetcode) < minlen)
{
log_info (_("Reset Code is too short; minimum length is %d\n"),
minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
}
if (chvno == 3)
app->did_chv3 = 0;
else
app->did_chv1 = app->did_chv2 = 0;
if (!use_pinpad)
{
/* TRANSLATORS: Do not translate the "|*|" prefixes but
keep it at the start of the string. We need this elsewhere
to get some infos on the string. */
rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") :
chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"),
&pinvalue);
if (rc || pinvalue == NULL)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc));
goto leave;
}
}
if (resetcode)
{
char *buffer;
buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1);
if (!buffer)
rc = gpg_error_from_syserror ();
else
{
strcpy (stpcpy (buffer, resetcode), pinvalue);
rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81,
buffer, strlen (buffer));
wipememory (buffer, strlen (buffer));
xfree (buffer);
}
}
else if (set_resetcode)
{
if (strlen (pinvalue) < 8)
{
log_error (_("Reset Code is too short; minimum length is %d\n"), 8);
rc = gpg_error (GPG_ERR_BAD_PIN);
}
else
rc = iso7816_put_data (app->slot, 0, 0xD3,
pinvalue, strlen (pinvalue));
}
else if (reset_mode)
{
rc = iso7816_reset_retry_counter (app->slot, 0x81,
pinvalue, strlen (pinvalue));
if (!rc && !app->app_local->extcap.is_v2)
rc = iso7816_reset_retry_counter (app->slot, 0x82,
pinvalue, strlen (pinvalue));
}
else if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (chvno == 1 || chvno == 2)
{
rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0,
pinvalue, strlen (pinvalue));
if (!rc)
rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0,
pinvalue, strlen (pinvalue));
}
else /* CHVNO == 3 */
{
rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0,
pinvalue, strlen (pinvalue));
}
}
else
{
/* Version 2 cards. */
assert (chvno == 1 || chvno == 3);
if (use_pinpad)
{
rc = pincb (pincb_arg,
chvno == 3 ?
_("||Please enter the Admin PIN and New Admin PIN") :
_("||Please enter the PIN and New PIN"), NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
rc = iso7816_change_reference_data_kp (app->slot, 0x80 + chvno, 0,
&pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
rc = iso7816_change_reference_data (app->slot, 0x80 + chvno,
oldpinvalue, strlen (oldpinvalue),
pinvalue, strlen (pinvalue));
}
if (pinvalue)
{
wipememory (pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
flush_cache_after_error (app);
leave:
if (resetcode)
{
wipememory (resetcode, strlen (resetcode));
xfree (resetcode);
}
if (oldpinvalue)
{
wipememory (oldpinvalue, strlen (oldpinvalue));
xfree (oldpinvalue);
}
return rc;
}
/* Check whether a key already exists. KEYIDX is the index of the key
(0..2). If FORCE is TRUE a diagnositic will be printed but no
error returned if the key already exists. The flag GENERATING is
only used to print correct messages. */
static gpg_error_t
does_key_exist (app_t app, int keyidx, int generating, int force)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int i;
assert (keyidx >=0 && keyidx <= 2);
if (iso7816_get_data (app->slot, 0, 0x006E, &buffer, &buflen))
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n < 60)
{
log_error (_("error reading fingerprint DO\n"));
xfree (buffer);
return gpg_error (GPG_ERR_GENERAL);
}
fpr += 20*keyidx;
for (i=0; i < 20 && !fpr[i]; i++)
;
xfree (buffer);
if (i!=20 && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
else if (i!=20)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Create a TLV tag and value and store it at BUFFER. Return the length
of tag and length. A LENGTH greater than 65535 is truncated. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
unsigned char *p = buffer;
assert (tag <= 0xffff);
if ( tag > 0xff )
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
if (length > 0xffff)
length = 0xffff;
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
static gpg_error_t
build_privkey_template (app_t app, int keyno,
const unsigned char *rsa_n, size_t rsa_n_len,
const unsigned char *rsa_e, size_t rsa_e_len,
const unsigned char *rsa_p, size_t rsa_p_len,
const unsigned char *rsa_q, size_t rsa_q_len,
const unsigned char *rsa_u, size_t rsa_u_len,
const unsigned char *rsa_dp, size_t rsa_dp_len,
const unsigned char *rsa_dq, size_t rsa_dq_len,
unsigned char **result, size_t *resultlen)
{
size_t rsa_e_reqlen;
unsigned char privkey[7*(1+3+3)];
size_t privkey_len;
unsigned char exthdr[2+2+3];
size_t exthdr_len;
unsigned char suffix[2+3];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
*result = NULL;
*resultlen = 0;
switch (app->app_local->keyattr[keyno].rsa.format)
{
case RSA_STD:
case RSA_STD_N:
case RSA_CRT:
case RSA_CRT_N:
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
/* Get the required length for E. Rounded up to the nearest byte */
rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8;
assert (rsa_e_len <= rsa_e_reqlen);
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x91, rsa_e_reqlen);
datalen += rsa_e_reqlen;
tp += add_tlv (tp, 0x92, rsa_p_len);
datalen += rsa_p_len;
tp += add_tlv (tp, 0x93, rsa_q_len);
datalen += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x94, rsa_u_len);
datalen += rsa_u_len;
tp += add_tlv (tp, 0x95, rsa_dp_len);
datalen += rsa_dp_len;
tp += add_tlv (tp, 0x96, rsa_dq_len);
datalen += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x97, rsa_n_len);
datalen += rsa_n_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 3 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < rsa_e_reqlen)
{
/* Right justify E. */
memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len);
memset (tp, 0, rsa_e_reqlen - rsa_e_len);
}
tp += rsa_e_reqlen;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_u, rsa_u_len);
tp += rsa_u_len;
memcpy (tp, rsa_dp, rsa_dp_len);
tp += rsa_dp_len;
memcpy (tp, rsa_dq, rsa_dq_len);
tp += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_n, rsa_n_len);
tp += rsa_n_len;
}
/* Sanity check. We don't know the exact length because we
allocated 3 bytes for the first length header. */
assert (tp - template <= template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
static gpg_error_t
build_ecc_privkey_template (app_t app, int keyno,
const unsigned char *ecc_d, size_t ecc_d_len,
const unsigned char *ecc_q, size_t ecc_q_len,
unsigned char **result, size_t *resultlen)
{
unsigned char privkey[2+2];
size_t privkey_len;
unsigned char exthdr[2+2+1];
size_t exthdr_len;
unsigned char suffix[2+1];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
int pubkey_required;
pubkey_required = !!(app->app_local->keyattr[keyno].ecc.flags
& ECC_FLAG_PUBKEY);
*result = NULL;
*resultlen = 0;
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x92, ecc_d_len);
datalen += ecc_d_len;
if (pubkey_required)
{
tp += add_tlv (tp, 0x99, ecc_q_len);
datalen += ecc_q_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 1 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
if (exthdr_len + privkey_len + suffix_len + datalen >= 128)
template_size++;
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
memcpy (tp, ecc_d, ecc_d_len);
tp += ecc_d_len;
if (pubkey_required)
{
memcpy (tp, ecc_q, ecc_q_len);
tp += ecc_q_len;
}
assert (tp - template == template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
/* Helper for do_writekley to change the size of a key. Not ethat
this deletes the entire key without asking. */
static gpg_error_t
change_keyattr (app_t app, int keyno, const unsigned char *buf, size_t buflen,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
assert (keyno >=0 && keyno <= 2);
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
return err;
/* Change the attribute. */
err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buf, buflen);
if (err)
log_error ("error changing key attribute (key=%d)\n", keyno+1);
else
log_info ("key attribute changed (key=%d)\n", keyno+1);
flush_cache (app);
parse_algorithm_attribute (app, keyno);
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
return err;
}
static gpg_error_t
change_rsa_keyattr (app_t app, int keyno, unsigned int nbits,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err = 0;
unsigned char *buf;
size_t buflen;
void *relptr;
/* Read the current attributes into a buffer. */
relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL);
if (!relptr)
err = gpg_error (GPG_ERR_CARD);
else if (buflen < 6 || buf[0] != PUBKEY_ALGO_RSA)
{
/* Attriutes too short or not an RSA key. */
xfree (relptr);
err = gpg_error (GPG_ERR_CARD);
}
else
{
/* We only change n_bits and don't touch anything else. Before we
do so, we round up NBITS to a sensible way in the same way as
gpg's key generation does it. This may help to sort out problems
with a few bits too short keys. */
nbits = ((nbits + 31) / 32) * 32;
buf[1] = (nbits >> 8);
buf[2] = nbits;
err = change_keyattr (app, keyno, buf, buflen, pincb, pincb_arg);
xfree (relptr);
}
return err;
}
/* Helper to process an setattr command for name KEY-ATTR.
In (VALUE,VALUELEN), it expects following string:
RSA: "--force <key> <algo> rsa<nbits>"
ECC: "--force <key> <algo> <curvename>"
*/
static gpg_error_t
change_keyattr_from_string (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *value, size_t valuelen)
{
gpg_error_t err = 0;
char *string;
int key, keyno, algo;
int n = 0;
/* VALUE is expected to be a string but not guaranteed to be
terminated. Thus copy it to an allocated buffer first. */
string = xtrymalloc (valuelen+1);
if (!string)
return gpg_error_from_syserror ();
memcpy (string, value, valuelen);
string[valuelen] = 0;
/* Because this function deletes the key we require the string
"--force" in the data to make clear that something serious might
happen. */
sscanf (string, "--force %d %d %n", &key, &algo, &n);
if (n < 12)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
keyno = key - 1;
if (keyno < 0 || keyno > 2)
err = gpg_error (GPG_ERR_INV_ID);
else if (algo == PUBKEY_ALGO_RSA)
{
unsigned int nbits;
errno = 0;
nbits = strtoul (string+n+3, NULL, 10);
if (errno)
err = gpg_error (GPG_ERR_INV_DATA);
else if (nbits < 1024)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (nbits > 4096)
err = gpg_error (GPG_ERR_TOO_LARGE);
else
err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg);
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
{
const char *oidstr;
gcry_mpi_t oid;
const unsigned char *oidbuf;
size_t oid_len;
oidstr = openpgp_curve_to_oid (string+n, NULL);
if (!oidstr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
oid_len = (n+7)/8;
/* We have enough room at STRING. */
string[0] = algo;
memcpy (string+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, keyno, string, oid_len, pincb, pincb_arg);
gcry_mpi_release (oid);
}
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
leave:
xfree (string);
return err;
}
static gpg_error_t
rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
unsigned int nbits;
unsigned int maxbits;
unsigned char *template = NULL;
unsigned char *tp;
size_t template_len;
unsigned char fprbuf[20];
u32 created_at = 0;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA)
{
log_error (_("unsupported algorithm: %s"), "RSA");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0;
if (opt.verbose)
log_info ("RSA modulus size is %u bits\n", nbits);
if (nbits && nbits != maxbits
&& app->app_local->extcap.algo_attr_change)
{
/* Try to switch the key to a new length. */
err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg);
if (!err)
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
}
if (nbits != maxbits)
{
log_error (_("RSA modulus missing or not of size %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.e_bits;
if (maxbits > 32 && !app->app_local->extcap.is_v2)
maxbits = 32; /* Our code for v1 does only support 32 bits. */
nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0;
if (nbits < 2 || nbits > maxbits)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2;
nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"P", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"Q", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
unsigned char *rsa_u, *rsa_dp, *rsa_dq;
size_t rsa_u_len, rsa_dp_len, rsa_dq_len;
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_u = gcry_mpi_snew (0);
gcry_mpi_t mpi_dp = gcry_mpi_snew (0);
gcry_mpi_t mpi_dq = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
int exmode;
/* Calculate the u, dp and dq components needed by RSA_CRT cards */
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_invm (mpi_u, mpi_q, mpi_p);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_u);
gcry_mpi_release (mpi_dp);
gcry_mpi_release (mpi_dq);
gcry_mpi_release (mpi_tmp);
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
err = build_privkey_template (app, keyno,
rsa_n, rsa_n_len,
rsa_e, rsa_e_len,
rsa_p, rsa_p_len,
rsa_q, rsa_q_len,
rsa_u, rsa_u_len,
rsa_dp, rsa_dp_len,
rsa_dq, rsa_dq_len,
&template, &template_len);
xfree(rsa_u);
xfree(rsa_dp);
xfree(rsa_dq);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
template, template_len);
}
else
{
/* Build the private key template as described in section 4.3.3.6 of
the OpenPGP card specs version 1.1:
0xC0 <length> public exponent
0xC1 <length> prime p
0xC2 <length> prime q
*/
assert (rsa_e_len <= 4);
template_len = (1 + 1 + 4
+ 1 + 1 + rsa_p_len
+ 1 + 1 + rsa_q_len);
template = tp = xtrymalloc_secure (template_len);
if (!template)
{
err = gpg_error_from_syserror ();
goto leave;
}
*tp++ = 0xC0;
*tp++ = 4;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < 4)
{
/* Right justify E. */
memmove (tp+4-rsa_e_len, tp, rsa_e_len);
memset (tp, 0, 4-rsa_e_len);
}
tp += 4;
*tp++ = 0xC1;
*tp++ = rsa_p_len;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
*tp++ = 0xC2;
*tp++ = rsa_q_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
assert (tp - template == template_len);
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
err = iso7816_put_data (app->slot, 0,
(app->card_version > 0x0007? 0xE0:0xE9)+keyno,
template, template_len);
}
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
rsa_n, rsa_n_len, rsa_e, rsa_e_len);
if (err)
goto leave;
leave:
xfree (template);
return err;
}
static gpg_error_t
ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
const char *curve = NULL;
u32 created_at = 0;
const char *oidstr;
int flag_djb_tweak = 0;
int algo;
gcry_mpi_t oid = NULL;
const unsigned char *oidbuf;
unsigned int n;
size_t oid_len;
unsigned char fprbuf[20];
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "NIST P-256" */
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "secp256k1" */
/* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)):
curve = "Ed25519" */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *curve_name;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
curve_name = xtrymalloc (toklen+1);
if (!curve_name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (curve_name, tok, toklen);
curve_name[toklen] = 0;
curve = openpgp_is_curve_supported (curve_name, NULL, NULL);
xfree (curve_name);
}
else if (tok && toklen == 5 && !memcmp (tok, "flags", 5))
{
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok)
{
if ((toklen == 5 && !memcmp (tok, "eddsa", 5))
|| (toklen == 9 && !memcmp (tok, "djb-tweak", 9)))
flag_djb_tweak = 1;
}
}
else if (tok && toklen == 1)
{
const unsigned char **buf2;
size_t *buf2len;
int native = flag_djb_tweak;
switch (*tok)
{
case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break;
case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; native = 0; break;
default: buf2 = NULL; buf2len = NULL; break;
}
if (buf2 && *buf2)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && buf2)
{
if (!native)
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*buf2 = tok;
*buf2len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!curve)
{
log_error (_("unsupported curve\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (flag_djb_tweak && keyno != 1)
algo = PUBKEY_ALGO_EDDSA;
else if (keyno == 1)
algo = PUBKEY_ALGO_ECDH;
else
algo = PUBKEY_ALGO_ECDSA;
oidstr = openpgp_curve_to_oid (curve, NULL);
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC
|| app->app_local->keyattr[keyno].ecc.curve != curve
|| (flag_djb_tweak !=
(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)))
{
if (app->app_local->extcap.algo_attr_change)
{
unsigned char *keyattr;
if (!oid_len)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
keyattr = xtrymalloc (oid_len);
if (!keyattr)
{
err = gpg_error_from_syserror ();
goto leave;
}
keyattr[0] = algo;
memcpy (keyattr+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, keyno, keyattr, oid_len, pincb, pincb_arg);
xfree (keyattr);
if (err)
goto leave;
}
else
{
log_error ("key attribute on card doesn't match\n");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
unsigned char *template;
size_t template_len;
int exmode;
err = build_ecc_privkey_template (app, keyno,
ecc_d, ecc_d_len,
ecc_q, ecc_q_len,
&template, &template_len);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
{
xfree (template);
goto leave;
}
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
template, template_len);
xfree (template);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len,
ecc_q, ecc_q_len, ecdh_params (curve), (size_t)4);
leave:
gcry_mpi_release (oid);
return err;
}
/* Handle the WRITEKEY command for OpenPGP. This function expects a
canonical encoded S-expression with the secret key in KEYDATA and
its length (for assertions) in KEYDATALEN. KEYID needs to be the
usual keyid which for OpenPGP is the string "OPENPGP.n" with
n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall
get overwritten. PINCB and PINCB_ARG are the usual arguments for
the pinentry callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
int keyno;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
(void)ctrl;
if (!strcmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!strcmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!strcmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = does_key_exist (app, keyno, 0, force);
if (err)
return err;
/*
Parse the S-expression
*/
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
err = rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
err = ecc_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
else
{
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
goto leave;
}
leave:
return err;
}
/* Handle the GENKEY command. */
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags,
time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char numbuf[30];
unsigned char *buffer = NULL;
const unsigned char *keydata;
size_t buflen, keydatalen;
u32 created_at;
int keyno = atoi (keynostr) - 1;
int force = (flags & 1);
time_t start_at;
int exmode = 0;
int le_value = 256; /* Use legacy value. */
if (keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* We flush the cache to increase the traffic before a key
generation. This _might_ help a card to gather more entropy. */
flush_cache (app);
/* Obviously we need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
/* Check whether a key already exists. */
err = does_key_exist (app, keyno, 1, force);
if (err)
return err;
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
unsigned int keybits = app->app_local->keyattr[keyno].rsa.n_bits;
/* Because we send the key parameter back via status lines we need
to put a limit on the max. allowed keysize. 2048 bit will
already lead to a 527 byte long status line and thus a 4096 bit
key would exceed the Assuan line length limit. */
if (keybits > 4096)
return gpg_error (GPG_ERR_TOO_LARGE);
if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
exmode = 1; /* Use extended length w/o a limit. */
le_value = determine_rsa_response (app, keyno);
/* No need to check le_value because it comes from a 16 bit
value and thus can't create an overflow on a 32 bit
system. */
}
}
/* Prepare for key generation by verifying the Admin PIN. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
return err;
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
err = iso7816_generate_keypair (app->slot, exmode,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
log_error (_("generating key failed\n"));
return gpg_error (GPG_ERR_CARD);
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
created_at = (u32)(createtime? createtime : gnupg_get_time ());
sprintf (numbuf, "%u", created_at);
send_status_info (ctrl, "KEY-CREATED-AT",
numbuf, (size_t)strlen(numbuf), NULL, 0);
err = read_public_key (app, ctrl, created_at, keyno, buffer, buflen);
leave:
xfree (buffer);
return err;
}
static unsigned long
convert_sig_counter_value (const unsigned char *value, size_t valuelen)
{
unsigned long ul;
if (valuelen == 3 )
ul = (value[0] << 16) | (value[1] << 8) | value[2];
else
{
log_error (_("invalid structure of OpenPGP card (DO 0x93)\n"));
ul = 0;
}
return ul;
}
static unsigned long
get_sig_counter (app_t app)
{
void *relptr;
unsigned char *value;
size_t valuelen;
unsigned long ul;
relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL);
if (!relptr)
return 0;
ul = convert_sig_counter_value (value, valuelen);
xfree (relptr);
return ul;
}
static gpg_error_t
compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int rc, i;
assert (keyno >= 0 && keyno <= 2);
rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0);
if (rc)
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n != 60)
{
xfree (buffer);
log_error (_("error reading fingerprint DO\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr += keyno*20;
for (i=0; i < 20; i++)
if (sha1fpr[i] != fpr[i])
{
xfree (buffer);
log_info (_("fingerprint on card does not match requested one\n"));
return gpg_error (GPG_ERR_WRONG_SECKEY);
}
xfree (buffer);
return 0;
}
/* If a fingerprint has been specified check it against the one on the
card. This allows for a meaningful error message in case the key
on the card has been replaced but the shadow information known to
gpg has not been updated. If there is no fingerprint we assume
that this is okay. */
static gpg_error_t
check_against_given_fingerprint (app_t app, const char *fpr, int key)
{
unsigned char tmp[20];
const char *s;
int n;
for (s=fpr, n=0; hexdigitp (s); s++, n++)
;
if (n != 40)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* okay */
else
return gpg_error (GPG_ERR_INV_ID);
for (s=fpr, n=0; n < 20; s += 2, n++)
tmp[n] = xtoi_2 (s);
return compare_fingerprint (app, key-1, tmp);
}
/* Compute a digital signature on INDATA which is expected to be the
raw message digest. For this application the KEYIDSTR consists of
the serialnumber and the fingerprint delimited by a slash.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match).
As a special feature a KEYIDSTR of "OPENPGP.3" redirects the
operation to the auth command.
*/
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
int rc;
unsigned char data[19+64];
size_t datalen;
unsigned char tmp_sn[20]; /* Actually 16 bytes but also for the fpr. */
const char *s;
int n;
const char *fpr = NULL;
unsigned long sigcount;
int use_auth = 0;
int exmode, le_value;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
/* Strip off known prefixes. */
#define X(a,b,c,d) \
if (hashalgo == GCRY_MD_ ## a \
&& (d) \
&& indatalen == sizeof b ## _prefix + (c) \
&& !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \
{ \
indata = (const char*)indata + sizeof b ## _prefix; \
indatalen -= sizeof b ## _prefix; \
}
if (indatalen == 20)
; /* Assume a plain SHA-1 or RMD160 digest has been given. */
else X(SHA1, sha1, 20, 1)
else X(RMD160, rmd160, 20, 1)
else X(SHA224, sha224, 28, app->app_local->extcap.is_v2)
else X(SHA256, sha256, 32, app->app_local->extcap.is_v2)
else X(SHA384, sha384, 48, app->app_local->extcap.is_v2)
else X(SHA512, sha512, 64, app->app_local->extcap.is_v2)
else if ((indatalen == 28 || indatalen == 32
|| indatalen == 48 || indatalen ==64)
&& app->app_local->extcap.is_v2)
; /* Assume a plain SHA-3 digest has been given. */
else
{
log_error (_("card does not support digest algorithm %s\n"),
gcry_md_algo_name (hashalgo));
/* Or the supplied digest length does not match an algorithm. */
return gpg_error (GPG_ERR_INV_VALUE);
}
#undef X
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.1"))
;
else if (!strcmp (keyidstr, "OPENPGP.3"))
use_auth = 1;
else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
return gpg_error (GPG_ERR_INV_ID);
else
{
for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
;
if (n != 32)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* no fingerprint given: we allow this for now. */
else if (*s == '/')
fpr = s + 1;
else
return gpg_error (GPG_ERR_INV_ID);
for (s=keyidstr, n=0; n < 16; s += 2, n++)
tmp_sn[n] = xtoi_2 (s);
if (app->serialnolen != 16)
return gpg_error (GPG_ERR_INV_CARD);
if (memcmp (app->serialno, tmp_sn, 16))
return gpg_error (GPG_ERR_WRONG_CARD);
}
/* If a fingerprint has been specified check it against the one on
the card. This is allows for a meaningful error message in case
the key on the card has been replaced but the shadow information
known to gpg was not updated. If there is no fingerprint, gpg
will detect a bogus signature anyway due to the
verify-after-signing feature. */
rc = fpr? check_against_given_fingerprint (app, fpr, 1) : 0;
if (rc)
return rc;
/* Concatenate prefix and digest. */
#define X(a,b,d) \
if (hashalgo == GCRY_MD_ ## a && (d) ) \
{ \
datalen = sizeof b ## _prefix + indatalen; \
assert (datalen <= sizeof data); \
memcpy (data, b ## _prefix, sizeof b ## _prefix); \
memcpy (data + sizeof b ## _prefix, indata, indatalen); \
}
if (use_auth
|| app->app_local->keyattr[use_auth? 2: 0].key_type == KEY_TYPE_RSA)
{
X(SHA1, sha1, 1)
else X(RMD160, rmd160, 1)
else X(SHA224, sha224, app->app_local->extcap.is_v2)
else X(SHA256, sha256, app->app_local->extcap.is_v2)
else X(SHA384, sha384, app->app_local->extcap.is_v2)
else X(SHA512, sha512, app->app_local->extcap.is_v2)
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
else
{
datalen = indatalen;
memcpy (data, indata, indatalen);
}
#undef X
/* Redirect to the AUTH command if asked to. */
if (use_auth)
{
return do_auth (app, "OPENPGP.3", pincb, pincb_arg,
data, datalen,
outdata, outdatalen);
}
/* Show the number of signature done using this key. */
sigcount = get_sig_counter (app);
log_info (_("signatures created so far: %lu\n"), sigcount);
/* Check CHV if needed. */
if (!app->did_chv1 || app->force_chv1 )
{
char *pinvalue;
rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount, &pinvalue);
if (rc)
return rc;
app->did_chv1 = 1;
/* For cards with versions < 2 we want to keep CHV1 and CHV2 in
sync, thus we verify CHV2 here using the given PIN. Cards
with version2 to not have the need for a separate CHV2 and
internally use just one. Obviously we can't do that if the
pinpad has been used. */
if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2)
{
rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc));
xfree (pinvalue);
flush_cache_after_error (app);
return rc;
}
app->did_chv2 = 1;
}
xfree (pinvalue);
}
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[0].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[0].rsa.n_bits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value,
outdata, outdatalen);
return rc;
}
/* Compute a digital signature using the INTERNAL AUTHENTICATE command
on INDATA which is expected to be the raw message digest. For this
application the KEYIDSTR consists of the serialnumber and the
fingerprint delimited by a slash. Optionally the id OPENPGP.3 may
be given.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match). */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
int rc;
unsigned char tmp_sn[20]; /* Actually 16 but we use it also for the fpr. */
const char *s;
int n;
const char *fpr = NULL;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
&& indatalen > 101) /* For a 2048 bit key. */
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
if (!(app->app_local->keyattr[2].ecc.flags & ECC_FLAG_DJB_TWEAK)
&& (indatalen == 51 || indatalen == 67 || indatalen == 83))
{
const char *p = (const char *)indata + 19;
indata = p;
indatalen -= 19;
}
else
{
const char *p = (const char *)indata + 15;
indata = p;
indatalen -= 15;
}
}
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.3"))
;
else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
return gpg_error (GPG_ERR_INV_ID);
else
{
for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
;
if (n != 32)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* no fingerprint given: we allow this for now. */
else if (*s == '/')
fpr = s + 1;
else
return gpg_error (GPG_ERR_INV_ID);
for (s=keyidstr, n=0; n < 16; s += 2, n++)
tmp_sn[n] = xtoi_2 (s);
if (app->serialnolen != 16)
return gpg_error (GPG_ERR_INV_CARD);
if (memcmp (app->serialno, tmp_sn, 16))
return gpg_error (GPG_ERR_WRONG_CARD);
}
/* If a fingerprint has been specified check it against the one on
the card. This is allows for a meaningful error message in case
the key on the card has been replaced but the shadow information
known to gpg was not updated. If there is no fingerprint, gpg
will detect a bogus signature anyway due to the
verify-after-signing feature. */
rc = fpr? check_against_given_fingerprint (app, fpr, 3) : 0;
if (rc)
return rc;
rc = verify_chv2 (app, pincb, pincb_arg);
if (!rc)
{
int exmode, le_value;
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[2].rsa.n_bits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
rc = iso7816_internal_authenticate (app->slot, exmode,
indata, indatalen, le_value,
outdata, outdatalen);
}
return rc;
}
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
int rc;
unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
const char *s;
int n;
const char *fpr = NULL;
int exmode, le_value;
unsigned char *fixbuf = NULL;
int padind = 0;
int fixuplen = 0;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.2"))
;
else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
return gpg_error (GPG_ERR_INV_ID);
else
{
for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
;
if (n != 32)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* no fingerprint given: we allow this for now. */
else if (*s == '/')
fpr = s + 1;
else
return gpg_error (GPG_ERR_INV_ID);
for (s=keyidstr, n=0; n < 16; s += 2, n++)
tmp_sn[n] = xtoi_2 (s);
if (app->serialnolen != 16)
return gpg_error (GPG_ERR_INV_CARD);
if (memcmp (app->serialno, tmp_sn, 16))
return gpg_error (GPG_ERR_WRONG_CARD);
}
/* If a fingerprint has been specified check it against the one on
the card. This is allows for a meaningful error message in case
the key on the card has been replaced but the shadow information
known to gpg was not updated. If there is no fingerprint, the
decryption won't produce the right plaintext anyway. */
rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0;
if (rc)
return rc;
rc = verify_chv2 (app, pincb, pincb_arg);
if (rc)
return rc;
if ((indatalen == 16 + 1 || indatalen == 32 + 1)
&& ((char *)indata)[0] == 0x02)
{
/* PSO:DECIPHER with symmetric key. */
padind = -1;
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA)
{
/* We might encounter a couple of leading zeroes in the
cryptogram. Due to internal use of MPIs these leading zeroes
are stripped. However the OpenPGP card expects exactly 128
bytes for the cryptogram (for a 1k key). Thus we need to fix
it up. We do this for up to 16 leading zero bytes; a
cryptogram with more than this is with a very high
probability anyway broken. If a signed conversion was used
we may also encounter one leading zero followed by the correct
length. We fix that as well. */
if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */
fixuplen = 128 - indatalen;
else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */
fixuplen = 192 - indatalen;
else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */
fixuplen = 256 - indatalen;
else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */
fixuplen = 384 - indatalen;
else if (indatalen >= (512-16) && indatalen < 512) /* 4096 bit key. */
fixuplen = 512 - indatalen;
else if (!*(const char *)indata && (indatalen == 129
|| indatalen == 193
|| indatalen == 257
|| indatalen == 385
|| indatalen == 513))
fixuplen = -1;
else
fixuplen = 0;
if (fixuplen > 0)
{
/* While we have to prepend stuff anyway, we can also
include the padding byte here so that iso1816_decipher
does not need to do another data mangling. */
fixuplen++;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
memset (fixbuf, 0, fixuplen);
memcpy (fixbuf+fixuplen, indata, indatalen);
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1; /* Already padded. */
}
else if (fixuplen < 0)
{
/* We use the extra leading zero as the padding byte. */
padind = -1;
}
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
int old_format_len = 0;
if ((app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK))
{
if (indatalen > 32 && (indatalen % 2))
{ /*
* Skip the prefix. It may be 0x40 (in new format), or MPI
* head of 0x00 (in old format).
*/
indata = (const char *)indata + 1;
indatalen--;
}
else if (indatalen < 32)
{ /*
* Old format trancated by MPI handling.
*/
old_format_len = indatalen;
indatalen = 32;
}
}
fixuplen = 7;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
/* Build 'Cipher DO' */
fixbuf[0] = '\xa6';
fixbuf[1] = (char)(indatalen+5);
fixbuf[2] = '\x7f';
fixbuf[3] = '\x49';
fixbuf[4] = (char)(indatalen+2);
fixbuf[5] = '\x86';
fixbuf[6] = (char)indatalen;
if (old_format_len)
{
memset (fixbuf+fixuplen, 0, 32 - old_format_len);
memcpy (fixbuf+fixuplen + 32 - old_format_len,
indata, old_format_len);
}
else
{
memcpy (fixbuf+fixuplen, indata, indatalen);
}
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->cardcap.ext_lc_le
&& (indatalen > 254
|| (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP)))
{
exmode = 1; /* Extended length w/o a limit. */
le_value = app->app_local->keyattr[1].rsa.n_bits / 8;
}
else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
{
exmode = -254; /* Command chaining with max. 254 bytes. */
le_value = 0;
}
else
exmode = le_value = 0;
rc = iso7816_decipher (app->slot, exmode,
indata, indatalen, le_value, padind,
outdata, outdatalen);
xfree (fixbuf);
if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
unsigned char prefix = 0;
if (app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK)
prefix = 0x40;
else if ((*outdatalen % 2) == 0) /* No 0x04 -> x-coordinate only */
prefix = 0x41;
if (prefix)
{ /* Add the prefix */
fixbuf = xtrymalloc (*outdatalen + 1);
if (!fixbuf)
{
xfree (*outdata);
return gpg_error_from_syserror ();
}
fixbuf[0] = prefix;
memcpy (fixbuf+1, *outdata, *outdatalen);
xfree (*outdata);
*outdata = fixbuf;
*outdatalen = *outdatalen + 1;
}
}
if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */
&& app->app_local->manufacturer == 5
&& app->card_version == 0x0200)
log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
" do not work with encryption keys > 2048 bits\n");
*r_info |= APP_DECIPHER_INFO_NOPAD;
return rc;
}
/* Perform a simple verify operation for CHV1 and CHV2, so that
further operations won't ask for CHV2 and it is possible to do a
cheap check on the PIN: If there is something wrong with the PIN
entry system, only the regular CHV will get blocked and not the
dangerous CHV3. KEYIDSTR is the usual card's serial number; an
optional fingerprint part will be ignored.
There is a special mode if the keyidstr is "<serialno>[CHV3]" with
the "[CHV3]" being a literal string: The Admin Pin is checked if
and only if the retry counter is still at 3. */
static gpg_error_t
do_check_pin (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
unsigned char tmp_sn[20];
const char *s;
int n;
int admin_pin = 0;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check whether an OpenPGP card of any version has been requested. */
if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
return gpg_error (GPG_ERR_INV_ID);
for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
;
if (n != 32)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* No fingerprint given: we allow this for now. */
else if (*s == '/')
; /* We ignore a fingerprint. */
else if (!strcmp (s, "[CHV3]") )
admin_pin = 1;
else
return gpg_error (GPG_ERR_INV_ID);
for (s=keyidstr, n=0; n < 16; s += 2, n++)
tmp_sn[n] = xtoi_2 (s);
if (app->serialnolen != 16)
return gpg_error (GPG_ERR_INV_CARD);
if (memcmp (app->serialno, tmp_sn, 16))
return gpg_error (GPG_ERR_WRONG_CARD);
/* Yes, there is a race conditions: The user might pull the card
right here and we won't notice that. However this is not a
problem and the check above is merely for a graceful failure
between operations. */
if (admin_pin)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int count;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return gpg_error (GPG_ERR_CARD);
}
count = value[6];
xfree (relptr);
if (!count)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_BAD_PIN);
}
else if (count < 3)
{
log_info (_("verification of Admin PIN is currently prohibited "
"through this command\n"));
return gpg_error (GPG_ERR_GENERAL);
}
app->did_chv3 = 0; /* Force verification. */
return verify_chv3 (app, pincb, pincb_arg);
}
else
return verify_chv2 (app, pincb, pincb_arg);
}
/* Show information about card capabilities. */
static void
show_caps (struct app_local_s *s)
{
log_info ("Version-2 ......: %s\n", s->extcap.is_v2? "yes":"no");
log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no");
if (s->extcap.get_challenge)
log_printf (" (%u bytes max)", s->extcap.max_get_challenge);
log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no");
log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no");
log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no");
log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no");
log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no");
if (s->extcap.sm_supported)
log_printf (" (%s)", s->extcap.sm_algo==2? "3DES":
(s->extcap.sm_algo==2? "AES-128" : "AES-256"));
log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3);
log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no");
log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no");
log_info ("Status Indicator: %02X\n", s->status_indicator);
log_info ("Symmetric crypto: %s\n", s->extcap.has_decrypt? "yes":"no");
log_info ("Button..........: %s\n", s->extcap.has_button? "yes":"no");
log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no");
log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no");
}
/* Parse the historical bytes in BUFFER of BUFLEN and store them in
APPLOC. */
static void
parse_historical (struct app_local_s *apploc,
const unsigned char * buffer, size_t buflen)
{
/* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */
if (buflen < 4)
{
log_error ("warning: historical bytes are too short\n");
return; /* Too short. */
}
if (*buffer)
{
log_error ("warning: bad category indicator in historical bytes\n");
return;
}
/* Skip category indicator. */
buffer++;
buflen--;
/* Get the status indicator. */
apploc->status_indicator = buffer[buflen-3];
buflen -= 3;
/* Parse the compact TLV. */
while (buflen)
{
unsigned int tag = (*buffer & 0xf0) >> 4;
unsigned int len = (*buffer & 0x0f);
if (len+1 > buflen)
{
log_error ("warning: bad Compact-TLV in historical bytes\n");
return; /* Error. */
}
buffer++;
buflen--;
if (tag == 7 && len == 3)
{
/* Card capabilities. */
apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80);
apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40);
}
buffer += len;
buflen -= len;
}
}
/*
* Check if the OID in an DER encoding is available by GnuPG/libgcrypt,
* and return the curve name. Return NULL if not available.
* The constant string is not allocated dynamically, never free it.
*/
static const char *
ecc_curve (unsigned char *buf, size_t buflen)
{
gcry_mpi_t oid;
char *oidstr;
const char *result;
unsigned char *oidbuf;
oidbuf = xtrymalloc (buflen + 1);
if (!oidbuf)
return NULL;
memcpy (oidbuf+1, buf, buflen);
oidbuf[0] = buflen;
oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8);
if (!oid)
{
xfree (oidbuf);
return NULL;
}
oidstr = openpgp_oid_to_str (oid);
gcry_mpi_release (oid);
if (!oidstr)
return NULL;
result = openpgp_oid_to_curve (oidstr, 1);
xfree (oidstr);
return result;
}
/* Parse and optionally show the algorithm attributes for KEYNO.
KEYNO must be in the range 0..2. */
static void
parse_algorithm_attribute (app_t app, int keyno)
{
unsigned char *buffer;
size_t buflen;
void *relptr;
const char desc[3][5] = {"sign", "encr", "auth"};
assert (keyno >=0 && keyno <= 2);
app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA;
app->app_local->keyattr[keyno].rsa.n_bits = 0;
relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL);
if (!relptr)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
return;
}
if (buflen < 1)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
xfree (relptr);
return;
}
if (opt.verbose)
log_info ("Key-Attr-%s ..: ", desc[keyno]);
if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6))
{
app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]);
app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]);
app->app_local->keyattr[keyno].rsa.format = 0;
if (buflen < 6)
app->app_local->keyattr[keyno].rsa.format = RSA_STD;
else
app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD :
buffer[5] == 1? RSA_STD_N :
buffer[5] == 2? RSA_CRT :
buffer[5] == 3? RSA_CRT_N :
RSA_UNKNOWN_FMT);
if (opt.verbose)
log_printf
("RSA, n=%u, e=%u, fmt=%s\n",
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format == RSA_STD? "std" :
app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n":
app->app_local->keyattr[keyno].rsa.format == RSA_CRT? "crt" :
app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?");
}
else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA
|| *buffer == PUBKEY_ALGO_EDDSA)
{
const char *curve;
int oidlen = buflen - 1;
app->app_local->keyattr[keyno].ecc.flags = 0;
if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff)
{ /* Found "pubkey required"-byte for private key template. */
oidlen--;
if (buffer[buflen-1] == 0xff)
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_PUBKEY;
}
curve = ecc_curve (buffer + 1, oidlen);
if (!curve)
log_printhex ("Curve with OID not supported: ", buffer+1, buflen-1);
else
{
app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC;
app->app_local->keyattr[keyno].ecc.curve = curve;
if (*buffer == PUBKEY_ALGO_EDDSA
|| (*buffer == PUBKEY_ALGO_ECDH
&& !strcmp (app->app_local->keyattr[keyno].ecc.curve,
"Curve25519")))
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_DJB_TWEAK;
if (opt.verbose)
log_printf
("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.curve,
!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)?
"": keyno==1? " (djb-tweak)": " (eddsa)");
}
}
else if (opt.verbose)
log_printhex ("", buffer, buflen);
xfree (relptr);
}
/* Select the OpenPGP application on the card in SLOT. This function
must be used before any other OpenPGP application functions. */
gpg_error_t
app_select_openpgp (app_t app)
{
static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
int slot = app->slot;
int rc;
unsigned char *buffer;
size_t buflen;
void *relptr;
/* Note that the card can't cope with P2=0xCO, thus we need to pass a
special flag value. */
rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001);
if (!rc)
{
unsigned int manufacturer;
app->apptype = "OPENPGP";
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* The OpenPGP card returns the serial number as part of the
AID; because we prefer to use OpenPGP serial numbers, we
replace a possibly already set one from a EF.GDO with this
one. Note, that for current OpenPGP cards, no EF.GDO exists
and thus it won't matter at all. */
rc = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen);
if (rc)
goto leave;
if (opt.verbose)
{
log_info ("AID: ");
log_printhex ("", buffer, buflen);
}
app->card_version = buffer[6] << 8;
app->card_version |= buffer[7];
manufacturer = (buffer[8]<<8 | buffer[9]);
xfree (app->serialno);
app->serialno = buffer;
app->serialnolen = buflen;
buffer = NULL;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->app_local->manufacturer = manufacturer;
if (app->card_version >= 0x0200)
app->app_local->extcap.is_v2 = 1;
/* Read the historical bytes. */
relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL);
if (relptr)
{
if (opt.verbose)
{
log_info ("Historical Bytes: ");
log_printhex ("", buffer, buflen);
}
parse_historical (app->app_local, buffer, buflen);
xfree (relptr);
}
/* Read the force-chv1 flag. */
relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"CHV Status Bytes");
goto leave;
}
app->force_chv1 = (buflen && *buffer == 0);
xfree (relptr);
/* Read the extended capabilities. */
relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"Extended Capability Flags" );
goto leave;
}
if (buflen)
{
app->app_local->extcap.sm_supported = !!(*buffer & 0x80);
app->app_local->extcap.get_challenge = !!(*buffer & 0x40);
app->app_local->extcap.key_import = !!(*buffer & 0x20);
app->app_local->extcap.change_force_chv = !!(*buffer & 0x10);
app->app_local->extcap.private_dos = !!(*buffer & 0x08);
app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04);
app->app_local->extcap.has_decrypt = !!(*buffer & 0x02);
}
if (buflen >= 10)
{
/* Available with v2 cards. */
app->app_local->extcap.sm_algo = buffer[1];
app->app_local->extcap.max_get_challenge
= (buffer[2] << 8 | buffer[3]);
app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]);
}
xfree (relptr);
/* Some of the first cards accidentally don't set the
CHANGE_FORCE_CHV bit but allow it anyway. */
if (app->card_version <= 0x0100 && manufacturer == 1)
app->app_local->extcap.change_force_chv = 1;
/* Check optional DO of "General Feature Management" for button. */
relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL);
if (relptr)
/* It must be: 03 81 01 20 */
app->app_local->extcap.has_button = 1;
parse_login_data (app);
if (opt.verbose)
show_caps (app->app_local);
parse_algorithm_attribute (app, 0);
parse_algorithm_attribute (app, 1);
parse_algorithm_attribute (app, 2);
if (opt.verbose > 1)
dump_all_do (slot);
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-p15.c b/scd/app-p15.c
index 4072fb715..68e8c4faa 100644
--- a/scd/app-p15.c
+++ b/scd/app-p15.c
@@ -1,3424 +1,3424 @@
/* app-p15.c - The pkcs#15 card application.
* Copyright (C) 2005 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* Information pertaining to the BELPIC developer card samples:
Unblock PUK: "222222111111"
Reset PIN: "333333111111")
e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF
and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF
should change the PIN into 1234.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
+#include "../common/tlv.h"
#include "apdu.h" /* fixme: we should move the card detection to a
separate file */
/* Types of cards we know and which needs special treatment. */
typedef enum
{
CARD_TYPE_UNKNOWN,
CARD_TYPE_TCOS,
CARD_TYPE_MICARDO,
CARD_TYPE_BELPIC /* Belgian eID card specs. */
}
card_type_t;
/* A list card types with ATRs noticed with these cards. */
#define X(a) ((unsigned char const *)(a))
static struct
{
size_t atrlen;
unsigned char const *atr;
card_type_t type;
} card_atr_list[] = {
{ 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80"
"\x90\x00\x8B"),
CARD_TYPE_TCOS }, /* SLE44 */
{ 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80"
"\x90\x00\x91"),
CARD_TYPE_TCOS }, /* SLE66S */
{ 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80"
"\x90\x00\x66"),
CARD_TYPE_TCOS }, /* SLE66P */
{ 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00"
"\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"),
CARD_TYPE_MICARDO }, /* German BMI card */
{ 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80"
"\x00\x90\x00"),
CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */
{ 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49"
"\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"),
CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */
{ 0 }
};
#undef X
/* The AID of PKCS15. */
static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The Belgian eID variant - they didn't understood why a shared AID
is useful for a standard. Oh well. */
static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The PIN types as defined in pkcs#15 v1.1 */
typedef enum
{
PIN_TYPE_BCD = 0,
PIN_TYPE_ASCII_NUMERIC = 1,
PIN_TYPE_UTF8 = 2,
PIN_TYPE_HALF_NIBBLE_BCD = 3,
PIN_TYPE_ISO9564_1 = 4
} pin_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* To avoid reading a certificate more than once, we cache it in an
allocated memory IMAGE of IMAGELEN. */
size_t imagelen;
unsigned char *image;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the CDF and the path itself.
path[0] is the top DF (usually 0x3f00). The path will never be
empty. */
size_t pathlen;
unsigned short path[1];
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The keyReference and a flag telling whether it is valid. */
unsigned long key_reference;
int key_reference_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the PrKDF and the path itself.
path[0] is the top DF (usually 0x3f00). */
size_t pathlen;
unsigned short path[1];
};
typedef struct prkdf_object_s *prkdf_object_t;
/* This is an object to store information about a Authentication
Object Directory File (AODF) in a format suitable for further
processing by us. To keep memory management, simple we use a linked
list of items; i.e. one such object represents one authentication
object and the list the entire AOKDF. */
struct aodf_object_s
{
/* Link to next item when used in a linked list. */
struct aodf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The PIN Flags. */
struct
{
unsigned int case_sensitive: 1;
unsigned int local: 1;
unsigned int change_disabled: 1;
unsigned int unblock_disabled: 1;
unsigned int initialized: 1;
unsigned int needs_padding: 1;
unsigned int unblocking_pin: 1;
unsigned int so_pin: 1;
unsigned int disable_allowed: 1;
unsigned int integrity_protected: 1;
unsigned int confidentiality_protected: 1;
unsigned int exchange_ref_data: 1;
} pinflags;
/* The PIN Type. */
pin_type_t pintype;
/* The minimum length of a PIN. */
unsigned long min_length;
/* The stored length of a PIN. */
unsigned long stored_length;
/* The maximum length of a PIN and a flag telling whether it is valid. */
unsigned long max_length;
int max_length_valid;
/* The pinReference and a flag telling whether it is valid. */
unsigned long pin_reference;
int pin_reference_valid;
/* The padChar and a flag telling whether it is valid. */
char pad_char;
int pad_char_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the Aodf and the path itself.
path[0] is the top DF (usually 0x3f00). PATH is optional and thus
may be NULL. Malloced.*/
size_t pathlen;
unsigned short *path;
};
typedef struct aodf_object_s *aodf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* The home DF. Note, that we don't yet support a multilevel
hierarchy. Thus we assume this is directly below the MF. */
unsigned short home_df;
/* The type of the card. */
card_type_t card_type;
/* Flag indicating whether we may use direct path selection. */
int direct_path_selection;
/* Structure with the EFIDs of the objects described in the ODF
file. */
struct
{
unsigned short private_keys;
unsigned short public_keys;
unsigned short trusted_public_keys;
unsigned short secret_keys;
unsigned short certificates;
unsigned short trusted_certificates;
unsigned short useful_certificates;
unsigned short data_objects;
unsigned short auth_objects;
} odf;
/* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */
unsigned char *serialno;
size_t serialnolen;
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all useful certificates. */
cdf_object_t useful_certificate_info;
/* Information on all private keys. */
prkdf_object_t private_key_info;
/* Information on all authentication objects. */
aodf_object_t auth_object_info;
};
/*** Local prototypes. ***/
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
xfree (a->image);
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->objid);
xfree (a->authid);
xfree (a);
a = tmp;
}
}
/* Release just one aodf object. */
void
release_aodf_object (aodf_object_t a)
{
if (a)
{
xfree (a->objid);
xfree (a->authid);
xfree (a->path);
xfree (a);
}
}
/* Release the AODF list A. */
static void
release_aodflist (aodf_object_t a)
{
while (a)
{
aodf_object_t tmp = a->next;
release_aodf_object (a);
a = tmp;
}
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_cdflist (app->app_local->certificate_info);
release_cdflist (app->app_local->trusted_certificate_info);
release_cdflist (app->app_local->useful_certificate_info);
release_prkdflist (app->app_local->private_key_info);
release_aodflist (app->app_local->auth_object_info);
xfree (app->app_local->serialno);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
desctription of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. */
static gpg_error_t
select_and_read_binary (int slot, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen)
{
gpg_error_t err;
err = iso7816_select_file (slot, efid, 0);
if (err)
{
log_error ("error selecting %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (slot, 0, 0, buffer, buflen);
if (err)
{
log_error ("error reading %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
return 0;
}
/* This function calls select file to read a file using a complete
path which may or may not start at the master file (MF). */
static gpg_error_t
select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen)
{
gpg_error_t err;
int i, j;
if (!pathlen)
return gpg_error (GPG_ERR_INV_VALUE);
if (pathlen && *path != 0x3f00 )
log_debug ("WARNING: relative path selection not yet implemented\n");
if (app->app_local->direct_path_selection)
{
err = iso7816_select_path (app->slot, path+1, pathlen-1);
if (err)
{
log_error ("error selecting path ");
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
else
{
/* FIXME: Need code to remember the last PATH so that we can decide
what select commands to send in case the path does not start off
with 3F00. We might also want to use direct path selection if
supported by the card. */
for (i=0; i < pathlen; i++)
{
err = iso7816_select_file (app->slot, path[i], !(i+1 == pathlen));
if (err)
{
log_error ("error selecting part %d from path ", i);
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
}
return 0;
}
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (app_t app, const char *certid,
unsigned char **r_objid, size_t *r_objidlen)
{
char tmpbuf[10];
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (app->app_local->home_df)
snprintf (tmpbuf, sizeof tmpbuf,
"P15-%04X.", (unsigned int)(app->app_local->home_df & 0xffff));
else
strcpy (tmpbuf, "P15.");
if (strncmp (certid, tmpbuf, strlen (tmpbuf)) )
{
if (!strncmp (certid, "P15.", 4)
|| (!strncmp (certid, "P15-", 4)
&& hexdigitp (certid+4)
&& hexdigitp (certid+5)
&& hexdigitp (certid+6)
&& hexdigitp (certid+7)
&& certid[8] == '.'))
return gpg_error (GPG_ERR_NOT_FOUND);
return gpg_error (GPG_ERR_INV_ID);
}
certid += strlen (tmpbuf);
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
err = parse_certid (app, certid, &objid, &objidlen);
if (err)
return err;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
xfree (objid);
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (app, keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Read and parse the Object Directory File and store away the
pointers. ODF_FID shall contain the FID of the ODF.
Example of such a file:
A0 06 30 04 04 02 60 34 = Private Keys
A4 06 30 04 04 02 60 35 = Certificates
A5 06 30 04 04 02 60 36 = TrustedCertificates
A7 06 30 04 04 02 60 37 = DataObjects
A8 06 30 04 04 02 60 38 = AuthObjects
These are all PathOrObjects using the path CHOICE element. The
paths are octet strings of length 2. Using this Path CHOICE
element is recommended, so we only implement that for now.
*/
static gpg_error_t
read_ef_odf (app_t app, unsigned short odf_fid)
{
gpg_error_t err;
unsigned char *buffer, *p;
size_t buflen;
unsigned short value;
size_t offset;
err = select_and_read_binary (app->slot, odf_fid, "ODF", &buffer, &buflen);
if (err)
return err;
if (buflen < 8)
{
log_error ("error: ODF too short\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
p = buffer;
while (buflen && *p && *p != 0xff)
{
if ( buflen >= 8
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) )
{
offset = 6;
}
else if ( buflen >= 12
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7)
&& app->app_local->home_df == ((p[8]<<8)|p[9]) )
{
/* We only allow a full path if all files are at the same
level and below the home directory. The extend this we
would need to make use of new data type capable of
keeping a full path. */
offset = 10;
}
else
{
log_error ("ODF format is not supported by us\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
switch ((p[0] & 0x0f))
{
case 0: value = app->app_local->odf.private_keys; break;
case 1: value = app->app_local->odf.public_keys; break;
case 2: value = app->app_local->odf.trusted_public_keys; break;
case 3: value = app->app_local->odf.secret_keys; break;
case 4: value = app->app_local->odf.certificates; break;
case 5: value = app->app_local->odf.trusted_certificates; break;
case 6: value = app->app_local->odf.useful_certificates; break;
case 7: value = app->app_local->odf.data_objects; break;
case 8: value = app->app_local->odf.auth_objects; break;
default: value = 0; break;
}
if (value)
{
log_error ("duplicate object type %d in ODF ignored\n",(p[0]&0x0f));
continue;
}
value = ((p[offset] << 8) | p[offset+1]);
switch ((p[0] & 0x0f))
{
case 0: app->app_local->odf.private_keys = value; break;
case 1: app->app_local->odf.public_keys = value; break;
case 2: app->app_local->odf.trusted_public_keys = value; break;
case 3: app->app_local->odf.secret_keys = value; break;
case 4: app->app_local->odf.certificates = value; break;
case 5: app->app_local->odf.trusted_certificates = value; break;
case 6: app->app_local->odf.useful_certificates = value; break;
case 7: app->app_local->odf.data_objects = value; break;
case 8: app->app_local->odf.auth_objects = value; break;
default:
log_error ("unknown object type %d in ODF ignored\n", (p[0]&0x0f));
}
offset += 2;
if (buflen < offset)
break;
p += offset;
buflen -= offset;
}
if (buflen)
log_info ("warning: %u bytes of garbage detected at end of ODF\n",
(unsigned int)buflen);
xfree (buffer);
return 0;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
/* Read and parse the Private Key Directory Files. */
/*
6034 (privatekeys)
30 33 30 11 0C 08 53 4B 2E 43 48 2E 44 53 03 02 030...SK.CH.DS..
06 80 04 01 07 30 0C 04 01 01 03 03 06 00 40 02 .....0........@.
02 00 50 A1 10 30 0E 30 08 04 06 3F 00 40 16 00 ..P..0.0...?.@..
50 02 02 04 00 30 33 30 11 0C 08 53 4B 2E 43 48 P....030...SK.CH
2E 4B 45 03 02 06 80 04 01 0A 30 0C 04 01 0C 03 .KE.......0.....
03 06 44 00 02 02 00 52 A1 10 30 0E 30 08 04 06 ..D....R..0.0...
3F 00 40 16 00 52 02 02 04 00 30 34 30 12 0C 09 ?.@..R....040...
53 4B 2E 43 48 2E 41 55 54 03 02 06 80 04 01 0A SK.CH.AUT.......
30 0C 04 01 0D 03 03 06 20 00 02 02 00 51 A1 10 0....... ....Q..
30 0E 30 08 04 06 3F 00 40 16 00 51 02 02 04 00 0.0...?.@..Q....
30 37 30 15 0C 0C 53 4B 2E 43 48 2E 44 53 2D 53 070...SK.CH.DS-S
50 58 03 02 06 80 04 01 0A 30 0C 04 01 02 03 03 PX.......0......
06 20 00 02 02 00 53 A1 10 30 0E 30 08 04 06 3F . ....S..0.0...?
00 40 16 00 53 02 02 04 00 00 00 00 00 00 00 00 .@..S...........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 30 51: SEQUENCE {
2 30 17: SEQUENCE { -- commonObjectAttributes
4 0C 8: UTF8String 'SK.CH.DS'
14 03 2: BIT STRING 6 unused bits
: '01'B (bit 0)
18 04 1: OCTET STRING --authid
: 07
: }
21 30 12: SEQUENCE { -- commonKeyAttributes
23 04 1: OCTET STRING
: 01
26 03 3: BIT STRING 6 unused bits
: '1000000000'B (bit 9)
31 02 2: INTEGER 80 -- keyReference (optional)
: }
35 A1 16: [1] { -- keyAttributes
37 30 14: SEQUENCE { -- privateRSAKeyAttributes
39 30 8: SEQUENCE { -- objectValue
41 04 6: OCTET STRING --path
: 3F 00 40 16 00 50
: }
49 02 2: INTEGER 1024 -- modulus
: }
: }
: }
*/
static gpg_error_t
read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
prkdf_object_t prkdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */
err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_cdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
const unsigned char *authid = NULL;
size_t authidlen = 0;
keyusage_flags_t usageflags;
unsigned long key_reference = 0;
int key_reference_valid = 0;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
authid = ppp;
authidlen = objlen;
}
no_authid:
;
}
/* Parse the commonKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
err = parse_keyusage_flags (ppp, objlen, &usageflags);
if (err)
goto parse_error;
ppp += objlen;
nnn -= objlen;
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* Skip the accessFlags. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* Yep, this is the keyReference. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
key_reference = ul;
key_reference_valid = 1;
}
leave_cki:
;
}
/* Skip subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* RSA */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "EC key objects are not supported"; break;
case 1: errstr = "DH key objects are not supported"; break;
case 2: errstr = "DSA key objects are not supported"; break;
case 3: errstr = "KEA key objects are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, (sizeof *prkdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->objidlen = objidlen;
prkdf->objid = xtrymalloc (objidlen);
if (!prkdf->objid)
{
err = gpg_error_from_syserror ();
xfree (prkdf);
goto leave;
}
memcpy (prkdf->objid, objid, objidlen);
if (authid)
{
prkdf->authidlen = authidlen;
prkdf->authid = xtrymalloc (authidlen);
if (!prkdf->authid)
{
err = gpg_error_from_syserror ();
xfree (prkdf->objid);
xfree (prkdf);
goto leave;
}
memcpy (prkdf->authid, authid, authidlen);
}
prkdf->pathlen = objlen/2;
for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2)
prkdf->path[i] = ((pp[0] << 8) | pp[1]);
prkdf->usageflags = usageflags;
prkdf->key_reference = key_reference;
prkdf->key_reference_valid = key_reference_valid;
if (nn)
{
/* An index and length follows. */
prkdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->len = ul;
}
log_debug ("PrKDF %04hX: id=", fid);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
log_printf (" path=");
for (i=0; i < prkdf->pathlen; i++)
log_printf ("%04hX", prkdf->path[i]);
if (prkdf->have_off)
log_printf ("[%lu/%lu]", prkdf->off, prkdf->len);
if (prkdf->authid)
{
log_printf (" authid=");
for (i=0; i < prkdf->authidlen; i++)
log_printf ("%02X", prkdf->authid[i]);
}
if (prkdf->key_reference_valid)
log_printf (" keyref=0x%02lX", prkdf->key_reference);
log_printf (" usage=");
s = "";
if (prkdf->usageflags.encrypt) log_printf ("%sencrypt", s), s = ",";
if (prkdf->usageflags.decrypt) log_printf ("%sdecrypt", s), s = ",";
if (prkdf->usageflags.sign ) log_printf ("%ssign", s), s = ",";
if (prkdf->usageflags.sign_recover)
log_printf ("%ssign_recover", s), s = ",";
if (prkdf->usageflags.wrap ) log_printf ("%swrap", s), s = ",";
if (prkdf->usageflags.unwrap ) log_printf ("%sunwrap", s), s = ",";
if (prkdf->usageflags.verify ) log_printf ("%sverify", s), s = ",";
if (prkdf->usageflags.verify_recover)
log_printf ("%sverify_recover", s), s = ",";
if (prkdf->usageflags.derive ) log_printf ("%sderive", s), s = ",";
if (prkdf->usageflags.non_repudiation)
log_printf ("%snon_repudiation", s), s = ",";
log_printf ("\n");
/* Put it into the list. */
prkdf->next = prkdflist;
prkdflist = prkdf;
prkdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing PrKDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
if (prkdf)
{
xfree (prkdf->objid);
xfree (prkdf->authid);
xfree (prkdf);
}
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_prkdflist (prkdflist);
else
*result = prkdflist;
return err;
}
/* Read and parse the Certificate Directory Files identified by FID.
On success a newlist of CDF object gets stored at RESULT and the
caller is then responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_cdf (app_t app, unsigned short fid, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
cdf_object_t cdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */
err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Skip the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, (sizeof *cdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
cdf->pathlen = objlen/2;
for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2)
cdf->path[i] = ((pp[0] << 8) | pp[1]);
if (nn)
{
/* An index and length follows. */
cdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->len = ul;
}
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" path=");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%04hX", cdf->path[i]);
if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len);
log_printf ("\n");
/* Put it into the list. */
cdf->next = cdflist;
cdflist = cdf;
cdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing CDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
xfree (cdf);
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_cdflist (cdflist);
else
*result = cdflist;
return err;
}
/*
SEQUENCE {
SEQUENCE { -- CommonObjectAttributes
UTF8String 'specific PIN for DS'
BIT STRING 0 unused bits
'00000011'B
}
SEQUENCE { -- CommonAuthenticationObjectAttributes
OCTET STRING
07 -- iD
}
[1] { -- typeAttributes
SEQUENCE { -- PinAttributes
BIT STRING 0 unused bits
'0000100000110010'B -- local,initialized,needs-padding
-- exchangeRefData
ENUMERATED 1 -- ascii-numeric
INTEGER 6 -- minLength
INTEGER 6 -- storedLength
INTEGER 8 -- maxLength
[0]
02 -- pinReference
GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange
SEQUENCE {
OCTET STRING
3F 00 40 16 -- path to DF of PIN
}
}
}
}
*/
/* Read and parse an Authentication Object Directory File identified
by FID. On success a newlist of AODF objects gets stored at RESULT
and the caller is responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
aodf_object_t aodflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No authentication objects. */
err = select_and_read_binary (app->slot, fid, "AODF", &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_prkdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
aodf_object_t aodf = NULL;
unsigned long ul;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing AODF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Allocate memory for a new AODF list item. */
aodf = xtrycalloc (1, sizeof *aodf);
if (!aodf)
goto no_core;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
aodf->authidlen = objlen;
aodf->authid = xtrymalloc (objlen);
if (!aodf->authid)
goto no_core;
memcpy (aodf->authid, ppp, objlen);
}
no_authid:
;
}
/* Parse the CommonAuthenticationObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
aodf->objidlen = objlen;
aodf->objid = xtrymalloc (objlen);
if (!aodf->objid)
goto no_core;
memcpy (aodf->objid, ppp, objlen);
}
/* Parse the typeAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* PinAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "biometric auth types are not supported"; break;
case 1: errstr = "authKey auth types are not supported"; break;
case 2: errstr = "external auth type are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* PinFlags */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || !objlen
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
unsigned int bits, mask;
int unused, full;
unused = *pp++; nn--; objlen--;
if ((!objlen && unused) || unused/8 > objlen)
{
err = gpg_error (GPG_ERR_ENCODING_PROBLEM);
goto parse_error;
}
full = objlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* The first octet */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
if ((bits & 0x80)) /* ASN.1 bit 0. */
aodf->pinflags.case_sensitive = 1;
if ((bits & 0x40)) /* ASN.1 bit 1. */
aodf->pinflags.local = 1;
if ((bits & 0x20))
aodf->pinflags.change_disabled = 1;
if ((bits & 0x10))
aodf->pinflags.unblock_disabled = 1;
if ((bits & 0x08))
aodf->pinflags.initialized = 1;
if ((bits & 0x04))
aodf->pinflags.needs_padding = 1;
if ((bits & 0x02))
aodf->pinflags.unblocking_pin = 1;
if ((bits & 0x01))
aodf->pinflags.so_pin = 1;
/* The second octet. */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
if ((bits & 0x80))
aodf->pinflags.disable_allowed = 1;
if ((bits & 0x40))
aodf->pinflags.integrity_protected = 1;
if ((bits & 0x20))
aodf->pinflags.confidentiality_protected = 1;
if ((bits & 0x10))
aodf->pinflags.exchange_ref_data = 1;
/* Skip remaining bits. */
pp += objlen;
nn -= objlen;
}
/* PinType */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && (objlen > sizeof (pin_type_t) || objlen > sizeof (ul)))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pintype = ul;
/* minLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->min_length = ul;
/* storedLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->stored_length = ul;
/* optional maxLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->max_length = ul;
aodf->max_length_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional pinReference. */
if (class == CLASS_CONTEXT && tag == 0)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pin_reference = ul;
aodf->pin_reference_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional padChar. */
if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING)
{
if (objlen != 1)
{
errstr = "padChar is not of size(1)";
goto parse_error;
}
aodf->pad_char = *pp++; nn--;
aodf->pad_char_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Skip optional lastPinChange. */
if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional Path object. */
if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE)
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero FID and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
aodf->pathlen = objlen/2;
aodf->path = xtrymalloc (aodf->pathlen);
if (!aodf->path)
goto no_core;
for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2)
aodf->path[i] = ((ppp[0] << 8) | ppp[1]);
if (nnn)
{
/* An index and length follows. */
aodf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->off = ul;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->len = ul;
}
}
/* Igonore further objects which might be there due to future
extensions of pkcs#15. */
ready:
log_debug ("AODF %04hX: id=", fid);
for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]);
if (aodf->authid)
{
log_printf (" authid=");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
log_printf (" flags=");
s = "";
if (aodf->pinflags.case_sensitive)
log_printf ("%scase_sensitive", s), s = ",";
if (aodf->pinflags.local)
log_printf ("%slocal", s), s = ",";
if (aodf->pinflags.change_disabled)
log_printf ("%schange_disabled", s), s = ",";
if (aodf->pinflags.unblock_disabled)
log_printf ("%sunblock_disabled", s), s = ",";
if (aodf->pinflags.initialized)
log_printf ("%sinitialized", s), s = ",";
if (aodf->pinflags.needs_padding)
log_printf ("%sneeds_padding", s), s = ",";
if (aodf->pinflags.unblocking_pin)
log_printf ("%sunblocking_pin", s), s = ",";
if (aodf->pinflags.so_pin)
log_printf ("%sso_pin", s), s = ",";
if (aodf->pinflags.disable_allowed)
log_printf ("%sdisable_allowed", s), s = ",";
if (aodf->pinflags.integrity_protected)
log_printf ("%sintegrity_protected", s), s = ",";
if (aodf->pinflags.confidentiality_protected)
log_printf ("%sconfidentiality_protected", s), s = ",";
if (aodf->pinflags.exchange_ref_data)
log_printf ("%sexchange_ref_data", s), s = ",";
{
char numbuf[50];
switch (aodf->pintype)
{
case PIN_TYPE_BCD: s = "bcd"; break;
case PIN_TYPE_ASCII_NUMERIC: s = "ascii-numeric"; break;
case PIN_TYPE_UTF8: s = "utf8"; break;
case PIN_TYPE_HALF_NIBBLE_BCD: s = "half-nibble-bcd"; break;
case PIN_TYPE_ISO9564_1: s = "iso9564-1"; break;
default:
sprintf (numbuf, "%lu", (unsigned long)aodf->pintype);
s = numbuf;
}
log_printf (" type=%s", s);
}
log_printf (" min=%lu", aodf->min_length);
log_printf (" stored=%lu", aodf->stored_length);
if (aodf->max_length_valid)
log_printf (" max=%lu", aodf->max_length);
if (aodf->pad_char_valid)
log_printf (" pad=0x%02x", aodf->pad_char);
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
if (aodf->pathlen)
{
log_printf (" path=");
for (i=0; i < aodf->pathlen; i++)
log_printf ("%04hX", aodf->path[i]);
if (aodf->have_off)
log_printf ("[%lu/%lu]", aodf->off, aodf->len);
}
log_printf ("\n");
/* Put it into the list. */
aodf->next = aodflist;
aodflist = aodf;
aodf = NULL;
continue; /* Ready. */
no_core:
err = gpg_error_from_syserror ();
release_aodf_object (aodf);
goto leave;
parse_error:
log_error ("error parsing AODF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
release_aodf_object (aodf);
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_aodflist (aodflist);
else
*result = aodflist;
return err;
}
/* Read and parse the EF(TokenInfo).
TokenInfo ::= SEQUENCE {
version INTEGER {v1(0)} (v1,...),
serialNumber OCTET STRING,
manufacturerID Label OPTIONAL,
label [0] Label OPTIONAL,
tokenflags TokenFlags,
seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL,
recordInfo [1] RecordInfo OPTIONAL,
supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL,
...,
issuerId [3] Label OPTIONAL,
holderId [4] Label OPTIONAL,
lastUpdate [5] LastUpdate OPTIONAL,
preferredLanguage PrintableString OPTIONAL -- In accordance with
-- IETF RFC 1766
} (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --})
TokenFlags ::= BIT STRING {
readOnly (0),
loginRequired (1),
prnGeneration (2),
eidCompliant (3)
}
5032:
30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T
72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic
65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card.
02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 49: SEQUENCE {
2 1: INTEGER 0
5 4: OCTET STRING 05 45 36 9F
11 12: UTF8String 'D-Trust GmbH'
25 20: [0] 'Office identity card'
47 2: BIT STRING
: '00000010'B (bit 1)
: Error: Spurious zero bits in bitstring.
: }
*/
static gpg_error_t
read_ef_tokeninfo (app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
unsigned long ul;
err = select_and_read_binary (app->slot, 0x5032, "TokenInfo",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err));
goto leave;
}
n = objlen;
/* Version. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*p++) & 0xff;
n--;
}
if (ul)
{
log_error ("invalid version %lu in TokenInfo\n", ul);
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* serialNumber. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
xfree (app->app_local->serialno);
app->app_local->serialno = xtrymalloc (objlen);
if (!app->app_local->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->app_local->serialno, p, objlen);
app->app_local->serialnolen = objlen;
log_printhex ("Serialnumber from EF(TokenInfo) is:", p, objlen);
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the pkcs#15 card, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_p15_info (app_t app)
{
gpg_error_t err;
if (!read_ef_tokeninfo (app))
{
/* If we don't have a serial number yet but the TokenInfo provides
one, use that. */
if (!app->serialno && app->app_local->serialno)
{
app->serialno = app->app_local->serialno;
app->serialnolen = app->app_local->serialnolen;
app->app_local->serialno = NULL;
app->app_local->serialnolen = 0;
err = app_munge_serialno (app);
if (err)
return err;
}
}
/* Read the ODF so that we know the location of all directory
files. */
/* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */
err = read_ef_odf (app, 0x5031);
if (err)
return err;
/* Read certificate information. */
assert (!app->app_local->certificate_info);
assert (!app->app_local->trusted_certificate_info);
assert (!app->app_local->useful_certificate_info);
err = read_ef_cdf (app, app->app_local->odf.certificates,
&app->app_local->certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.trusted_certificates,
&app->app_local->trusted_certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.useful_certificates,
&app->app_local->useful_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about private keys. */
assert (!app->app_local->private_key_info);
err = read_ef_prkdf (app, app->app_local->odf.private_keys,
&app->app_local->private_key_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about authentication objects. */
assert (!app->app_local->auth_object_info);
err = read_ef_aodf (app, app->app_local->odf.auth_objects,
&app->app_local->auth_object_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (app_t app, ctrl_t ctrl, const char *certtype,
cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
buf = xtrymalloc (9 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (certinfo->objid, certinfo->objidlen, p);
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
NULL, (size_t)0);
xfree (buf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
keygrip gets returned in the caller provided 41 byte buffer
R_GRIPSTR. */
static gpg_error_t
keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
/* FIXME: We should check whether a public key directory file and a
matching public key for PRKDF is available. This should make
extraction of the key much easier. My current test card doesn't
have one, so we can only use the fallback solution bu looking for
a matching certificate and extract the key from there. */
/* Look for a matching certificate. A certificate matches if the Id
matches the one of the private key info. */
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
return err;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, r_gripstr);
ksba_cert_release (cert);
return err;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. FIXME: much code duplication from
send_certinfo(). */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo)
{
gpg_error_t err;
for (; keyinfo; keyinfo = keyinfo->next)
{
char gripstr[40+1];
char *buf, *p;
int j;
buf = xtrymalloc (9 + keyinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (keyinfo->objid, keyinfo->objidlen, p);
err = keygripstr_from_prkdf (app, keyinfo, gripstr);
if (err)
{
log_error ("can't get keygrip from ");
for (j=0; j < keyinfo->pathlen; j++)
log_printf ("%04hX", keyinfo->path[j]);
log_printf (": %s\n", gpg_strerror (err));
}
else
{
assert (strlen (gripstr) == 40);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
buf, strlen (buf),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if ((flags & 1))
err = 0;
else
{
err = send_certinfo (app, ctrl, "100", app->app_local->certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "101",
app->app_local->trusted_certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "102",
app->app_local->useful_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
return err;
}
/* Read a certifciate using the information in CDF and return the
certificate in a newly llocated buffer R_CERT and its length
R_CERTLEN. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
*r_cert = NULL;
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->image)
{
*r_cert = xtrymalloc (cdf->imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, cdf->image, cdf->imagelen);
*r_certlen = cdf->imagelen;
return 0;
}
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = select_ef_by_path (app, cdf->path, cdf->pathlen);
if (err)
goto leave;
err = iso7816_read_binary (app->slot, cdf->off, cdf->len, &buffer, &buflen);
if (!err && (!buflen || *buffer == 0xff))
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("error reading certificate with Id ");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
/* Try to cache it. */
if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen)))
{
memcpy (cdf->image, *r_cert, *r_certlen);
cdf->imagelen = *r_certlen;
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!strcmp (name, "$AUTHKEYID"))
{
char *buf, *p;
prkdf_object_t prkdf;
/* We return the ID of the first private keycapable of
signing. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->usageflags.sign)
break;
if (prkdf)
{
buf = xtrymalloc (9 + prkdf->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (prkdf->objid, prkdf->objidlen, p);
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
return 0;
}
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
/* For certain cards we return special IDs. There is no
general rule for it so we need to decide case by case. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
/* The eID card has a card number printed on the front matter
which seems to be a good indication. */
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 };
err = select_ef_by_path (app, path, DIM(path) );
if (!err)
err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error accessing EF(ID): %s\n", gpg_strerror (err));
return err;
}
p = find_tlv (buffer, buflen, 1, &n);
if (p && n == 12)
{
char tmp[12+2+1];
memcpy (tmp, p, 3);
tmp[3] = '-';
memcpy (tmp+4, p+3, 7);
tmp[11] = '-';
memcpy (tmp+12, p+10, 2);
tmp[14] = 0;
send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0);
xfree (buffer);
return 0;
}
xfree (buffer);
}
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Micardo cards require special treatment. This is a helper for the
crypto functions to manage the security environment. We expect that
the key file has already been selected. FID is the one of the
selected key. */
static gpg_error_t
micardo_mse (app_t app, unsigned short fid)
{
gpg_error_t err;
int recno;
unsigned short refdata = 0;
int se_num;
unsigned char msebuf[10];
/* Read the KeyD file containing extra information on keys. */
err = iso7816_select_file (app->slot, 0x0013, 0);
if (err)
{
log_error ("error reading EF_keyD: %s\n", gpg_strerror (err));
return err;
}
for (recno = 1, se_num = -1; ; recno++)
{
unsigned char *buffer;
size_t buflen;
size_t n, nn;
const unsigned char *p, *pp;
err = iso7816_read_record (app->slot, recno, 1, 0, &buffer, &buflen);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
break; /* ready */
if (err)
{
log_error ("error reading EF_keyD record: %s\n",
gpg_strerror (err));
return err;
}
log_printhex ("keyD record:", buffer, buflen);
p = find_tlv (buffer, buflen, 0x83, &n);
if (p && n == 4 && ((p[2]<<8)|p[3]) == fid)
{
refdata = ((p[0]<<8)|p[1]);
/* Locate the SE DO and the there included sec env number. */
p = find_tlv (buffer, buflen, 0x7b, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x80, &nn);
if (pp && nn == 1)
{
se_num = *pp;
xfree (buffer);
break; /* found. */
}
}
}
xfree (buffer);
}
if (se_num == -1)
{
log_error ("CRT for keyfile %04hX not found\n", fid);
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Restore the security environment to SE_NUM if needed */
if (se_num)
{
err = iso7816_manage_security_env (app->slot, 0xf3, se_num, NULL, 0);
if (err)
{
log_error ("restoring SE to %d failed: %s\n",
se_num, gpg_strerror (err));
return err;
}
}
/* Set the DST reference data. */
msebuf[0] = 0x83;
msebuf[1] = 0x03;
msebuf[2] = 0x80;
msebuf[3] = (refdata >> 8);
msebuf[4] = refdata;
err = iso7816_manage_security_env (app->slot, 0x41, 0xb6, msebuf, 5);
if (err)
{
log_error ("setting SE to reference file %04hX failed: %s\n",
refdata, gpg_strerror (err));
return err;
}
return 0;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
gpg_error_t err;
int i;
unsigned char data[36]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix above and also
fit the 36 bytes of md5sha1. */
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
int no_data_padding = 0; /* True if the card want the data without padding.*/
int mse_done = 0; /* Set to true if the MSE has been done. */
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen != 20 && indatalen != 16 && indatalen != 35 && indatalen != 36)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
||prkdf->usageflags.non_repudiation))
{
log_error ("key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (!prkdf->authid)
{
log_error ("no authentication object defined for %s\n", keyidstr);
/* fixme: we might want to go ahead and do without PIN
verification. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
/* Find the authentication object to this private key object. */
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf)
{
log_error ("authentication object for %s missing\n", keyidstr);
return gpg_error (GPG_ERR_INV_CARD);
}
if (aodf->authid)
{
log_error ("PIN verification is protected by an "
"additional authentication token\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pinflags.integrity_protected
|| aodf->pinflags.confidentiality_protected)
{
log_error ("PIN verification requires unsupported protection method\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (!aodf->stored_length && aodf->pinflags.needs_padding)
{
log_error ("PIN verification requires padding but no length known\n");
return gpg_error (GPG_ERR_INV_CARD);
}
/* Select the key file. Note that this may change the security
environment thus we do it before PIN verification. */
err = select_ef_by_path (app, prkdf->path, prkdf->pathlen);
if (err)
{
log_error ("error selecting file for key %s: %s\n",
keyidstr, gpg_strerror (errno));
return err;
}
/* Due to the fact that the non-repudiation signature on a BELPIC
card requires a verify immediately before the DSO we set the
MSE before we do the verification. Other cards might also allow
this but I don't want to break anything, thus we do it only
for the BELPIC card here. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
unsigned char mse[5];
mse[0] = 4; /* Length of the template. */
mse[1] = 0x80; /* Algorithm reference tag. */
if (hashalgo == MD_USER_TLS_MD5SHA1)
mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */
else
mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */
mse[3] = 0x84; /* Private key reference tag. */
mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82;
err = iso7816_manage_security_env (app->slot,
0x41, 0xB6,
mse, sizeof mse);
no_data_padding = 1;
mse_done = 1;
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
/* Now that we have all the information available, prepare and run
the PIN verification.*/
if (1)
{
char *pinvalue;
size_t pinvaluelen;
const char *errstr;
const char *s;
if (prkdf->usageflags.non_repudiation
&& app->app_local->card_type == CARD_TYPE_BELPIC)
err = pincb (pincb_arg, "PIN (qualified signature!)", &pinvalue);
else
err = pincb (pincb_arg, "PIN", &pinvalue);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
/* We might need to cope with UTF8 things here. Not sure how
min_length etc. are exactly defined, for now we take them as
a plain octet count. */
if (strlen (pinvalue) < aodf->min_length)
{
log_error ("PIN is too short; minimum length is %lu\n",
aodf->min_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length)
{
/* This would otherwise truncate the PIN silently. */
log_error ("PIN is too large; maximum length is %lu\n",
aodf->stored_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length)
{
log_error ("PIN is too large; maximum length is %lu\n",
aodf->max_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
if (err)
{
xfree (pinvalue);
return err;
}
errstr = NULL;
err = 0;
switch (aodf->pintype)
{
case PIN_TYPE_BCD:
case PIN_TYPE_ASCII_NUMERIC:
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
errstr = "Non-numeric digits found in PIN";
err = gpg_error (GPG_ERR_BAD_PIN);
}
break;
case PIN_TYPE_UTF8:
break;
case PIN_TYPE_HALF_NIBBLE_BCD:
errstr = "PIN type Half-Nibble-BCD is not supported";
break;
case PIN_TYPE_ISO9564_1:
errstr = "PIN type ISO9564-1 is not supported";
break;
default:
errstr = "Unknown PIN type";
break;
}
if (errstr)
{
log_error ("can't verify PIN: %s\n", errstr);
xfree (pinvalue);
return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pintype == PIN_TYPE_BCD )
{
char *paddedpin;
int ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < aodf->stored_length && *s)
paddedpin[i++] = (((*s - '0') << 4)
|((aodf->pad_char_valid?aodf->pad_char:0)&0x0f));
if (aodf->pinflags.needs_padding)
while (i < aodf->stored_length)
paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0;
xfree (pinvalue);
pinvalue = paddedpin;
pinvaluelen = i;
}
else if (aodf->pinflags.needs_padding)
{
char *paddedpin;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++)
paddedpin[i] = *s;
/* Not sure what padding char to use if none has been set.
For now we use 0x00; maybe a space would be better. */
for (; i < aodf->stored_length; i++)
paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0;
paddedpin[i] = 0;
pinvaluelen = i;
xfree (pinvalue);
pinvalue = paddedpin;
}
else
pinvaluelen = strlen (pinvalue);
err = iso7816_verify (app->slot,
aodf->pin_reference_valid? aodf->pin_reference : 0,
pinvalue, pinvaluelen);
xfree (pinvalue);
if (err)
{
log_error ("PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
log_debug ("PIN verification succeeded\n");
}
/* Prepare the DER object from INDATA. */
if (indatalen == 36)
{
/* No ASN.1 container used. */
if (hashalgo != MD_USER_TLS_MD5SHA1)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160
&& !memcmp (indata, rmd160_prefix, 15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else
{
/* Need to prepend the prefix. */
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
}
/* Manage security environment needs to be weaked for certain cards. */
if (mse_done)
err = 0;
else if (app->app_local->card_type == CARD_TYPE_TCOS)
{
/* TCOS creates signatures always using the local key 0. MSE
may not be used. */
}
else if (app->app_local->card_type == CARD_TYPE_MICARDO)
{
if (!prkdf->pathlen)
err = gpg_error (GPG_ERR_BUG);
else
err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]);
}
else if (prkdf->key_reference_valid)
{
unsigned char mse[3];
mse[0] = 0x84; /* Select asym. key. */
mse[1] = 1;
mse[2] = prkdf->key_reference;
err = iso7816_manage_security_env (app->slot,
0x41, 0xB6,
mse, sizeof mse);
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
if (hashalgo == MD_USER_TLS_MD5SHA1)
err = iso7816_compute_ds (app->slot, 0, data, 36, 0, outdata, outdatalen);
else if (no_data_padding)
err = iso7816_compute_ds (app->slot, 0, data+15, 20, 0,outdata,outdatalen);
else
err = iso7816_compute_ds (app->slot, 0, data, 35, 0, outdata, outdatalen);
return err;
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!prkdf->usageflags.sign)
{
log_error ("key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Assume that EF(DIR) has been selected. Read its content and figure
out the home EF of pkcs#15. Return that home DF or 0 if not found
and the value at the address of BELPIC indicates whether it was
found by the belpic aid. */
static unsigned short
read_home_df (int slot, int *r_belpic)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p, *pp;
size_t buflen, n, nn;
unsigned short result = 0;
*r_belpic = 0;
err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err));
return 0;
}
/* FIXME: We need to scan all records. */
p = find_tlv (buffer, buflen, 0x61, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x4f, &nn);
if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn))
|| (*r_belpic = (nn == sizeof pkcs15be_aid
&& !memcmp (pp, pkcs15be_aid, nn)))))
{
pp = find_tlv (p, n, 0x50, &nn);
if (pp) /* fixme: Filter log value? */
log_info ("pkcs#15 application label from EF(DIR) is '%.*s'\n",
(int)nn, pp);
pp = find_tlv (p, n, 0x51, &nn);
if (pp && nn == 4 && *pp == 0x3f && !pp[1])
{
result = ((pp[2] << 8) | pp[3]);
log_info ("pkcs#15 application directory is 0x%04hX\n", result);
}
}
}
xfree (buffer);
return result;
}
/*
Select the PKCS#15 application on the card in SLOT.
*/
gpg_error_t
app_select_p15 (app_t app)
{
int slot = app->slot;
int rc;
unsigned short def_home_df = 0;
card_type_t card_type = CARD_TYPE_UNKNOWN;
int direct = 0;
int is_belpic = 0;
rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid, 0);
if (rc)
{ /* Not found: Try to locate it from 2F00. We use direct path
selection here because it seems that the Belgian eID card
does only allow for that. Many other cards supports this
selection method too. Note, that we don't use
select_application above for the Belgian card - the call
works but it seems that it did not switch to the correct DF.
Using the 2f02 just works. */
unsigned short path[1] = { 0x2f00 };
rc = iso7816_select_path (app->slot, path, 1);
if (!rc)
{
direct = 1;
def_home_df = read_home_df (slot, &is_belpic);
if (def_home_df)
{
path[0] = def_home_df;
rc = iso7816_select_path (app->slot, path, 1);
}
}
}
if (rc)
{ /* Still not found: Try the default DF. */
def_home_df = 0x5015;
rc = iso7816_select_file (slot, def_home_df, 1);
}
if (!rc)
{
/* Determine the type of the card. The general case is to look
it up from the ATR table. For the Belgian eID card we know
it instantly from the AID. */
if (is_belpic)
{
card_type = CARD_TYPE_BELPIC;
}
else
{
unsigned char *atr;
size_t atrlen;
int i;
atr = apdu_get_atr (app->slot, &atrlen);
if (!atr)
rc = gpg_error (GPG_ERR_INV_CARD);
else
{
for (i=0; card_atr_list[i].atrlen; i++)
if (card_atr_list[i].atrlen == atrlen
&& !memcmp (card_atr_list[i].atr, atr, atrlen))
{
card_type = card_atr_list[i].type;
break;
}
xfree (atr);
}
}
}
if (!rc)
{
app->apptype = "P15";
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* Set the home DF. Note that we currently can't do that if the
selection via application ID worked. This will store 0 there
instead. FIXME: We either need to figure the home_df via the
DIR file or using the return values from the select file
APDU. */
app->app_local->home_df = def_home_df;
/* Store the card type. FIXME: We might want to put this into
the common APP structure. */
app->app_local->card_type = card_type;
/* Store whether we may and should use direct path selection. */
app->app_local->direct_path_selection = direct;
/* Read basic information and thus check whether this is a real
card. */
rc = read_p15_info (app);
if (rc)
goto leave;
/* Special serial number munging. We need to check for a German
prototype card right here because we need to access to
EF(TokenInfo). We mark such a serial number by the using a
prefix of FF0100. */
if (app->serialnolen == 12
&& !memcmp (app->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12))
{
/* This is a German card with a silly serial number. Try to get
the serial number from the EF(TokenInfo). . */
unsigned char *p;
/* FIXME: actually get it from EF(TokenInfo). */
p = xtrymalloc (3 + app->serialnolen);
if (!p)
rc = gpg_error (gpg_err_code_from_errno (errno));
else
{
memcpy (p, "\xff\x01", 3);
memcpy (p+3, app->serialno, app->serialnolen);
app->serialnolen += 3;
xfree (app->serialno);
app->serialno = p;
}
}
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = NULL;
app->fnc.change_pin = NULL;
app->fnc.check_pin = NULL;
leave:
if (rc)
do_deinit (app);
}
return rc;
}
diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c
index dddc8dcfe..8094b2463 100644
--- a/scd/app-sc-hsm.c
+++ b/scd/app-sc-hsm.c
@@ -1,2084 +1,2084 @@
/* app-sc-hsm.c - The SmartCard-HSM card application (www.smartcard-hsm.com).
* Copyright (C) 2005 Free Software Foundation, Inc.
* Copyright (C) 2014 Andreas Schwier <andreas.schwier@cardcontact.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
Code in this driver is based on app-p15.c with modifications.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
#include "app-common.h"
-#include "tlv.h"
+#include "../common/tlv.h"
#include "apdu.h"
/* The AID of the SmartCard-HSM applet. */
static char const sc_hsm_aid[] = { 0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81,
0xC3, 0x1F, 0x02, 0x01 };
/* Special file identifier for SmartCard-HSM */
typedef enum
{
SC_HSM_PRKD_PREFIX = 0xC4,
SC_HSM_CD_PREFIX = 0xC8,
SC_HSM_DCOD_PREFIX = 0xC9,
SC_HSM_CA_PREFIX = 0xCA,
SC_HSM_KEY_PREFIX = 0xCC,
SC_HSM_EE_PREFIX = 0xCE
} fid_prefix_type_t;
/* The key types supported by the SmartCard-HSM */
typedef enum
{
KEY_TYPE_RSA,
KEY_TYPE_ECC
} key_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* To avoid reading a certificate more than once, we cache it in an
allocated memory IMAGE of IMAGELEN. */
size_t imagelen;
unsigned char *image;
/* EF containing certificate */
unsigned short fid;
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Key type */
key_type_t keytype;
/* Key size in bits or 0 if unknown */
size_t keysize;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The keyReference */
unsigned char key_reference;
};
typedef struct prkdf_object_s *prkdf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all private keys. */
prkdf_object_t private_key_info;
};
/*** Local prototypes. ***/
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
xfree (a->image);
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_cdflist (app->app_local->certificate_info);
release_cdflist (app->app_local->trusted_certificate_info);
release_prkdflist (app->app_local->private_key_info);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Get the list of EFs from the SmartCard-HSM.
* On success a dynamically buffer containing the EF list is returned.
* The caller is responsible for freeing the buffer.
*/
static gpg_error_t
list_ef (int slot, unsigned char **result, size_t *resultlen)
{
int sw;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send_le (slot, 1, 0x80, 0x58, 0x00, 0x00, -1, NULL, 65536,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
return iso7816_map_sw (sw);
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
description of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. */
static gpg_error_t
select_and_read_binary (int slot, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen, int maxread)
{
gpg_error_t err;
unsigned char cdata[4];
int sw;
cdata[0] = 0x54; /* Create ISO 7861-4 odd ins READ BINARY */
cdata[1] = 0x02;
cdata[2] = 0x00;
cdata[3] = 0x00;
sw = apdu_send_le(slot, 1, 0x00, 0xB1, efid >> 8, efid & 0xFF,
4, cdata, maxread, buffer, buflen);
if (sw == SW_EOF_REACHED)
sw = SW_SUCCESS;
err = iso7816_map_sw (sw);
if (err)
{
log_error ("error reading %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
return 0;
}
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (const char *certid, unsigned char **r_objid, size_t *r_objidlen)
{
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (strncmp (certid, "HSM.", 4))
return gpg_error (GPG_ERR_INV_ID);
certid += 4;
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
err = parse_certid (certid, &objid, &objidlen);
if (err)
return err;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
xfree (objid);
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
/* Read and parse a Private Key Directory File containing a single key
description in PKCS#15 format. For each private key a matching
certificate description is created, if the certificate EF exists
and contains a X.509 certificate.
Example data:
0000 30 2A 30 13 0C 11 4A 6F 65 20 44 6F 65 20 28 52 0*0...Joe Doe (R
0010 53 41 32 30 34 38 29 30 07 04 01 01 03 02 02 74 SA2048)0.......t
0020 A1 0A 30 08 30 02 04 00 02 02 08 00 ..0.0.......
Decoded example:
SEQUENCE SIZE( 42 )
SEQUENCE SIZE( 19 )
UTF8-STRING SIZE( 17 ) -- label
0000 4A 6F 65 20 44 6F 65 20 28 52 53 41 32 30 34 38 Joe Doe (RSA2048
0010 29 )
SEQUENCE SIZE( 7 )
OCTET-STRING SIZE( 1 ) -- id
0000 01
BIT-STRING SIZE( 2 ) -- key usage
0000 02 74
A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 10 )
SEQUENCE SIZE( 8 )
SEQUENCE SIZE( 2 )
OCTET-STRING SIZE( 0 ) -- empty path, req object in PKCS#15
INTEGER SIZE( 2 ) -- modulus size in bits
0000 08 00
*/
static gpg_error_t
read_ef_prkd (app_t app, unsigned short fid, prkdf_object_t *prkdresult,
cdf_object_t *cdresult)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
int i;
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
keyusage_flags_t usageflags;
const char *s;
key_type_t keytype;
size_t keysize;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */
err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen, 255);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || (tag != TAG_SEQUENCE && tag != 0x00)))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err));
goto leave;
}
keytype = tag == 0x00 ? KEY_TYPE_ECC : KEY_TYPE_RSA;
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional Label
(UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
/* AuthId ignored */
}
no_authid:
;
}
/* Parse the commonKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
err = parse_keyusage_flags (ppp, objlen, &usageflags);
if (err)
goto parse_error;
ppp += objlen;
nnn -= objlen;
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* Skip the accessFlags. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* Yep, this is the keyReference.
Note: UL is currently not used. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
}
leave_cki:
;
}
/* Skip subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
pp += objlen;
nn -= objlen;
/* Parse the key size object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
keysize = 0;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && objlen == 2)
{
keysize = *pp++ << 8;
keysize += *pp++;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, sizeof *prkdf);
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->keytype = keytype;
prkdf->keysize = keysize;
prkdf->objidlen = objidlen;
prkdf->objid = xtrymalloc (objidlen);
if (!prkdf->objid)
{
err = gpg_error_from_syserror ();
xfree (prkdf);
prkdf = NULL;
goto leave;
}
memcpy (prkdf->objid, objid, objidlen);
prkdf->usageflags = usageflags;
prkdf->key_reference = fid & 0xFF;
log_debug ("PrKDF %04hX: id=", fid);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
log_printf (" keyref=0x%02X", prkdf->key_reference);
log_printf (" keysize=%zu", prkdf->keysize);
log_printf (" usage=");
s = "";
if (prkdf->usageflags.encrypt)
{
log_printf ("%sencrypt", s);
s = ",";
}
if (prkdf->usageflags.decrypt)
{
log_printf ("%sdecrypt", s);
s = ",";
}
if (prkdf->usageflags.sign)
{
log_printf ("%ssign", s);
s = ",";
}
if (prkdf->usageflags.sign_recover)
{
log_printf ("%ssign_recover", s);
s = ",";
}
if (prkdf->usageflags.wrap )
{
log_printf ("%swrap", s);
s = ",";
}
if (prkdf->usageflags.unwrap )
{
log_printf ("%sunwrap", s);
s = ",";
}
if (prkdf->usageflags.verify )
{
log_printf ("%sverify", s);
s = ",";
}
if (prkdf->usageflags.verify_recover)
{
log_printf ("%sverify_recover", s);
s = ",";
}
if (prkdf->usageflags.derive )
{
log_printf ("%sderive", s);
s = ",";
}
if (prkdf->usageflags.non_repudiation)
{
log_printf ("%snon_repudiation", s);
}
log_printf ("\n");
xfree (buffer);
buffer = NULL;
buflen = 0;
err = select_and_read_binary (app->slot,
((SC_HSM_EE_PREFIX << 8) | (fid & 0xFF)),
"CertEF", &buffer, &buflen, 1);
if (!err && buffer[0] == 0x30)
{
/* Create a matching CDF list item. */
cdf = xtrycalloc (1, sizeof *cdf);
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = prkdf->objidlen;
cdf->objid = xtrymalloc (cdf->objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
cdf = NULL;
goto leave;
}
memcpy (cdf->objid, prkdf->objid, objidlen);
cdf->fid = (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF);
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" fid=%04X\n", cdf->fid);
}
goto leave; /* Ready. */
parse_error:
log_error ("error parsing PrKDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
leave:
xfree (buffer);
if (err)
{
if (prkdf)
{
if (prkdf->objid)
xfree (prkdf->objid);
xfree (prkdf);
}
if (cdf)
{
if (cdf->objid)
xfree (cdf->objid);
xfree (cdf);
}
}
else
{
if (prkdf)
prkdf->next = *prkdresult;
*prkdresult = prkdf;
if (cdf)
{
cdf->next = *cdresult;
*cdresult = cdf;
}
}
return err;
}
/* Read and parse the Certificate Description File identified by FID.
On success a the CDF list gets stored at RESULT and the caller is
then responsible of releasing the object.
Example data:
0000 30 35 30 11 0C 0B 43 65 72 74 69 66 69 63 61 74 050...Certificat
0010 65 03 02 06 40 30 16 04 14 C2 01 7C 2F BA A4 4A e...@0.....|/..J
0020 4A BB B8 49 11 DB 4A CA AA 7E 6A 2D 1B A1 08 30 J..I..J..~j-...0
0030 06 30 04 04 02 CA 00 .0.....
Decoded example:
SEQUENCE SIZE( 53 )
SEQUENCE SIZE( 17 )
UTF8-STRING SIZE( 11 ) -- label
0000 43 65 72 74 69 66 69 63 61 74 65 Certificate
BIT-STRING SIZE( 2 ) -- common object attributes
0000 06 40
SEQUENCE SIZE( 22 )
OCTET-STRING SIZE( 20 ) -- id
0000 C2 01 7C 2F BA A4 4A 4A BB B8 49 11 DB 4A CA AA
0010 7E 6A 2D 1B
A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 8 )
SEQUENCE SIZE( 6 )
SEQUENCE SIZE( 4 )
OCTET-STRING SIZE( 2 ) -- path
0000 CA 00 ..
*/
static gpg_error_t
read_ef_cd (app_t app, unsigned short fid, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
int i;
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
const unsigned char *objid;
size_t objidlen;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */
err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen, 255);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Skip the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, sizeof *cdf);
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
cdf = NULL;
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
cdf->fid = (SC_HSM_CA_PREFIX << 8) | (fid & 0xFF);
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
goto leave;
parse_error:
log_error ("error parsing CDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
leave:
xfree (buffer);
if (err)
{
if (cdf)
{
if (cdf->objid)
xfree (cdf->objid);
xfree (cdf);
}
}
else
{
if (cdf)
cdf->next = *result;
*result = cdf;
}
return err;
}
/* Read the device certificate and extract the serial number.
EF.C_DevAut (2F02) contains two CVCs, the first is the device
certificate, the second is the issuer certificate.
Example data:
0000 7F 21 81 E2 7F 4E 81 9B 5F 29 01 00 42 0B 55 54 .!...N.._)..B.UT
0010 43 43 30 32 30 30 30 30 32 7F 49 4F 06 0A 04 00 CC0200002.IO....
0020 7F 00 07 02 02 02 02 03 86 41 04 6D FF D6 85 57 .........A.m...W
0030 40 FB 10 5D 94 71 8A 94 D2 5E 50 33 E7 1E C0 6C @..].q...^P3...l
0040 63 D5 C8 FC BA F3 02 1D 70 23 F6 47 E8 35 48 EF c.......p#.G.5H.
0050 B5 94 72 3C 6F BE C0 EB 9A C7 FB 06 59 26 CF 65 ..r<o.......Y&.e
0060 EF A1 72 E0 98 F3 F0 44 1B B7 71 5F 20 10 55 54 ..r....D..q_ .UT
0070 43 43 30 32 30 30 30 31 33 30 30 30 30 30 7F 4C CC020001300000.L
0080 10 06 0B 2B 06 01 04 01 81 C3 1F 03 01 01 53 01 ...+..........S.
0090 00 5F 25 06 01 04 00 07 01 01 5F 24 06 02 01 00 ._%......._$....
00A0 03 02 07 5F 37 40 7F 73 04 3B 06 63 79 41 BE 1A ..._7@.s.;.cyA..
00B0 9F FC F6 77 67 2B 8A 41 D1 11 F6 9B 54 44 AD 19 ...wg+.A....TD..
00C0 FB B8 0C C6 2F 34 71 8E 4F F6 92 59 34 61 D9 4F ..../4q.O..Y4a.O
00D0 4A 86 36 A8 D8 9A C6 3C 17 7E 71 CE A8 26 D0 C5 J.6....<.~q..&..
00E0 25 61 78 9D 01 F8 7F 21 81 E0 7F 4E 81 99 5F 29 %ax....!...N.._)
00F0 01 00 42 0E 55 54 53 52 43 41 43 43 31 30 30 30 ..B.UTSRCACC1000
0100 30 31 7F 49 4F 06 0A 04 00 7F 00 07 02 02 02 02 01.IO...........
0110 03 86 41 04 2F EA 33 47 7F 45 81 E2 FC CB 66 87 ..A./.3G.E....f.
0120 4B 96 21 1D 68 81 73 F2 9F 8F 6B 91 F0 DE 4B 54 K.!.h.s...k...KT
0130 8E D8 F0 82 3D CB BE 10 98 A3 1E 4F F0 72 5C E5 ....=......O.r\.
0140 7B 1E F7 3C 68 09 03 E8 A0 3F 3E 06 C1 B0 3C 18 {..<h....?>...<.
0150 6B AC 06 EA 5F 20 0B 55 54 43 43 30 32 30 30 30 k..._ .UTCC02000
0160 30 32 7F 4C 10 06 0B 2B 06 01 04 01 81 C3 1F 03 02.L...+........
0170 01 01 53 01 80 5F 25 06 01 03 00 03 02 08 5F 24 ..S.._%......._$
0180 06 02 01 00 03 02 07 5F 37 40 93 C1 42 8B B3 8E ......._7@..B...
0190 42 61 6F 2C 19 E6 98 41 BD AA 60 BD E0 DD 4E F0 Bao,...A..`...N.
01A0 15 D5 4F 71 B7 BB C3 3A F2 AD 27 5E DD EE 6D 12 ..Oq...:..'^..m.
01B0 76 E6 2B A0 4C 01 CA C1 26 0C 45 6D C6 CB EC 92 v.+.L...&.Em....
01C0 BF 38 18 AD 8F B2 29 40 A9 51 .8....)@.Q
The certificate format is defined in BSI TR-03110:
7F21 [ APPLICATION 33 ] IMPLICIT SEQUENCE SIZE( 226 )
7F4E [ APPLICATION 78 ] IMPLICIT SEQUENCE SIZE( 155 )
5F29 [ APPLICATION 41 ] SIZE( 1 ) -- profile id
0000 00
42 [ APPLICATION 2 ] SIZE( 11 ) -- CAR
0000 55 54 43 43 30 32 30 30 30 30 32 UTCC0200002
7F49 [ APPLICATION 73 ] IMPLICIT SEQUENCE SIZE( 79 ) -- public key
OBJECT IDENTIFIER = { id-TA-ECDSA-SHA-256 }
86 [ CONTEXT 6 ] SIZE( 65 )
0000 04 6D FF D6 85 57 40 FB 10 5D 94 71 8A 94 D2 5E
0010 50 33 E7 1E C0 6C 63 D5 C8 FC BA F3 02 1D 70 23
0020 F6 47 E8 35 48 EF B5 94 72 3C 6F BE C0 EB 9A C7
0030 FB 06 59 26 CF 65 EF A1 72 E0 98 F3 F0 44 1B B7
0040 71
5F20 [ APPLICATION 32 ] SIZE( 16 ) -- CHR
0000 55 54 43 43 30 32 30 30 30 31 33 30 30 30 30 30 UTCC020001300000
7F4C [ APPLICATION 76 ] IMPLICIT SEQUENCE SIZE( 16 ) -- CHAT
OBJECT IDENTIFIER = { 1 3 6 1 4 1 24991 3 1 1 }
53 [ APPLICATION 19 ] SIZE( 1 )
0000 00
5F25 [ APPLICATION 37 ] SIZE( 6 ) -- Valid from
0000 01 04 00 07 01 01
5F24 [ APPLICATION 36 ] SIZE( 6 ) -- Valid to
0000 02 01 00 03 02 07
5F37 [ APPLICATION 55 ] SIZE( 64 ) -- Signature
0000 7F 73 04 3B 06 63 79 41 BE 1A 9F FC F6 77 67 2B
0010 8A 41 D1 11 F6 9B 54 44 AD 19 FB B8 0C C6 2F 34
0020 71 8E 4F F6 92 59 34 61 D9 4F 4A 86 36 A8 D8 9A
0030 C6 3C 17 7E 71 CE A8 26 D0 C5 25 61 78 9D 01 F8
The serial number is contained in tag 5F20, while the last 5 digits
are truncated.
*/
static gpg_error_t
read_serialno(app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p,*chr;
size_t n, objlen, hdrlen, chrlen;
int class, tag, constructed, ndef;
err = select_and_read_binary (app->slot, 0x2F02, "EF.C_DevAut",
&buffer, &buflen, 512);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != 0x21))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing C_DevAut: %s\n", gpg_strerror (err));
goto leave;
}
chr = find_tlv (p, objlen, 0x5F20, &chrlen);
if (!chr || chrlen <= 5)
{
err = gpg_error (GPG_ERR_INV_OBJ);
log_error ("CHR not found in CVC\n");
goto leave;
}
chrlen -= 5;
app->serialno = xtrymalloc (chrlen);
if (!app->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
app->serialnolen = chrlen;
memcpy (app->serialno, chr, chrlen);
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the SmartCard-HSM, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_meta (app_t app)
{
gpg_error_t err;
unsigned char *eflist = NULL;
size_t eflistlen = 0;
int i;
err = read_serialno(app);
if (err)
return err;
err = list_ef (app->slot, &eflist, &eflistlen);
if (err)
return err;
for (i = 0; i < eflistlen; i += 2)
{
switch(eflist[i])
{
case SC_HSM_KEY_PREFIX:
if (eflist[i + 1] == 0) /* No key with ID=0 */
break;
err = read_ef_prkd (app, ((SC_HSM_PRKD_PREFIX << 8) | eflist[i + 1]),
&app->app_local->private_key_info,
&app->app_local->certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
break;
case SC_HSM_CD_PREFIX:
err = read_ef_cd (app, ((eflist[i] << 8) | eflist[i + 1]),
&app->app_local->trusted_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
break;
}
}
xfree (eflist);
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (ctrl_t ctrl, const char *certtype, cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
buf = xtrymalloc (4 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (certinfo->objid, certinfo->objidlen, p);
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
NULL, (size_t)0);
xfree (buf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
keygrip gets returned in the caller provided 41 byte buffer
R_GRIPSTR. */
static gpg_error_t
keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
/* Look for a matching certificate. A certificate matches if the Id
matches the one of the private key info. */
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
return err;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, r_gripstr);
ksba_cert_release (cert);
return err;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo)
{
gpg_error_t err;
for (; keyinfo; keyinfo = keyinfo->next)
{
char gripstr[40+1];
char *buf, *p;
buf = xtrymalloc (4 + keyinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (keyinfo->objid, keyinfo->objidlen, p);
err = keygripstr_from_prkdf (app, keyinfo, gripstr);
if (err)
{
log_error ("can't get keygrip from %04X\n", keyinfo->key_reference);
}
else
{
assert (strlen (gripstr) == 40);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
buf, strlen (buf),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if ((flags & 1))
err = 0;
else
{
err = send_certinfo (ctrl, "100", app->app_local->certificate_info);
if (!err)
err = send_certinfo (ctrl, "101",
app->app_local->trusted_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
return err;
}
/* Read a certificate using the information in CDF and return the
certificate in a newly allocated buffer R_CERT and its length
R_CERTLEN. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
*r_cert = NULL;
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->image)
{
*r_cert = xtrymalloc (cdf->imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, cdf->image, cdf->imagelen);
*r_certlen = cdf->imagelen;
return 0;
}
err = select_and_read_binary (app->slot, cdf->fid, "CD",
&buffer, &buflen, 4096);
if (err)
{
log_error ("error reading certificate with Id ");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
/* Try to cache it. */
if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen)))
{
memcpy (cdf->image, *r_cert, *r_certlen);
cdf->imagelen = *r_certlen;
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
if (!strcmp (name, "$AUTHKEYID"))
{
char *buf, *p;
prkdf_object_t prkdf;
/* We return the ID of the first private key capable of
signing. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->usageflags.sign)
break;
if (prkdf)
{
buf = xtrymalloc (4 + prkdf->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (prkdf->objid, prkdf->objidlen, p);
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
return 0;
}
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
send_status_info (ctrl, name, app->serialno, app->serialnolen, NULL, 0);
return 0;
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Apply PKCS#1 V1.5 padding for signature operation. The function
* combines padding, digest info and the hash value. The buffer must
* be allocated by the caller matching the key size. */
static void
apply_PKCS_padding(const unsigned char *dig, int diglen,
const unsigned char *prefix, int prefixlen,
unsigned char *buff, int bufflen)
{
int i, n_ff;
/* Caller must ensure a sufficient buffer. */
if (diglen + prefixlen + 4 > bufflen)
return;
n_ff = bufflen - diglen - prefixlen - 3;
*buff++ = 0x00;
*buff++ = 0x01;
for (i=0; i < n_ff; i++)
*buff++ = 0xFF;
*buff++ = 0x00;
if (prefix)
memcpy (buff, prefix, prefixlen);
buff += prefixlen;
memcpy (buff, dig, diglen);
}
/* Decode a digest info structure (DI,DILEN) to extract the hash
* value. The buffer HASH to receive the digest must be provided by
* the caller with HASHLEN pointing to the inbound length. HASHLEN is
* updated to the outbound length. */
static int
hash_from_digestinfo (const unsigned char *di, size_t dilen,
unsigned char *hash, size_t *hashlen)
{
const unsigned char *p,*pp;
size_t n, nn, objlen, hdrlen;
int class, tag, constructed, ndef;
gpg_error_t err;
p = di;
n = dilen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
pp = p;
nn = objlen;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
pp += objlen;
nn -= objlen;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
if (*hashlen < objlen)
return gpg_error (GPG_ERR_TOO_SHORT);
memcpy (hash, pp, objlen);
*hashlen = objlen;
return 0;
}
/* Perform PIN verification
*/
static gpg_error_t
verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
pininfo_t pininfo;
char *pinvalue;
char *prompt;
int sw;
sw = apdu_send_simple (app->slot, 0, 0x00, ISO7816_VERIFY, 0x00, 0x81,
-1, NULL);
if (sw == SW_SUCCESS)
return 0; /* PIN already verified */
if (sw == SW_REF_DATA_INV)
{
log_error ("SmartCard-HSM not initialized. Run sc-hsm-tool first\n");
return gpg_error (GPG_ERR_NO_PIN);
}
if (sw == SW_CHV_BLOCKED)
{
log_error ("PIN Blocked\n");
return gpg_error (GPG_ERR_PIN_BLOCKED);
}
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = 0;
pininfo.minlen = 6;
pininfo.maxlen = 15;
prompt = "||Please enter the PIN";
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
{
err = pincb (pincb_arg, prompt, NULL);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
err = iso7816_verify_kp (app->slot, 0x81, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
err = pincb (pincb_arg, prompt, &pinvalue);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
err = iso7816_verify (app->slot, 0x81, pinvalue, strlen(pinvalue));
xfree (pinvalue);
}
if (err)
{
log_error ("PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
log_debug ("PIN verification succeeded\n");
return err;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument.
The API is somewhat inconsistent: The caller can either supply
a plain hash and the algorithm in hashalgo or a complete
DigestInfo structure. The former is detect by characteristic length
of the provided data (20,28,32,48 or 64 byte).
The function returns the RSA block in the size of the modulus or
the ECDSA signature in X9.62 format (SEQ/INT(r)/INT(s))
*/
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
gpg_error_t err;
unsigned char cdsblk[256]; /* Raw PKCS#1 V1.5 block with padding
(RSA) or hash. */
prkdf_object_t prkdf; /* The private key object. */
size_t cdsblklen;
unsigned char algoid;
int sw;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen > 124) /* Limit for 1024 bit key */
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
||prkdf->usageflags.non_repudiation))
{
log_error ("key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (prkdf->keytype == KEY_TYPE_RSA)
{
algoid = 0x20;
cdsblklen = prkdf->keysize >> 3;
if (!cdsblklen)
cdsblklen = 256;
if (hashalgo == GCRY_MD_SHA1 && indatalen == 20)
apply_PKCS_padding (indata, indatalen,
sha1_prefix, sizeof(sha1_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_MD5 && indatalen == 20)
apply_PKCS_padding (indata, indatalen,
rmd160_prefix, sizeof(rmd160_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA224 && indatalen == 28)
apply_PKCS_padding (indata, indatalen,
sha224_prefix, sizeof(sha224_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA256 && indatalen == 32)
apply_PKCS_padding (indata, indatalen,
sha256_prefix, sizeof(sha256_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA384 && indatalen == 48)
apply_PKCS_padding (indata, indatalen,
sha384_prefix, sizeof(sha384_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA512 && indatalen == 64)
apply_PKCS_padding (indata, indatalen,
sha512_prefix, sizeof(sha512_prefix),
cdsblk, cdsblklen);
else /* Assume it's already a digest info or TLS_MD5SHA1 */
apply_PKCS_padding (indata, indatalen, NULL, 0, cdsblk, cdsblklen);
}
else
{
algoid = 0x70;
if (indatalen != 20 && indatalen != 28 && indatalen != 32
&& indatalen != 48 && indatalen != 64)
{
cdsblklen = sizeof(cdsblk);
err = hash_from_digestinfo (indata, indatalen, cdsblk, &cdsblklen);
if (err)
{
log_error ("DigestInfo invalid: %s\n", gpg_strerror (err));
return err;
}
}
else
{
memcpy (cdsblk, indata, indatalen);
cdsblklen = indatalen;
}
}
err = verify_pin (app, pincb, pincb_arg);
if (err)
return err;
sw = apdu_send_le (app->slot, 1, 0x80, 0x68, prkdf->key_reference, algoid,
cdsblklen, cdsblk, 0, outdata, outdatalen);
return iso7816_map_sw (sw);
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!prkdf->usageflags.sign)
{
log_error ("key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Check PKCS#1 V1.5 padding and extract plain text. The function
* allocates a buffer for the plain text. The caller must release the
* buffer. */
static gpg_error_t
strip_PKCS15_padding(unsigned char *src, int srclen, unsigned char **dst,
size_t *dstlen)
{
unsigned char *p;
if (srclen < 2)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
if (*src++ != 0x00)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
if (*src++ != 0x02)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
srclen -= 2;
while ((srclen > 0) && *src)
{
src++;
srclen--;
}
if (srclen < 2)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
src++;
srclen--;
p = xtrymalloc (srclen);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, src, srclen);
*dst = p;
*dstlen = srclen;
return 0;
}
/* Decrypt a PKCS#1 V1.5 formatted cryptogram using the referenced
key. */
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
unsigned char p1blk[256]; /* Enciphered P1 block */
prkdf_object_t prkdf; /* The private key object. */
unsigned char *rspdata;
size_t rspdatalen;
size_t p1blklen;
int sw;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap))
{
log_error ("key %s may not be used for deciphering\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (prkdf->keytype != KEY_TYPE_RSA)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
p1blklen = prkdf->keysize >> 3;
if (!p1blklen)
p1blklen = 256;
/* The input may be shorter (due to MPIs not storing leading zeroes)
or longer than the block size. We put INDATA right aligned into
the buffer. If INDATA is longer than the block size we truncate
it on the left. */
memset (p1blk, 0, sizeof(p1blk));
if (indatalen > p1blklen)
memcpy (p1blk, (unsigned char *)indata + (indatalen - p1blklen), p1blklen);
else
memcpy (p1blk + (p1blklen - indatalen), indata, indatalen);
err = verify_pin(app, pincb, pincb_arg);
if (err)
return err;
sw = apdu_send_le (app->slot, 1, 0x80, 0x62, prkdf->key_reference, 0x21,
p1blklen, p1blk, 0, &rspdata, &rspdatalen);
err = iso7816_map_sw (sw);
if (err)
{
log_error ("Decrypt failed: %s\n", gpg_strerror (err));
return err;
}
err = strip_PKCS15_padding (rspdata, rspdatalen, outdata, outdatalen);
xfree (rspdata);
if (!err)
*r_info |= APP_DECIPHER_INFO_NOPAD;
return err;
}
/*
* Select the SmartCard-HSM application on the card in SLOT.
*/
gpg_error_t
app_select_sc_hsm (app_t app)
{
int slot = app->slot;
int rc;
rc = iso7816_select_application (slot, sc_hsm_aid, sizeof sc_hsm_aid, 0);
if (!rc)
{
app->apptype = "SC-HSM";
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = read_meta (app);
if (rc)
goto leave;
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = NULL;
app->fnc.check_pin = NULL;
leave:
if (rc)
do_deinit (app);
}
return rc;
}
diff --git a/scd/app.c b/scd/app.c
index e1c4e100d..472adc70f 100644
--- a/scd/app.c
+++ b/scd/app.c
@@ -1,1120 +1,1120 @@
/* app.c - Application selection.
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <npth.h>
#include "scdaemon.h"
-#include "exechelp.h"
+#include "../common/exechelp.h"
#include "app-common.h"
#include "iso7816.h"
#include "apdu.h"
-#include "tlv.h"
+#include "../common/tlv.h"
static npth_mutex_t app_list_lock;
static app_t app_top;
static void
print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
{
ctrl_t ctrl = opaque;
char line[100];
if (ctrl)
{
snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot);
send_status_direct (ctrl, "PROGRESS", line);
}
}
/* Lock the reader SLOT. This function shall be used right before
calling any of the actual application functions to serialize access
to the reader. We do this always even if the reader is not
actually used. This allows an actual connection to assume that it
never shares a reader (while performing one command). Returns 0 on
success; only then the unlock_reader function must be called after
returning from the handler. */
static gpg_error_t
lock_app (app_t app, ctrl_t ctrl)
{
if (npth_mutex_lock (&app->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to acquire APP lock for %p: %s\n",
app, gpg_strerror (err));
return err;
}
apdu_set_progress_cb (app->slot, print_progress_line, ctrl);
return 0;
}
/* Release a lock on the reader. See lock_reader(). */
static void
unlock_app (app_t app)
{
apdu_set_progress_cb (app->slot, NULL, NULL);
if (npth_mutex_unlock (&app->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to release APP lock for %p: %s\n",
app, gpg_strerror (err));
}
}
/* This function may be called to print information pertaining to the
current state of this module to the log. */
void
app_dump_state (void)
{
app_t a;
npth_mutex_lock (&app_list_lock);
for (a = app_top; a; a = a->next)
log_info ("app_dump_state: app=%p type='%s'\n", a, a->apptype);
npth_mutex_unlock (&app_list_lock);
}
/* Check wether the application NAME is allowed. This does not mean
we have support for it though. */
static int
is_app_allowed (const char *name)
{
strlist_t l;
for (l=opt.disabled_applications; l; l = l->next)
if (!strcmp (l->d, name))
return 0; /* no */
return 1; /* yes */
}
static gpg_error_t
check_conflict (app_t app, const char *name)
{
if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name)))
return 0;
log_info ("application '%s' in use - can't switch\n",
app->apptype? app->apptype : "<null>");
return gpg_error (GPG_ERR_CONFLICT);
}
/* This function is used by the serialno command to check for an
application conflict which may appear if the serialno command is
used to request a specific application and the connection has
already done a select_application. */
gpg_error_t
check_application_conflict (const char *name, app_t app)
{
return check_conflict (app, name);
}
gpg_error_t
app_reset (app_t app, ctrl_t ctrl, int send_reset)
{
gpg_error_t err = 0;
if (send_reset)
{
int sw;
lock_app (app, ctrl);
sw = apdu_reset (app->slot);
if (sw)
err = gpg_error (GPG_ERR_CARD_RESET);
app->reset_requested = 1;
unlock_app (app);
scd_kick_the_loop ();
gnupg_sleep (1);
}
else
{
ctrl->app_ctx = NULL;
release_application (app, 0);
}
return err;
}
static gpg_error_t
app_new_register (int slot, ctrl_t ctrl, const char *name,
int periodical_check_needed)
{
gpg_error_t err = 0;
app_t app = NULL;
unsigned char *result = NULL;
size_t resultlen;
int want_undefined;
/* Need to allocate a new one. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating context: %s\n", gpg_strerror (err));
return err;
}
app->slot = slot;
app->card_status = (unsigned int)-1;
if (npth_mutex_init (&app->lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
xfree (app);
return err;
}
err = lock_app (app, ctrl);
if (err)
{
xfree (app);
return err;
}
want_undefined = (name && !strcmp (name, "undefined"));
/* Try to read the GDO file first to get a default serial number.
We skip this if the undefined application has been requested. */
if (!want_undefined)
{
err = iso7816_select_file (slot, 0x3F00, 1);
if (!err)
err = iso7816_select_file (slot, 0x2F02, 0);
if (!err)
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
if (!err)
{
size_t n;
const unsigned char *p;
p = find_tlv_unchecked (result, resultlen, 0x5A, &n);
if (p)
resultlen -= (p-result);
if (p && n > resultlen && n == 0x0d && resultlen+1 == n)
{
/* The object it does not fit into the buffer. This is an
invalid encoding (or the buffer is too short. However, I
have some test cards with such an invalid encoding and
therefore I use this ugly workaround to return something
I can further experiment with. */
log_info ("enabling BMI testcard workaround\n");
n--;
}
if (p && n <= resultlen)
{
/* The GDO file is pretty short, thus we simply reuse it for
storing the serial number. */
memmove (result, p, n);
app->serialno = result;
app->serialnolen = n;
err = app_munge_serialno (app);
if (err)
goto leave;
}
else
xfree (result);
result = NULL;
}
}
/* For certain error codes, there is no need to try more. */
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT
|| gpg_err_code (err) == GPG_ERR_ENODEV)
goto leave;
/* Figure out the application to use. */
if (want_undefined)
{
/* We switch to the "undefined" application only if explicitly
requested. */
app->apptype = "UNDEFINED";
err = 0;
}
else
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err && is_app_allowed ("openpgp")
&& (!name || !strcmp (name, "openpgp")))
err = app_select_openpgp (app);
if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks")))
err = app_select_nks (app);
if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15")))
err = app_select_p15 (app);
if (err && is_app_allowed ("geldkarte")
&& (!name || !strcmp (name, "geldkarte")))
err = app_select_geldkarte (app);
if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig")))
err = app_select_dinsig (app);
if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm")))
err = app_select_sc_hsm (app);
if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
if (err)
{
if (name)
log_info ("can't select application '%s': %s\n",
name, gpg_strerror (err));
else
log_info ("no supported card application found: %s\n",
gpg_strerror (err));
unlock_app (app);
xfree (app);
return err;
}
app->periodical_check_needed = periodical_check_needed;
npth_mutex_lock (&app_list_lock);
app->next = app_top;
app_top = app;
npth_mutex_unlock (&app_list_lock);
unlock_app (app);
return 0;
}
/* If called with NAME as NULL, select the best fitting application
and return a context; otherwise select the application with NAME
and return a context. Returns an error code and stores NULL at
R_APP if no application was found or no card is present. */
gpg_error_t
select_application (ctrl_t ctrl, const char *name, app_t *r_app,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
gpg_error_t err = 0;
app_t a;
*r_app = NULL;
if (scan || !app_top)
{
struct dev_list *l;
int periodical_check_needed = 0;
/* Scan the devices to find new device(s). */
err = apdu_dev_list_start (opt.reader_port, &l);
if (err)
return err;
while (1)
{
int slot;
int periodical_check_needed_this;
slot = apdu_open_reader (l, !app_top);
if (slot < 0)
break;
periodical_check_needed_this = apdu_connect (slot);
if (periodical_check_needed_this < 0)
{
/* We close a reader with no card. */
err = gpg_error (GPG_ERR_ENODEV);
}
else
{
err = app_new_register (slot, ctrl, name,
periodical_check_needed_this);
if (periodical_check_needed_this)
periodical_check_needed = 1;
}
if (err)
apdu_close_reader (slot);
}
apdu_dev_list_finish (l);
/* If periodical check is needed for new device(s), kick the
scdaemon loop. */
if (periodical_check_needed)
scd_kick_the_loop ();
}
npth_mutex_lock (&app_list_lock);
for (a = app_top; a; a = a->next)
{
lock_app (a, ctrl);
if (serialno_bin == NULL)
break;
if (a->serialnolen == serialno_bin_len
&& !memcmp (a->serialno, serialno_bin, a->serialnolen))
break;
unlock_app (a);
}
if (a)
{
err = check_conflict (a, name);
if (!err)
{
a->ref_count++;
*r_app = a;
}
unlock_app (a);
}
else
err = gpg_error (GPG_ERR_ENODEV);
npth_mutex_unlock (&app_list_lock);
return err;
}
char *
get_supported_applications (void)
{
const char *list[] = {
"openpgp",
"nks",
"p15",
"geldkarte",
"dinsig",
"sc-hsm",
/* Note: "undefined" is not listed here because it needs special
treatment by the client. */
NULL
};
int idx;
size_t nbytes;
char *buffer, *p;
for (nbytes=1, idx=0; list[idx]; idx++)
nbytes += strlen (list[idx]) + 1 + 1;
buffer = xtrymalloc (nbytes);
if (!buffer)
return NULL;
for (p=buffer, idx=0; list[idx]; idx++)
if (is_app_allowed (list[idx]))
p = stpcpy (stpcpy (p, list[idx]), ":\n");
*p = 0;
return buffer;
}
/* Deallocate the application. */
static void
deallocate_app (app_t app)
{
app_t a, a_prev = NULL;
for (a = app_top; a; a = a->next)
if (a == app)
{
if (a_prev == NULL)
app_top = a->next;
else
a_prev->next = a->next;
break;
}
else
a_prev = a;
if (app->ref_count)
log_error ("trying to release context used yet (%d)\n", app->ref_count);
if (app->fnc.deinit)
{
app->fnc.deinit (app);
app->fnc.deinit = NULL;
}
xfree (app->serialno);
unlock_app (app);
xfree (app);
}
/* Free the resources associated with the application APP. APP is
allowed to be NULL in which case this is a no-op. Note that we are
using reference counting to track the users of the application and
actually deferring the deallocation to allow for a later reuse by
a new connection. */
void
release_application (app_t app, int locked_already)
{
if (!app)
return;
/* We don't deallocate app here. Instead, we keep it. This is
useful so that a card does not get reset even if only one session
is using the card - this way the PIN cache and other cached data
are preserved. */
if (!locked_already)
lock_app (app, NULL);
if (!app->ref_count)
log_bug ("trying to release an already released context\n");
--app->ref_count;
if (!locked_already)
unlock_app (app);
}
/* The serial number may need some cosmetics. Do it here. This
function shall only be called once after a new serial number has
been put into APP->serialno.
Prefixes we use:
FF 00 00 = For serial numbers starting with an FF
FF 01 00 = Some german p15 cards return an empty serial number so the
serial number from the EF(TokenInfo) is used instead.
FF 7F 00 = No serialno.
All other serial number not starting with FF are used as they are.
*/
gpg_error_t
app_munge_serialno (app_t app)
{
if (app->serialnolen && app->serialno[0] == 0xff)
{
/* The serial number starts with our special prefix. This
requires that we put our default prefix "FF0000" in front. */
unsigned char *p = xtrymalloc (app->serialnolen + 3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\0", 3);
memcpy (p+3, app->serialno, app->serialnolen);
app->serialnolen += 3;
xfree (app->serialno);
app->serialno = p;
}
else if (!app->serialnolen)
{
unsigned char *p = xtrymalloc (3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\x7f", 3);
app->serialnolen = 3;
xfree (app->serialno);
app->serialno = p;
}
return 0;
}
/* Retrieve the serial number of the card. The serial number is
returned as a malloced string (hex encoded) in SERIAL. Caller must
free SERIAL unless the function returns an error. */
char *
app_get_serialno (app_t app)
{
char *serial;
if (!app)
return NULL;
if (!app->serialnolen)
serial = xtrystrdup ("FF7F00");
else
serial = bin2hex (app->serialno, app->serialnolen, NULL);
return serial;
}
/* Write out the application specifig status lines for the LEARN
command. */
gpg_error_t
app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if (!app)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->fnc.learn_status)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We do not send APPTYPE if only keypairinfo is requested. */
if (app->apptype && !(flags & 1))
send_status_direct (ctrl, "APPTYPE", app->apptype);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.learn_status (app, ctrl, flags);
unlock_app (app);
return err;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
gpg_error_t
app_readcert (app_t app, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
if (!app)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.readcert)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.readcert (app, certid, cert, certlen);
unlock_app (app);
return err;
}
/* Read the key with ID KEYID. On success a canonical encoded
S-expression with the public key will get stored at PK and its
length (for assertions) at PKLEN; the caller must release that
buffer. On error NULL will be stored at PK and PKLEN and an error
code returned.
This function might not be supported by all applications. */
gpg_error_t
app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
if (pk)
*pk = NULL;
if (pklen)
*pklen = 0;
if (!app || !keyid || !pk || !pklen)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.readkey)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err= app->fnc.readkey (app, advanced, keyid, pk, pklen);
unlock_app (app);
return err;
}
/* Perform a GETATTR operation. */
gpg_error_t
app_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!app || !name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (app->apptype && name && !strcmp (name, "APPTYPE"))
{
send_status_direct (ctrl, "APPTYPE", app->apptype);
return 0;
}
if (name && !strcmp (name, "SERIALNO"))
{
char *serial;
serial = app_get_serialno (app);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
return 0;
}
if (!app->fnc.getattr)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.getattr (app, ctrl, name);
unlock_app (app);
return err;
}
/* Perform a SETATTR operation. */
gpg_error_t
app_setattr (app_t app, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
if (!app || !name || !*name || !value)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.setattr)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen);
unlock_app (app);
return err;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.sign)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.sign (app, keyidstr, hashalgo,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
unlock_app (app);
if (opt.verbose)
log_info ("operation sign result: %s\n", gpg_strerror (err));
return err;
}
/* Create the signature using the INTERNAL AUTHENTICATE command and
return the allocated result in OUTDATA. If a PIN is required the
PINCB will be used to ask for the PIN; it should return the PIN in
an allocated buffer and put it into PIN. */
gpg_error_t
app_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.auth)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.auth (app, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
unlock_app (app);
if (opt.verbose)
log_info ("operation auth result: %s\n", gpg_strerror (err));
return err;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
*r_info = 0;
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.decipher)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.decipher (app, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen,
r_info);
unlock_app (app);
if (opt.verbose)
log_info ("operation decipher result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITECERT operation. */
gpg_error_t
app_writecert (app_t app, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *data, size_t datalen)
{
gpg_error_t err;
if (!app || !certidstr || !*certidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.writecert)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.writecert (app, ctrl, certidstr,
pincb, pincb_arg, data, datalen);
unlock_app (app);
if (opt.verbose)
log_info ("operation writecert result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITEKEY operation. */
gpg_error_t
app_writekey (app_t app, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
if (!app || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.writekey)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.writekey (app, ctrl, keyidstr, flags,
pincb, pincb_arg, keydata, keydatalen);
unlock_app (app);
if (opt.verbose)
log_info ("operation writekey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a SETATTR operation. */
gpg_error_t
app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags,
time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!app || !keynostr || !*keynostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.genkey)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.genkey (app, ctrl, keynostr, flags,
createtime, pincb, pincb_arg);
unlock_app (app);
if (opt.verbose)
log_info ("operation genkey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GET CHALLENGE operation. This function is special as it
directly accesses the card without any application specific
wrapper. */
gpg_error_t
app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer)
{
gpg_error_t err;
if (!app || !nbytes || !buffer)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
err = lock_app (app, ctrl);
if (err)
return err;
err = iso7816_get_challenge (app->slot, nbytes, buffer);
unlock_app (app);
return err;
}
/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
gpg_error_t
app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!app || !chvnostr || !*chvnostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.change_pin)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode,
pincb, pincb_arg);
unlock_app (app);
if (opt.verbose)
log_info ("operation change_pin result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a VERIFY operation without doing anything lese. This may
be used to initialze a the PIN cache for long lasting other
operations. Its use is highly application dependent. */
gpg_error_t
app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!app || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if (!app->ref_count)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!app->fnc.check_pin)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = lock_app (app, ctrl);
if (err)
return err;
err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg);
unlock_app (app);
if (opt.verbose)
log_info ("operation check_pin result: %s\n", gpg_strerror (err));
return err;
}
static void
report_change (int slot, int old_status, int cur_status)
{
char *homestr, *envstr;
char *fname;
char templ[50];
FILE *fp;
snprintf (templ, sizeof templ, "reader_%d.status", slot);
fname = make_filename (gnupg_homedir (), templ, NULL );
fp = fopen (fname, "w");
if (fp)
{
fprintf (fp, "%s\n",
(cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
fclose (fp);
}
xfree (fname);
homestr = make_filename (gnupg_homedir (), NULL);
if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
log_error ("out of core while building environment\n");
else
{
gpg_error_t err;
const char *args[9], *envs[2];
char numbuf1[30], numbuf2[30], numbuf3[30];
envs[0] = envstr;
envs[1] = NULL;
sprintf (numbuf1, "%d", slot);
sprintf (numbuf2, "0x%04X", old_status);
sprintf (numbuf3, "0x%04X", cur_status);
args[0] = "--reader-port";
args[1] = numbuf1;
args[2] = "--old-code";
args[3] = numbuf2;
args[4] = "--new-code";
args[5] = numbuf3;
args[6] = "--status";
args[7] = ((cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
args[8] = NULL;
fname = make_filename (gnupg_homedir (), "scd-event", NULL);
err = gnupg_spawn_process_detached (fname, args, envs);
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("failed to run event handler '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
xfree (envstr);
}
xfree (homestr);
}
int
scd_update_reader_status_file (void)
{
app_t a, app_next;
int periodical_check_needed = 0;
npth_mutex_lock (&app_list_lock);
for (a = app_top; a; a = app_next)
{
int sw;
unsigned int status;
lock_app (a, NULL);
app_next = a->next;
if (a->reset_requested)
status = 0;
else
{
sw = apdu_get_status (a->slot, 0, &status);
if (sw == SW_HOST_NO_READER)
{
/* Most likely the _reader_ has been unplugged. */
status = 0;
}
else if (sw)
{
/* Get status failed. Ignore that. */
if (a->periodical_check_needed)
periodical_check_needed = 1;
unlock_app (a);
continue;
}
}
if (a->card_status != status)
{
report_change (a->slot, a->card_status, status);
send_client_notifications (a, status == 0);
if (status == 0)
{
log_debug ("Removal of a card: %d\n", a->slot);
apdu_close_reader (a->slot);
deallocate_app (a);
}
else
{
a->card_status = status;
if (a->periodical_check_needed)
periodical_check_needed = 1;
unlock_app (a);
}
}
else
{
if (a->periodical_check_needed)
periodical_check_needed = 1;
unlock_app (a);
}
}
npth_mutex_unlock (&app_list_lock);
return periodical_check_needed;
}
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
gpg_error_t
initialize_module_command (void)
{
gpg_error_t err;
if (npth_mutex_init (&app_list_lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
return err;
}
return apdu_init ();
}
void
app_send_card_list (ctrl_t ctrl)
{
app_t a;
char buf[65];
npth_mutex_lock (&app_list_lock);
for (a = app_top; a; a = a->next)
{
if (DIM (buf) < 2 * a->serialnolen + 1)
continue;
bin2hex (a->serialno, a->serialnolen, buf);
send_status_direct (ctrl, "SERIALNO", buf);
}
npth_mutex_unlock (&app_list_lock);
}
diff --git a/scd/command.c b/scd/command.c
index b1d5539cb..56fdf7489 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -1,1983 +1,1983 @@
/* command.c - SCdaemon command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2007, 2008, 2009, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "scdaemon.h"
#include <assuan.h>
#include <ksba.h>
#include "app-common.h"
#include "iso7816.h"
#include "apdu.h" /* Required for apdu_*_reader (). */
#include "atr.h"
#ifdef HAVE_LIBUSB
#include "ccid-driver.h"
#endif
-#include "asshelp.h"
-#include "server-help.h"
+#include "../common/asshelp.h"
+#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local)
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
unsigned long event_signal; /* Or 0 if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
/* If a session has been locked we store a link to its server object
in this variable. */
static struct server_local_s *locked_session;
/* Convert the STRING into a newly allocated buffer while translating
the hex numbers. Stops at the first invalid character. Blanks and
colons are allowed to separate the hex digits. Returns NULL on
error or a newly malloced buffer and its length in LENGTH. */
static unsigned char *
hex_to_buffer (const char *string, size_t *r_length)
{
unsigned char *buffer;
const char *s;
size_t n;
buffer = xtrymalloc (strlen (string)+1);
if (!buffer)
return NULL;
for (s=string, n=0; *s; s++)
{
if (spacep (s) || *s == ':')
continue;
if (hexdigitp (s) && hexdigitp (s+1))
{
buffer[n++] = xtoi_2 (s);
s++;
}
else
break;
}
*r_length = n;
return buffer;
}
/* Reset the card and free the application context. With SEND_RESET
set to true actually send a RESET to the reader; this is the normal
way of calling the function. */
static void
do_reset (ctrl_t ctrl, int send_reset)
{
app_t app = ctrl->app_ctx;
if (app)
app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset);
/* If we hold a lock, unlock now. */
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESET\n");
}
}
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
do_reset (ctrl, 1);
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = strtoul (value, NULL, 16);
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
/* If the card has not yet been opened, do it. */
static gpg_error_t
open_card (ctrl_t ctrl)
{
/* If we ever got a card not present error code, return that. Only
the SERIALNO command and a reset are able to clear from that
state. */
if (ctrl->server_local->card_removed)
return gpg_error (GPG_ERR_CARD_REMOVED);
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if (ctrl->app_ctx)
return 0;
return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0);
}
/* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */
static gpg_error_t
open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno)
{
gpg_error_t err;
unsigned char *serialno_bin = NULL;
size_t serialno_bin_len = 0;
app_t app = ctrl->app_ctx;
/* If we are already initialized for one specific application we
need to check that the client didn't requested a specific
application different from the one in use before we continue. */
if (apptype && ctrl->app_ctx)
return check_application_conflict (apptype, ctrl->app_ctx);
/* Re-scan USB devices. Release APP, before the scan. */
ctrl->app_ctx = NULL;
release_application (app, 0);
if (serialno)
serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
err = select_application (ctrl, apptype, &ctrl->app_ctx, 1,
serialno_bin, serialno_bin_len);
xfree (serialno_bin);
return err;
}
static const char hlp_serialno[] =
"SERIALNO [--demand=<serialno>] [<apptype>]\n"
"\n"
"Return the serial number of the card using a status response. This\n"
"function should be used to check for the presence of a card.\n"
"\n"
"If --demand is given, an application on the card with SERIALNO is\n"
"selected and an error is returned if no such card available.\n"
"\n"
"If APPTYPE is given, an application of that type is selected and an\n"
"error is returned if the application is not supported or available.\n"
"The default is to auto-select the application using a hardwired\n"
"preference system. Note, that a future extension to this function\n"
"may enable specifying a list and order of applications to try.\n"
"\n"
"This function is special in that it can be used to reset the card.\n"
"Most other functions will return an error when a card change has\n"
"been detected and the use of this function is therefore required.\n"
"\n"
"Background: We want to keep the client clear of handling card\n"
"changes between operations; i.e. the client can assume that all\n"
"operations are done on the same card unless he calls this function.";
static gpg_error_t
cmd_serialno (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
struct server_local_s *sl;
int rc = 0;
char *serial;
const char *demand;
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if ((demand = has_option_name (line, "--demand")))
{
if (*demand != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
line = (char *)++demand;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
}
else
demand = NULL;
/* Clear the remove flag so that the open_card is able to reread it. */
if (ctrl->server_local->card_removed)
ctrl->server_local->card_removed = 0;
if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand)))
{
ctrl->server_local->card_removed = 1;
return rc;
}
/* Success, clear the card_removed flag for all sessions. */
for (sl=session_list; sl; sl = sl->next_session)
{
ctrl_t c = sl->ctrl_backlink;
if (c != ctrl)
c->server_local->card_removed = 0;
}
serial = app_get_serialno (ctrl->app_ctx);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
xfree (serial);
return rc;
}
static const char hlp_learn[] =
"LEARN [--force] [--keypairinfo]\n"
"\n"
"Learn all useful information of the currently inserted card. When\n"
"used without the force options, the command might do an INQUIRE\n"
"like this:\n"
"\n"
" INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n"
"\n"
"The client should just send an \"END\" if the processing should go on\n"
"or a \"CANCEL\" to force the function to terminate with a Cancel\n"
"error message.\n"
"\n"
"With the option --keypairinfo only KEYPARIINFO lstatus lines are\n"
"returned.\n"
"\n"
"The response of this command is a list of status lines formatted as\n"
"this:\n"
"\n"
" S APPTYPE <apptype>\n"
"\n"
"This returns the type of the application, currently the strings:\n"
"\n"
" P15 = PKCS-15 structure used\n"
" DINSIG = DIN SIG\n"
" OPENPGP = OpenPGP card\n"
" NKS = NetKey card\n"
"\n"
"are implemented. These strings are aliases for the AID\n"
"\n"
" S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>\n"
"\n"
"If there is no certificate yet stored on the card a single 'X' is\n"
"returned as the keygrip. In addition to the keypair info, information\n"
"about all certificates stored on the card is also returned:\n"
"\n"
" S CERTINFO <certtype> <hexstring_with_id>\n"
"\n"
"Where CERTTYPE is a number indicating the type of certificate:\n"
" 0 := Unknown\n"
" 100 := Regular X.509 cert\n"
" 101 := Trusted X.509 cert\n"
" 102 := Useful X.509 cert\n"
" 110 := Root CA cert in a special format (e.g. DINSIG)\n"
" 111 := Root CA cert as standard X509 cert.\n"
"\n"
"For certain cards, more information will be returned:\n"
"\n"
" S KEY-FPR <no> <hexstring>\n"
"\n"
"For OpenPGP cards this returns the stored fingerprints of the\n"
"keys. This can be used check whether a key is available on the\n"
"card. NO may be 1, 2 or 3.\n"
"\n"
" S CA-FPR <no> <hexstring>\n"
"\n"
"Similar to above, these are the fingerprints of keys assumed to be\n"
"ultimately trusted.\n"
"\n"
" S DISP-NAME <name_of_card_holder>\n"
"\n"
"The name of the card holder as stored on the card; percent\n"
"escaping takes place, spaces are encoded as '+'\n"
"\n"
" S PUBKEY-URL <url>\n"
"\n"
"The URL to be used for locating the entire public key.\n"
" \n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
int only_keypairinfo = has_option (line, "--keypairinfo");
if ((rc = open_card (ctrl)))
return rc;
/* Unless the force option is used we try a shortcut by identifying
the card using a serial number and inquiring the client with
that. The client may choose to cancel the operation if he already
knows about this card */
if (!only_keypairinfo)
{
const char *reader;
char *serial;
app_t app = ctrl->app_ctx;
if (!app)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
reader = apdu_get_reader_name (app->slot);
if (!reader)
return out_of_core ();
send_status_direct (ctrl, "READER", reader);
/* No need to free the string of READER. */
serial = app_get_serialno (ctrl->app_ctx);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
if (!has_option (line, "--force"))
{
char *command;
rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
rc = assuan_inquire (ctx, command, NULL, NULL, 0);
xfree (command);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED)
log_error ("inquire KNOWNCARDP failed: %s\n",
gpg_strerror (rc));
xfree (serial);
return rc;
}
/* Not canceled, so we have to proceeed. */
}
xfree (serial);
}
/* Let the application print out its collection of useful status
information. */
if (!rc)
rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo);
return rc;
}
static const char hlp_readcert[] =
"READCERT <hexified_certid>|<keyid>\n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_readcert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *cert;
size_t ncert;
if ((rc = open_card (ctrl)))
return rc;
line = xstrdup (line); /* Need a copy of the line. */
rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
xfree (line);
line = NULL;
if (!rc)
{
rc = assuan_send_data (ctx, cert, ncert);
xfree (cert);
if (rc)
return rc;
}
return rc;
}
static const char hlp_readkey[] =
"READKEY [--advanced] <keyid>\n"
"\n"
"Return the public key for the given cert or key ID as a standard\n"
"S-expression.\n"
"In --advanced mode it returns the S-expression in advanced format.\n"
"\n"
"Note that this function may even be used on a locked card.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int advanced = 0;
unsigned char *cert = NULL;
size_t ncert, n;
ksba_cert_t kc = NULL;
ksba_sexp_t p;
unsigned char *pk;
size_t pklen;
if ((rc = open_card (ctrl)))
return rc;
if (has_option (line, "--advanced"))
advanced = 1;
line = skip_options (line);
line = xstrdup (line); /* Need a copy of the line. */
/* If the application supports the READKEY function we use that.
Otherwise we use the old way by extracting it from the
certificate. */
rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen);
if (!rc)
{ /* Yeah, got that key - send it back. */
rc = assuan_send_data (ctx, pk, pklen);
xfree (pk);
xfree (line);
line = NULL;
goto leave;
}
if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION)
log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
else
{
rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
}
xfree (line);
line = NULL;
if (rc)
goto leave;
rc = ksba_cert_new (&kc);
if (rc)
goto leave;
rc = ksba_cert_init_from_mem (kc, cert, ncert);
if (rc)
{
log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc));
goto leave;
}
p = ksba_cert_get_public_key (kc);
if (!p)
{
rc = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
rc = assuan_send_data (ctx, p, n);
xfree (p);
leave:
ksba_cert_release (kc);
xfree (cert);
return rc;
}
static const char hlp_setdata[] =
"SETDATA [--append] <hexstring>\n"
"\n"
"The client should use this command to tell us the data he want to sign.\n"
"With the option --append, the data is appended to the data set by a\n"
"previous SETDATA command.";
static gpg_error_t
cmd_setdata (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int append;
int n, i, off;
char *p;
unsigned char *buf;
append = (ctrl->in_data.value && has_option (line, "--append"));
line = skip_options (line);
if (locked_session && locked_session != ctrl->server_local)
return gpg_error (GPG_ERR_LOCKED);
/* Parse the hexstring. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if (!n)
return set_error (GPG_ERR_ASS_PARAMETER, "no data given");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
n /= 2;
if (append)
{
if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA)
return set_error (GPG_ERR_TOO_LARGE,
"limit on total size of data reached");
buf = xtrymalloc (ctrl->in_data.valuelen + n);
}
else
buf = xtrymalloc (n);
if (!buf)
return out_of_core ();
if (append)
{
memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen);
off = ctrl->in_data.valuelen;
}
else
off = 0;
for (p=line, i=0; i < n; p += 2, i++)
buf[off+i] = xtoi_2 (p);
xfree (ctrl->in_data.value);
ctrl->in_data.value = buf;
ctrl->in_data.valuelen = off+n;
return 0;
}
static gpg_error_t
pin_cb (void *opaque, const char *info, char **retstr)
{
assuan_context_t ctx = opaque;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
if (!retstr)
{
/* We prompt for pinpad entry. To make sure that the popup has
been show we use an inquire and not just a status message.
We ignore any value returned. */
if (info)
{
log_debug ("prompting for pinpad entry '%s'\n", info);
rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
}
else
{
log_debug ("dismiss pinpad entry prompt\n");
rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT",
&value, &valuelen, MAXLEN_PIN);
}
if (!rc)
xfree (value);
return rc;
}
*retstr = NULL;
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
if (rc)
return rc;
if (!valuelen || value[valuelen-1])
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] <hexified_id>\n"
"\n"
"The --hash option is optional; the default is SHA1.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
int hash_algo;
if (has_option (line, "--hash=rmd160"))
hash_algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=sha1"))
hash_algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
hash_algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
hash_algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
hash_algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
hash_algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=md5"))
hash_algo = GCRY_MD_MD5;
else if (!strstr (line, "--"))
hash_algo = GCRY_MD_SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
rc = app_sign (ctrl->app_ctx, ctrl,
keyidstr, hash_algo,
pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
xfree (keyidstr);
if (rc)
{
log_error ("app_sign failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkauth[] =
"PKAUTH <hexified_id>";
static gpg_error_t
cmd_pkauth (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
xfree (keyidstr);
if (rc)
{
log_error ("app_auth failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT <hexified_id>";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
unsigned int infoflags;
if ((rc = open_card (ctrl)))
return rc;
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen, &infoflags);
xfree (keyidstr);
if (rc)
{
log_error ("app_decipher failed: %s\n", gpg_strerror (rc));
}
else
{
/* If the card driver told us that there is no padding, send a
status line. If there is a padding it is assumed that the
caller knows what padding is used. It would have been better
to always send that information but for backward
compatibility we can't do that. */
if ((infoflags & APP_DECIPHER_INFO_NOPAD))
send_status_direct (ctrl, "PADDING", "0");
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_getattr[] =
"GETATTR <name>\n"
"\n"
"This command is used to retrieve data from a smartcard. The\n"
"allowed names depend on the currently selected smartcard\n"
"application. NAME must be percent and '+' escaped. The value is\n"
"returned through status message, see the LEARN command for details.\n"
"\n"
"However, the current implementation assumes that Name is not escaped;\n"
"this works as long as no one uses arbitrary escaping. \n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_getattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
const char *keyword;
if ((rc = open_card (ctrl)))
return rc;
keyword = line;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
/* (We ignore any garbage for now.) */
/* FIXME: Applications should not return sensitive data if the card
is locked. */
rc = app_getattr (ctrl->app_ctx, ctrl, keyword);
return rc;
}
static const char hlp_setattr[] =
"SETATTR <name> <value> \n"
"\n"
"This command is used to store data on a smartcard. The allowed\n"
"names and values are depend on the currently selected smartcard\n"
"application. NAME and VALUE must be percent and '+' escaped.\n"
"\n"
"However, the current implementation assumes that NAME is not\n"
"escaped; this works as long as no one uses arbitrary escaping.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"setattr function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_setattr (assuan_context_t ctx, char *orig_line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyword;
int keywordlen;
size_t nbytes;
char *line, *linebuf;
if ((rc = open_card (ctrl)))
return rc;
/* We need to use a copy of LINE, because PIN_CB uses the same
context and thus reuses the Assuan provided LINE. */
line = linebuf = xtrystrdup (orig_line);
if (!line)
return out_of_core ();
keyword = line;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
if (*line)
*line++ = 0;
while (spacep (line))
line++;
nbytes = percent_plus_unescape_inplace (line, 0);
rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx,
(const unsigned char*)line, nbytes);
xfree (linebuf);
return rc;
}
static const char hlp_writecert[] =
"WRITECERT <hexified_certid>\n"
"\n"
"This command is used to store a certifciate on a smartcard. The\n"
"allowed certids depend on the currently selected smartcard\n"
"application. The actual certifciate is requested using the inquiry\n"
"\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n"
"\n"
"In almost all cases a PIN will be requested. See the related\n"
"writecert function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *certid;
unsigned char *certdata;
size_t certdatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no certid given");
certid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
certid = xtrystrdup (certid);
if (!certid)
return out_of_core ();
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "CERTDATA",
&certdata, &certdatalen, MAXLEN_CERTDATA);
if (rc)
{
xfree (certid);
return rc;
}
/* Write the certificate to the card. */
rc = app_writecert (ctrl->app_ctx, ctrl, certid,
pin_cb, ctx, certdata, certdatalen);
xfree (certid);
xfree (certdata);
return rc;
}
static const char hlp_writekey[] =
"WRITEKEY [--force] <keyid> \n"
"\n"
"This command is used to store a secret key on a smartcard. The\n"
"allowed keyids depend on the currently selected smartcard\n"
"application. The actual keydata is requested using the inquiry\n"
"\"KEYDATA\" and need to be provided without any protection. With\n"
"--force set an existing key under this KEYID will get overwritten.\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"writekey function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyid;
int force = has_option (line, "--force");
unsigned char *keydata;
size_t keydatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given");
keyid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyid = xtrystrdup (keyid);
if (!keyid)
return out_of_core ();
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
{
xfree (keyid);
return rc;
}
/* Write the key to the card. */
rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0,
pin_cb, ctx, keydata, keydatalen);
xfree (keyid);
xfree (keydata);
return rc;
}
static const char hlp_genkey[] =
"GENKEY [--force] [--timestamp=<isodate>] <no>\n"
"\n"
"Generate a key on-card identified by NO, which is application\n"
"specific. Return values are application specific. For OpenPGP\n"
"cards 3 status lines are returned:\n"
"\n"
" S KEY-FPR <hexstring>\n"
" S KEY-CREATED-AT <seconds_since_epoch>\n"
" S KEY-DATA [-|p|n] <hexdata>\n"
"\n"
" 'p' and 'n' are the names of the RSA parameters; '-' is used to\n"
" indicate that HEXDATA is the first chunk of a parameter given\n"
" by the next KEY-DATA.\n"
"\n"
"--force is required to overwrite an already existing key. The\n"
"KEY-CREATED-AT is required for further processing because it is\n"
"part of the hashed key material for the fingerprint.\n"
"\n"
"If --timestamp is given an OpenPGP key will be created using this\n"
"value. The value needs to be in ISO Format; e.g.\n"
"\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n"
"\n"
"The public part of the key can also later be retrieved using the\n"
"READKEY command.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyno;
int force;
const char *s;
time_t timestamp;
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
timestamp = isotime2epoch (s+1);
if (timestamp < 1)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
}
else
timestamp = 0;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no key number given");
keyno = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyno = xtrystrdup (keyno);
if (!keyno)
return out_of_core ();
rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0,
timestamp, pin_cb, ctx);
xfree (keyno);
return rc;
}
static const char hlp_random[] =
"RANDOM <nbytes>\n"
"\n"
"Get NBYTES of random from the card and send them back as data.\n"
"This usually involves EEPROM write on the card and thus excessive\n"
"use of this command may destroy the card.\n"
"\n"
"Note, that this function may be even be used on a locked card.";
static gpg_error_t
cmd_random (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
size_t nbytes;
unsigned char *buffer;
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER,
"number of requested bytes missing");
nbytes = strtoul (line, NULL, 0);
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
buffer = xtrymalloc (nbytes);
if (!buffer)
return out_of_core ();
rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer);
if (!rc)
{
rc = assuan_send_data (ctx, buffer, nbytes);
xfree (buffer);
return rc; /* that is already an assuan error code */
}
xfree (buffer);
return rc;
}
static const char hlp_passwd[] =
"PASSWD [--reset] [--nullpin] <chvno>\n"
"\n"
"Change the PIN or, if --reset is given, reset the retry counter of\n"
"the card holder verification vector CHVNO. The option --nullpin is\n"
"used for TCOS cards to set the initial PIN. The format of CHVNO\n"
"depends on the card application.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *chvnostr;
unsigned int flags = 0;
if (has_option (line, "--reset"))
flags |= APP_CHANGE_FLAG_RESET;
if (has_option (line, "--nullpin"))
flags |= APP_CHANGE_FLAG_NULLPIN;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given");
chvnostr = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
chvnostr = xtrystrdup (chvnostr);
if (!chvnostr)
return out_of_core ();
rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx);
if (rc)
log_error ("command passwd failed: %s\n", gpg_strerror (rc));
xfree (chvnostr);
return rc;
}
static const char hlp_checkpin[] =
"CHECKPIN <idstr>\n"
"\n"
"Perform a VERIFY operation without doing anything else. This may\n"
"be used to initialize a the PIN cache earlier to long lasting\n"
"operations. Its use is highly application dependent.\n"
"\n"
"For OpenPGP:\n"
"\n"
" Perform a simple verify operation for CHV1 and CHV2, so that\n"
" further operations won't ask for CHV2 and it is possible to do a\n"
" cheap check on the PIN: If there is something wrong with the PIN\n"
" entry system, only the regular CHV will get blocked and not the\n"
" dangerous CHV3. IDSTR is the usual card's serial number in hex\n"
" notation; an optional fingerprint part will get ignored. There\n"
" is however a special mode if the IDSTR is sffixed with the\n"
" literal string \"[CHV3]\": In this case the Admin PIN is checked\n"
" if and only if the retry counter is still at 3.\n"
"\n"
"For Netkey:\n"
"\n"
" Any of the valid PIN Ids may be used. These are the strings:\n"
"\n"
" PW1.CH - Global password 1\n"
" PW2.CH - Global password 2\n"
" PW1.CH.SIG - SigG password 1\n"
" PW2.CH.SIG - SigG password 2\n"
"\n"
" For a definitive list, see the implementation in app-nks.c.\n"
" Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n"
" 3.0 they are technically alternative PINs used to mutally\n"
" unblock each other.";
static gpg_error_t
cmd_checkpin (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *idstr;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid. */
idstr = xtrystrdup (line);
if (!idstr)
return out_of_core ();
rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx);
xfree (idstr);
if (rc)
log_error ("app_check_pin failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_lock[] =
"LOCK [--wait]\n"
"\n"
"Grant exclusive card access to this session. Note that there is\n"
"no lock counter used and a second lock from the same session will\n"
"be ignored. A single unlock (or RESET) unlocks the session.\n"
"Return GPG_ERR_LOCKED if another session has locked the reader.\n"
"\n"
"If the option --wait is given the command will wait until a\n"
"lock has been released.";
static gpg_error_t
cmd_lock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
retry:
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
}
else
locked_session = ctrl->server_local;
#ifdef USE_NPTH
if (rc && has_option (line, "--wait"))
{
rc = 0;
npth_sleep (1); /* Better implement an event mechanism. However,
for card operations this should be
sufficient. */
/* FIXME: Need to check that the connection is still alive.
This can be done by issuing status messages. */
goto retry;
}
#endif /*USE_NPTH*/
if (rc)
log_error ("cmd_lock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_unlock[] =
"UNLOCK\n"
"\n"
"Release exclusive card access.";
static gpg_error_t
cmd_unlock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
(void)line;
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
else
locked_session = NULL;
}
else
rc = gpg_error (GPG_ERR_NOT_LOCKED);
if (rc)
log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" connections - Return number of active connections.\n"
" status - Return the status of the current reader (in the future,\n"
" may also return the status of all readers). The status\n"
" is a list of one-character flags. The following flags\n"
" are currently defined:\n"
" 'u' Usable card present.\n"
" 'r' Card removed. A reset is necessary.\n"
" These flags are exclusive.\n"
" reader_list - Return a list of detected card readers. Does\n"
" currently only work with the internal CCID driver.\n"
" deny_admin - Returns OK if admin commands are not allowed or\n"
" GPG_ERR_GENERAL if admin commands are allowed.\n"
" app_list - Return a list of supported applications. One\n"
" application per line, fields delimited by colons,\n"
" first field is the name.\n"
" card_list - Return a list of serial numbers of active cards,\n"
" using a status response.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = scd_get_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "status"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char flag;
if (open_card (ctrl))
flag = 'r';
else
flag = 'u';
rc = assuan_send_data (ctx, &flag, 1);
}
else if (!strcmp (line, "reader_list"))
{
#ifdef HAVE_LIBUSB
char *s = ccid_get_reader_list ();
#else
char *s = NULL;
#endif
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
xfree (s);
}
else if (!strcmp (line, "deny_admin"))
rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0;
else if (!strcmp (line, "app_list"))
{
char *s = get_supported_applications ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = 0;
xfree (s);
}
else if (!strcmp (line, "card_list"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
app_send_card_list (ctrl);
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_restart[] =
"RESTART\n"
"\n"
"Restart the current connection; this is a kind of warm reset. It\n"
"deletes the context used by this connection but does not send a\n"
"RESET to the card. Thus the card itself won't get reset. \n"
"\n"
"This is used by gpg-agent to reuse a primary pipe connection and\n"
"may be used by clients to backup from a conflict in the serial\n"
"command; i.e. to select another application.";
static gpg_error_t
cmd_restart (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
app_t app = ctrl->app_ctx;
(void)line;
if (app)
{
ctrl->app_ctx = NULL;
release_application (app, 0);
}
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESTART\n");
}
return 0;
}
static const char hlp_disconnect[] =
"DISCONNECT\n"
"\n"
"Disconnect the card if the backend supports a disconnect operation.";
static gpg_error_t
cmd_disconnect (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (!ctrl->app_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
apdu_disconnect (ctrl->app_ctx->slot);
return 0;
}
static const char hlp_apdu[] =
"APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n"
"\n"
"Send an APDU to the current reader. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU. If HEXSTRING is not given no\n"
"commands are set to the card but the command will implictly check\n"
"whether the card is ready for use. \n"
"\n"
"Using the option \"--atr\" returns the ATR of the card as a status\n"
"message before any data like this:\n"
" S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n"
"\n"
"Using the option --more handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used\n"
"(currently 4096).";
static gpg_error_t
cmd_apdu (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
app_t app;
int rc;
unsigned char *apdu;
size_t apdulen;
int with_atr;
int handle_more;
const char *s;
size_t exlen;
if (has_option (line, "--dump-atr"))
with_atr = 2;
else
with_atr = has_option (line, "--atr");
handle_more = has_option (line, "--more");
if ((s=has_option_name (line, "--exlen")))
{
if (*s == '=')
exlen = strtoul (s+1, NULL, 0);
else
exlen = 4096;
}
else
exlen = 0;
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
app = ctrl->app_ctx;
if (!app)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
if (with_atr)
{
unsigned char *atr;
size_t atrlen;
char hexbuf[400];
atr = apdu_get_atr (app->slot, &atrlen);
if (!atr || atrlen > sizeof hexbuf - 2 )
{
rc = gpg_error (GPG_ERR_INV_CARD);
goto leave;
}
if (with_atr == 2)
{
char *string, *p, *pend;
string = atr_dump (atr, atrlen);
if (string)
{
for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1)
{
rc = assuan_send_data (ctx, p, pend - p + 1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (!rc && *p)
rc = assuan_send_data (ctx, p, strlen (p));
es_free (string);
if (rc)
goto leave;
}
}
else
{
bin2hex (atr, atrlen, hexbuf);
send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0);
}
xfree (atr);
}
apdu = hex_to_buffer (line, &apdulen);
if (!apdu)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (apdulen)
{
unsigned char *result = NULL;
size_t resultlen;
rc = apdu_send_direct (app->slot, exlen,
apdu, apdulen, handle_more,
&result, &resultlen);
if (rc)
log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc));
else
{
rc = assuan_send_data (ctx, result, resultlen);
xfree (result);
}
}
xfree (apdu);
leave:
return rc;
}
static const char hlp_killscd[] =
"KILLSCD\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killscd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SERIALNO", cmd_serialno, hlp_serialno },
{ "LEARN", cmd_learn, hlp_learn },
{ "READCERT", cmd_readcert, hlp_readcert },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "SETDATA", cmd_setdata, hlp_setdata },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKAUTH", cmd_pkauth, hlp_pkauth },
{ "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETATTR", cmd_getattr, hlp_getattr },
{ "SETATTR", cmd_setattr, hlp_setattr },
{ "WRITECERT", cmd_writecert,hlp_writecert },
{ "WRITEKEY", cmd_writekey, hlp_writekey },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "RANDOM", cmd_random, hlp_random },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "CHECKPIN", cmd_checkpin, hlp_checkpin },
{ "LOCK", cmd_lock, hlp_lock },
{ "UNLOCK", cmd_unlock, hlp_unlock },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "RESTART", cmd_restart, hlp_restart },
{ "DISCONNECT", cmd_disconnect,hlp_disconnect },
{ "APDU", cmd_apdu, hlp_apdu },
{ "KILLSCD", cmd_killscd, hlp_killscd },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
scd_command_handler (ctrl_t ctrl, int fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
scd_exit (2);
}
if (fd == -1)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD(fd),
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
assuan_set_pointer (ctx, ctrl);
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Cleanup. We don't send an explicit reset to the card. */
do_reset (ctrl, 0);
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
scd_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}
/* Send a line with status information via assuan and escape all given
buffers. The variable elements are pairs of (char *, size_t),
terminated with a (NULL, 0). */
void
send_status_info (ctrl_t ctrl, const char *keyword, ...)
{
va_list arg_ptr;
const unsigned char *value;
size_t valuelen;
char buf[950], *p;
size_t n;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
p = buf;
n = 0;
while ( (value = va_arg (arg_ptr, const unsigned char *)) )
{
valuelen = va_arg (arg_ptr, size_t);
if (!valuelen)
continue; /* empty buffer */
if (n)
{
*p++ = ' ';
n++;
}
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
{
if (*value == '+' || *value == '\"' || *value == '%'
|| *value < ' ')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
}
*p = 0;
assuan_write_status (ctx, keyword, buf);
va_end (arg_ptr);
}
/* Send a ready formatted status line via assuan. */
void
send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (strchr (args, '\n'))
log_error ("error: LF detected in status line - not sending\n");
else
assuan_write_status (ctx, keyword, args);
}
/* Helper to send the clients a status change notification. */
void
send_client_notifications (app_t app, int removal)
{
struct {
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
} killed[50];
int killidx = 0;
int kidx;
struct server_local_s *sl;
for (sl=session_list; sl; sl = sl->next_session)
if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app)
{
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
if (removal)
{
sl->ctrl_backlink->app_ctx = NULL;
sl->card_removed = 1;
release_application (app, 1);
}
if (!sl->event_signal || !sl->assuan_ctx)
continue;
pid = assuan_get_pid (sl->assuan_ctx);
#ifdef HAVE_W32_SYSTEM
handle = (void *)sl->event_signal;
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].handle == handle)
break;
if (kidx < killidx)
log_info ("event %lx (%p) already triggered for client %d\n",
sl->event_signal, handle, (int)pid);
else
{
log_info ("triggering event %lx (%p) for client %d\n",
sl->event_signal, handle, (int)pid);
if (!SetEvent (handle))
log_error ("SetEvent(%lx) failed: %s\n",
sl->event_signal, w32_strerror (-1));
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].handle = handle;
killidx++;
}
}
#else /*!HAVE_W32_SYSTEM*/
signo = sl->event_signal;
if (pid != (pid_t)(-1) && pid && signo > 0)
{
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].signo == signo)
break;
if (kidx < killidx)
log_info ("signal %d already sent to client %d\n",
signo, (int)pid);
else
{
log_info ("sending signal %d to client %d\n",
signo, (int)pid);
kill (pid, signo);
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].signo = signo;
killidx++;
}
}
}
#endif /*!HAVE_W32_SYSTEM*/
}
}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 13cf2e6e0..26e89dd8d 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -1,1395 +1,1395 @@
/* scdaemon.c - The GnuPG Smartcard Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define GNUPG_COMMON_NEED_AFLOCAL
#include "scdaemon.h"
#include <ksba.h>
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "app-common.h"
#include "iso7816.h"
#include "apdu.h"
#include "ccid-driver.h"
-#include "gc-opt-flags.h"
-#include "asshelp.h"
-#include "exechelp.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/asshelp.h"
+#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugCCIDDriver,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oBatch,
oReaderPort,
oCardTimeout,
octapiDriver,
opcscDriver,
oDisableCCID,
oDisableOpenSC,
oDisablePinpad,
oAllowAdmin,
oDenyAdmin,
oDisableApplication,
oEnablePinpadVarlen,
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oReaderPort, "reader-port",
N_("|N|connect to reader at port N")),
ARGPARSE_s_s (octapiDriver, "ctapi-driver",
N_("|NAME|use NAME as ct-API driver")),
ARGPARSE_s_s (opcscDriver, "pcsc-driver",
N_("|NAME|use NAME as PC/SC driver")),
ARGPARSE_s_n (oDisableCCID, "disable-ccid",
#ifdef HAVE_LIBUSB
N_("do not use the internal CCID driver")
#else
"@"
#endif
/* end --disable-ccid */),
ARGPARSE_s_u (oCardTimeout, "card-timeout",
N_("|N|disconnect the card after N seconds of inactivity")),
ARGPARSE_s_n (oDisablePinpad, "disable-pinpad",
N_("do not use a reader's pinpad")),
ARGPARSE_ignore (300, "disable-keypad"),
ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"),
ARGPARSE_s_n (oDenyAdmin, "deny-admin",
N_("deny the use of admin card commands")),
ARGPARSE_s_s (oDisableApplication, "disable-application", "@"),
ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen",
N_("use variable length input for pinpad")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CARD_IO_VALUE, "cardio" },
{ DBG_READER_VALUE , "reader" },
{ 0, NULL }
};
/* The card driver we use by default for PC/SC. */
#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__)
#define DEFAULT_PCSC_DRIVER "winscard.dll"
#elif defined(__APPLE__)
#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC"
#elif defined(__GLIBC__)
#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1"
#else
#define DEFAULT_PCSC_DRIVER "libpcsclite.so"
#endif
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (int listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 11: p = "@SCDAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 1:
case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
scd_exit(2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned int configlineno;
int parse_debug = 0;
const char *debug_level = NULL;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
const char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.allow_admin = 1;
opt.pcsc_driver = DEFAULT_PCSC_DRIVER;
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
}
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (), SCDAEMON_NAME EXTSEP_S "conf",
NULL );
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugCCIDDriver:
#ifdef HAVE_LIBUSB
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
#endif /*HAVE_LIBUSB*/
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oDisableOpenSC: break;
case oDisablePinpad: opt.disable_pinpad = 1; break;
case oAllowAdmin: /* Dummy because allow is now the default. */
break;
case oDenyAdmin: opt.allow_admin = 0; break;
case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break;
case oDisableApplication:
add_to_strlist (&opt.disabled_applications, pargs.r.ret_str);
break;
case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the config name for use by --gpgconf-list. */
config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (initialize_module_command ())
{
log_error ("initialization failed\n");
cleanup ();
exit (1);
}
if (gpgconf_list == 2)
scd_exit (0);
if (gpgconf_list)
{
/* List options and default values in the GPG Conf format. */
char *filename = NULL;
char *filename_esc;
if (config_filename)
filename = xstrdup (config_filename);
else
filename = make_filename (gnupg_homedir (),
SCDAEMON_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, SCDAEMON_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("pcsc-driver:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER );
#ifdef HAVE_LIBUSB
es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE );
#endif
es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0);
es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE );
scd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
int fd = -1;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT(create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
scd_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
scd_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
scd_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != -1)
close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
int fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT (create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export SCDAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
if (chdir("/"))
{
log_error ("chdir to / failed: %s\n", strerror (errno));
exit (1);
}
#endif /*!HAVE_W32_SYSTEM*/
handle_connections (fd);
close (fd);
}
return 0;
}
void
scd_exit (int rc)
{
apdu_prepare_exit ();
#if 0
#warning no update_random_seed_file
update_random_seed_file();
#endif
#if 0
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
static void
scd_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
scd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
scd_get_socket_name ()
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration and resetting cards\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
app_dump_state ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
scd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
scd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Retunrs: Pointer to an allcoated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
scd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
scd_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
scd_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), 5 ) == -1)
{
log_error (_("listen() failed: %s\n"),
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
scd_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD2INT(ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (scd_command_handler (ctrl, FD2INT(ctrl->thread_startup.fd))
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD2INT (ctrl->thread_startup.fd));
scd_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
scd_kick_the_loop ();
return NULL;
}
void
scd_kick_the_loop (void)
{
int ret;
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
w32_strerror (-1));
#else
ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (int listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
int fd;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
ret = npth_attr_init(&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#else
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != -1)
{
FD_SET (listen_fd, &fdset);
nfd = listen_fd;
}
for (;;)
{
int periodical_check;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = -1;
}
periodical_check = scd_update_reader_status_file ();
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset))
{
ctrl_t ctrl;
plen = sizeof paddr;
fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen);
if (fd == -1)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", fd);
ctrl->thread_startup.fd = INT2FD (fd);
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/sm/Makefile.am b/sm/Makefile.am
index 4cfb24686..ddc2e225f 100644
--- a/sm/Makefile.am
+++ b/sm/Makefile.am
@@ -1,70 +1,70 @@
# Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = ChangeLog-2011 gpgsm-w32info.rc
bin_PROGRAMS = gpgsm
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS)
-AM_CPPFLAGS = -I$(top_srcdir)/common -DKEYBOX_WITH_X509=1
+AM_CPPFLAGS = -DKEYBOX_WITH_X509=1
include $(top_srcdir)/am/cmacros.am
if HAVE_W32_SYSTEM
resource_objs += gpgsm-w32info.o
endif
gpgsm_SOURCES = \
gpgsm.c gpgsm.h \
misc.c \
keydb.c keydb.h \
server.c \
call-agent.c \
call-dirmngr.c \
fingerprint.c \
certlist.c \
certdump.c \
certcheck.c \
certchain.c \
keylist.c \
verify.c \
sign.c \
encrypt.c \
decrypt.c \
import.c \
export.c \
delete.c \
certreqgen.c \
certreqgen-ui.c \
minip12.c minip12.h \
qualified.c \
passphrase.c passphrase.h
common_libs = ../kbx/libkeybox509.a $(libcommon)
gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \
$(GPG_ERROR_LIBS) $(LIBREADLINE) $(LIBINTL) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgsm_LDFLAGS = $(extra_bin_ldflags)
# Make sure that all libs are build before we use them. This is
# important for things like make -j2.
$(PROGRAMS): $(common_libs)
diff --git a/sm/call-agent.c b/sm/call-agent.c
index 16a2497c6..0e47c1448 100644
--- a/sm/call-agent.c
+++ b/sm/call-agent.c
@@ -1,1351 +1,1351 @@
/* call-agent.c - Divert GPGSM operations to the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h>
-#include "i18n.h"
-#include "asshelp.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
#include "keydb.h" /* fixme: Move this to import.c */
-#include "membuf.h"
-#include "shareddefs.h"
+#include "../common/membuf.h"
+#include "../common/shareddefs.h"
#include "passphrase.h"
static assuan_context_t agent_ctx = NULL;
struct cipher_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *ciphertext;
size_t ciphertextlen;
};
struct genkey_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *sexp;
size_t sexplen;
};
struct learn_parm_s
{
int error;
ctrl_t ctrl;
assuan_context_t ctx;
membuf_t *data;
};
struct import_key_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const void *key;
size_t keylen;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
};
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0",
warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl)
{
int rc;
if (agent_ctx)
rc = 0; /* fixme: We need a context for each thread or
serialize the access to the agent (which is
suitable given that the agent is not MT. */
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
gpgsm_status2, ctrl);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc && !(rc = warn_version_mismatch (ctrl, agent_ctx,
GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications. No
error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
}
}
}
if (!ctrl->agent_seen)
{
ctrl->agent_seen = 1;
audit_log_ok (ctrl->audit, AUDIT_AGENT_READY, rc);
}
return rc;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
ctrl_t ctrl = parm->ctrl;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpgsm_proxy_pinentry_notify (ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
log_error ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. */
int
gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s inq_parm;
*r_buf = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
sprintf (line, "SETHASH %d ", digestalgo);
p = line + strlen (line);
for (i=0; i < digestlen ; i++, p += 2 )
sprintf (p, "%02X", digest[i]);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, "PKSIGN",
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL))
{
xfree (*r_buf); *r_buf = NULL;
return gpg_error (GPG_ERR_INV_VALUE);
}
return *r_buf? 0 : out_of_core ();
}
/* Call the scdaemon to do a sign operation using the key identified by
the hex string KEYID. */
int
gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
const char *hashopt;
unsigned char *sigbuf;
size_t sigbuflen;
struct default_inq_parm_s inq_parm;
(void)desc;
*r_buf = NULL;
switch(digestalgo)
{
case GCRY_MD_SHA1: hashopt = "--hash=sha1"; break;
case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break;
case GCRY_MD_MD5: hashopt = "--hash=md5"; break;
case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break;
default:
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
p = stpcpy (line, "SCD SETDATA " );
for (i=0; i < digestlen ; i++, p += 2 )
sprintf (p, "%02X", digest[i]);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "SCD PKSIGN %s %s", hashopt, keyid);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
sigbuf = get_membuf (&data, &sigbuflen);
/* Create an S-expression from it which is formatted like this:
"(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" Fixme: If a card ever
creates non-RSA keys we need to change things. */
*r_buflen = 21 + 11 + sigbuflen + 4;
p = xtrymalloc (*r_buflen);
*r_buf = (unsigned char*)p;
if (!p)
{
xfree (sigbuf);
return 0;
}
p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
sprintf (p, "%u:", (unsigned int)sigbuflen);
p += strlen (p);
memcpy (p, sigbuf, sigbuflen);
p += sigbuflen;
strcpy (p, ")))");
xfree (sigbuf);
assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL));
return 0;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to do a decrypt operation using the key identified by
the hex string KEYGRIP. */
int
gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
ksba_const_sexp_t ciphertext,
char **r_buf, size_t *r_buflen )
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct cipher_parm_s cipher_parm;
size_t n, len;
char *p, *buf, *endp;
size_t ciphertextlen;
if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL);
if (!ciphertextlen)
return gpg_error (GPG_ERR_INV_VALUE);
rc = start_agent (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
assert ( DIM(line) >= 50 );
snprintf (line, DIM(line), "SETKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
init_membuf (&data, 1024);
cipher_parm.ctrl = ctrl;
cipher_parm.ctx = agent_ctx;
cipher_parm.ciphertext = ciphertext;
cipher_parm.ciphertextlen = ciphertextlen;
rc = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &cipher_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
assert (len); /* (we forced Nul termination.) */
if (*buf == '(')
{
if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
return gpg_error (GPG_ERR_INV_SEXP);
len -= 11; /* Count only the data of the second part. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
}
else
{
/* For compatibility with older gpg-agents handle the old style
incomplete S-exps. */
len--; /* Do not count the Nul. */
p = buf;
}
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
return gpg_error (GPG_ERR_INV_SEXP);
endp++;
if (endp-p+n > len)
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "KEYPARAM"))
{
rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to generate a newkey */
int
gpgsm_agent_genkey (ctrl_t ctrl,
ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey)
{
int rc;
struct genkey_parm_s gk_parm;
membuf_t data;
size_t len;
unsigned char *buf;
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
gk_parm.ctrl = ctrl;
gk_parm.ctx = agent_ctx;
gk_parm.sexp = keyparms;
gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL);
if (!gk_parm.sexplen)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_transact (agent_ctx, "GENKEY",
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to read the public key part for a given keygrip. If
FROMCARD is true, the key is directly read from the current
smartcard. In this case HEXKEYGRIP should be the keyID
(e.g. OPENPGP.3). */
int
gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
ksba_sexp_t *r_pubkey)
{
int rc;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
snprintf (line, DIM(line), "%sREADKEY %s",
fromcard? "SCD ":"", hexkeygrip);
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_serialno_status_cb (void *opaque, const char *line)
{
char **r_serialno = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (*r_serialno);
*r_serialno = store_serialno (line);
}
return 0;
}
/* Call the agent to read the serial number of the current card. */
int
gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno)
{
int rc;
char *serialno = NULL;
struct default_inq_parm_s inq_parm;
*r_serialno = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "SCD SERIALNO",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_serialno_status_cb, &serialno);
if (!rc && !serialno)
rc = gpg_error (GPG_ERR_INTERNAL);
if (rc)
{
xfree (serialno);
return rc;
}
*r_serialno = serialno;
return 0;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
strlist_t *listaddr = opaque;
const char *keyword = line;
int keywordlen;
strlist_t sl;
char *p;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
sl = append_to_strlist (listaddr, line);
p = sl->d;
/* Make sure that we only have two tokes so that future
extensions of the format won't change the format expected by
the caller. */
while (*p && !spacep (p))
p++;
if (*p)
{
while (spacep (p))
p++;
while (*p && !spacep (p))
p++;
*p = 0;
}
}
return 0;
}
/* Call the agent to read the keypairinfo lines of the current card.
The list is returned as a string made up of the keygrip, a space
and the keyid. */
int
gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list)
{
int rc;
strlist_t list = NULL;
struct default_inq_parm_s inq_parm;
*r_list = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "SCD LEARN --force",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &list);
if (!rc && !list)
rc = gpg_error (GPG_ERR_NO_DATA);
if (rc)
{
free_strlist (list);
return rc;
}
*r_list = list;
return 0;
}
static gpg_error_t
istrusted_status_cb (void *opaque, const char *line)
{
struct rootca_flags_s *flags = opaque;
const char *s;
if ((s = has_leading_keyword (line, "TRUSTLISTFLAG")))
{
line = s;
if (has_leading_keyword (line, "relax"))
flags->relax = 1;
else if (has_leading_keyword (line, "cm"))
flags->chain_model = 1;
}
return 0;
}
/* Ask the agent whether the certificate is in the list of trusted
keys. The certificate is either specified by the CERT object or by
the fingerprint HEXFPR. ROOTCA_FLAGS is guaranteed to be cleared
on error. */
int
gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
struct rootca_flags_s *rootca_flags)
{
int rc;
char line[ASSUAN_LINELENGTH];
memset (rootca_flags, 0, sizeof *rootca_flags);
if (cert && hexfpr)
return gpg_error (GPG_ERR_INV_ARG);
rc = start_agent (ctrl);
if (rc)
return rc;
if (hexfpr)
{
snprintf (line, DIM(line), "ISTRUSTED %s", hexfpr);
}
else
{
char *fpr;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
{
log_error ("error getting the fingerprint\n");
return gpg_error (GPG_ERR_GENERAL);
}
snprintf (line, DIM(line), "ISTRUSTED %s", fpr);
xfree (fpr);
}
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
istrusted_status_cb, rootca_flags);
if (!rc)
rootca_flags->valid = 1;
return rc;
}
/* Ask the agent to mark CERT as a trusted Root-CA one */
int
gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert)
{
int rc;
char *fpr, *dn, *dnfmt;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
{
log_error ("error getting the fingerprint\n");
return gpg_error (GPG_ERR_GENERAL);
}
dn = ksba_cert_get_issuer (cert, 0);
if (!dn)
{
xfree (fpr);
return gpg_error (GPG_ERR_GENERAL);
}
dnfmt = gpgsm_format_name2 (dn, 0);
xfree (dn);
if (!dnfmt)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "MARKTRUSTED %s S %s", fpr, dnfmt);
ksba_free (dnfmt);
xfree (fpr);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent whether the a corresponding secret key is available
for the given keygrip */
int
gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip)
{
int rc;
char line[ASSUAN_LINELENGTH];
rc = start_agent (ctrl);
if (rc)
return rc;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "HAVEKEY %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct learn_parm_s *parm = opaque;
const char *s;
/* Pass progress data to the caller. */
if ((s = has_leading_keyword (line, "PROGRESS")))
{
line = s;
if (parm->ctrl)
{
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
return 0;
}
static gpg_error_t
learn_cb (void *opaque, const void *buffer, size_t length)
{
struct learn_parm_s *parm = opaque;
size_t len;
char *buf;
ksba_cert_t cert;
int rc;
if (parm->error)
return 0;
if (buffer)
{
put_membuf (parm->data, buffer, length);
return 0;
}
/* END encountered - process what we have */
buf = get_membuf (parm->data, &len);
if (!buf)
{
parm->error = gpg_error (GPG_ERR_ENOMEM);
return 0;
}
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, "learncard C 0 0"))
return gpg_error (GPG_ERR_ASS_CANCELED);
/* FIXME: this should go into import.c */
rc = ksba_cert_new (&cert);
if (rc)
{
parm->error = rc;
return 0;
}
rc = ksba_cert_init_from_mem (cert, buf, len);
if (rc)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc));
ksba_cert_release (cert);
parm->error = rc;
return 0;
}
/* We do not store a certifciate with missing issuers as ephemeral
because we can assume that the --learn-card command has been used
on purpose. */
rc = gpgsm_basic_cert_check (parm->ctrl, cert);
if (rc && gpg_err_code (rc) != GPG_ERR_MISSING_CERT
&& gpg_err_code (rc) != GPG_ERR_MISSING_ISSUER_CERT)
log_error ("invalid certificate: %s\n", gpg_strerror (rc));
else
{
int existed;
if (!keydb_store_cert (parm->ctrl, cert, 0, &existed))
{
if (opt.verbose > 1 && existed)
log_info ("certificate already in DB\n");
else if (opt.verbose && !existed)
log_info ("certificate imported\n");
}
}
ksba_cert_release (cert);
init_membuf (parm->data, 4096);
return 0;
}
/* Call the agent to learn about a smartcard */
int
gpgsm_agent_learn (ctrl_t ctrl)
{
int rc;
struct learn_parm_s learn_parm;
membuf_t data;
size_t len;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = warn_version_mismatch (ctrl, agent_ctx, SCDAEMON_NAME, 2);
if (rc)
return rc;
init_membuf (&data, 4096);
learn_parm.error = 0;
learn_parm.ctrl = ctrl;
learn_parm.ctx = agent_ctx;
learn_parm.data = &data;
rc = assuan_transact (agent_ctx, "LEARN --send",
learn_cb, &learn_parm,
NULL, NULL,
learn_status_cb, &learn_parm);
xfree (get_membuf (&data, &len));
if (rc)
return rc;
return learn_parm.error;
}
/* Ask the agent to change the passphrase of the key identified by
HEXKEYGRIP. If DESC is not NULL, display instead of the default
description message. */
int
gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
snprintf (line, DIM(line), "PASSWD %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "GET_CONFIRMATION %s", desc);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Return 0 if the agent is alive. This is useful to make sure that
an agent has been started. */
gpg_error_t
gpgsm_agent_send_nop (ctrl_t ctrl)
{
int rc;
rc = start_agent (ctrl);
if (!rc)
rc = assuan_transact (agent_ctx, "NOP",
NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *s, *s2;
if ((s = has_leading_keyword (line, "KEYINFO")) && !*serialno)
{
s = strchr (s, ' ');
if (s && s[1] == 'T' && s[2] == ' ' && s[3])
{
s += 3;
s2 = strchr (s, ' ');
if ( s2 > s )
{
*serialno = xtrymalloc ((s2 - s)+1);
if (*serialno)
{
memcpy (*serialno, s, s2 - s);
(*serialno)[s2 - s] = 0;
}
}
}
}
return 0;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO. */
gpg_error_t
gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *serialno = NULL;
*r_serialno = NULL;
err = start_agent (ctrl);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &serialno);
if (!err && serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (serialno);
else
*r_serialno = serialno;
return err;
}
/* Ask for the passphrase (this is used for pkcs#12 import/export. On
success the caller needs to free the string stored at R_PASSPHRASE.
On error NULL will be stored at R_PASSPHRASE and an appropriate
error code returned. If REPEAT is true the agent tries to get a
new passphrase (i.e. asks the user to confirm it). */
gpg_error_t
gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat,
char **r_passphrase)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s inq_parm;
*r_passphrase = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg)))
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_PASSPHRASE --data%s -- X X X %s",
repeat? " --repeat=1 --check --qualitybar":"",
arg4);
xfree (arg4);
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
return err;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be use for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_kek = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, parm->key, parm->keylen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
err = default_inq_cb (&inq_parm, line);
}
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen)
{
gpg_error_t err;
struct import_key_parm_s parm;
err = start_agent (ctrl);
if (err)
return err;
parm.ctrl = ctrl;
parm.ctx = agent_ctx;
parm.key = key;
parm.keylen = keylen;
err = assuan_transact (agent_ctx, "IMPORT_KEY",
NULL, NULL, inq_import_key_parms, &parm, NULL, NULL);
return err;
}
/* Receive a secret key from the agent. KEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). On success the key is
stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char **r_result, size_t *r_resultlen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_result = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s", keygrip);
init_membuf_secure (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c
index 45303e8c4..5ee15b777 100644
--- a/sm/call-dirmngr.c
+++ b/sm/call-dirmngr.c
@@ -1,1048 +1,1048 @@
/* call-dirmngr.c - Communication with the dirmngr
* Copyright (C) 2002, 2003, 2005, 2007, 2008,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include <ctype.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keydb.h"
-#include "asshelp.h"
+#include "../common/asshelp.h"
struct membuf {
size_t len;
size_t size;
char *buf;
int out_of_core;
};
/* fixme: We need a context for each thread or serialize the access to
the dirmngr. */
static assuan_context_t dirmngr_ctx = NULL;
static assuan_context_t dirmngr2_ctx = NULL;
static int dirmngr_ctx_locked;
static int dirmngr2_ctx_locked;
struct inq_certificate_parm_s {
ctrl_t ctrl;
assuan_context_t ctx;
ksba_cert_t cert;
ksba_cert_t issuer_cert;
};
struct isvalid_status_parm_s {
ctrl_t ctrl;
int seen;
unsigned char fpr[20];
};
struct lookup_parm_s {
ctrl_t ctrl;
assuan_context_t ctx;
void (*cb)(void *, ksba_cert_t);
void *cb_value;
struct membuf data;
int error;
};
struct run_command_parm_s {
ctrl_t ctrl;
assuan_context_t ctx;
};
static gpg_error_t get_cached_cert (assuan_context_t ctx,
const unsigned char *fpr,
ksba_cert_t *r_cert);
/* A simple implementation of a dynamic buffer. Use init_membuf() to
create a buffer, put_membuf to append bytes and get_membuf to
release and return the buffer. Allocation errors are detected but
only returned at the final get_membuf(), this helps not to clutter
the code with out of core checks. */
static void
init_membuf (struct membuf *mb, int initiallen)
{
mb->len = 0;
mb->size = initiallen;
mb->out_of_core = 0;
mb->buf = xtrymalloc (initiallen);
if (!mb->buf)
mb->out_of_core = 1;
}
static void
put_membuf (struct membuf *mb, const void *buf, size_t len)
{
if (mb->out_of_core)
return;
if (mb->len + len >= mb->size)
{
char *p;
mb->size += len + 1024;
p = xtryrealloc (mb->buf, mb->size);
if (!p)
{
mb->out_of_core = 1;
return;
}
mb->buf = p;
}
memcpy (mb->buf + mb->len, buf, len);
mb->len += len;
}
static void *
get_membuf (struct membuf *mb, size_t *len)
{
char *p;
if (mb->out_of_core)
{
xfree (mb->buf);
mb->buf = NULL;
return NULL;
}
p = mb->buf;
*len = mb->len;
mb->buf = NULL;
mb->out_of_core = 1; /* don't allow a reuse */
return p;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0",
warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* This function prepares the dirmngr for a new session. The
audit-events option is used so that other dirmngr clients won't get
disturbed by such events. */
static void
prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err)
{
struct keyserver_spec *server;
if (!err)
err = warn_version_mismatch (ctrl, ctx, DIRMNGR_NAME, 0);
if (!err)
{
err = assuan_transact (ctx, "OPTION audit-events=1",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* Allow the use of old dirmngr versions. */
}
audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err);
if (!ctx || err)
return;
server = opt.keyserver;
while (server)
{
char line[ASSUAN_LINELENGTH];
char *user = server->user ? server->user : "";
char *pass = server->pass ? server->pass : "";
char *base = server->base ? server->base : "";
snprintf (line, DIM (line), "LDAPSERVER %s:%i:%s:%s:%s",
server->host, server->port, user, pass, base);
assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
/* The code below is not required because we don't return an error. */
/* err = [above call] */
/* if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) */
/* err = 0; /\* Allow the use of old dirmngr versions. *\/ */
server = server->next;
}
}
/* Return a new assuan context for a Dirmngr connection. */
static gpg_error_t
start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r)
{
gpg_error_t err;
assuan_context_t ctx;
if (opt.disable_dirmngr || ctrl->offline)
return gpg_error (GPG_ERR_NO_DIRMNGR);
if (*ctx_r)
return 0;
/* Note: if you change this to multiple connections, you also need
to take care of the implicit option sending caching. */
err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program,
opt.autostart, opt.verbose, DBG_IPC,
gpgsm_status2, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no dirmngr running in this session\n"));
}
}
prepare_dirmngr (ctrl, ctx, err);
if (err)
return err;
*ctx_r = ctx;
return 0;
}
static int
start_dirmngr (ctrl_t ctrl)
{
gpg_error_t err;
assert (! dirmngr_ctx_locked);
dirmngr_ctx_locked = 1;
err = start_dirmngr_ext (ctrl, &dirmngr_ctx);
/* We do not check ERR but the existence of a context because the
error might come from a failed command send to the dirmngr.
Fixme: Why don't we close the drimngr context if we encountered
an error in prepare_dirmngr? */
if (!dirmngr_ctx)
dirmngr_ctx_locked = 0;
return err;
}
static void
release_dirmngr (ctrl_t ctrl)
{
(void)ctrl;
if (!dirmngr_ctx_locked)
log_error ("WARNING: trying to release a non-locked dirmngr ctx\n");
dirmngr_ctx_locked = 0;
}
static int
start_dirmngr2 (ctrl_t ctrl)
{
gpg_error_t err;
assert (! dirmngr2_ctx_locked);
dirmngr2_ctx_locked = 1;
err = start_dirmngr_ext (ctrl, &dirmngr2_ctx);
if (!dirmngr2_ctx)
dirmngr2_ctx_locked = 0;
return err;
}
static void
release_dirmngr2 (ctrl_t ctrl)
{
(void)ctrl;
if (!dirmngr2_ctx_locked)
log_error ("WARNING: trying to release a non-locked dirmngr2 ctx\n");
dirmngr2_ctx_locked = 0;
}
/* Handle a SENDCERT inquiry. */
static gpg_error_t
inq_certificate (void *opaque, const char *line)
{
struct inq_certificate_parm_s *parm = opaque;
const char *s;
int rc;
size_t n;
const unsigned char *der;
size_t derlen;
int issuer_mode = 0;
ksba_sexp_t ski = NULL;
if ((s = has_leading_keyword (line, "SENDCERT")))
{
line = s;
}
else if ((s = has_leading_keyword (line, "SENDCERT_SKI")))
{
/* Send a certificate where a sourceKeyIdentifier is included. */
line = s;
ski = make_simple_sexp_from_hexstr (line, &n);
line += n;
while (*line == ' ')
line++;
}
else if ((s = has_leading_keyword (line, "SENDISSUERCERT")))
{
line = s;
issuer_mode = 1;
}
else if ((s = has_leading_keyword (line, "ISTRUSTED")))
{
/* The server is asking us whether the certificate is a trusted
root certificate. */
char fpr[41];
struct rootca_flags_s rootca_flags;
line = s;
for (s=line,n=0; hexdigitp (s); s++, n++)
;
if (*s || n != 40)
return gpg_error (GPG_ERR_ASS_PARAMETER);
for (s=line, n=0; n < 40; s++, n++)
fpr[n] = (*s >= 'a')? (*s & 0xdf): *s;
fpr[n] = 0;
if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags))
rc = assuan_send_data (parm->ctx, "1", 1);
else
rc = 0;
return rc;
}
else
{
log_error ("unsupported inquiry '%s'\n", line);
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
if (!*line)
{ /* Send the current certificate. */
der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert,
&derlen);
if (!der)
rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
else
rc = assuan_send_data (parm->ctx, der, derlen);
}
else if (issuer_mode)
{
log_error ("sending specific issuer certificate back "
"is not yet implemented\n");
rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
else
{ /* Send the given certificate. */
int err;
ksba_cert_t cert;
err = gpgsm_find_cert (parm->ctrl, line, ski, &cert);
if (err)
{
log_error ("certificate not found: %s\n", gpg_strerror (err));
rc = gpg_error (GPG_ERR_NOT_FOUND);
}
else
{
der = ksba_cert_get_image (cert, &derlen);
if (!der)
rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
else
rc = assuan_send_data (parm->ctx, der, derlen);
ksba_cert_release (cert);
}
}
xfree (ski);
return rc;
}
/* Take a 20 byte hexencoded string and put it into the provided
20 byte buffer FPR in binary format. */
static int
unhexify_fpr (const char *hexstr, unsigned char *fpr)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if (*s || (n != 40))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s; s += 2, n++)
fpr[n] = xtoi_2 (s);
return 1; /* okay */
}
static gpg_error_t
isvalid_status_cb (void *opaque, const char *line)
{
struct isvalid_status_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (parm->ctrl)
{
line = s;
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
else if ((s = has_leading_keyword (line, "ONLY_VALID_IF_CERT_VALID")))
{
parm->seen++;
if (!*s || !unhexify_fpr (s, parm->fpr))
parm->seen++; /* Bumb it to indicate an error. */
}
return 0;
}
/* Call the directory manager to check whether the certificate is valid
Returns 0 for valid or usually one of the errors:
GPG_ERR_CERTIFICATE_REVOKED
GPG_ERR_NO_CRL_KNOWN
GPG_ERR_CRL_TOO_OLD
Values for USE_OCSP:
0 = Do CRL check.
1 = Do an OCSP check.
2 = Do an OCSP check using only the default responder.
*/
int
gpgsm_dirmngr_isvalid (ctrl_t ctrl,
ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp)
{
static int did_options;
int rc;
char *certid;
char line[ASSUAN_LINELENGTH];
struct inq_certificate_parm_s parm;
struct isvalid_status_parm_s stparm;
rc = start_dirmngr (ctrl);
if (rc)
return rc;
if (use_ocsp)
{
certid = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
}
else
{
certid = gpgsm_get_certid (cert);
if (!certid)
{
log_error ("error getting the certificate ID\n");
release_dirmngr (ctrl);
return gpg_error (GPG_ERR_GENERAL);
}
}
if (opt.verbose > 1)
{
char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
log_info ("asking dirmngr about %s%s\n", fpr,
use_ocsp? " (using OCSP)":"");
xfree (fpr);
}
parm.ctx = dirmngr_ctx;
parm.ctrl = ctrl;
parm.cert = cert;
parm.issuer_cert = issuer_cert;
stparm.ctrl = ctrl;
stparm.seen = 0;
memset (stparm.fpr, 0, 20);
/* FIXME: If --disable-crl-checks has been set, we should pass an
option to dirmngr, so that no fallback CRL check is done after an
ocsp check. It is not a problem right now as dirmngr does not
fallback to CRL checking. */
/* It is sufficient to send the options only once because we have
one connection per process only. */
if (!did_options)
{
if (opt.force_crl_refresh)
assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1",
NULL, NULL, NULL, NULL, NULL, NULL);
did_options = 1;
}
snprintf (line, DIM(line), "ISVALID%s %s",
use_ocsp == 2? " --only-ocsp --force-default-responder":"",
certid);
xfree (certid);
rc = assuan_transact (dirmngr_ctx, line, NULL, NULL,
inq_certificate, &parm,
isvalid_status_cb, &stparm);
if (opt.verbose > 1)
log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay");
if (!rc && stparm.seen)
{
/* Need to also check the certificate validity. */
if (stparm.seen != 1)
{
log_error ("communication problem with dirmngr detected\n");
rc = gpg_error (GPG_ERR_INV_CRL);
}
else
{
ksba_cert_t rspcert = NULL;
if (get_cached_cert (dirmngr_ctx, stparm.fpr, &rspcert))
{
/* Ooops: Something went wrong getting the certificate
from the dirmngr. Try our own cert store now. */
KEYDB_HANDLE kh;
kh = keydb_new ();
if (!kh)
rc = gpg_error (GPG_ERR_ENOMEM);
if (!rc)
rc = keydb_search_fpr (ctrl, kh, stparm.fpr);
if (!rc)
rc = keydb_get_cert (kh, &rspcert);
if (rc)
{
log_error ("unable to find the certificate used "
"by the dirmngr: %s\n", gpg_strerror (rc));
rc = gpg_error (GPG_ERR_INV_CRL);
}
keydb_release (kh);
}
if (!rc)
{
rc = gpgsm_cert_use_ocsp_p (rspcert);
if (rc)
rc = gpg_error (GPG_ERR_INV_CRL);
else
{
/* Note the no_dirmngr flag: This avoids checking
this certificate over and over again. */
rc = gpgsm_validate_chain (ctrl, rspcert, "", NULL, 0, NULL,
VALIDATE_FLAG_NO_DIRMNGR, NULL);
if (rc)
{
log_error ("invalid certificate used for CRL/OCSP: %s\n",
gpg_strerror (rc));
rc = gpg_error (GPG_ERR_INV_CRL);
}
}
}
ksba_cert_release (rspcert);
}
}
release_dirmngr (ctrl);
return rc;
}
/* Lookup helpers*/
static gpg_error_t
lookup_cb (void *opaque, const void *buffer, size_t length)
{
struct lookup_parm_s *parm = opaque;
size_t len;
char *buf;
ksba_cert_t cert;
int rc;
if (parm->error)
return 0;
if (buffer)
{
put_membuf (&parm->data, buffer, length);
return 0;
}
/* END encountered - process what we have */
buf = get_membuf (&parm->data, &len);
if (!buf)
{
parm->error = gpg_error (GPG_ERR_ENOMEM);
return 0;
}
rc = ksba_cert_new (&cert);
if (rc)
{
parm->error = rc;
return 0;
}
rc = ksba_cert_init_from_mem (cert, buf, len);
if (rc)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc));
}
else
{
parm->cb (parm->cb_value, cert);
}
ksba_cert_release (cert);
init_membuf (&parm->data, 4096);
return 0;
}
/* Return a properly escaped pattern from NAMES. The only error
return is NULL to indicate a malloc failure. */
static char *
pattern_from_strlist (strlist_t names)
{
strlist_t sl;
int n;
const char *s;
char *pattern, *p;
for (n=0, sl=names; sl; sl = sl->next)
{
for (s=sl->d; *s; s++, n++)
{
if (*s == '%' || *s == ' ' || *s == '+')
n += 2;
}
n++;
}
p = pattern = xtrymalloc (n+1);
if (!pattern)
return NULL;
for (sl=names; sl; sl = sl->next)
{
for (s=sl->d; *s; s++)
{
switch (*s)
{
case '%':
*p++ = '%';
*p++ = '2';
*p++ = '5';
break;
case ' ':
*p++ = '%';
*p++ = '2';
*p++ = '0';
break;
case '+':
*p++ = '%';
*p++ = '2';
*p++ = 'B';
break;
default:
*p++ = *s;
break;
}
}
*p++ = ' ';
}
if (p == pattern)
*pattern = 0; /* is empty */
else
p[-1] = '\0'; /* remove trailing blank */
return pattern;
}
static gpg_error_t
lookup_status_cb (void *opaque, const char *line)
{
struct lookup_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (parm->ctrl)
{
line = s;
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
else if ((s = has_leading_keyword (line, "TRUNCATED")))
{
if (parm->ctrl)
{
line = s;
gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line);
}
}
return 0;
}
/* Run the Directory Manager's lookup command using the pattern
compiled from the strings given in NAMES. The caller must provide
the callback CB which will be passed cert by cert. Note that CTRL
is optional. With CACHE_ONLY the dirmngr will search only its own
key cache. */
int
gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only,
void (*cb)(void*, ksba_cert_t), void *cb_value)
{
int rc;
char *pattern;
char line[ASSUAN_LINELENGTH];
struct lookup_parm_s parm;
size_t len;
assuan_context_t ctx;
/* The lookup function can be invoked from the callback of a lookup
function, for example to walk the chain. */
if (!dirmngr_ctx_locked)
{
rc = start_dirmngr (ctrl);
if (rc)
return rc;
ctx = dirmngr_ctx;
}
else if (!dirmngr2_ctx_locked)
{
rc = start_dirmngr2 (ctrl);
if (rc)
return rc;
ctx = dirmngr2_ctx;
}
else
{
log_fatal ("both dirmngr contexts are in use\n");
}
pattern = pattern_from_strlist (names);
if (!pattern)
{
if (ctx == dirmngr_ctx)
release_dirmngr (ctrl);
else
release_dirmngr2 (ctrl);
return out_of_core ();
}
snprintf (line, DIM(line), "LOOKUP%s %s",
cache_only? " --cache-only":"", pattern);
xfree (pattern);
parm.ctrl = ctrl;
parm.ctx = ctx;
parm.cb = cb;
parm.cb_value = cb_value;
parm.error = 0;
init_membuf (&parm.data, 4096);
rc = assuan_transact (ctx, line, lookup_cb, &parm,
NULL, NULL, lookup_status_cb, &parm);
xfree (get_membuf (&parm.data, &len));
if (ctx == dirmngr_ctx)
release_dirmngr (ctrl);
else
release_dirmngr2 (ctrl);
if (rc)
return rc;
return parm.error;
}
static gpg_error_t
get_cached_cert_data_cb (void *opaque, const void *buffer, size_t length)
{
struct membuf *mb = opaque;
if (buffer)
put_membuf (mb, buffer, length);
return 0;
}
/* Return a certificate from the Directory Manager's cache. This
function only returns one certificate which must be specified using
the fingerprint FPR and will be stored at R_CERT. On error NULL is
stored at R_CERT and an error code returned. Note that the caller
must provide the locked dirmngr context CTX. */
static gpg_error_t
get_cached_cert (assuan_context_t ctx,
const unsigned char *fpr, ksba_cert_t *r_cert)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char hexfpr[2*20+1];
struct membuf mb;
char *buf;
size_t buflen = 0;
ksba_cert_t cert;
*r_cert = NULL;
bin2hex (fpr, 20, hexfpr);
snprintf (line, DIM(line), "LOOKUP --single --cache-only 0x%s", hexfpr);
init_membuf (&mb, 4096);
err = assuan_transact (ctx, line, get_cached_cert_data_cb, &mb,
NULL, NULL, NULL, NULL);
buf = get_membuf (&mb, &buflen);
if (err)
{
xfree (buf);
return err;
}
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
err = ksba_cert_new (&cert);
if (err)
{
xfree (buf);
return err;
}
err = ksba_cert_init_from_mem (cert, buf, buflen);
xfree (buf);
if (err)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
ksba_cert_release (cert);
return err;
}
*r_cert = cert;
return 0;
}
/* Run Command helpers*/
/* Fairly simple callback to write all output of dirmngr to stdout. */
static gpg_error_t
run_command_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
if (buffer)
{
if ( fwrite (buffer, length, 1, stdout) != 1 )
log_error ("error writing to stdout: %s\n", strerror (errno));
}
return 0;
}
/* Handle inquiries from the dirmngr COMMAND. */
static gpg_error_t
run_command_inq_cb (void *opaque, const char *line)
{
struct run_command_parm_s *parm = opaque;
const char *s;
int rc = 0;
if ((s = has_leading_keyword (line, "SENDCERT")))
{ /* send the given certificate */
int err;
ksba_cert_t cert;
const unsigned char *der;
size_t derlen;
line = s;
if (!*line)
return gpg_error (GPG_ERR_ASS_PARAMETER);
err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert);
if (err)
{
log_error ("certificate not found: %s\n", gpg_strerror (err));
rc = gpg_error (GPG_ERR_NOT_FOUND);
}
else
{
der = ksba_cert_get_image (cert, &derlen);
if (!der)
rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
else
rc = assuan_send_data (parm->ctx, der, derlen);
ksba_cert_release (cert);
}
}
else if ((s = has_leading_keyword (line, "PRINTINFO")))
{ /* Simply show the message given in the argument. */
line = s;
log_info ("dirmngr: %s\n", line);
}
else
{
log_error ("unsupported inquiry '%s'\n", line);
rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
return rc;
}
static gpg_error_t
run_command_status_cb (void *opaque, const char *line)
{
ctrl_t ctrl = opaque;
const char *s;
if (opt.verbose)
{
log_info ("dirmngr status: %s\n", line);
}
if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (ctrl)
{
line = s;
if (gpgsm_status (ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
return 0;
}
/* Pass COMMAND to dirmngr and print all output generated by Dirmngr
to stdout. A couple of inquiries are defined (see above). ARGC
arguments in ARGV are given to the Dirmngr. Spaces, plus and
percent characters within the argument strings are percent escaped
so that blanks can act as delimiters. */
int
gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
int argc, char **argv)
{
int rc;
int i;
const char *s;
char *line, *p;
size_t len;
struct run_command_parm_s parm;
rc = start_dirmngr (ctrl);
if (rc)
return rc;
parm.ctrl = ctrl;
parm.ctx = dirmngr_ctx;
len = strlen (command) + 1;
for (i=0; i < argc; i++)
len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
line = xtrymalloc (len);
if (!line)
{
release_dirmngr (ctrl);
return out_of_core ();
}
p = stpcpy (line, command);
for (i=0; i < argc; i++)
{
*p++ = ' ';
for (s=argv[i]; *s; s++)
{
if (!isascii (*s))
*p++ = *s;
else if (*s == ' ')
*p++ = '+';
else if (!isprint (*s) || *s == '+')
{
sprintf (p, "%%%02X", *(const unsigned char *)s);
p += 3;
}
else
*p++ = *s;
}
}
*p = 0;
rc = assuan_transact (dirmngr_ctx, line,
run_command_cb, NULL,
run_command_inq_cb, &parm,
run_command_status_cb, ctrl);
xfree (line);
log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay");
release_dirmngr (ctrl);
return rc;
}
diff --git a/sm/certchain.c b/sm/certchain.c
index b3e8656ca..a361acaf0 100644
--- a/sm/certchain.c
+++ b/sm/certchain.c
@@ -1,2153 +1,2153 @@
/* certchain.c - certificate chain validation
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2006, 2007, 2008, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdarg.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
-#include "i18n.h"
-#include "tlv.h"
+#include "../common/i18n.h"
+#include "../common/tlv.h"
/* Object to keep track of certain root certificates. */
struct marktrusted_info_s
{
struct marktrusted_info_s *next;
unsigned char fpr[20];
};
static struct marktrusted_info_s *marktrusted_info;
/* While running the validation function we want to keep track of the
certificates in the chain. This type is used for that. */
struct chain_item_s
{
struct chain_item_s *next;
ksba_cert_t cert; /* The certificate. */
int is_root; /* The certificate is the root certificate. */
};
typedef struct chain_item_s *chain_item_t;
static int is_root_cert (ksba_cert_t cert,
const char *issuerdn, const char *subjectdn);
static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen);
/* This function returns true if we already asked during this session
whether the root certificate CERT shall be marked as trusted. */
static int
already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
/* No context switches in the loop! */
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return 1;
return 0;
}
/* Flag certificate CERT as already asked whether it shall be marked
as trusted. */
static void
set_already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return; /* Already marked. */
r = xtrycalloc (1, sizeof *r);
if (!r)
return;
memcpy (r->fpr, fpr, 20);
r->next = marktrusted_info;
marktrusted_info = r;
}
/* If LISTMODE is true, print FORMAT using LISTMODE to FP. If
LISTMODE is false, use the string to print an log_info or, if
IS_ERROR is true, and log_error. */
static void
do_list (int is_error, int listmode, estream_t fp, const char *format, ...)
{
va_list arg_ptr;
va_start (arg_ptr, format) ;
if (listmode)
{
if (fp)
{
es_fputs (" [", fp);
es_vfprintf (fp, format, arg_ptr);
es_fputs ("]\n", fp);
}
}
else
{
log_logv (is_error? GPGRT_LOG_ERROR: GPGRT_LOG_INFO, format, arg_ptr);
log_printf ("\n");
}
va_end (arg_ptr);
}
/* Return 0 if A and B are equal. */
static int
compare_certs (ksba_cert_t a, ksba_cert_t b)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (a, &len_a);
if (!img_a)
return 1;
img_b = ksba_cert_get_image (b, &len_b);
if (!img_b)
return 1;
return !(len_a == len_b && !memcmp (img_a, img_b, len_a));
}
/* Return true if CERT has the validityModel extensions and defines
the use of the chain model. */
static int
has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp)
{
gpg_error_t err;
int idx, yes;
const char *oid;
size_t off, derlen, objlen, hdrlen;
const unsigned char *der;
int class, tag, constructed, ndef;
char *oidbuf;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &derlen));idx++)
if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") )
break;
if (err)
return 0; /* Not found. */
der = ksba_cert_get_image (cert, NULL);
if (!der)
{
err = gpg_error (GPG_ERR_INV_OBJ); /* Oops */
goto leave;
}
der += off;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
derlen = objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OBJECT_ID))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
oidbuf = ksba_oid_to_str (der, objlen);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
do_list (0, listmode, listfp,
_("validation model requested by certificate: %s"),
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") :
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") :
/* */ oidbuf);
yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1");
ksba_free (oidbuf);
return yes;
leave:
log_error ("error parsing validityModel: %s\n", gpg_strerror (err));
return 0;
}
static int
unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp)
{
static const char *known[] = {
"2.5.29.15", /* keyUsage */
"2.5.29.17", /* subjectAltName
Japanese DoCoMo certs mark them as critical. PKIX
only requires them as critical if subjectName is
empty. I don't know whether our code gracefully
handles such empry subjectNames but that is
another story. */
"2.5.29.19", /* basic Constraints */
"2.5.29.32", /* certificatePolicies */
"2.5.29.37", /* extendedKeyUsage - handled by certlist.c */
"1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */
NULL
};
int rc = 0, i, idx, crit;
const char *oid;
gpg_error_t err;
int unsupported;
strlist_t sl;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &crit, NULL, NULL));idx++)
{
if (!crit)
continue;
for (i=0; known[i] && strcmp (known[i],oid); i++)
;
unsupported = !known[i];
/* If this critical extension is not supported. Check the list
of to be ignored extensions to see whether we claim that it
is supported. */
if (unsupported && opt.ignored_cert_extensions)
{
for (sl=opt.ignored_cert_extensions;
sl && strcmp (sl->d, oid); sl = sl->next)
;
if (sl)
unsupported = 0;
}
if (unsupported)
{
do_list (1, listmode, fp,
_("critical certificate extension %s is not supported"),
oid);
rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
}
}
/* We ignore the error codes EOF as well as no-value. The later will
occur for certificates with no extensions at all. */
if (err
&& gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
rc = err;
return rc;
}
/* Check whether CERT is an allowed certificate. This requires that
CERT matches all requirements for such a CA, i.e. the
BasicConstraints extension. The function returns 0 on success and
the allowed length of the chain at CHAINLEN. */
static int
allowed_ca (ctrl_t ctrl,
ksba_cert_t cert, int *chainlen, int listmode, estream_t fp)
{
gpg_error_t err;
int flag;
err = ksba_cert_is_ca (cert, &flag, chainlen);
if (err)
return err;
if (!flag)
{
if (get_regtp_ca_info (ctrl, cert, chainlen))
{
/* Note that dirmngr takes a different way to cope with such
certs. */
return 0; /* RegTP issued certificate. */
}
do_list (1, listmode, fp,_("issuer certificate is not marked as a CA"));
return gpg_error (GPG_ERR_BAD_CA_CERT);
}
return 0;
}
static int
check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist)
{
gpg_error_t err;
char *policies;
FILE *fp;
int any_critical;
err = ksba_cert_get_cert_policies (cert, &policies);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 0; /* No policy given. */
if (err)
return err;
/* STRING is a line delimited list of certificate policies as stored
in the certificate. The line itself is colon delimited where the
first field is the OID of the policy and the second field either
N or C for normal or critical extension */
if (opt.verbose > 1 && !listmode)
log_info ("certificate's policy list: %s\n", policies);
/* The check is very minimal but won't give false positives */
any_critical = !!strstr (policies, ":C");
if (!opt.policy_file)
{
xfree (policies);
if (any_critical)
{
do_list (1, listmode, fplist,
_("critical marked policy without configured policies"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
return 0;
}
fp = fopen (opt.policy_file, "r");
if (!fp)
{
if (opt.verbose || errno != ENOENT)
log_info (_("failed to open '%s': %s\n"),
opt.policy_file, strerror (errno));
xfree (policies);
/* With no critical policies this is only a warning */
if (!any_critical)
{
if (!opt.quiet)
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
for (;;)
{
int c;
char *p, line[256];
char *haystack, *allowed;
/* read line */
do
{
if (!fgets (line, DIM(line)-1, fp) )
{
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
xfree (policies);
if (feof (fp))
{
fclose (fp);
/* With no critical policies this is only a warning */
if (!any_critical)
{
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
fclose (fp);
return tmperr;
}
if (!*line || line[strlen(line)-1] != '\n')
{
/* eat until end of line */
while ( (c=getc (fp)) != EOF && c != '\n')
;
fclose (fp);
xfree (policies);
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
/* Parse line. Note that the line has always a LF and spacep
does not consider a LF a space. Thus strpbrk will always
succeed. */
for (allowed=line; spacep (allowed); allowed++)
;
p = strpbrk (allowed, " :\n");
if (!*p || p == allowed)
{
fclose (fp);
xfree (policies);
return gpg_error (GPG_ERR_CONFIGURATION);
}
*p = 0; /* strip the rest of the line */
/* See whether we find ALLOWED (which is an OID) in POLICIES */
for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1)
{
if ( !(p == policies || p[-1] == '\n') )
continue; /* Does not match the begin of a line. */
if (p[strlen (allowed)] != ':')
continue; /* The length does not match. */
/* Yep - it does match so return okay. */
fclose (fp);
xfree (policies);
return 0;
}
}
}
/* Helper function for find_up. This resets the key handle and search
for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns
0 on success or -1 when not found. */
static int
find_up_search_by_keyid (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
ksba_cert_t cert = NULL;
ksba_sexp_t subj = NULL;
int anyfound = 0;
ksba_isotime_t not_before, last_not_before;
keydb_search_reset (kh);
while (!(rc = keydb_search_subject (ctrl, kh, issuer)))
{
ksba_cert_release (cert); cert = NULL;
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = -1;
break;
}
xfree (subj);
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
{
if (!cmp_simple_canon_sexp (keyid, subj))
{
/* Found matching cert. */
rc = ksba_cert_get_validity (cert, 0, not_before);
if (rc)
{
log_error ("keydb_get_validity() failed: rc=%d\n", rc);
rc = -1;
break;
}
if (!anyfound || strcmp (last_not_before, not_before) < 0)
{
/* This certificate is the first one found or newer
than the previous one. This copes with
re-issuing CA certificates while keeping the same
key information. */
anyfound = 1;
gnupg_copy_time (last_not_before, not_before);
keydb_push_found_state (kh);
}
}
}
}
if (anyfound)
{
/* Take the last saved one. */
keydb_pop_found_state (kh);
rc = 0; /* Ignore EOF or other error after the first cert. */
}
ksba_cert_release (cert);
xfree (subj);
return rc? -1:0;
}
struct find_up_store_certs_s
{
ctrl_t ctrl;
int count;
};
static void
find_up_store_certs_cb (void *cb_value, ksba_cert_t cert)
{
struct find_up_store_certs_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing issuer certificate as ephemeral\n");
parm->count++;
}
/* Helper for find_up(). Locate the certificate for ISSUER using an
external lookup. KH is the keydb context we are currently using.
On success 0 is returned and the certificate may be retrieved from
the keydb using keydb_get_cert(). KEYID is the keyIdentifier from
the AKI or NULL. */
static int
find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
const char *s;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer at external location\n"));
/* The Dirmngr process is confused about unknown attributes. As a
quick and ugly hack we locate the CN and use the issuer string
starting at this attribite. Fixme: we should have far better
parsing for external lookups in the Dirmngr. */
s = strstr (issuer, "CN=");
if (!s || s == issuer || s[-1] != ',')
s = issuer;
pattern = xtrymalloc (strlen (s)+2);
if (!pattern)
return gpg_error_from_syserror ();
strcpy (stpcpy (pattern, "/"), s);
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, 0, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of issuers matching: %d\n"),
find_up_store_certs_parm.count);
if (rc)
{
log_error ("external key lookup failed: %s\n", gpg_strerror (rc));
rc = -1;
}
else if (!find_up_store_certs_parm.count)
rc = -1;
else
{
int old;
/* The issuers are currently stored in the ephemeral key DB, so
we temporary switch to ephemeral mode. */
old = keydb_set_ephemeral (kh, 1);
if (keyid)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
return rc;
}
/* Helper for find_up(). Ask the dirmngr for the certificate for
ISSUER with optional SERIALNO. KH is the keydb context we are
currently using. With SUBJECT_MODE set, ISSUER is searched as the
subject. On success 0 is returned and the certificate is available
in the ephemeral DB. */
static int
find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_sexp_t serialno, const char *issuer, int subject_mode)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
(void)kh;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer from the Dirmngr cache\n"));
if (subject_mode)
{
pattern = xtrymalloc (strlen (issuer)+2);
if (pattern)
strcpy (stpcpy (pattern, "/"), issuer);
}
else if (serialno)
pattern = gpgsm_format_sn_issuer (serialno, issuer);
else
{
pattern = xtrymalloc (strlen (issuer)+3);
if (pattern)
strcpy (stpcpy (pattern, "#/"), issuer);
}
if (!pattern)
return gpg_error_from_syserror ();
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, 1, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of matching certificates: %d\n"),
find_up_store_certs_parm.count);
if (rc && !opt.quiet)
log_info (_("dirmngr cache-only key lookup failed: %s\n"),
gpg_strerror (rc));
return (!rc && find_up_store_certs_parm.count)? 0 : -1;
}
/* Locate issuing certificate for CERT. ISSUER is the name of the
issuer used as a fallback if the other methods don't work. If
FIND_NEXT is true, the function shall return the next possible
issuer. The certificate itself is not directly returned but a
keydb_get_cert on the keyDb context KH will return it. Returns 0
on success, -1 if not found or an error code. */
static int
find_up (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_cert_t cert, const char *issuer, int find_next)
{
ksba_name_t authid;
ksba_sexp_t authidno;
ksba_sexp_t keyid;
int rc = -1;
if (DBG_X509)
log_debug ("looking for parent certificate\n");
if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno))
{
const char *s = ksba_name_enum (authid, 0);
if (s && *authidno)
{
rc = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (rc)
keydb_search_reset (kh);
if (!rc && DBG_X509)
log_debug (" found via authid and sn+issuer\n");
/* In case of an error, try to get the certificate from the
dirmngr. That is done by trying to put that certifcate
into the ephemeral DB and let the code below do the
actual retrieve. Thus there is no error checking.
Skipped in find_next mode as usual. */
if (rc == -1 && !find_next)
find_up_dirmngr (ctrl, kh, authidno, s, 0);
/* In case of an error try the ephemeral DB. We can't do
that in find_next mode because we can't keep the search
state then. */
if (rc == -1 && !find_next)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
{
rc = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (rc)
keydb_search_reset (kh);
if (!rc && DBG_X509)
log_debug (" found via authid and sn+issuer (ephem)\n");
}
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
}
if (rc == -1 && keyid && !find_next)
{
/* Not found by AIK.issuer_sn. Lets try the AIK.ki
instead. Loop over all certificates with that issuer as
subject and stop for the one with a matching
subjectKeyIdentifier. */
/* Fixme: Should we also search in the dirmngr? */
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and keyid\n");
if (rc)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and keyid (ephem)\n");
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
}
/* If we still didn't found it, try to find it via the subject
from the dirmngr-cache. */
if (rc == -1 && !find_next)
{
if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1))
{
int old = keydb_set_ephemeral (kh, 1);
if (keyid)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
if (!rc && DBG_X509)
log_debug (" found via authid and issuer from dirmngr cache\n");
}
/* If we still didn't found it, try an external lookup. */
if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
{
rc = find_up_external (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and external lookup\n");
}
/* Print a note so that the user does not feel too helpless when
an issuer certificate was found and gpgsm prints BAD
signature because it is not the correct one. */
if (rc == -1 && opt.quiet)
;
else if (rc == -1)
{
log_info ("%sissuer certificate ", find_next?"next ":"");
if (keyid)
{
log_printf ("{");
gpgsm_dump_serial (keyid);
log_printf ("} ");
}
if (authidno)
{
log_printf ("(#");
gpgsm_dump_serial (authidno);
log_printf ("/");
gpgsm_dump_string (s);
log_printf (") ");
}
log_printf ("not found using authorityKeyIdentifier\n");
}
else if (rc)
log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc);
xfree (keyid);
ksba_name_release (authid);
xfree (authidno);
}
if (rc) /* Not found via authorithyKeyIdentifier, try regular issuer name. */
rc = keydb_search_subject (ctrl, kh, issuer);
if (rc == -1 && !find_next)
{
int old;
/* Also try to get it from the Dirmngr cache. The function
merely puts it into the ephemeral database. */
find_up_dirmngr (ctrl, kh, NULL, issuer, 0);
/* Not found, let us see whether we have one in the ephemeral key DB. */
old = keydb_set_ephemeral (kh, 1);
if (!old)
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
if (!rc && DBG_X509)
log_debug (" found via issuer\n");
}
/* Still not found. If enabled, try an external lookup. */
if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
{
rc = find_up_external (ctrl, kh, issuer, NULL);
if (!rc && DBG_X509)
log_debug (" found via issuer and external lookup\n");
}
return rc;
}
/* Return the next certificate up in the chain starting at START.
Returns -1 when there are no more certificates. */
int
gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next)
{
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = keydb_new ();
*r_next = NULL;
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (start, 0);
subject = ksba_cert_get_subject (start, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (!subject)
{
log_error ("no subject found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (start, issuer, subject))
{
rc = -1; /* we are at the root */
goto leave;
}
rc = find_up (ctrl, kh, start, issuer, 0);
if (rc)
{
/* It is quite common not to have a certificate, so better don't
print an error here. */
if (rc != -1 && opt.verbose > 1)
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
rc = keydb_get_cert (kh, r_next);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
return rc;
}
/* Helper for gpgsm_is_root_cert. This one is used if the subject and
issuer DNs are already known. */
static int
is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
{
gpg_error_t err;
int result = 0;
ksba_sexp_t serialno;
ksba_sexp_t ak_keyid;
ksba_name_t ak_name;
ksba_sexp_t ak_sn;
const char *ak_name_str;
ksba_sexp_t subj_keyid = NULL;
if (!issuerdn || !subjectdn)
return 0; /* No. */
if (strcmp (issuerdn, subjectdn))
return 0; /* No. */
err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 1; /* Yes. Without a authorityKeyIdentifier this needs
to be the Root certifcate (our trust anchor). */
log_error ("error getting authorityKeyIdentifier: %s\n",
gpg_strerror (err));
return 0; /* Well, it is broken anyway. Return No. */
}
serialno = ksba_cert_get_serial (cert);
if (!serialno)
{
log_error ("error getting serialno: %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether the auth name's matches the issuer name+sn. If
that is the case this is a root certificate. */
ak_name_str = ksba_name_enum (ak_name, 0);
if (ak_name_str
&& !strcmp (ak_name_str, issuerdn)
&& !cmp_simple_canon_sexp (ak_sn, serialno))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
/* Similar for the ak_keyid. */
if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
&& !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
leave:
ksba_free (subj_keyid);
ksba_free (ak_keyid);
ksba_name_release (ak_name);
ksba_free (ak_sn);
ksba_free (serialno);
return result;
}
/* Check whether the CERT is a root certificate. Returns True if this
is the case. */
int
gpgsm_is_root_cert (ksba_cert_t cert)
{
char *issuer;
char *subject;
int yes;
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
yes = is_root_cert (cert, issuer, subject);
xfree (issuer);
xfree (subject);
return yes;
}
/* This is a helper for gpgsm_validate_chain. */
static gpg_error_t
is_cert_still_valid (ctrl_t ctrl, int force_ocsp, int lm, estream_t fp,
ksba_cert_t subject_cert, ksba_cert_t issuer_cert,
int *any_revoked, int *any_no_crl, int *any_crl_too_old)
{
gpg_error_t err;
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
{
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK,
gpg_error (GPG_ERR_NOT_ENABLED));
return 0;
}
err = gpgsm_dirmngr_isvalid (ctrl,
subject_cert, issuer_cert,
force_ocsp? 2 : !!ctrl->use_ocsp);
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err);
if (err)
{
if (!lm)
gpgsm_cert_log_name (NULL, subject_cert);
switch (gpg_err_code (err))
{
case GPG_ERR_CERT_REVOKED:
do_list (1, lm, fp, _("certificate has been revoked"));
*any_revoked = 1;
/* Store that in the keybox so that key listings are able to
return the revoked flag. We don't care about error,
though. */
keydb_set_cert_flags (ctrl, subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0,
~0, VALIDITY_REVOKED);
break;
case GPG_ERR_NO_CRL_KNOWN:
do_list (1, lm, fp, _("no CRL found for certificate"));
*any_no_crl = 1;
break;
case GPG_ERR_NO_DATA:
do_list (1, lm, fp, _("the status of the certificate is unknown"));
*any_no_crl = 1;
break;
case GPG_ERR_CRL_TOO_OLD:
do_list (1, lm, fp, _("the available CRL is too old"));
if (!lm)
log_info (_("please make sure that the "
"\"dirmngr\" is properly installed\n"));
*any_crl_too_old = 1;
break;
default:
do_list (1, lm, fp, _("checking the CRL failed: %s"),
gpg_strerror (err));
return err;
}
}
return 0;
}
/* Helper for gpgsm_validate_chain to check the validity period of
SUBJECT_CERT. The caller needs to pass EXPTIME which will be
updated to the nearest expiration time seen. A DEPTH of 0 indicates
the target certifciate, -1 the final root certificate and other
values intermediate certificates. */
static gpg_error_t
check_validity_period (ksba_isotime_t current_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (*not_after)
{
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
}
if (*not_before && strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*not_after && strcmp (current_time, not_after) > 0 )
{
do_list (opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ? _("certificate has expired") :
depth == -1 ? _("root certificate has expired") :
/* other */ _("intermediate certificate has expired"));
if (!listmode)
{
log_info (" (expired at ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* This is a variant of check_validity_period used with the chain
model. The dextra contraint here is that notBefore and notAfter
must exists and if the additional argument CHECK_TIME is given this
time is used to check the validity period of SUBJECT_CERT. */
static gpg_error_t
check_validity_period_cm (ksba_isotime_t current_time,
ksba_isotime_t check_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*not_before || !*not_after)
{
do_list (1, listmode, listfp,
_("required certificate attributes missing: %s%s%s"),
!*not_before? "notBefore":"",
(!*not_before && !*not_after)? ", ":"",
!*not_before? "notAfter":"");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (strcmp (not_before, not_after) > 0 )
{
do_list (1, listmode, listfp,
_("certificate with invalid validity"));
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (" expired at ");
dump_isotime (not_after);
log_printf (")\n");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
if (strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*check_time
&& (strcmp (check_time, not_before) < 0
|| strcmp (check_time, not_after) > 0))
{
/* Note that we don't need a case for the root certificate
because its own consitency has already been checked. */
do_list(opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ?
_("signature not created during lifetime of certificate") :
depth == 1 ?
_("certificate not created during lifetime of issuer") :
_("intermediate certificate not created during lifetime "
"of issuer"));
if (!listmode)
{
log_info (depth== 0? _(" ( signature created at ") :
/* */ _(" (certificate created at ") );
dump_isotime (check_time);
log_printf (")\n");
log_info (depth==0? _(" (certificate valid from ") :
/* */ _(" ( issuer valid from ") );
dump_isotime (not_before);
log_info (" to ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* Ask the user whether he wants to mark the certificate CERT trusted.
Returns true if the CERT is the trusted. We also check whether the
agent is at all enabled to allow marktrusted and don't call it in
this session again if it is not. */
static int
ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode)
{
static int no_more_questions;
int rc;
char *fpr;
int success = 0;
fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
xfree (fpr);
if (no_more_questions)
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
rc = gpgsm_agent_marktrusted (ctrl, cert);
if (!rc)
{
log_info (_("root certificate has now been marked as trusted\n"));
success = 1;
}
else if (!listmode)
{
gpgsm_dump_cert ("issuer", cert);
log_info ("after checking the fingerprint, you may want "
"to add it manually to the list of trusted certificates.\n");
}
if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED)
{
if (!no_more_questions)
log_info (_("interactive marking as trusted "
"not enabled in gpg-agent\n"));
no_more_questions = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_CANCELED)
{
log_info (_("interactive marking as trusted "
"disabled for this session\n"));
no_more_questions = 1;
}
else
set_already_asked_marktrusted (cert);
return success;
}
/* Validate a chain and optionally return the nearest expiration time
in R_EXPTIME. With LISTMODE set to 1 a special listmode is
activated where only information about the certificate is printed
to LISTFP and no output is send to the usual log stream. If
CHECKTIME_ARG is set, it is used only in the chain model instead of the
current time.
Defined flag bits
VALIDATE_FLAG_NO_DIRMNGR - Do not do any dirmngr isvalid checks.
VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model.
VALIDATE_FLAG_STEED - Check according to the STEED model.
*/
static int
do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
struct rootca_flags_s *rootca_flags)
{
int rc = 0, depth, maxdepth;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = NULL;
ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
ksba_isotime_t current_time;
ksba_isotime_t check_time;
ksba_isotime_t exptime;
int any_expired = 0;
int any_revoked = 0;
int any_no_crl = 0;
int any_crl_too_old = 0;
int any_no_policy_match = 0;
int is_qualified = -1; /* Indicates whether the certificate stems
from a qualified root certificate.
-1 = unknown, 0 = no, 1 = yes. */
chain_item_t chain = NULL; /* A list of all certificates in the chain. */
gnupg_get_isotime (current_time);
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
if (!strcmp (checktime_arg, "19700101T000000"))
{
do_list (1, listmode, listfp,
_("WARNING: creation time of signature not known - "
"assuming current time"));
gnupg_copy_time (check_time, current_time);
}
else
gnupg_copy_time (check_time, checktime_arg);
}
else
*check_time = 0;
if (r_exptime)
*r_exptime = 0;
*exptime = 0;
if (opt.no_chain_validation && !listmode)
{
log_info ("WARNING: bypassing certificate chain validation\n");
return 0;
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (DBG_X509 && !listmode)
gpgsm_dump_cert ("target", cert);
subject_cert = cert;
ksba_cert_ref (subject_cert);
maxdepth = 50;
depth = 0;
for (;;)
{
int is_root;
gpg_error_t istrusted_rc = -1;
/* Put the certificate on our list. */
{
chain_item_t ci;
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
{
rc = gpg_error_from_syserror ();
goto leave;
}
ksba_cert_ref (subject_cert);
ci->cert = subject_cert;
ci->next = chain;
chain = ci;
}
xfree (issuer);
xfree (subject);
issuer = ksba_cert_get_issuer (subject_cert, 0);
subject = ksba_cert_get_subject (subject_cert, 0);
if (!issuer)
{
do_list (1, listmode, listfp, _("no issuer found in certificate"));
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* Is this a self-issued certificate (i.e. the root certificate)? */
is_root = is_root_cert (subject_cert, issuer, subject);
if (is_root)
{
chain->is_root = 1;
/* Check early whether the certificate is listed as trusted.
We used to do this only later but changed it to call the
check right here so that we can access special flags
associated with that specific root certificate. */
if (gpgsm_cert_has_well_known_private_key (subject_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL,
rootca_flags);
audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED,
subject_cert, istrusted_rc);
/* If the chain model extended attribute is used, make sure
that our chain model flag is set. */
if (!(flags & VALIDATE_FLAG_STEED)
&& has_validation_model_chain (subject_cert, listmode, listfp))
rootca_flags->chain_model = 1;
}
/* Check the validity period. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
rc = check_validity_period_cm (current_time, check_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
else
rc = check_validity_period (current_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
any_expired = 1;
else if (rc)
goto leave;
/* Assert that we understand all critical extensions. */
rc = unknown_criticals (subject_cert, listmode, listfp);
if (rc)
goto leave;
/* Do a policy check. */
if (!opt.no_policy_check)
{
rc = check_cert_policy (subject_cert, listmode, listfp);
if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
{
any_no_policy_match = 1;
rc = 1; /* Be on the safe side and set RC. */
}
else if (rc)
goto leave;
}
/* If this is the root certificate we are at the end of the chain. */
if (is_root)
{
if (!istrusted_rc)
; /* No need to check the certificate for a trusted one. */
else if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
{
/* We only check the signature if the certificate is not
trusted for better diagnostics. */
do_list (1, listmode, listfp,
_("self-signed certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", subject_cert);
}
rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
: GPG_ERR_BAD_CERT);
goto leave;
}
if (!rootca_flags->relax)
{
rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp);
if (rc)
goto leave;
}
/* Set the flag for qualified signatures. This flag is
deduced from a list of root certificates allowed for
qualified signatures. */
if (is_qualified == -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
size_t buflen;
char buf[1];
if (!ksba_cert_get_user_data (cert, "is_qualified",
&buf, sizeof (buf),
&buflen) && buflen)
{
/* We already checked this for this certificate,
thus we simply take it from the user data. */
is_qualified = !!*buf;
}
else
{
/* Need to consult the list of root certificates for
qualified signatures. */
err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL);
if (!err)
is_qualified = 1;
else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND)
is_qualified = 0;
else
log_error ("checking the list of qualified "
"root certificates failed: %s\n",
gpg_strerror (err));
if ( is_qualified != -1 )
{
/* Cache the result but don't care too much
about an error. */
buf[0] = !!is_qualified;
err = ksba_cert_set_user_data (subject_cert,
"is_qualified", buf, 1);
if (err)
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
}
}
}
/* Act on the check for a trusted root certificates. */
rc = istrusted_rc;
if (!rc)
;
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
{
do_list (0, listmode, listfp,
_("root certificate is not marked trusted"));
/* If we already figured out that the certificate is
expired it does not make much sense to ask the user
whether we wants to trust the root certificate. We
should do this only if the certificate under question
will then be usable. If the certificate has a well
known private key asking the user does not make any
sense. */
if ( !any_expired
&& !gpgsm_cert_has_well_known_private_key (subject_cert)
&& (!listmode || !already_asked_marktrusted (subject_cert))
&& ask_marktrusted (ctrl, subject_cert, listmode) )
rc = 0;
}
else
{
log_error (_("checking the trust list failed: %s\n"),
gpg_strerror (rc));
}
if (rc)
goto leave;
/* Check for revocations etc. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
;
else if ((flags & VALIDATE_FLAG_STEED))
; /* Fixme: check revocations via DNS. */
else if (opt.no_trusted_cert_crl_check || rootca_flags->relax)
;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, subject_cert,
&any_revoked, &any_no_crl,
&any_crl_too_old);
if (rc)
goto leave;
break; /* Okay: a self-signed certicate is an end-point. */
} /* End is_root. */
/* Take care that the chain does not get too long. */
if ((depth+1) > maxdepth)
{
do_list (1, listmode, listfp, _("certificate chain too long\n"));
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, subject_cert, issuer, 0);
if (rc)
{
if (rc == -1)
{
do_list (0, listmode, listfp, _("issuer certificate not found"));
if (!listmode)
{
log_info ("issuer certificate: #/");
gpgsm_dump_string (issuer);
log_printf ("\n");
}
}
else
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
try_another_cert:
if (DBG_X509)
{
log_debug ("got issuer's certificate:\n");
gpgsm_dump_cert ("issuer", issuer_cert);
}
rc = gpgsm_check_cert_sig (issuer_cert, subject_cert);
if (rc)
{
do_list (0, listmode, listfp, _("certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", subject_cert);
}
if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
{
/* We now try to find other issuer certificates which
might have been used. This is required because some
CAs are reusing the issuer and subject DN for new
root certificates. */
/* FIXME: Do this only if we don't have an
AKI.keyIdentifier */
rc = find_up (ctrl, kh, subject_cert, issuer, 1);
if (!rc)
{
ksba_cert_t tmp_cert;
rc = keydb_get_cert (kh, &tmp_cert);
if (rc || !compare_certs (issuer_cert, tmp_cert))
{
/* The find next did not work or returned an
identical certificate. We better stop here
to avoid infinite checks. */
/* No need to set RC because it is not used:
rc = gpg_error (GPG_ERR_BAD_SIGNATURE); */
ksba_cert_release (tmp_cert);
}
else
{
do_list (0, listmode, listfp,
_("found another possible matching "
"CA certificate - trying again"));
ksba_cert_release (issuer_cert);
issuer_cert = tmp_cert;
goto try_another_cert;
}
}
}
/* We give a more descriptive error code than the one
returned from the signature checking. */
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
is_root = gpgsm_is_root_cert (issuer_cert);
istrusted_rc = -1;
/* Check that a CA is allowed to issue certificates. */
{
int chainlen;
rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp);
if (rc)
{
/* Not allowed. Check whether this is a trusted root
certificate and whether we allow special exceptions.
We could carry the result of the test over to the
regular root check at the top of the loop but for
clarity we won't do that. Given that the majority of
certificates carry proper BasicContraints our way of
overriding an error in the way is justified for
performance reasons. */
if (is_root)
{
if (gpgsm_cert_has_well_known_private_key (issuer_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted
(ctrl, issuer_cert, NULL, rootca_flags);
if (!istrusted_rc && rootca_flags->relax)
{
/* Ignore the error due to the relax flag. */
rc = 0;
chainlen = -1;
}
}
}
if (rc)
goto leave;
if (chainlen >= 0 && depth > chainlen)
{
do_list (1, listmode, listfp,
_("certificate chain longer than allowed by CA (%d)"),
chainlen);
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
}
/* Is the certificate allowed to sign other certificates. */
if (!listmode)
{
rc = gpgsm_cert_use_cert_p (issuer_cert);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage",
numbuf, NULL);
goto leave;
}
}
/* Check for revocations etc. Note that for a root certificate
this test is done a second time later. This should eventually
be fixed. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
rc = 0;
else if ((flags & VALIDATE_FLAG_STEED))
rc = 0; /* Fixme: XXX */
else if (is_root && (opt.no_trusted_cert_crl_check
|| (!istrusted_rc && rootca_flags->relax)))
rc = 0;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, issuer_cert,
&any_revoked, &any_no_crl, &any_crl_too_old);
if (rc)
goto leave;
if (opt.verbose && !listmode)
log_info (depth == 0 ? _("certificate is good\n") :
!is_root ? _("intermediate certificate is good\n") :
/* other */ _("root certificate is good\n"));
/* Under the chain model the next check time is the creation
time of the subject certificate. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
rc = ksba_cert_get_validity (subject_cert, 0, check_time);
if (rc)
{
/* That will never happen as we have already checked
this above. */
BUG ();
}
}
/* For the next round the current issuer becomes the new subject. */
keydb_search_reset (kh);
ksba_cert_release (subject_cert);
subject_cert = issuer_cert;
issuer_cert = NULL;
depth++;
} /* End chain traversal. */
if (!listmode && !opt.quiet)
{
if (opt.no_policy_check)
log_info ("policies not checked due to %s option\n",
"--disable-policy-checks");
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
log_info ("CRLs not checked due to %s option\n",
ctrl->offline ? "offline" : "--disable-crl-checks");
}
if (!rc)
{ /* If we encountered an error somewhere during the checks, set
the error code to the most critical one */
if (any_revoked)
rc = gpg_error (GPG_ERR_CERT_REVOKED);
else if (any_expired)
rc = gpg_error (GPG_ERR_CERT_EXPIRED);
else if (any_no_crl)
rc = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (any_crl_too_old)
rc = gpg_error (GPG_ERR_CRL_TOO_OLD);
else if (any_no_policy_match)
rc = gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
leave:
/* If we have traversed a complete chain up to the root we will
reset the ephemeral flag for all these certificates. This is done
regardless of any error because those errors may only be
transient. */
if (chain && chain->is_root)
{
gpg_error_t err;
chain_item_t ci;
for (ci = chain; ci; ci = ci->next)
{
/* Note that it is possible for the last certificate in the
chain (i.e. our target certificate) that it has not yet
been stored in the keybox and thus the flag can't be set.
We ignore this error because it will later be stored
anyway. */
err = keydb_set_cert_flags (ctrl, ci->cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (err));
}
}
/* If we have figured something about the qualified signature
capability of the certificate under question, store the result as
user data in all certificates of the chain. We do this even if the
validation itself failed. */
if (is_qualified != -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
chain_item_t ci;
char buf[1];
buf[0] = !!is_qualified;
for (ci = chain; ci; ci = ci->next)
{
err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1);
if (err)
{
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
if (!rc)
rc = err;
}
}
}
/* If auditing has been enabled, record what is in the chain. */
if (ctrl->audit)
{
chain_item_t ci;
audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN);
for (ci = chain; ci; ci = ci->next)
{
audit_log_cert (ctrl->audit,
ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT,
ci->cert, 0);
}
audit_log (ctrl->audit, AUDIT_CHAIN_END);
}
if (r_exptime)
gnupg_copy_time (r_exptime, exptime);
xfree (issuer);
xfree (subject);
keydb_release (kh);
while (chain)
{
chain_item_t ci_next = chain->next;
ksba_cert_release (chain->cert);
xfree (chain);
chain = ci_next;
}
ksba_cert_release (issuer_cert);
ksba_cert_release (subject_cert);
return rc;
}
/* Validate a certificate chain. For a description see
do_validate_chain. This function is a wrapper to handle a root
certificate with the chain_model flag set. If RETFLAGS is not
NULL, flags indicating now the verification was done are stored
there. The only defined vits for RETFLAGS are
VALIDATE_FLAG_CHAIN_MODEL and VALIDATE_FLAG_STEED.
If you are verifying a signature you should set CHECKTIME to the
creation time of the signature. If your are verifying a
certificate, set it nil (i.e. the empty string). If the creation
date of the signature is not known use the special date
"19700101T000000" which is treated in a special way here. */
int
gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
unsigned int *retflags)
{
int rc;
struct rootca_flags_s rootca_flags;
unsigned int dummy_retflags;
if (!retflags)
retflags = &dummy_retflags;
/* If the session requested a certain validation mode make sure the
corresponding flags are set. */
if (ctrl->validation_model == 1)
flags |= VALIDATE_FLAG_CHAIN_MODEL;
else if (ctrl->validation_model == 2)
flags |= VALIDATE_FLAG_STEED;
/* If the chain model was forced, set this immediately into
RETFLAGS. */
*retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL);
memset (&rootca_flags, 0, sizeof rootca_flags);
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp, flags,
&rootca_flags);
if (!rc && (flags & VALIDATE_FLAG_STEED))
{
*retflags |= VALIDATE_FLAG_STEED;
}
else if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED
&& !(flags & VALIDATE_FLAG_CHAIN_MODEL)
&& (rootca_flags.valid && rootca_flags.chain_model))
{
do_list (0, listmode, listfp, _("switching to chain model"));
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp,
(flags |= VALIDATE_FLAG_CHAIN_MODEL),
&rootca_flags);
*retflags |= VALIDATE_FLAG_CHAIN_MODEL;
}
if (opt.verbose)
do_list (0, listmode, listfp, _("validation model used: %s"),
(*retflags & VALIDATE_FLAG_STEED)?
"steed" :
(*retflags & VALIDATE_FLAG_CHAIN_MODEL)?
_("chain"):_("shell"));
return rc;
}
/* Check that the given certificate is valid but DO NOT check any
constraints. We assume that the issuers certificate is already in
the DB and that this one is valid; which it should be because it
has been checked using this function. */
int
gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert)
{
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh;
ksba_cert_t issuer_cert = NULL;
if (opt.no_chain_validation)
{
log_info ("WARNING: bypassing basic certificate checks\n");
return 0;
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (cert, issuer, subject))
{
rc = gpgsm_check_cert_sig (cert, cert);
if (rc)
{
log_error ("self-signed certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
}
else
{
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, cert, issuer, 0);
if (rc)
{
if (rc == -1)
{
log_info ("issuer certificate (#/");
gpgsm_dump_string (issuer);
log_printf (") not found\n");
}
else
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gpgsm_check_cert_sig (issuer_cert, cert);
if (rc)
{
log_error ("certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (opt.verbose)
log_info (_("certificate is good\n"));
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
ksba_cert_release (issuer_cert);
return rc;
}
/* Check whether the certificate CERT has been issued by the German
authority for qualified signature. They do not set the
basicConstraints and thus we need this workaround. It works by
looking up the root certificate and checking whether that one is
listed as a qualified certificate for Germany.
We also try to cache this data but as long as don't keep a
reference to the certificate this won't be used.
Returns: True if CERT is a RegTP issued CA cert (i.e. the root
certificate itself or one of the CAs). In that case CHAINLEN will
receive the length of the chain which is either 0 or 1.
*/
static int
get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen)
{
gpg_error_t err;
ksba_cert_t next;
int rc = 0;
int i, depth;
char country[3];
ksba_cert_t array[4];
char buf[2];
size_t buflen;
int dummy_chainlen;
if (!chainlen)
chainlen = &dummy_chainlen;
*chainlen = 0;
err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen",
&buf, sizeof (buf), &buflen);
if (!err)
{
/* Got info. */
if (buflen < 2 || !*buf)
return 0; /* Nothing found. */
*chainlen = buf[1];
return 1; /* This is a regtp CA. */
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_error ("ksba_cert_get_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
return 0; /* Nothing found. */
}
/* Need to gather the info. This requires to walk up the chain
until we have found the root. Because we are only interested in
German Bundesnetzagentur (former RegTP) derived certificates 3
levels are enough. (The German signature law demands a 3 tier
hierarchy; thus there is only one CA between the EE and the Root
CA.) */
memset (&array, 0, sizeof array);
depth = 0;
ksba_cert_ref (cert);
array[depth++] = cert;
ksba_cert_ref (cert);
while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next)))
{
ksba_cert_release (cert);
ksba_cert_ref (next);
array[depth++] = next;
cert = next;
}
ksba_cert_release (cert);
if (rc != -1 || !depth || depth == DIM(array) )
{
/* We did not reached the root. */
goto leave;
}
/* If this is a German signature law issued certificate, we store
additional information. */
if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country)
&& !strcmp (country, "de"))
{
/* Setting the pathlen for the root CA and the CA flag for the
next one is all what we need to do. */
err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen",
"\x01\x01", 2);
if (!err && depth > 1)
err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen",
"\x01\x00", 2);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
*chainlen = (depth>1? 0:1);
return 1;
}
leave:
/* Nothing special with this certificate. Mark the target
certificate anyway to avoid duplicate lookups. */
err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
return 0;
}
diff --git a/sm/certcheck.c b/sm/certcheck.c
index 04b391717..1102bccad 100644
--- a/sm/certcheck.c
+++ b/sm/certcheck.c
@@ -1,436 +1,436 @@
/* certcheck.c - check one certificate
* Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/* Return the number of bits of the Q parameter from the DSA key
KEY. */
static unsigned int
get_dsa_qbits (gcry_sexp_t key)
{
gcry_sexp_t l1, l2;
gcry_mpi_t q;
unsigned int nbits;
l1 = gcry_sexp_find_token (key, "public-key", 0);
if (!l1)
return 0; /* Does not contain a key object. */
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = gcry_sexp_find_token (l2, "q", 1);
gcry_sexp_release (l2);
if (!l1)
return 0; /* Invalid object. */
q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l1);
if (!q)
return 0; /* Missing value. */
nbits = gcry_mpi_get_nbits (q);
gcry_mpi_release (q);
return nbits;
}
static int
do_encode_md (gcry_md_hd_t md, int algo, int pkalgo, unsigned int nbits,
gcry_sexp_t pkey, gcry_mpi_t *r_val)
{
int n;
size_t nframe;
unsigned char *frame;
if (pkalgo == GCRY_PK_DSA || pkalgo == GCRY_PK_ECDSA)
{
unsigned int qbits;
if ( pkalgo == GCRY_PK_ECDSA )
qbits = gcry_pk_get_nbits (pkey);
else
qbits = get_dsa_qbits (pkey);
if ( (qbits%8) )
{
log_error(_("DSA requires the hash length to be a"
" multiple of 8 bits\n"));
return gpg_error (GPG_ERR_INTERNAL);
}
/* Don't allow any Q smaller than 160 bits. We don't want
someone to issue signatures from a key with a 16-bit Q or
something like that, which would look correct but allow
trivial forgeries. Yes, I know this rules out using MD5 with
DSA. ;) */
if (qbits < 160)
{
log_error (_("%s key uses an unsafe (%u bit) hash\n"),
gcry_pk_algo_name (pkalgo), qbits);
return gpg_error (GPG_ERR_INTERNAL);
}
/* Check if we're too short. Too long is safe as we'll
automatically left-truncate. */
nframe = gcry_md_get_algo_dlen (algo);
if (nframe < qbits/8)
{
log_error (_("a %u bit hash is not valid for a %u bit %s key\n"),
(unsigned int)nframe*8,
gcry_pk_get_nbits (pkey),
gcry_pk_algo_name (pkalgo));
/* FIXME: we need to check the requirements for ECDSA. */
if (nframe < 20 || pkalgo == GCRY_PK_DSA )
return gpg_error (GPG_ERR_INTERNAL);
}
frame = xtrymalloc (nframe);
if (!frame)
return out_of_core ();
memcpy (frame, gcry_md_read (md, algo), nframe);
n = nframe;
/* Truncate. */
if (n > qbits/8)
n = qbits/8;
}
else
{
int i;
unsigned char asn[100];
size_t asnlen;
size_t len;
nframe = (nbits+7) / 8;
asnlen = DIM(asn);
if (!algo || gcry_md_test_algo (algo))
return gpg_error (GPG_ERR_DIGEST_ALGO);
if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen))
{
log_error ("no object identifier for algo %d\n", algo);
return gpg_error (GPG_ERR_INTERNAL);
}
len = gcry_md_get_algo_dlen (algo);
if ( len + asnlen + 4 > nframe )
{
log_error ("can't encode a %d bit MD into a %d bits frame\n",
(int)(len*8), (int)nbits);
return gpg_error (GPG_ERR_INTERNAL);
}
/* We encode the MD in this way:
*
* 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes)
*
* PAD consists of FF bytes.
*/
frame = xtrymalloc (nframe);
if (!frame)
return out_of_core ();
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* block type */
i = nframe - len - asnlen -3 ;
assert ( i > 1 );
memset ( frame+n, 0xff, i ); n += i;
frame[n++] = 0;
memcpy ( frame+n, asn, asnlen ); n += asnlen;
memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len;
assert ( n == nframe );
}
if (DBG_CRYPTO)
{
int j;
log_debug ("encoded hash:");
for (j=0; j < nframe; j++)
log_printf (" %02X", frame[j]);
log_printf ("\n");
}
gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe);
xfree (frame);
return 0;
}
/* Return the public key algorithm id from the S-expression PKEY.
FIXME: libgcrypt should provide such a function. Note that this
implementation uses the names as used by libksba. */
static int
pk_algo_from_sexp (gcry_sexp_t pkey)
{
gcry_sexp_t l1, l2;
const char *name;
size_t n;
int algo;
l1 = gcry_sexp_find_token (pkey, "public-key", 0);
if (!l1)
return 0; /* Not found. */
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
name = gcry_sexp_nth_data (l2, 0, &n);
if (!name)
algo = 0; /* Not found. */
else if (n==3 && !memcmp (name, "rsa", 3))
algo = GCRY_PK_RSA;
else if (n==3 && !memcmp (name, "dsa", 3))
algo = GCRY_PK_DSA;
/* Because this function is called only for verification we can
assume that ECC actually means ECDSA. */
else if (n==3 && !memcmp (name, "ecc", 3))
algo = GCRY_PK_ECDSA;
else if (n==13 && !memcmp (name, "ambiguous-rsa", 13))
algo = GCRY_PK_RSA;
else
algo = 0;
gcry_sexp_release (l2);
return algo;
}
/* Check the signature on CERT using the ISSUER-CERT. This function
does only test the cryptographic signature and nothing else. It is
assumed that the ISSUER_CERT is valid. */
int
gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
{
const char *algoid;
gcry_md_hd_t md;
int rc, algo;
gcry_mpi_t frame;
ksba_sexp_t p;
size_t n;
gcry_sexp_t s_sig, s_hash, s_pkey;
algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert)));
if (!algo)
{
log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
return gpg_error (GPG_ERR_GENERAL);
}
rc = gcry_md_open (&md, algo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
return rc;
}
if (DBG_HASHING)
gcry_md_debug (md, "hash.cert");
rc = ksba_cert_hash (cert, 1, HASH_FNC, md);
if (rc)
{
log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
return rc;
}
gcry_md_final (md);
p = ksba_cert_get_sig_val (cert);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
gcry_md_close (md);
ksba_free (p);
return gpg_error (GPG_ERR_BUG);
}
if (DBG_CRYPTO)
{
int j;
log_debug ("signature value:");
for (j=0; j < n; j++)
log_printf (" %02X", p[j]);
log_printf ("\n");
}
rc = gcry_sexp_sscan ( &s_sig, NULL, (char*)p, n);
ksba_free (p);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
return rc;
}
p = ksba_cert_get_public_key (issuer_cert);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
gcry_md_close (md);
ksba_free (p);
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_BUG);
}
rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n);
ksba_free (p);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
gcry_sexp_release (s_sig);
return rc;
}
rc = do_encode_md (md, algo, pk_algo_from_sexp (s_pkey),
gcry_pk_get_nbits (s_pkey), s_pkey, &frame);
if (rc)
{
gcry_md_close (md);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_pkey);
return rc;
}
/* put hash into the S-Exp s_hash */
if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
BUG ();
gcry_mpi_release (frame);
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (DBG_X509)
log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc));
gcry_md_close (md);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return rc;
}
int
gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval,
gcry_md_hd_t md, int mdalgo, int *r_pkalgo)
{
int rc;
ksba_sexp_t p;
gcry_mpi_t frame;
gcry_sexp_t s_sig, s_hash, s_pkey;
size_t n;
int pkalgo;
if (r_pkalgo)
*r_pkalgo = 0;
n = gcry_sexp_canon_len (sigval, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
return gpg_error (GPG_ERR_BUG);
}
rc = gcry_sexp_sscan (&s_sig, NULL, (char*)sigval, n);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
return rc;
}
p = ksba_cert_get_public_key (cert);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
ksba_free (p);
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_BUG);
}
if (DBG_CRYPTO)
log_printhex ("public key: ", p, n);
rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n);
ksba_free (p);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
gcry_sexp_release (s_sig);
return rc;
}
pkalgo = pk_algo_from_sexp (s_pkey);
if (r_pkalgo)
*r_pkalgo = pkalgo;
rc = do_encode_md (md, mdalgo, pkalgo,
gcry_pk_get_nbits (s_pkey), s_pkey, &frame);
if (rc)
{
gcry_sexp_release (s_sig);
gcry_sexp_release (s_pkey);
return rc;
}
/* put hash into the S-Exp s_hash */
if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
BUG ();
gcry_mpi_release (frame);
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (DBG_X509)
log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc));
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return rc;
}
int
gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert,
gcry_md_hd_t md, int mdalgo,
unsigned char **r_sigval)
{
int rc;
char *grip, *desc;
size_t siglen;
grip = gpgsm_get_keygrip_hexstring (cert);
if (!grip)
return gpg_error (GPG_ERR_BAD_CERT);
desc = gpgsm_format_keydesc (cert);
rc = gpgsm_agent_pksign (ctrl, grip, desc, gcry_md_read(md, mdalgo),
gcry_md_get_algo_dlen (mdalgo), mdalgo,
r_sigval, &siglen);
xfree (desc);
xfree (grip);
return rc;
}
diff --git a/sm/certdump.c b/sm/certdump.c
index bd37da406..edee76fa2 100644
--- a/sm/certdump.c
+++ b/sm/certdump.c
@@ -1,858 +1,858 @@
/* certdump.c - Dump a certificate for debugging
* Copyright (C) 2001-2010, 2014-2015 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
struct dn_array_s {
char *key;
char *value;
int multivalued;
int done;
};
/* Print the first element of an S-Expression. */
void
gpgsm_print_serial (estream_t fp, ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
es_fputs (_("none"), fp);
else if (*p != '(')
es_fputs ("[Internal error - not an S-expression]", fp);
else
{
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p++ != ':')
es_fputs ("[Internal Error - invalid S-expression]", fp);
else
es_write_hexstring (fp, p, n, 0, NULL);
}
}
/* Dump the serial number or any other simple S-expression. */
void
gpgsm_dump_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
log_printf ("none");
else if (*p != '(')
log_printf ("ERROR - not an S-expression");
else
{
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p!=':')
log_printf ("ERROR - invalid S-expression");
else
{
for (p++; n; n--, p++)
log_printf ("%02X", *(const unsigned char *)p);
}
}
}
char *
gpgsm_format_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
char *buffer;
int i;
if (!p)
return NULL;
if (*p != '(')
BUG (); /* Not a valid S-expression. */
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p!=':')
BUG (); /* Not a valid S-expression. */
p++;
buffer = xtrymalloc (n*2+1);
if (buffer)
{
for (i=0; n; n--, p++, i+=2)
sprintf (buffer+i, "%02X", *(unsigned char *)p);
buffer[i] = 0;
}
return buffer;
}
void
gpgsm_print_time (estream_t fp, ksba_isotime_t t)
{
if (!t || !*t)
es_fputs (_("none"), fp);
else
es_fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s",
t, t+4, t+6, t+9, t+11, t+13);
}
void
gpgsm_dump_string (const char *string)
{
if (!string)
log_printf ("[error]");
else
{
const unsigned char *s;
for (s=(const unsigned char*)string; *s; s++)
{
if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
break;
}
if (!*s && *string != '[')
log_printf ("%s", string);
else
{
log_printf ( "[ ");
log_printhex (NULL, string, strlen (string));
log_printf ( " ]");
}
}
}
/* This simple dump function is mainly used for debugging purposes. */
void
gpgsm_dump_cert (const char *text, ksba_cert_t cert)
{
ksba_sexp_t sexp;
char *p;
char *dn;
ksba_isotime_t t;
log_debug ("BEGIN Certificate '%s':\n", text? text:"");
if (cert)
{
sexp = ksba_cert_get_serial (cert);
log_debug (" serial: ");
gpgsm_dump_serial (sexp);
ksba_free (sexp);
log_printf ("\n");
ksba_cert_get_validity (cert, 0, t);
log_debug (" notBefore: ");
dump_isotime (t);
log_printf ("\n");
ksba_cert_get_validity (cert, 1, t);
log_debug (" notAfter: ");
dump_isotime (t);
log_printf ("\n");
dn = ksba_cert_get_issuer (cert, 0);
log_debug (" issuer: ");
gpgsm_dump_string (dn);
ksba_free (dn);
log_printf ("\n");
dn = ksba_cert_get_subject (cert, 0);
log_debug (" subject: ");
gpgsm_dump_string (dn);
ksba_free (dn);
log_printf ("\n");
log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert));
p = gpgsm_get_fingerprint_string (cert, 0);
log_debug (" SHA1 Fingerprint: %s\n", p);
xfree (p);
}
log_debug ("END Certificate\n");
}
/* Return a new string holding the format serial number and issuer
("#SN/issuer"). No filtering on invalid characters is done.
Caller must release the string. On memory failure NULL is
returned. */
char *
gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer)
{
char *p, *p1;
if (sn && issuer)
{
p1 = gpgsm_format_serial (sn);
if (!p1)
p = xtrystrdup ("[invalid SN]");
else
{
p = xtrymalloc (strlen (p1) + strlen (issuer) + 2 + 1);
if (p)
{
*p = '#';
strcpy (stpcpy (stpcpy (p+1, p1),"/"), issuer);
}
xfree (p1);
}
}
else
p = xtrystrdup ("[invalid SN/issuer]");
return p;
}
/* Log the certificate's name in "#SN/ISSUERDN" format along with
TEXT. */
void
gpgsm_cert_log_name (const char *text, ksba_cert_t cert)
{
log_info ("%s", text? text:"certificate" );
if (cert)
{
ksba_sexp_t sn;
char *p;
p = ksba_cert_get_issuer (cert, 0);
sn = ksba_cert_get_serial (cert);
if (p && sn)
{
log_printf (" #");
gpgsm_dump_serial (sn);
log_printf ("/");
gpgsm_dump_string (p);
}
else
log_printf (" [invalid]");
ksba_free (sn);
xfree (p);
}
log_printf ("\n");
}
/* helper for the rfc2253 string parser */
static const unsigned char *
parse_dn_part (struct dn_array_s *array, const unsigned char *string)
{
static struct {
const char *label;
const char *oid;
} label_map[] = {
/* Warning: When adding new labels, make sure that the buffer
below we be allocated large enough. */
{"EMail", "1.2.840.113549.1.9.1" },
{"T", "2.5.4.12" },
{"GN", "2.5.4.42" },
{"SN", "2.5.4.4" },
{"NameDistinguisher", "0.2.262.1.10.7.20"},
{"ADDR", "2.5.4.16" },
{"BC", "2.5.4.15" },
{"D", "2.5.4.13" },
{"PostalCode", "2.5.4.17" },
{"Pseudo", "2.5.4.65" },
{"SerialNumber", "2.5.4.5" },
{NULL, NULL}
};
const unsigned char *s, *s1;
size_t n;
char *p;
int i;
/* Parse attributeType */
for (s = string+1; *s && *s != '='; s++)
;
if (!*s)
return NULL; /* error */
n = s - string;
if (!n)
return NULL; /* empty key */
/* We need to allocate a few bytes more due to the possible mapping
from the shorter OID to the longer label. */
array->key = p = xtrymalloc (n+10);
if (!array->key)
return NULL;
memcpy (p, string, n);
p[n] = 0;
trim_trailing_spaces (p);
if (digitp (p))
{
for (i=0; label_map[i].label; i++ )
if ( !strcmp (p, label_map[i].oid) )
{
strcpy (p, label_map[i].label);
break;
}
}
string = s + 1;
if (*string == '#')
{ /* hexstring */
string++;
for (s=string; hexdigitp (s); s++)
s++;
n = s - string;
if (!n || (n & 1))
return NULL; /* Empty or odd number of digits. */
n /= 2;
array->value = p = xtrymalloc (n+1);
if (!p)
return NULL;
for (s1=string; n; s1 += 2, n--, p++)
{
*(unsigned char *)p = xtoi_2 (s1);
if (!*p)
*p = 0x01; /* Better print a wrong value than truncating
the string. */
}
*p = 0;
}
else
{ /* regular v3 quoted string */
for (n=0, s=string; *s; s++)
{
if (*s == '\\')
{ /* pair */
s++;
if (*s == ',' || *s == '=' || *s == '+'
|| *s == '<' || *s == '>' || *s == '#' || *s == ';'
|| *s == '\\' || *s == '\"' || *s == ' ')
n++;
else if (hexdigitp (s) && hexdigitp (s+1))
{
s++;
n++;
}
else
return NULL; /* invalid escape sequence */
}
else if (*s == '\"')
return NULL; /* invalid encoding */
else if (*s == ',' || *s == '=' || *s == '+'
|| *s == '<' || *s == '>' || *s == ';' )
break;
else
n++;
}
array->value = p = xtrymalloc (n+1);
if (!p)
return NULL;
for (s=string; n; s++, n--)
{
if (*s == '\\')
{
s++;
if (hexdigitp (s))
{
*(unsigned char *)p++ = xtoi_2 (s);
s++;
}
else
*p++ = *s;
}
else
*p++ = *s;
}
*p = 0;
}
return s;
}
/* Parse a DN and return an array-ized one. This is not a validating
parser and it does not support any old-stylish syntax; KSBA is
expected to return only rfc2253 compatible strings. */
static struct dn_array_s *
parse_dn (const unsigned char *string)
{
struct dn_array_s *array;
size_t arrayidx, arraysize;
int i;
arraysize = 7; /* C,ST,L,O,OU,CN,email */
arrayidx = 0;
array = xtrymalloc ((arraysize+1) * sizeof *array);
if (!array)
return NULL;
while (*string)
{
while (*string == ' ')
string++;
if (!*string)
break; /* ready */
if (arrayidx >= arraysize)
{
struct dn_array_s *a2;
arraysize += 5;
a2 = xtryrealloc (array, (arraysize+1) * sizeof *array);
if (!a2)
goto failure;
array = a2;
}
array[arrayidx].key = NULL;
array[arrayidx].value = NULL;
string = parse_dn_part (array+arrayidx, string);
if (!string)
goto failure;
while (*string == ' ')
string++;
array[arrayidx].multivalued = (*string == '+');
array[arrayidx].done = 0;
arrayidx++;
if (*string && *string != ',' && *string != ';' && *string != '+')
goto failure; /* invalid delimiter */
if (*string)
string++;
}
array[arrayidx].key = NULL;
array[arrayidx].value = NULL;
return array;
failure:
for (i=0; i < arrayidx; i++)
{
xfree (array[i].key);
xfree (array[i].value);
}
xfree (array);
return NULL;
}
/* Print a DN part to STREAM. */
static void
print_dn_part (estream_t stream,
struct dn_array_s *dn, const char *key, int translate)
{
struct dn_array_s *first_dn = dn;
for (; dn->key; dn++)
{
if (!dn->done && !strcmp (dn->key, key))
{
/* Forward to the last multi-valued RDN, so that we can
print them all in reverse in the correct order. Note
that this overrides the standard sequence but that
seems to a reasonable thing to do with multi-valued
RDNs. */
while (dn->multivalued && dn[1].key)
dn++;
next:
if (!dn->done && dn->value && *dn->value)
{
es_fprintf (stream, "/%s=", dn->key);
if (translate)
print_utf8_buffer3 (stream, dn->value, strlen (dn->value),
"/");
else
es_write_sanitized (stream, dn->value, strlen (dn->value),
"/", NULL);
}
dn->done = 1;
if (dn > first_dn && dn[-1].multivalued)
{
dn--;
goto next;
}
}
}
}
/* Print all parts of a DN in a "standard" sequence. We first print
all the known parts, followed by the uncommon ones */
static void
print_dn_parts (estream_t stream,
struct dn_array_s *dn, int translate)
{
const char *stdpart[] = {
"CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL
};
int i;
for (i=0; stdpart[i]; i++)
print_dn_part (stream, dn, stdpart[i], translate);
/* Now print the rest without any specific ordering */
for (; dn->key; dn++)
print_dn_part (stream, dn, dn->key, translate);
}
/* Print the S-Expression in BUF to extended STREAM, which has a valid
length of BUFLEN, as a human readable string in one line to FP. */
static void
pretty_es_print_sexp (estream_t fp, const unsigned char *buf, size_t buflen)
{
size_t len;
gcry_sexp_t sexp;
char *result, *p;
if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
{
es_fputs (_("[Error - invalid encoding]"), fp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
assert (len);
result = xtrymalloc (len);
if (!result)
{
es_fputs (_("[Error - out of core]"), fp);
gcry_sexp_release (sexp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
assert (len);
for (p = result; len; len--, p++)
{
if (*p == '\n')
{
if (len > 1) /* Avoid printing the trailing LF. */
es_fputs ("\\n", fp);
}
else if (*p == '\r')
es_fputs ("\\r", fp);
else if (*p == '\v')
es_fputs ("\\v", fp);
else if (*p == '\t')
es_fputs ("\\t", fp);
else
es_putc (*p, fp);
}
xfree (result);
gcry_sexp_release (sexp);
}
/* This is a variant of gpgsm_print_name sending it output to an estream. */
void
gpgsm_es_print_name2 (estream_t fp, const char *name, int translate)
{
const unsigned char *s = (const unsigned char *)name;
int i;
if (!s)
{
es_fputs (_("[Error - No name]"), fp);
}
else if (*s == '<')
{
const char *s2 = strchr ( (char*)s+1, '>');
if (s2)
{
if (translate)
print_utf8_buffer (fp, s + 1, s2 - (char*)s - 1);
else
es_write_sanitized (fp, s + 1, s2 - (char*)s - 1, NULL, NULL);
}
}
else if (*s == '(')
{
pretty_es_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
}
else if (!((*s >= '0' && *s < '9')
|| (*s >= 'A' && *s <= 'Z')
|| (*s >= 'a' && *s <= 'z')))
es_fputs (_("[Error - invalid encoding]"), fp);
else
{
struct dn_array_s *dn = parse_dn (s);
if (!dn)
es_fputs (_("[Error - invalid DN]"), fp);
else
{
print_dn_parts (fp, dn, translate);
for (i=0; dn[i].key; i++)
{
xfree (dn[i].key);
xfree (dn[i].value);
}
xfree (dn);
}
}
}
void
gpgsm_es_print_name (estream_t fp, const char *name)
{
gpgsm_es_print_name2 (fp, name, 1);
}
/* A cookie structure used for the memory stream. */
struct format_name_cookie
{
char *buffer; /* Malloced buffer with the data to deliver. */
size_t size; /* Allocated size of this buffer. */
size_t len; /* strlen (buffer). */
int error; /* system error code if any. */
};
/* The writer function for the memory stream. */
static gpgrt_ssize_t
format_name_writer (void *cookie, const void *buffer, size_t size)
{
struct format_name_cookie *c = cookie;
char *p;
if (!c->buffer)
{
p = xtrymalloc (size + 1 + 1);
if (p)
{
c->size = size + 1;
c->buffer = p;
c->len = 0;
}
}
else if (c->len + size < c->len)
{
p = NULL;
gpg_err_set_errno (ENOMEM);
}
else if (c->size < c->len + size)
{
p = xtryrealloc (c->buffer, c->len + size + 1);
if (p)
{
c->size = c->len + size;
c->buffer = p;
}
}
else
p = c->buffer;
if (!p)
{
c->error = errno;
xfree (c->buffer);
c->buffer = NULL;
gpg_err_set_errno (c->error);
return -1;
}
memcpy (p + c->len, buffer, size);
c->len += size;
p[c->len] = 0; /* Terminate string. */
return (gpgrt_ssize_t)size;
}
/* Format NAME which is expected to be in rfc2253 format into a better
human readable format. Caller must free the returned string. NULL
is returned in case of an error. With TRANSLATE set to true the
name will be translated to the native encoding. Note that NAME is
internally always UTF-8 encoded. */
char *
gpgsm_format_name2 (const char *name, int translate)
{
estream_t fp;
struct format_name_cookie cookie;
es_cookie_io_functions_t io = { NULL };
memset (&cookie, 0, sizeof cookie);
io.func_write = format_name_writer;
fp = es_fopencookie (&cookie, "w", io);
if (!fp)
{
int save_errno = errno;
log_error ("error creating memory stream: %s\n", strerror (save_errno));
gpg_err_set_errno (save_errno);
return NULL;
}
gpgsm_es_print_name2 (fp, name, translate);
es_fclose (fp);
if (cookie.error || !cookie.buffer)
{
xfree (cookie.buffer);
gpg_err_set_errno (cookie.error);
return NULL;
}
return cookie.buffer;
}
char *
gpgsm_format_name (const char *name)
{
return gpgsm_format_name2 (name, 1);
}
/* Return fingerprint and a percent escaped name in a human readable
format suitable for status messages like GOODSIG. May return NULL
on error (out of core). */
char *
gpgsm_fpr_and_name_for_status (ksba_cert_t cert)
{
char *fpr, *name, *p;
char *buffer;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
return NULL;
name = ksba_cert_get_subject (cert, 0);
if (!name)
{
xfree (fpr);
return NULL;
}
p = gpgsm_format_name2 (name, 0);
ksba_free (name);
name = p;
if (!name)
{
xfree (fpr);
return NULL;
}
buffer = xtrymalloc (strlen (fpr) + 1 + 3*strlen (name) + 1);
if (buffer)
{
const char *s;
p = stpcpy (stpcpy (buffer, fpr), " ");
for (s = name; *s; s++)
{
if (*s < ' ')
{
sprintf (p, "%%%02X", *(const unsigned char*)s);
p += 3;
}
else
*p++ = *s;
}
*p = 0;
}
xfree (fpr);
xfree (name);
return buffer;
}
/* Create a key description for the CERT, this may be passed to the
pinentry. The caller must free the returned string. NULL may be
returned on error. */
char *
gpgsm_format_keydesc (ksba_cert_t cert)
{
char *name, *subject, *buffer;
ksba_isotime_t t;
char created[20];
char expires[20];
char *sn;
ksba_sexp_t sexp;
char *orig_codeset;
name = ksba_cert_get_subject (cert, 0);
subject = name? gpgsm_format_name2 (name, 0) : NULL;
ksba_free (name); name = NULL;
sexp = ksba_cert_get_serial (cert);
sn = sexp? gpgsm_format_serial (sexp) : NULL;
ksba_free (sexp);
ksba_cert_get_validity (cert, 0, t);
if (*t)
sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6);
else
*created = 0;
ksba_cert_get_validity (cert, 1, t);
if (*t)
sprintf (expires, "%.4s-%.2s-%.2s", t, t+4, t+6);
else
*expires = 0;
orig_codeset = i18n_switchto_utf8 ();
name = xtryasprintf (_("Please enter the passphrase to unlock the"
" secret key for the X.509 certificate:\n"
"\"%s\"\n"
"S/N %s, ID 0x%08lX,\n"
"created %s, expires %s.\n" ),
subject? subject:"?",
sn? sn: "?",
gpgsm_get_short_fingerprint (cert, NULL),
created, expires);
i18n_switchback (orig_codeset);
if (!name)
{
xfree (subject);
xfree (sn);
return NULL;
}
xfree (subject);
xfree (sn);
buffer = percent_plus_escape (name);
xfree (name);
return buffer;
}
diff --git a/sm/certlist.c b/sm/certlist.c
index bfc35cefc..e493cda97 100644
--- a/sm/certlist.c
+++ b/sm/certlist.c
@@ -1,570 +1,570 @@
/* certlist.c - build list of certificates
* Copyright (C) 2001, 2003, 2004, 2005, 2007,
* 2008, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
/* Return 0 if the cert is usable for encryption. A MODE of 0 checks
for signing a MODE of 1 checks for encryption, a MODE of 2 checks
for verification and a MODE of 3 for decryption (just for
debugging). MODE 4 is for certificate signing, MODE for COSP
response signing. */
static int
cert_usage_p (ksba_cert_t cert, int mode)
{
gpg_error_t err;
unsigned int use;
char *extkeyusages;
int have_ocsp_signing = 0;
err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* no policy given */
if (!err)
{
unsigned int extusemask = ~0; /* Allow all. */
if (extkeyusages)
{
char *p, *pend;
int any_critical = 0;
extusemask = 0;
p = extkeyusages;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
/* Only care about critical flagged usages. */
if ( *pend == 'C' )
{
any_critical = 1;
if ( !strcmp (p, oid_kp_serverAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_clientAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_codeSigning))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
else if ( !strcmp (p, oid_kp_emailProtection))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_timeStamping))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION);
}
/* This is a hack to cope with OCSP. Note that we do
not yet fully comply with the requirements and that
the entire CRL/OCSP checking thing should undergo a
thorough review and probably redesign. */
if ( !strcmp (p, oid_kp_ocspSigning))
have_ocsp_signing = 1;
if ((p = strchr (pend, '\n')))
p++;
}
xfree (extkeyusages);
extkeyusages = NULL;
if (!any_critical)
extusemask = ~0; /* Reset to the don't care mask. */
}
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
err = 0;
if (opt.verbose && mode < 2)
log_info (_("no key usage specified - assuming all usages\n"));
use = ~0;
}
/* Apply extKeyUsage. */
use &= extusemask;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
xfree (extkeyusages);
return err;
}
if (mode == 4)
{
if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
return 0;
log_info (_("certificate should not have "
"been used for certification\n"));
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (mode == 5)
{
if (use != ~0
&& (have_ocsp_signing
|| (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
|KSBA_KEYUSAGE_CRL_SIGN))))
return 0;
log_info (_("certificate should not have "
"been used for OCSP response signing\n"));
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if ((use & ((mode&1)?
(KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT):
(KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
)
return 0;
log_info (mode==3? _("certificate should not have been used for encryption\n"):
mode==2? _("certificate should not have been used for signing\n"):
mode==1? _("certificate is not usable for encryption\n"):
_("certificate is not usable for signing\n"));
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Return 0 if the cert is usable for signing */
int
gpgsm_cert_use_sign_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 0);
}
/* Return 0 if the cert is usable for encryption */
int
gpgsm_cert_use_encrypt_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 1);
}
int
gpgsm_cert_use_verify_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 2);
}
int
gpgsm_cert_use_decrypt_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 3);
}
int
gpgsm_cert_use_cert_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 4);
}
int
gpgsm_cert_use_ocsp_p (ksba_cert_t cert)
{
return cert_usage_p (cert, 5);
}
/* Return true if CERT has the well known private key extension. */
int
gpgsm_cert_has_well_known_private_key (ksba_cert_t cert)
{
int idx;
const char *oid;
for (idx=0; !ksba_cert_get_extension (cert, idx,
&oid, NULL, NULL, NULL);idx++)
if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.2") )
return 1; /* Yes. */
return 0; /* No. */
}
static int
same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert)
{
char *subject2 = ksba_cert_get_subject (cert, 0);
char *issuer2 = ksba_cert_get_issuer (cert, 0);
int tmp;
tmp = (subject && subject2
&& !strcmp (subject, subject2)
&& issuer && issuer2
&& !strcmp (issuer, issuer2));
xfree (subject2);
xfree (issuer2);
return tmp;
}
/* Return true if CERT_A is the same as CERT_B. */
int
gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (cert_a, &len_a);
if (img_a)
{
img_b = ksba_cert_get_image (cert_b, &len_b);
if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a))
return 1; /* Identical. */
}
return 0;
}
/* Return true if CERT is already contained in CERTLIST. */
static int
is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (cert, &len_a);
if (img_a)
{
for ( ; certlist; certlist = certlist->next)
{
img_b = ksba_cert_get_image (certlist->cert, &len_b);
if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a))
return 1; /* Already contained. */
}
}
return 0;
}
/* Add CERT to the list of certificates at CERTADDR but avoid
duplicates. */
int
gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert,
certlist_t *listaddr, int is_encrypt_to)
{
(void)ctrl;
if (!is_cert_in_certlist (cert, *listaddr))
{
certlist_t cl = xtrycalloc (1, sizeof *cl);
if (!cl)
return out_of_core ();
cl->cert = cert;
ksba_cert_ref (cert);
cl->next = *listaddr;
cl->is_encrypt_to = is_encrypt_to;
*listaddr = cl;
}
return 0;
}
/* Add a certificate to a list of certificate and make sure that it is
a valid certificate. With SECRET set to true a secret key must be
available for the certificate. IS_ENCRYPT_TO sets the corresponding
flag in the new create LISTADDR item. */
int
gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
certlist_t *listaddr, int is_encrypt_to)
{
int rc;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
ksba_cert_t cert = NULL;
rc = classify_user_id (name, &desc, 0);
if (!rc)
{
kh = keydb_new ();
if (!kh)
rc = gpg_error (GPG_ERR_ENOMEM);
else
{
int wrong_usage = 0;
char *first_subject = NULL;
char *first_issuer = NULL;
get_next:
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
rc = keydb_get_cert (kh, &cert);
if (!rc)
{
if (!first_subject)
{
/* Save the subject and the issuer for key usage
and ambiguous name tests. */
first_subject = ksba_cert_get_subject (cert, 0);
first_issuer = ksba_cert_get_issuer (cert, 0);
}
rc = secret? gpgsm_cert_use_sign_p (cert)
: gpgsm_cert_use_encrypt_p (cert);
if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE)
{
/* There might be another certificate with the
correct usage, so we try again */
if (!wrong_usage)
{ /* save the first match */
wrong_usage = rc;
ksba_cert_release (cert);
cert = NULL;
goto get_next;
}
else if (same_subject_issuer (first_subject, first_issuer,
cert))
{
wrong_usage = rc;
ksba_cert_release (cert);
cert = NULL;
goto get_next;
}
else
wrong_usage = rc;
}
}
/* We want the error code from the first match in this case. */
if (rc && wrong_usage)
rc = wrong_usage;
if (!rc)
{
certlist_t dup_certs = NULL;
next_ambigious:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else if (!rc)
{
ksba_cert_t cert2 = NULL;
/* If this is the first possible duplicate, add the original
certificate to our list of duplicates. */
if (!dup_certs)
gpgsm_add_cert_to_certlist (ctrl, cert, &dup_certs, 0);
/* We have to ignore ambigious names as long as
there only fault is a bad key usage. This is
required to support encryption and signing
certificates of the same subject.
Further we ignore them if they are due to an
identical certificate (which may happen if a
certificate is accidential duplicated in the
keybox). */
if (!keydb_get_cert (kh, &cert2))
{
int tmp = (same_subject_issuer (first_subject,
first_issuer,
cert2)
&& ((gpg_err_code (
secret? gpgsm_cert_use_sign_p (cert2)
: gpgsm_cert_use_encrypt_p (cert2)
)
) == GPG_ERR_WRONG_KEY_USAGE));
if (tmp)
gpgsm_add_cert_to_certlist (ctrl, cert2,
&dup_certs, 0);
else
{
if (is_cert_in_certlist (cert2, dup_certs))
tmp = 1;
}
ksba_cert_release (cert2);
if (tmp)
goto next_ambigious;
}
rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
gpgsm_release_certlist (dup_certs);
}
xfree (first_subject);
xfree (first_issuer);
first_subject = NULL;
first_issuer = NULL;
if (!rc && !is_cert_in_certlist (cert, *listaddr))
{
if (!rc && secret)
{
char *p;
rc = gpg_error (GPG_ERR_NO_SECKEY);
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
if (!gpgsm_agent_havekey (ctrl, p))
rc = 0;
xfree (p);
}
}
if (!rc)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL,
0, NULL, 0, NULL);
if (!rc)
{
certlist_t cl = xtrycalloc (1, sizeof *cl);
if (!cl)
rc = out_of_core ();
else
{
cl->cert = cert; cert = NULL;
cl->next = *listaddr;
cl->is_encrypt_to = is_encrypt_to;
*listaddr = cl;
}
}
}
}
}
keydb_release (kh);
ksba_cert_release (cert);
return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc;
}
void
gpgsm_release_certlist (certlist_t list)
{
while (list)
{
certlist_t cl = list->next;
ksba_cert_release (list->cert);
xfree (list);
list = cl;
}
}
/* Like gpgsm_add_to_certlist, but look only for one certificate. No
chain validation is done. If KEYID is not NULL it is taken as an
additional filter value which must match the
subjectKeyIdentifier. */
int
gpgsm_find_cert (ctrl_t ctrl,
const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert)
{
int rc;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
*r_cert = NULL;
rc = classify_user_id (name, &desc, 0);
if (!rc)
{
kh = keydb_new ();
if (!kh)
rc = gpg_error (GPG_ERR_ENOMEM);
else
{
nextone:
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
{
rc = keydb_get_cert (kh, r_cert);
if (!rc && keyid)
{
ksba_sexp_t subj;
rc = ksba_cert_get_subj_key_id (*r_cert, NULL, &subj);
if (!rc)
{
if (cmp_simple_canon_sexp (keyid, subj))
{
xfree (subj);
goto nextone;
}
xfree (subj);
/* Okay: Here we know that the certificate's
subjectKeyIdentifier matches the requested
one. */
}
else if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
goto nextone;
}
}
/* If we don't have the KEYID filter we need to check for
ambigious search results. Note, that it is somehwat
reasonable to assume that a specification of a KEYID
won't lead to ambiguous names. */
if (!rc && !keyid)
{
next_ambiguous:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else
{
if (!rc)
{
ksba_cert_t cert2 = NULL;
if (!keydb_get_cert (kh, &cert2))
{
if (gpgsm_certs_identical_p (*r_cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
ksba_cert_release (*r_cert);
*r_cert = NULL;
}
}
}
}
keydb_release (kh);
return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc;
}
diff --git a/sm/certreqgen-ui.c b/sm/certreqgen-ui.c
index b50d338ae..9772a3baf 100644
--- a/sm/certreqgen-ui.c
+++ b/sm/certreqgen-ui.c
@@ -1,431 +1,431 @@
/* certreqgen-ui.c - Simple user interface for certreqgen.c
* Copyright (C) 2007, 2010, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
-#include "i18n.h"
-#include "ttyio.h"
-#include "membuf.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
+#include "../common/membuf.h"
/* Prompt for lines and append them to MB. */
static void
ask_mb_lines (membuf_t *mb, const char *prefix)
{
char *answer = NULL;
do
{
xfree (answer);
answer = tty_get ("> ");
tty_kill_prompt ();
trim_spaces (answer);
if (*answer)
{
put_membuf_str (mb, prefix);
put_membuf_str (mb, answer);
put_membuf (mb, "\n", 1);
}
}
while (*answer);
xfree (answer);
}
/* Helper to store stuff in a membuf. */
void
store_key_value_lf (membuf_t *mb, const char *key, const char *value)
{
put_membuf_str (mb, key);
put_membuf_str (mb, value);
put_membuf (mb, "\n", 1);
}
/* Helper tp store a membuf create by mb_ask_lines into MB. Returns
-1 on error. */
int
store_mb_lines (membuf_t *mb, membuf_t *lines)
{
char *p;
if (get_membuf_len (lines))
{
put_membuf (lines, "", 1);
p = get_membuf (lines, NULL);
if (!p)
return -1;
put_membuf_str (mb, p);
xfree (p);
}
return 0;
}
/* Chech whether we have a key for the key with HEXGRIP. Returns NULL
if not or a string describing the type of the key (RSA, ELG, DSA,
etc..). */
static const char *
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
ksba_sexp_t public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = gpgsm_agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return NULL;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
switch (algo)
{
case GCRY_PK_RSA: return "RSA";
case GCRY_PK_DSA: return "DSA";
case GCRY_PK_ELG: return "ELG";
case GCRY_PK_EDDSA: return "ECDSA";
default: return NULL;
}
}
/* This function is used to create a certificate request from the
command line. In the past the similar gpgsm-gencert.sh script has
been used for it; however that scripts requires a full Unix shell
and thus is not suitable for the Windows port. So here is the
re-implementation. */
void
gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t output_stream)
{
gpg_error_t err;
char *answer;
int selection;
estream_t fp = NULL;
int method;
char *keytype_buffer = NULL;
const char *keytype;
char *keygrip = NULL;
unsigned int nbits;
int minbits = 1024;
int maxbits = 4096;
int defbits = 2048;
const char *keyusage;
char *subject_name;
membuf_t mb_email, mb_dns, mb_uri, mb_result;
char *result = NULL;
const char *s, *s2;
int selfsigned;
answer = NULL;
init_membuf (&mb_email, 100);
init_membuf (&mb_dns, 100);
init_membuf (&mb_uri, 100);
init_membuf (&mb_result, 512);
again:
/* Get the type of the key. */
tty_printf (_("Please select what kind of key you want:\n"));
tty_printf (_(" (%d) RSA\n"), 1 );
tty_printf (_(" (%d) Existing key\n"), 2 );
tty_printf (_(" (%d) Existing key from card\n"), 3 );
do
{
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
selection = *answer? atoi (answer): 1;
}
while (!(selection >= 1 && selection <= 3));
method = selection;
/* Get size of the key. */
if (method == 1)
{
keytype = "RSA";
for (;;)
{
xfree (answer);
answer = tty_getf (_("What keysize do you want? (%u) "), defbits);
tty_kill_prompt ();
trim_spaces (answer);
nbits = *answer? atoi (answer): defbits;
if (nbits < minbits || nbits > maxbits)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
"RSA", minbits, maxbits);
else
break; /* Okay. */
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
/* We round it up so that it better matches the word size. */
if (( nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
tty_printf (_("rounded up to %u bits\n"), nbits);
}
}
else if (method == 2)
{
for (;;)
{
xfree (answer);
answer = tty_get (_("Enter the keygrip: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
goto again;
else if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf (_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(keytype = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
nbits = 1024; /* A dummy value is sufficient. */
}
else /* method == 3 */
{
char *serialno;
strlist_t keypairlist, sl;
int count;
err = gpgsm_agent_scd_serialno (ctrl, &serialno);
if (err)
{
tty_printf (_("error reading the card: %s\n"), gpg_strerror (err));
goto again;
}
tty_printf (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
err = gpgsm_agent_scd_keypairinfo (ctrl, &keypairlist);
if (err)
{
tty_printf (_("error reading the card: %s\n"), gpg_strerror (err));
goto again;
}
do
{
tty_printf (_("Available keys:\n"));
for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
tty_printf (" (%d) %s\n", count, sl->d);
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
trim_spaces (answer);
selection = atoi (answer);
}
while (!(selection > 0 && selection < count));
for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
if (count == selection)
break;
s = sl->d;
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
xfree (keygrip);
keygrip = NULL;
xfree (keytype_buffer);
keytype_buffer = xasprintf ("card:%s", s);
free_strlist (keypairlist);
keytype = keytype_buffer;
nbits = 1024; /* A dummy value is sufficient. */
}
/* Ask for the key usage. */
tty_printf (_("Possible actions for a %s key:\n"), "RSA");
tty_printf (_(" (%d) sign, encrypt\n"), 1 );
tty_printf (_(" (%d) sign\n"), 2 );
tty_printf (_(" (%d) encrypt\n"), 3 );
do
{
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
trim_spaces (answer);
selection = *answer? atoi (answer): 1;
switch (selection)
{
case 1: keyusage = "sign, encrypt"; break;
case 2: keyusage = "sign"; break;
case 3: keyusage = "encrypt"; break;
default: keyusage = NULL; break;
}
}
while (!keyusage);
/* Get the subject name. */
do
{
size_t erroff, errlen;
xfree (answer);
answer = tty_get (_("Enter the X.509 subject name: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
tty_printf (_("No subject name given\n"));
else if ( (err = ksba_dn_teststr (answer, 0, &erroff, &errlen)) )
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
tty_printf (_("Invalid subject name label '%.*s'\n"),
(int)errlen, answer+erroff);
else
{
/* TRANSLATORS: The 22 in the second string is the
length of the first string up to the "%s". Please
adjust it do the length of your translation. The
second string is merely passed to atoi so you can
drop everything after the number. */
tty_printf (_("Invalid subject name '%s'\n"), answer);
tty_printf ("%*s^\n",
atoi (_("22 translator: see "
"certreg-ui.c:gpgsm_gencertreq_tty"))
+ (int)erroff, "");
}
*answer = 0;
}
}
while (!*answer);
subject_name = answer;
answer = NULL;
/* Get the email addresses. */
tty_printf (_("Enter email addresses"));
tty_printf (_(" (end with an empty line):\n"));
ask_mb_lines (&mb_email, "Name-Email: ");
/* DNS names. */
tty_printf (_("Enter DNS names"));
tty_printf (_(" (optional; end with an empty line):\n"));
ask_mb_lines (&mb_dns, "Name-DNS: ");
/* URIs. */
tty_printf (_("Enter URIs"));
tty_printf (_(" (optional; end with an empty line):\n"));
ask_mb_lines (&mb_uri, "Name-URI: ");
/* Want a self-signed certificate? */
selfsigned = tty_get_answer_is_yes
(_("Create self-signed certificate? (y/N) "));
/* Put it all together. */
store_key_value_lf (&mb_result, "Key-Type: ", keytype);
{
char numbuf[30];
snprintf (numbuf, sizeof numbuf, "%u", nbits);
store_key_value_lf (&mb_result, "Key-Length: ", numbuf);
}
if (keygrip)
store_key_value_lf (&mb_result, "Key-Grip: ", keygrip);
store_key_value_lf (&mb_result, "Key-Usage: ", keyusage);
if (selfsigned)
store_key_value_lf (&mb_result, "Serial: ", "random");
store_key_value_lf (&mb_result, "Name-DN: ", subject_name);
if (store_mb_lines (&mb_result, &mb_email))
goto mem_error;
if (store_mb_lines (&mb_result, &mb_dns))
goto mem_error;
if (store_mb_lines (&mb_result, &mb_uri))
goto mem_error;
put_membuf (&mb_result, "", 1);
result = get_membuf (&mb_result, NULL);
if (!result)
goto mem_error;
tty_printf (_("These parameters are used:\n"));
for (s=result; (s2 = strchr (s, '\n')); s = s2+1)
tty_printf (" %.*s\n", (int)(s2-s), s);
tty_printf ("\n");
if (!tty_get_answer_is_yes ("Proceed with creation? (y/N) "))
goto leave;
/* Now create a parameter file and generate the key. */
fp = es_fopenmem (0, "w+");
if (!fp)
{
log_error (_("error creating temporary file: %s\n"), strerror (errno));
goto leave;
}
es_fputs (result, fp);
es_rewind (fp);
if (selfsigned)
tty_printf ("%s", _("Now creating self-signed certificate. "));
else
tty_printf ("%s", _("Now creating certificate request. "));
tty_printf ("%s", _("This may take a while ...\n"));
{
int save_pem = ctrl->create_pem;
ctrl->create_pem = 1; /* Force creation of PEM. */
err = gpgsm_genkey (ctrl, fp, output_stream);
ctrl->create_pem = save_pem;
}
if (!err)
{
if (selfsigned)
tty_printf (_("Ready.\n"));
else
tty_printf
(_("Ready. You should now send this request to your CA.\n"));
}
goto leave;
mem_error:
log_error (_("resource problem: out of core\n"));
leave:
es_fclose (fp);
xfree (answer);
xfree (subject_name);
xfree (keytype_buffer);
xfree (keygrip);
xfree (get_membuf (&mb_email, NULL));
xfree (get_membuf (&mb_dns, NULL));
xfree (get_membuf (&mb_uri, NULL));
xfree (get_membuf (&mb_result, NULL));
xfree (result);
}
diff --git a/sm/certreqgen.c b/sm/certreqgen.c
index fe35ea8c2..44318702a 100644
--- a/sm/certreqgen.c
+++ b/sm/certreqgen.c
@@ -1,1390 +1,1390 @@
/* certreqgen.c - Generate a key and a certification [request]
* Copyright (C) 2002, 2003, 2005, 2007, 2010,
* 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
The format of the parameter file is described in the manual under
"Unattended Usage".
Here is an example:
$ cat >foo <<EOF
%echo Generating a standard key
Key-Type: RSA
Key-Length: 2048
Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Ddorf,C=DE
Name-Email: joe@foo.bar
# Do a commit here, so that we can later print a "done"
%commit
%echo done
EOF
This parameter file was used to create the STEED CA:
Key-Type: RSA
Key-Length: 1024
Key-Grip: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
Key-Usage: cert
Serial: 1
Name-DN: CN=The STEED Self-Signing Nonthority
Not-Before: 2011-11-11
Not-After: 2106-02-06
Subject-Key-Id: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
Extension: 2.5.29.19 c 30060101ff020101
Extension: 1.3.6.1.4.1.11591.2.2.2 n 0101ff
Signing-Key: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
%commit
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
enum para_name
{
pKEYTYPE,
pKEYLENGTH,
pKEYGRIP,
pKEYUSAGE,
pNAMEDN,
pNAMEEMAIL,
pNAMEDNS,
pNAMEURI,
pSERIAL,
pISSUERDN,
pNOTBEFORE,
pNOTAFTER,
pSIGNINGKEY,
pHASHALGO,
pAUTHKEYID,
pSUBJKEYID,
pEXTENSION
};
struct para_data_s
{
struct para_data_s *next;
int lnr;
enum para_name key;
union {
unsigned int usage;
char value[1];
} u;
};
struct reqgen_ctrl_s
{
int lnr;
int dryrun;
};
static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35";
static const char oidstr_subjectKeyIdentifier[] = "2.5.29.14";
static const char oidstr_keyUsage[] = "2.5.29.15";
static const char oidstr_basicConstraints[] = "2.5.29.19";
static const char oidstr_standaloneCertificate[] = "1.3.6.1.4.1.11591.2.2.1";
static int proc_parameters (ctrl_t ctrl,
struct para_data_s *para,
estream_t out_fp,
struct reqgen_ctrl_s *outctrl);
static int create_request (ctrl_t ctrl,
struct para_data_s *para,
const char *carddirect,
ksba_const_sexp_t public,
ksba_const_sexp_t sigkey,
ksba_writer_t writer);
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
xfree(r);
}
}
static struct para_data_s *
get_parameter (struct para_data_s *para, enum para_name key, int seq)
{
struct para_data_s *r;
for (r = para; r ; r = r->next)
if ( r->key == key && !seq--)
return r;
return NULL;
}
static const char *
get_parameter_value (struct para_data_s *para, enum para_name key, int seq)
{
struct para_data_s *r = get_parameter (para, key, seq);
return (r && *r->u.value)? r->u.value : NULL;
}
static int
get_parameter_algo (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
if (!r)
return -1;
if (digitp (r->u.value))
return atoi( r->u.value );
return gcry_pk_map_name (r->u.value);
}
/* Parse the usage parameter. Returns 0 on success. Note that we
only care about sign and encrypt and don't (yet) allow all the
other X.509 usage to be specified; instead we will use a fixed
mapping to the X.509 usage flags. */
static int
parse_parameter_usage (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
char *p, *pn;
unsigned int use;
if (!r)
return 0; /* none (this is an optional parameter)*/
use = 0;
pn = r->u.value;
while ( (p = strsep (&pn, " \t,")) )
{
if (!*p)
;
else if ( !ascii_strcasecmp (p, "sign") )
use |= GCRY_PK_USAGE_SIGN;
else if ( !ascii_strcasecmp (p, "encrypt")
|| !ascii_strcasecmp (p, "encr") )
use |= GCRY_PK_USAGE_ENCR;
else if ( !ascii_strcasecmp (p, "cert") )
use |= GCRY_PK_USAGE_CERT;
else
{
log_error ("line %d: invalid usage list\n", r->lnr);
return -1; /* error */
}
}
r->u.usage = use;
return 0;
}
static unsigned int
get_parameter_uint (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
if (!r)
return 0;
if (r->key == pKEYUSAGE)
return r->u.usage;
return (unsigned int)strtoul (r->u.value, NULL, 10);
}
/* Read the certificate generation parameters from FP and generate
(all) certificate requests. */
static int
read_parameters (ctrl_t ctrl, estream_t fp, estream_t out_fp)
{
static struct {
const char *name;
enum para_name key;
int allow_dups;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Grip", pKEYGRIP },
{ "Key-Usage", pKEYUSAGE },
{ "Name-DN", pNAMEDN },
{ "Name-Email", pNAMEEMAIL, 1 },
{ "Name-DNS", pNAMEDNS, 1 },
{ "Name-URI", pNAMEURI, 1 },
{ "Serial", pSERIAL },
{ "Issuer-DN", pISSUERDN },
{ "Creation-Date", pNOTBEFORE },
{ "Not-Before", pNOTBEFORE },
{ "Expire-Date", pNOTAFTER },
{ "Not-After", pNOTAFTER },
{ "Signing-Key", pSIGNINGKEY },
{ "Hash-Algo", pHASHALGO },
{ "Authority-Key-Id", pAUTHKEYID },
{ "Subject-Key-Id", pSUBJKEYID },
{ "Extension", pEXTENSION, 1 },
{ NULL, 0 }
};
char line[1024], *p;
const char *err = NULL;
struct para_data_s *para, *r;
int i, rc = 0, any = 0;
struct reqgen_ctrl_s outctrl;
memset (&outctrl, 0, sizeof (outctrl));
err = NULL;
para = NULL;
while (es_fgets (line, DIM(line)-1, fp) )
{
char *keyword, *value;
outctrl.lnr++;
if (*line && line[strlen(line)-1] != '\n')
{
err = "line too long";
break;
}
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
keyword = p;
if (*keyword == '%')
{
for (; *p && !ascii_isspace (*p); p++)
;
if (*p)
*p++ = 0;
for (; ascii_isspace (*p); p++)
;
value = p;
trim_trailing_spaces (value);
if (!ascii_strcasecmp (keyword, "%echo"))
log_info ("%s\n", value);
else if (!ascii_strcasecmp (keyword, "%dry-run"))
outctrl.dryrun = 1;
else if (!ascii_strcasecmp( keyword, "%commit"))
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
release_parameter_list (para);
para = NULL;
}
else
log_info ("skipping control '%s' (%s)\n", keyword, value);
continue;
}
if (!(p = strchr (p, ':')) || p == keyword)
{
err = "missing colon";
break;
}
if (*p)
*p++ = 0;
for (; spacep (p); p++)
;
if (!*p)
{
err = "missing argument";
break;
}
value = p;
trim_trailing_spaces (value);
for (i=0; (keywords[i].name
&& ascii_strcasecmp (keywords[i].name, keyword)); i++)
;
if (!keywords[i].name)
{
err = "unknown keyword";
break;
}
if (keywords[i].key != pKEYTYPE && !para)
{
err = "parameter block does not start with \"Key-Type\"";
break;
}
if (keywords[i].key == pKEYTYPE && para)
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
release_parameter_list (para);
para = NULL;
}
else if (!keywords[i].allow_dups)
{
for (r = para; r && r->key != keywords[i].key; r = r->next)
;
if (r)
{
err = "duplicate keyword";
break;
}
}
r = xtrycalloc (1, sizeof *r + strlen( value ));
if (!r)
{
err = "out of core";
break;
}
r->lnr = outctrl.lnr;
r->key = keywords[i].key;
strcpy (r->u.value, value);
r->next = para;
para = r;
}
if (err)
{
log_error ("line %d: %s\n", outctrl.lnr, err);
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (es_ferror(fp))
{
log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) );
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (para)
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
}
if (!rc && !any)
rc = gpg_error (GPG_ERR_NO_DATA);
leave:
release_parameter_list (para);
return rc;
}
/* check whether there are invalid characters in the email address S */
static int
has_invalid_email_chars (const char *s)
{
int at_seen=0;
static char valid_chars[] = "01234567890_-."
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (; *s; s++)
{
if (*s & 0x80)
return 1;
if (*s == '@')
at_seen++;
else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+'))
return 1;
else if (at_seen && !strchr (valid_chars, *s))
return 1;
}
return at_seen != 1;
}
/* Check that all required parameters are given and perform the action */
static int
proc_parameters (ctrl_t ctrl, struct para_data_s *para,
estream_t out_fp, struct reqgen_ctrl_s *outctrl)
{
gpg_error_t err;
struct para_data_s *r;
const char *s, *string;
int i;
unsigned int nbits;
char numbuf[20];
unsigned char keyparms[100];
int rc = 0;
ksba_sexp_t public = NULL;
ksba_sexp_t sigkey = NULL;
int seq;
size_t erroff, errlen;
char *cardkeyid = NULL;
/* Check that we have all required parameters; */
assert (get_parameter (para, pKEYTYPE, 0));
/* We can only use RSA for now. There is a problem with pkcs-10 on
how to use ElGamal because it is expected that a PK algorithm can
always be used for signing. Another problem is that on-card
generated encryption keys may not be used for signing. */
i = get_parameter_algo (para, pKEYTYPE);
if (!i && (s = get_parameter_value (para, pKEYTYPE, 0)) && *s)
{
/* Hack to allow creation of certificates directly from a smart
card. For example: "Key-Type: card:OPENPGP.3". */
if (!strncmp (s, "card:", 5) && s[5])
cardkeyid = xtrystrdup (s+5);
}
if ( (i < 1 || i != GCRY_PK_RSA) && !cardkeyid )
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: invalid algorithm\n"), r->lnr);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the keylength. NOTE: If you change this make sure that it
macthes the gpgconflist item in gpgsm.c */
if (!get_parameter (para, pKEYLENGTH, 0))
nbits = 2048;
else
nbits = get_parameter_uint (para, pKEYLENGTH);
if ((nbits < 1024 || nbits > 4096) && !cardkeyid)
{
/* The BSI specs dated 2002-11-25 don't allow lengths below 1024. */
r = get_parameter (para, pKEYLENGTH, 0);
log_error (_("line %d: invalid key length %u (valid are %d to %d)\n"),
r->lnr, nbits, 1024, 4096);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the usage. */
if (parse_parameter_usage (para, pKEYUSAGE))
{
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check that there is a subject name and that this DN fits our
requirements. */
if (!(s=get_parameter_value (para, pNAMEDN, 0)))
{
r = get_parameter (para, pNAMEDN, 0);
log_error (_("line %d: no subject name given\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
err = ksba_dn_teststr (s, 0, &erroff, &errlen);
if (err)
{
r = get_parameter (para, pNAMEDN, 0);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
log_error (_("line %d: invalid subject name label '%.*s'\n"),
r->lnr, (int)errlen, s+erroff);
else
log_error (_("line %d: invalid subject name '%s' at pos %d\n"),
r->lnr, s, (int)erroff);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check that the optional email address is okay. */
for (seq=0; (s=get_parameter_value (para, pNAMEEMAIL, seq)); seq++)
{
if (has_invalid_email_chars (s)
|| *s == '@'
|| s[strlen(s)-1] == '@'
|| s[strlen(s)-1] == '.'
|| strstr(s, ".."))
{
r = get_parameter (para, pNAMEEMAIL, seq);
log_error (_("line %d: not a valid email address\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional serial number. */
string = get_parameter_value (para, pSERIAL, 0);
if (string)
{
if (!strcmp (string, "random"))
; /* Okay. */
else
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s)
{
r = get_parameter (para, pSERIAL, 0);
log_error (_("line %d: invalid serial number\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
}
/* Check the optional issuer DN. */
string = get_parameter_value (para, pISSUERDN, 0);
if (string)
{
err = ksba_dn_teststr (string, 0, &erroff, &errlen);
if (err)
{
r = get_parameter (para, pISSUERDN, 0);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
log_error (_("line %d: invalid issuer name label '%.*s'\n"),
r->lnr, (int)errlen, string+erroff);
else
log_error (_("line %d: invalid issuer name '%s' at pos %d\n"),
r->lnr, string, (int)erroff);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional creation date. */
string = get_parameter_value (para, pNOTBEFORE, 0);
if (string && !string2isotime (NULL, string))
{
r = get_parameter (para, pNOTBEFORE, 0);
log_error (_("line %d: invalid date given\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the optional expire date. */
string = get_parameter_value (para, pNOTAFTER, 0);
if (string && !string2isotime (NULL, string))
{
r = get_parameter (para, pNOTAFTER, 0);
log_error (_("line %d: invalid date given\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Get the optional signing key. */
string = get_parameter_value (para, pSIGNINGKEY, 0);
if (string)
{
rc = gpgsm_agent_readkey (ctrl, 0, string, &sigkey);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error getting signing key by keygrip '%s'"
": %s\n"), r->lnr, s, gpg_strerror (rc));
xfree (cardkeyid);
return rc;
}
}
/* Check the optional hash-algo. */
{
int mdalgo;
string = get_parameter_value (para, pHASHALGO, 0);
if (string && !((mdalgo = gcry_md_map_name (string))
&& (mdalgo == GCRY_MD_SHA1
|| mdalgo == GCRY_MD_SHA256
|| mdalgo == GCRY_MD_SHA384
|| mdalgo == GCRY_MD_SHA512)))
{
r = get_parameter (para, pHASHALGO, 0);
log_error (_("line %d: invalid hash algorithm given\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional AuthorityKeyId. */
string = get_parameter_value (para, pAUTHKEYID, 0);
if (string)
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s || (i&1))
{
r = get_parameter (para, pAUTHKEYID, 0);
log_error (_("line %d: invalid authority-key-id\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional SubjectKeyId. */
string = get_parameter_value (para, pSUBJKEYID, 0);
if (string)
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s || (i&1))
{
r = get_parameter (para, pSUBJKEYID, 0);
log_error (_("line %d: invalid subject-key-id\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional extensions. */
for (seq=0; (string=get_parameter_value (para, pEXTENSION, seq)); seq++)
{
int okay = 0;
s = strpbrk (string, " \t:");
if (s)
{
s++;
while (spacep (s))
s++;
if (*s && strchr ("nNcC", *s))
{
s++;
while (spacep (s))
s++;
if (*s == ':')
s++;
if (*s)
{
while (spacep (s))
s++;
for (i=0; hexdigitp (s); s++, i++)
;
if (!((*s && *s != ':') || !i || (i&1)))
okay = 1;
}
}
}
if (!okay)
{
r = get_parameter (para, pEXTENSION, seq);
log_error (_("line %d: invalid extension syntax\n"), r->lnr);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Create or retrieve the public key. */
if (cardkeyid) /* Take the key from the current smart card. */
{
rc = gpgsm_agent_readkey (ctrl, 1, cardkeyid, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error reading key '%s' from card: %s\n"),
r->lnr, cardkeyid, gpg_strerror (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
else if ((s=get_parameter_value (para, pKEYGRIP, 0))) /* Use existing key.*/
{
rc = gpgsm_agent_readkey (ctrl, 0, s, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error getting key by keygrip '%s': %s\n"),
r->lnr, s, gpg_strerror (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
else if (!outctrl->dryrun) /* Generate new key. */
{
sprintf (numbuf, "%u", nbits);
snprintf ((char*)keyparms, DIM (keyparms),
"(6:genkey(3:rsa(5:nbits%d:%s)))",
(int)strlen (numbuf), numbuf);
rc = gpgsm_agent_genkey (ctrl, keyparms, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: key generation failed: %s <%s>\n"),
r->lnr, gpg_strerror (rc), gpg_strsource (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
if (!outctrl->dryrun)
{
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
int create_cert ;
create_cert = !!get_parameter_value (para, pSERIAL, 0);
ctrl->pem_name = create_cert? "CERTIFICATE" : "CERTIFICATE REQUEST";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
log_error ("can't create writer: %s\n", gpg_strerror (rc));
else
{
rc = create_request (ctrl, para, cardkeyid, public, sigkey, writer);
if (!rc)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
log_error ("write failed: %s\n", gpg_strerror (rc));
else
{
gpgsm_status (ctrl, STATUS_KEY_CREATED, "P");
log_info ("certificate%s created\n",
create_cert?"":" request");
}
}
gnupg_ksba_destroy_writer (b64writer);
}
}
xfree (sigkey);
xfree (public);
xfree (cardkeyid);
return rc;
}
/* Parameters are checked, the key pair has been created. Now
generate the request and write it out */
static int
create_request (ctrl_t ctrl,
struct para_data_s *para,
const char *carddirect,
ksba_const_sexp_t public,
ksba_const_sexp_t sigkey,
ksba_writer_t writer)
{
ksba_certreq_t cr;
gpg_error_t err;
gcry_md_hd_t md;
ksba_stop_reason_t stopreason;
int rc = 0;
const char *s, *string;
unsigned int use;
int seq;
char *buf, *p;
size_t len;
char numbuf[30];
ksba_isotime_t atime;
int certmode = 0;
int mdalgo;
err = ksba_certreq_new (&cr);
if (err)
return err;
string = get_parameter_value (para, pHASHALGO, 0);
if (string)
mdalgo = gcry_md_map_name (string);
else
mdalgo = GCRY_MD_SHA256;
rc = gcry_md_open (&md, mdalgo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (md, "cr.cri");
ksba_certreq_set_hash_function (cr, HASH_FNC, md);
ksba_certreq_set_writer (cr, writer);
err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN, 0));
if (err)
{
log_error ("error setting the subject's name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
for (seq=0; (s = get_parameter_value (para, pNAMEEMAIL, seq)); seq++)
{
buf = xtrymalloc (strlen (s) + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
*buf = '<';
strcpy (buf+1, s);
strcat (buf+1, ">");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
for (seq=0; (s = get_parameter_value (para, pNAMEDNS, seq)); seq++)
{
len = strlen (s);
assert (len);
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (11 + strlen (numbuf) + len + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
p = stpcpy (p, "(8:dns-name");
p = stpcpy (p, numbuf);
p = stpcpy (p, s);
strcpy (p, ")");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
for (seq=0; (s = get_parameter_value (para, pNAMEURI, seq)); seq++)
{
len = strlen (s);
assert (len);
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (6 + strlen (numbuf) + len + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
p = stpcpy (p, "(3:uri");
p = stpcpy (p, numbuf);
p = stpcpy (p, s);
strcpy (p, ")");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
err = ksba_certreq_set_public_key (cr, public);
if (err)
{
log_error ("error setting the public key: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* Set key usage flags. */
use = get_parameter_uint (para, pKEYUSAGE);
if (use)
{
unsigned int mask, pos;
unsigned char der[4];
der[0] = 0x03;
der[1] = 0x02;
der[2] = 0;
der[3] = 0;
if ((use & GCRY_PK_USAGE_SIGN))
{
/* For signing only we encode the bits:
KSBA_KEYUSAGE_DIGITAL_SIGNATURE
KSBA_KEYUSAGE_NON_REPUDIATION = 0b11 -> 0b11000000 */
der[3] |= 0xc0;
}
if ((use & GCRY_PK_USAGE_ENCR))
{
/* For encrypt only we encode the bits:
KSBA_KEYUSAGE_KEY_ENCIPHERMENT
KSBA_KEYUSAGE_DATA_ENCIPHERMENT = 0b1100 -> 0b00110000 */
der[3] |= 0x30;
}
if ((use & GCRY_PK_USAGE_CERT))
{
/* For certify only we encode the bits:
KSBA_KEYUSAGE_KEY_CERT_SIGN
KSBA_KEYUSAGE_CRL_SIGN = 0b1100000 -> 0b00000110 */
der[3] |= 0x06;
}
/* Count number of unused bits. */
for (mask=1, pos=0; pos < 8 * sizeof mask; pos++, mask <<= 1)
{
if ((der[3] & mask))
break;
der[2]++;
}
err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, der, 4);
if (err)
{
log_error ("error setting the key usage: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* See whether we want to create an X.509 certificate. */
string = get_parameter_value (para, pSERIAL, 0);
if (string)
{
certmode = 1;
/* Store the serial number. */
if (!strcmp (string, "random"))
{
char snbuf[3+8+1];
memcpy (snbuf, "(8:", 3);
gcry_create_nonce (snbuf+3, 8);
/* Clear high bit to guarantee a positive integer. */
snbuf[3] &= 0x7f;
snbuf[3+8] = ')';
err = ksba_certreq_set_serial (cr, snbuf);
}
else
{
char *hexbuf;
/* Allocate a buffer large enough to prefix the string with
a '0' so to have an even number of digits. Prepend two
further '0' so that the binary result will have a leading
0 byte and thus can't be the representation of a negative
number. Note that ksba_certreq_set_serial strips all
unneeded leading 0 bytes. */
hexbuf = p = xtrymalloc (2 + 1 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((strlen (string) & 1))
*p++ = '0';
*p++ = '0';
*p++ = '0';
strcpy (p, string);
for (p=hexbuf, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[len++] = xtoi_2 (p);
/* Now build the S-expression. */
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (1 + strlen (numbuf) + len + 1 + 1);
if (!buf)
{
err = gpg_error_from_syserror ();
xfree (hexbuf);
goto leave;
}
p = stpcpy (stpcpy (buf, "("), numbuf);
memcpy (p, hexbuf, len);
p += len;
strcpy (p, ")");
xfree (hexbuf);
err = ksba_certreq_set_serial (cr, buf);
xfree (buf);
}
if (err)
{
log_error ("error setting the serial number: %s\n",
gpg_strerror (err));
goto leave;
}
/* Store the issuer DN. If no issuer DN is given and no signing
key has been set we add the standalone extension and the
basic constraints to mark it as a self-signed CA
certificate. */
string = get_parameter_value (para, pISSUERDN, 0);
if (string)
{
/* Issuer DN given. Note that this may be the same as the
subject DN and thus this could as well be a self-signed
certificate. However the caller needs to explicitly
specify basicConstraints and so forth. */
err = ksba_certreq_set_issuer (cr, string);
if (err)
{
log_error ("error setting the issuer DN: %s\n",
gpg_strerror (err));
goto leave;
}
}
else if (!string && !sigkey)
{
/* Self-signed certificate requested. Add basicConstraints
and the custom GnuPG standalone extension. */
err = ksba_certreq_add_extension (cr, oidstr_basicConstraints, 1,
"\x30\x03\x01\x01\xff", 5);
if (err)
goto leave;
err = ksba_certreq_add_extension (cr, oidstr_standaloneCertificate, 0,
"\x01\x01\xff", 3);
if (err)
goto leave;
}
/* Store the creation date. */
string = get_parameter_value (para, pNOTBEFORE, 0);
if (string)
{
if (!string2isotime (atime, string))
BUG (); /* We already checked the value. */
}
else
gnupg_get_isotime (atime);
err = ksba_certreq_set_validity (cr, 0, atime);
if (err)
{
log_error ("error setting the creation date: %s\n",
gpg_strerror (err));
goto leave;
}
/* Store the expire date. If it is not given, libksba inserts a
default value. */
string = get_parameter_value (para, pNOTAFTER, 0);
if (string)
{
if (!string2isotime (atime, string))
BUG (); /* We already checked the value. */
err = ksba_certreq_set_validity (cr, 1, atime);
if (err)
{
log_error ("error setting the expire date: %s\n",
gpg_strerror (err));
goto leave;
}
}
/* Figure out the signing algorithm. If no sigkey has been
given we set it to the public key to create a self-signed
certificate. */
if (!sigkey)
sigkey = public;
{
unsigned char *siginfo;
err = transform_sigval (sigkey,
gcry_sexp_canon_len (sigkey, 0, NULL, NULL),
mdalgo, &siginfo, NULL);
if (!err)
{
err = ksba_certreq_set_siginfo (cr, siginfo);
xfree (siginfo);
}
if (err)
{
log_error ("error setting the siginfo: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Insert the AuthorityKeyId. */
string = get_parameter_value (para, pAUTHKEYID, 0);
if (string)
{
char *hexbuf;
/* Allocate a buffer for in-place conversion. We also add 4
extra bytes space for the tags and lengths fields. */
hexbuf = xtrymalloc (4 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (hexbuf+4, string);
for (p=hexbuf+4, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[4+len++] = xtoi_2 (p);
if (len > 125)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (hexbuf);
goto leave;
}
hexbuf[0] = 0x30; /* Tag for a Sequence. */
hexbuf[1] = len+2;
hexbuf[2] = 0x80; /* Context tag for an implicit Octet string. */
hexbuf[3] = len;
err = ksba_certreq_add_extension (cr, oidstr_authorityKeyIdentifier,
0,
hexbuf, 4+len);
xfree (hexbuf);
if (err)
{
log_error ("error setting the authority-key-id: %s\n",
gpg_strerror (err));
goto leave;
}
}
/* Insert the SubjectKeyId. */
string = get_parameter_value (para, pSUBJKEYID, 0);
if (string)
{
char *hexbuf;
/* Allocate a buffer for in-place conversion. We also add 2
extra bytes space for the tag and length field. */
hexbuf = xtrymalloc (2 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (hexbuf+2, string);
for (p=hexbuf+2, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[2+len++] = xtoi_2 (p);
if (len > 127)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (hexbuf);
goto leave;
}
hexbuf[0] = 0x04; /* Tag for an Octet string. */
hexbuf[1] = len;
err = ksba_certreq_add_extension (cr, oidstr_subjectKeyIdentifier, 0,
hexbuf, 2+len);
xfree (hexbuf);
if (err)
{
log_error ("error setting the subject-key-id: %s\n",
gpg_strerror (err));
goto leave;
}
}
/* Insert additional extensions. */
for (seq=0; (string = get_parameter_value (para, pEXTENSION, seq)); seq++)
{
char *hexbuf;
char *oidstr;
int crit = 0;
s = strpbrk (string, " \t:");
if (!s)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = xtrymalloc (s - string + 1);
if (!oidstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (oidstr, string, (s-string));
oidstr[(s-string)] = 0;
s++;
while (spacep (s))
s++;
if (!*s)
{
err = gpg_error (GPG_ERR_INTERNAL);
xfree (oidstr);
goto leave;
}
if (strchr ("cC", *s))
crit = 1;
s++;
while (spacep (s))
s++;
if (*s == ':')
s++;
while (spacep (s))
s++;
hexbuf = xtrystrdup (s);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
xfree (oidstr);
goto leave;
}
for (p=hexbuf, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[len++] = xtoi_2 (p);
err = ksba_certreq_add_extension (cr, oidstr, crit,
hexbuf, len);
xfree (oidstr);
xfree (hexbuf);
}
}
else
sigkey = public;
do
{
err = ksba_certreq_build (cr, &stopreason);
if (err)
{
log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
if (stopreason == KSBA_SR_NEED_SIG)
{
gcry_sexp_t s_pkey;
size_t n;
unsigned char grip[20];
char hexgrip[41];
unsigned char *sigval, *newsigval;
size_t siglen;
n = gcry_sexp_canon_len (sigkey, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)sigkey, n);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
goto leave;
}
if ( !gcry_pk_get_keygrip (s_pkey, grip) )
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error ("can't figure out the keygrip\n");
gcry_sexp_release (s_pkey);
goto leave;
}
gcry_sexp_release (s_pkey);
bin2hex (grip, 20, hexgrip);
log_info ("about to sign the %s for key: &%s\n",
certmode? "certificate":"CSR", hexgrip);
if (carddirect)
rc = gpgsm_scd_pksign (ctrl, carddirect, NULL,
gcry_md_read (md, mdalgo),
gcry_md_get_algo_dlen (mdalgo),
mdalgo,
&sigval, &siglen);
else
{
char *orig_codeset;
char *desc;
orig_codeset = i18n_switchto_utf8 ();
desc = percent_plus_escape
(_("To complete this certificate request please enter"
" the passphrase for the key you just created once"
" more.\n"));
i18n_switchback (orig_codeset);
rc = gpgsm_agent_pksign (ctrl, hexgrip, desc,
gcry_md_read(md, mdalgo),
gcry_md_get_algo_dlen (mdalgo),
mdalgo,
&sigval, &siglen);
xfree (desc);
}
if (rc)
{
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto leave;
}
err = transform_sigval (sigval, siglen, mdalgo,
&newsigval, NULL);
xfree (sigval);
if (!err)
{
err = ksba_certreq_set_sig_val (cr, newsigval);
xfree (newsigval);
}
if (err)
{
log_error ("failed to store the sig_val: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
while (stopreason != KSBA_SR_READY);
leave:
gcry_md_close (md);
ksba_certreq_release (cr);
return rc;
}
/* Create a new key by reading the parameters from IN_FP. Multiple
keys may be created */
int
gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream)
{
int rc;
rc = read_parameters (ctrl, in_stream, out_stream);
if (rc)
{
log_error ("error creating certificate request: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
goto leave;
}
leave:
return rc;
}
diff --git a/sm/decrypt.c b/sm/decrypt.c
index cda4d29cb..976bd12f8 100644
--- a/sm/decrypt.c
+++ b/sm/decrypt.c
@@ -1,592 +1,592 @@
/* decrypt.c - Decrypt a message
* Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
struct decrypt_filter_parm_s
{
int algo;
int mode;
int blklen;
gcry_cipher_hd_t hd;
char iv[16];
size_t ivlen;
int any_data; /* dod we push anything through the filter at all? */
unsigned char lastblock[16]; /* to strip the padding we have to
keep this one */
char helpblock[16]; /* needed because there is no block buffering in
libgcrypt (yet) */
int helpblocklen;
};
/* Decrypt the session key and fill in the parm structure. The
algo and the IV is expected to be already in PARM. */
static int
prepare_decryption (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
ksba_const_sexp_t enc_val,
struct decrypt_filter_parm_s *parm)
{
char *seskey = NULL;
size_t n, seskeylen;
int rc;
rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val,
&seskey, &seskeylen);
if (rc)
{
log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_CRYPTO)
log_printhex ("pkcs1 encoded session key:", seskey, seskeylen);
n=0;
if (seskeylen == 24 || seskeylen == 16)
{
/* Smells like a 3-DES or AES-128 key. This might happen
* because a SC has already done the unpacking. A better
* solution would be to test for this only after we triggered
* the GPG_ERR_INV_SESSION_KEY. */
}
else
{
if (n + 7 > seskeylen )
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
/* FIXME: Actually the leading zero is required but due to the way
we encode the output in libgcrypt as an MPI we are not able to
encode that leading zero. However, when using a Smartcard we are
doing it the right way and therefore we have to skip the zero. This
should be fixed in gpg-agent of course. */
if (!seskey[n])
n++;
if (seskey[n] != 2 ) /* Wrong block type version. */
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
for (n++; n < seskeylen && seskey[n]; n++) /* Skip the random bytes. */
;
n++; /* and the zero byte */
if (n >= seskeylen )
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
}
if (DBG_CRYPTO)
log_printhex ("session key:", seskey+n, seskeylen-n);
rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0);
if (rc)
{
log_error ("error creating decryptor: %s\n", gpg_strerror (rc));
goto leave;
}
rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n);
if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY)
{
log_info (_("WARNING: message was encrypted with "
"a weak key in the symmetric cipher.\n"));
rc = 0;
}
if (rc)
{
log_error("key setup failed: %s\n", gpg_strerror(rc) );
goto leave;
}
gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen);
leave:
xfree (seskey);
return rc;
}
/* This function is called by the KSBA writer just before the actual
write is done. The function must take INLEN bytes from INBUF,
decrypt it and store it inoutbuf which has a maximum size of
maxoutlen. The valid bytes in outbuf should be return in outlen.
Due to different buffer sizes or different length of input and
output, it may happen that fewer bytes are processed or fewer bytes
are written. */
static gpg_error_t
decrypt_filter (void *arg,
const void *inbuf, size_t inlen, size_t *inused,
void *outbuf, size_t maxoutlen, size_t *outlen)
{
struct decrypt_filter_parm_s *parm = arg;
int blklen = parm->blklen;
size_t orig_inlen = inlen;
/* fixme: Should we issue an error when we have not seen one full block? */
if (!inlen)
return gpg_error (GPG_ERR_BUG);
if (maxoutlen < 2*parm->blklen)
return gpg_error (GPG_ERR_BUG);
/* Make some space because we will later need an extra block at the end. */
maxoutlen -= blklen;
if (parm->helpblocklen)
{
int i, j;
for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++)
parm->helpblock[i] = ((const char*)inbuf)[j];
inlen -= j;
if (blklen > maxoutlen)
return gpg_error (GPG_ERR_BUG);
if (i < blklen)
{
parm->helpblocklen = i;
*outlen = 0;
}
else
{
parm->helpblocklen = 0;
if (parm->any_data)
{
memcpy (outbuf, parm->lastblock, blklen);
*outlen =blklen;
}
else
*outlen = 0;
gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen,
parm->helpblock, blklen);
parm->any_data = 1;
}
*inused = orig_inlen - inlen;
return 0;
}
if (inlen > maxoutlen)
inlen = maxoutlen;
if (inlen % blklen)
{ /* store the remainder away */
parm->helpblocklen = inlen%blklen;
inlen = inlen/blklen*blklen;
memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen);
}
*inused = inlen + parm->helpblocklen;
if (inlen)
{
assert (inlen >= blklen);
if (parm->any_data)
{
gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen,
inbuf, inlen);
memcpy (outbuf, parm->lastblock, blklen);
memcpy (parm->lastblock,(char*)outbuf+inlen, blklen);
*outlen = inlen;
}
else
{
gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen);
memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen);
*outlen = inlen - blklen;
parm->any_data = 1;
}
}
else
*outlen = 0;
return 0;
}
/* Perform a decrypt operation. */
int
gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp)
{
int rc;
gnupg_ksba_io_t b64reader = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_reader_t reader;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh;
int recp;
estream_t in_fp = NULL;
struct decrypt_filter_parm_s dfparm;
memset (&dfparm, 0, sizeof dfparm);
audit_set_type (ctrl->audit, AUDIT_TYPE_DECRYPT);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
in_fp = es_fdopen_nc (in_fd, "rb");
if (!in_fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)),
in_fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, writer);
if (rc)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
/* Parser loop. */
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_debug ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA
|| stopreason == KSBA_SR_DETACHED_DATA)
{
int algo, mode;
const char *algoid;
int any_key = 0;
audit_log (ctrl->audit, AUDIT_GOT_DATA);
algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/);
algo = gcry_cipher_map_name (algoid);
mode = gcry_cipher_mode_from_oid (algoid);
if (!algo || !mode)
{
rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("unsupported algorithm '%s'\n", algoid? algoid:"?");
if (algoid && !strcmp (algoid, "1.2.840.113549.3.2"))
log_info (_("(this is the RC2 algorithm)\n"));
else if (!algoid)
log_info (_("(this does not seem to be an encrypted"
" message)\n"));
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm",
numbuf, algoid?algoid:"?", NULL);
audit_log_s (ctrl->audit, AUDIT_BAD_DATA_CIPHER_ALGO, algoid);
}
/* If it seems that this is not an encrypted message we
return a more sensible error code. */
if (!algoid)
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
audit_log_i (ctrl->audit, AUDIT_DATA_CIPHER_ALGO, algo);
dfparm.algo = algo;
dfparm.mode = mode;
dfparm.blklen = gcry_cipher_get_algo_blklen (algo);
if (dfparm.blklen > sizeof (dfparm.helpblock))
return gpg_error (GPG_ERR_BUG);
rc = ksba_cms_get_content_enc_iv (cms,
dfparm.iv,
sizeof (dfparm.iv),
&dfparm.ivlen);
if (rc)
{
log_error ("error getting IV: %s\n", gpg_strerror (rc));
goto leave;
}
for (recp=0; !any_key; recp++)
{
char *issuer;
ksba_sexp_t serial;
ksba_sexp_t enc_val;
char *hexkeygrip = NULL;
char *desc = NULL;
char kidbuf[16+1];
*kidbuf = 0;
rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
if (rc == -1 && recp)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
if (rc)
log_error ("recp %d - error getting info: %s\n",
recp, gpg_strerror (rc));
else
{
ksba_cert_t cert = NULL;
log_debug ("recp %d - issuer: '%s'\n",
recp, issuer? issuer:"[NONE]");
log_debug ("recp %d - serial: ", recp);
gpgsm_dump_serial (serial);
log_printf ("\n");
if (ctrl->audit)
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
}
keydb_search_reset (kh);
rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial);
if (rc)
{
log_error ("failed to find the certificate: %s\n",
gpg_strerror(rc));
goto oops;
}
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("failed to get cert: %s\n", gpg_strerror (rc));
goto oops;
}
/* Print the ENC_TO status line. Note that we can
do so only if we have the certificate. This is
in contrast to gpg where the keyID is commonly
included in the encrypted messages. It is too
cumbersome to retrieve the used algorithm, thus
we don't print it for now. We also record the
keyid for later use. */
{
unsigned long kid[2];
kid[0] = gpgsm_get_short_fingerprint (cert, kid+1);
snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX",
kid[1], kid[0]);
gpgsm_status2 (ctrl, STATUS_ENC_TO,
kidbuf, "0", "0", NULL);
}
/* Put the certificate into the audit log. */
audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, 0);
/* Just in case there is a problem with the own
certificate we print this message - should never
happen of course */
rc = gpgsm_cert_use_decrypt_p (cert);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage",
numbuf, NULL);
rc = 0;
}
hexkeygrip = gpgsm_get_keygrip_hexstring (cert);
desc = gpgsm_format_keydesc (cert);
oops:
xfree (issuer);
xfree (serial);
ksba_cert_release (cert);
}
if (!hexkeygrip)
;
else if (!(enc_val = ksba_cms_get_enc_val (cms, recp)))
log_error ("recp %d - error getting encrypted session key\n",
recp);
else
{
rc = prepare_decryption (ctrl,
hexkeygrip, desc, enc_val, &dfparm);
xfree (enc_val);
if (rc)
{
log_info ("decrypting session key failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_NO_SECKEY && *kidbuf)
gpgsm_status2 (ctrl, STATUS_NO_SECKEY, kidbuf, NULL);
}
else
{ /* setup the bulk decrypter */
any_key = 1;
ksba_writer_set_filter (writer,
decrypt_filter,
&dfparm);
}
audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc);
}
xfree (hexkeygrip);
xfree (desc);
}
/* If we write an audit log add the unused recipients to the
log as well. */
if (ctrl->audit && any_key)
{
for (;; recp++)
{
char *issuer;
ksba_sexp_t serial;
int tmp_rc;
tmp_rc = ksba_cms_get_issuer_serial (cms, recp,
&issuer, &serial);
if (tmp_rc == -1)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
if (tmp_rc)
log_error ("recp %d - error getting info: %s\n",
recp, gpg_strerror (rc));
else
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
xfree (issuer);
xfree (serial);
}
}
}
if (!any_key)
{
rc = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
}
else if (stopreason == KSBA_SR_END_DATA)
{
ksba_writer_set_filter (writer, NULL, NULL);
if (dfparm.any_data)
{ /* write the last block with padding removed */
int i, npadding = dfparm.lastblock[dfparm.blklen-1];
if (!npadding || npadding > dfparm.blklen)
{
log_error ("invalid padding with value %d\n", npadding);
rc = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
rc = ksba_writer_write (writer,
dfparm.lastblock,
dfparm.blklen - npadding);
if (rc)
goto leave;
for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++)
{
if (dfparm.lastblock[i] != npadding)
{
log_error ("inconsistent padding\n");
rc = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
}
}
}
while (stopreason != KSBA_SR_READY);
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL);
leave:
audit_log_ok (ctrl->audit, AUDIT_DECRYPTION_RESULT, rc);
if (rc)
{
gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL);
log_error ("message decryption failed: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
}
ksba_cms_release (cms);
gnupg_ksba_destroy_reader (b64reader);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
es_fclose (in_fp);
if (dfparm.hd)
gcry_cipher_close (dfparm.hd);
return rc;
}
diff --git a/sm/delete.c b/sm/delete.c
index 35369683b..56d5b1f8c 100644
--- a/sm/delete.c
+++ b/sm/delete.c
@@ -1,182 +1,182 @@
/* delete.c - Delete certificates from the keybox.
* Copyright (C) 2002, 2009 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/* Delete a certificate or an secret key from a key database. */
static int
delete_one (ctrl_t ctrl, const char *username)
{
int rc = 0;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
ksba_cert_t cert = NULL;
int duplicates = 0;
int is_ephem = 0;
rc = classify_user_id (username, &desc, 0);
if (rc)
{
log_error (_("certificate '%s' not found: %s\n"),
username, gpg_strerror (rc));
gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL);
goto leave;
}
kh = keydb_new ();
if (!kh)
{
log_error ("keydb_new failed\n");
goto leave;
}
/* If the key is specified in a unique way, include ephemeral keys
in the search. */
if ( desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_FPR20
|| desc.mode == KEYDB_SEARCH_MODE_FPR16
|| desc.mode == KEYDB_SEARCH_MODE_KEYGRIP )
{
is_ephem = 1;
keydb_set_ephemeral (kh, 1);
}
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
rc = keydb_get_cert (kh, &cert);
if (!rc && !is_ephem)
{
unsigned char fpr[20];
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
next_ambigious:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else if (!rc)
{
ksba_cert_t cert2 = NULL;
unsigned char fpr2[20];
/* We ignore all duplicated certificates which might have
been inserted due to program bugs. */
if (!keydb_get_cert (kh, &cert2))
{
gpgsm_get_fingerprint (cert2, 0, fpr2, NULL);
ksba_cert_release (cert2);
if (!memcmp (fpr, fpr2, 20))
{
duplicates++;
goto next_ambigious;
}
}
rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
}
if (rc)
{
if (rc == -1)
rc = gpg_error (GPG_ERR_NO_PUBKEY);
log_error (_("certificate '%s' not found: %s\n"),
username, gpg_strerror (rc));
gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL);
goto leave;
}
/* We need to search again to get back to the right position. */
rc = keydb_lock (kh);
if (rc)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (rc));
goto leave;
}
do
{
keydb_search_reset (kh);
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc)
{
log_error ("problem re-searching certificate: %s\n",
gpg_strerror (rc));
goto leave;
}
rc = keydb_delete (kh, duplicates ? 0 : 1);
if (rc)
goto leave;
if (opt.verbose)
{
if (duplicates)
log_info (_("duplicated certificate '%s' deleted\n"), username);
else
log_info (_("certificate '%s' deleted\n"), username);
}
}
while (duplicates--);
leave:
keydb_release (kh);
ksba_cert_release (cert);
return rc;
}
/* Delete the certificates specified by NAMES. */
int
gpgsm_delete (ctrl_t ctrl, strlist_t names)
{
int rc;
if (!names)
{
log_error ("nothing to delete\n");
return gpg_error (GPG_ERR_NO_DATA);
}
for (; names; names=names->next )
{
rc = delete_one (ctrl, names->d);
if (rc)
{
log_error (_("deleting certificate \"%s\" failed: %s\n"),
names->d, gpg_strerror (rc) );
return rc;
}
}
return 0;
}
diff --git a/sm/encrypt.c b/sm/encrypt.c
index 3a7d4bb18..c43a9e6a4 100644
--- a/sm/encrypt.c
+++ b/sm/encrypt.c
@@ -1,523 +1,523 @@
/* encrypt.c - Encrypt a message
* Copyright (C) 2001, 2003, 2004, 2007, 2008,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
struct dek_s {
const char *algoid;
int algo;
gcry_cipher_hd_t chd;
char key[32];
int keylen;
char iv[32];
int ivlen;
};
typedef struct dek_s *DEK;
/* Callback parameters for the encryption. */
struct encrypt_cb_parm_s
{
estream_t fp;
DEK dek;
int eof_seen;
int ready;
int readerror;
int bufsize;
unsigned char *buffer;
int buflen;
};
/* Initialize the data encryption key (session key). */
static int
init_dek (DEK dek)
{
int rc=0, mode, i;
dek->algo = gcry_cipher_map_name (dek->algoid);
mode = gcry_cipher_mode_from_oid (dek->algoid);
if (!dek->algo || !mode)
{
log_error ("unsupported algorithm '%s'\n", dek->algoid);
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
/* Extra check for algorithms we consider to be too weak for
encryption, although we support them for decryption. Note that
there is another check below discriminating on the key length. */
switch (dek->algo)
{
case GCRY_CIPHER_DES:
case GCRY_CIPHER_RFC2268_40:
log_error ("cipher algorithm '%s' not allowed: too weak\n",
gnupg_cipher_algo_name (dek->algo));
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
default:
break;
}
dek->keylen = gcry_cipher_get_algo_keylen (dek->algo);
if (!dek->keylen || dek->keylen > sizeof (dek->key))
return gpg_error (GPG_ERR_BUG);
dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo);
if (!dek->ivlen || dek->ivlen > sizeof (dek->iv))
return gpg_error (GPG_ERR_BUG);
/* Make sure we don't use weak keys. */
if (dek->keylen < 100/8)
{
log_error ("key length of '%s' too small\n", dek->algoid);
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE);
if (rc)
{
log_error ("failed to create cipher context: %s\n", gpg_strerror (rc));
return rc;
}
for (i=0; i < 8; i++)
{
gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM );
rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen);
if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY)
break;
log_info(_("weak key created - retrying\n") );
}
if (rc)
{
log_error ("failed to set the key: %s\n", gpg_strerror (rc));
gcry_cipher_close (dek->chd);
dek->chd = NULL;
return rc;
}
gcry_create_nonce (dek->iv, dek->ivlen);
rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen);
if (rc)
{
log_error ("failed to set the IV: %s\n", gpg_strerror (rc));
gcry_cipher_close (dek->chd);
dek->chd = NULL;
return rc;
}
return 0;
}
static int
encode_session_key (DEK dek, gcry_sexp_t * r_data)
{
gcry_sexp_t data;
char *p;
int rc;
p = xtrymalloc (64 + 2 * dek->keylen);
if (!p)
return gpg_error_from_syserror ();
strcpy (p, "(data\n (flags pkcs1)\n (value #");
bin2hex (dek->key, dek->keylen, p + strlen (p));
strcat (p, "#))\n");
rc = gcry_sexp_sscan (&data, NULL, p, strlen (p));
xfree (p);
*r_data = data;
return rc;
}
/* Encrypt the DEK under the key contained in CERT and return it as a
canonical S-Exp in encval. */
static int
encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval)
{
gcry_sexp_t s_ciph, s_data, s_pkey;
int rc;
ksba_sexp_t buf;
size_t len;
*encval = NULL;
/* get the key from the cert */
buf = ksba_cert_get_public_key (cert);
if (!buf)
{
log_error ("no public key for recipient\n");
return gpg_error (GPG_ERR_NO_PUBKEY);
}
len = gcry_sexp_canon_len (buf, 0, NULL, NULL);
if (!len)
{
log_error ("libksba did not return a proper S-Exp\n");
return gpg_error (GPG_ERR_BUG);
}
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len);
xfree (buf); buf = NULL;
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
return rc;
}
/* Put the encoded cleartext into a simple list. */
s_data = NULL; /* (avoid compiler warning) */
rc = encode_session_key (dek, &s_data);
if (rc)
{
log_error ("encode_session_key failed: %s\n", gpg_strerror (rc));
return rc;
}
/* pass it to libgcrypt */
rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
/* Reformat it. */
if (!rc)
{
rc = make_canon_sexp (s_ciph, encval, NULL);
gcry_sexp_release (s_ciph);
}
return rc;
}
/* do the actual encryption */
static int
encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
{
struct encrypt_cb_parm_s *parm = cb_value;
int blklen = parm->dek->ivlen;
unsigned char *p;
size_t n;
*nread = 0;
if (!buffer)
return -1; /* not supported */
if (parm->ready)
return -1;
if (count < blklen)
BUG ();
if (!parm->eof_seen)
{ /* fillup the buffer */
p = parm->buffer;
for (n=parm->buflen; n < parm->bufsize; n++)
{
int c = es_getc (parm->fp);
if (c == EOF)
{
if (es_ferror (parm->fp))
{
parm->readerror = errno;
return -1;
}
parm->eof_seen = 1;
break;
}
p[n] = c;
}
parm->buflen = n;
}
n = parm->buflen < count? parm->buflen : count;
n = n/blklen * blklen;
if (n)
{ /* encrypt the stuff */
gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n);
*nread = n;
/* Who cares about cycles, take the easy way and shift the buffer */
parm->buflen -= n;
memmove (parm->buffer, parm->buffer+n, parm->buflen);
}
else if (parm->eof_seen)
{ /* no complete block but eof: add padding */
/* fixme: we should try to do this also in the above code path */
int i, npad = blklen - (parm->buflen % blklen);
p = parm->buffer;
for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++)
p[n] = npad;
gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n);
*nread = n;
parm->ready = 1;
}
return 0;
}
/* Perform an encrypt operation.
Encrypt the data received on DATA-FD and write it to OUT_FP. The
recipients are take from the certificate given in recplist; if this
is NULL it will be encrypted for a default recipient */
int
gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp)
{
int rc = 0;
gnupg_ksba_io_t b64writer = NULL;
gpg_error_t err;
ksba_writer_t writer;
ksba_reader_t reader = NULL;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh = NULL;
struct encrypt_cb_parm_s encparm;
DEK dek = NULL;
int recpno;
estream_t data_fp = NULL;
certlist_t cl;
int count;
memset (&encparm, 0, sizeof encparm);
audit_set_type (ctrl->audit, AUDIT_TYPE_ENCRYPT);
/* Check that the certificate list is not empty and that at least
one certificate is not flagged as encrypt_to; i.e. is a real
recipient. */
for (cl = recplist; cl; cl = cl->next)
if (!cl->is_encrypt_to)
break;
if (!cl)
{
log_error(_("no valid recipients given\n"));
gpgsm_status (ctrl, STATUS_NO_RECP, "0");
audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, 0);
rc = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
for (count = 0, cl = recplist; cl; cl = cl->next)
count++;
audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, count);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Fixme: We should use the unlocked version of the es functions. */
data_fp = es_fdopen_nc (data_fd, "rb");
if (!data_fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
err = ksba_reader_new (&reader);
if (err)
rc = err;
if (!rc)
rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm);
if (rc)
goto leave;
encparm.fp = data_fp;
ctrl->pem_name = "ENCRYPTED MESSAGE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
err = ksba_cms_new (&cms);
if (err)
{
rc = err;
goto leave;
}
err = ksba_cms_set_reader_writer (cms, reader, writer);
if (err)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
audit_log (ctrl->audit, AUDIT_GOT_DATA);
/* We are going to create enveloped data with uninterpreted data as
inner content */
err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA);
if (!err)
err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA);
if (err)
{
log_debug ("ksba_cms_set_content_type failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* Create a session key */
dek = xtrycalloc_secure (1, sizeof *dek);
if (!dek)
rc = out_of_core ();
else
{
dek->algoid = opt.def_cipher_algoid;
rc = init_dek (dek);
}
if (rc)
{
log_error ("failed to create the session key: %s\n",
gpg_strerror (rc));
goto leave;
}
err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen);
if (err)
{
log_error ("ksba_cms_set_content_enc_algo failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
encparm.dek = dek;
/* Use a ~8k (AES) or ~4k (3DES) buffer */
encparm.bufsize = 500 * dek->ivlen;
encparm.buffer = xtrymalloc (encparm.bufsize);
if (!encparm.buffer)
{
rc = out_of_core ();
goto leave;
}
audit_log_s (ctrl->audit, AUDIT_SESSION_KEY, dek->algoid);
/* Gather certificates of recipients, encrypt the session key for
each and store them in the CMS object */
for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next)
{
unsigned char *encval;
rc = encrypt_dek (dek, cl->cert, &encval);
if (rc)
{
audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc);
log_error ("encryption failed for recipient no. %d: %s\n",
recpno, gpg_strerror (rc));
goto leave;
}
err = ksba_cms_add_recipient (cms, cl->cert);
if (err)
{
audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err);
log_error ("ksba_cms_add_recipient failed: %s\n",
gpg_strerror (err));
rc = err;
xfree (encval);
goto leave;
}
err = ksba_cms_set_enc_val (cms, recpno, encval);
xfree (encval);
audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err);
if (err)
{
log_error ("ksba_cms_set_enc_val failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Main control loop for encryption. */
recpno = 0;
do
{
err = ksba_cms_build (cms, &stopreason);
if (err)
{
log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
}
while (stopreason != KSBA_SR_READY);
if (encparm.readerror)
{
log_error ("error reading input: %s\n", strerror (encparm.readerror));
rc = gpg_error (gpg_err_code_from_errno (encparm.readerror));
goto leave;
}
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_ENCRYPTION_DONE);
log_info ("encrypted data created\n");
leave:
ksba_cms_release (cms);
gnupg_ksba_destroy_writer (b64writer);
ksba_reader_release (reader);
keydb_release (kh);
xfree (dek);
es_fclose (data_fp);
xfree (encparm.buffer);
return rc;
}
diff --git a/sm/export.c b/sm/export.c
index d721d52e6..29a5ac32e 100644
--- a/sm/export.c
+++ b/sm/export.c
@@ -1,755 +1,755 @@
/* export.c - Export certificates and private keys.
* Copyright (C) 2002, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "exechelp.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/exechelp.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "minip12.h"
/* A table to store a fingerprint as used in a duplicates table. We
don't need to hash here because a fingerprint is already a perfect
hash value. This we use the most significant bits to index the
table and then use a linked list for the overflow. Possible
enhancement for very large number of certificates: Add a second
level table and then resort to a linked list. */
struct duptable_s
{
struct duptable_s *next;
/* Note that we only need to store 19 bytes because the first byte
is implictly given by the table index (we require at least 8
bits). */
unsigned char fpr[19];
};
typedef struct duptable_s *duptable_t;
#define DUPTABLE_BITS 12
#define DUPTABLE_SIZE (1 << DUPTABLE_BITS)
static void print_short_info (ksba_cert_t cert, estream_t stream);
static gpg_error_t export_p12 (ctrl_t ctrl,
const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip,
int rawmode,
void **r_result, size_t *r_resultlen);
/* Create a table used to indetify duplicated certificates. */
static duptable_t *
create_duptable (void)
{
return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t));
}
static void
destroy_duptable (duptable_t *table)
{
int idx;
duptable_t t, t2;
if (table)
{
for (idx=0; idx < DUPTABLE_SIZE; idx++)
for (t = table[idx]; t; t = t2)
{
t2 = t->next;
xfree (t);
}
xfree (table);
}
}
/* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true
if the fingerprint already exists in the table. */
static gpg_error_t
insert_duptable (duptable_t *table, unsigned char *fpr, int *exists)
{
size_t idx;
duptable_t t;
*exists = 0;
idx = fpr[0];
#if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8
#error cannot handle a table larger than 16 bits or smaller than 8 bits
#elif DUPTABLE_BITS > 8
idx <<= (DUPTABLE_BITS - 8);
idx |= (fpr[1] & ~(~0U << 4));
#endif
for (t = table[idx]; t; t = t->next)
if (!memcmp (t->fpr, fpr+1, 19))
break;
if (t)
{
*exists = 1;
return 0;
}
/* Insert that fingerprint. */
t = xtrymalloc (sizeof *t);
if (!t)
return gpg_error_from_syserror ();
memcpy (t->fpr, fpr+1, 19);
t->next = table[idx];
table[idx] = t;
return 0;
}
/* Export all certificates or just those given in NAMES. The output
is written to STREAM. */
void
gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream)
{
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
strlist_t sl;
ksba_cert_t cert = NULL;
int rc=0;
int count = 0;
int i;
duptable_t *dtable;
dtable = create_duptable ();
if (!dtable)
{
log_error ("creating duplicates table failed: %s\n", strerror (errno));
goto leave;
}
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are exported. */
if (names && ndesc)
{
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR16
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
keydb_set_ephemeral (hd, 1);
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned char fpr[20];
int exists;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
rc = insert_duptable (dtable, fpr, &exists);
if (rc)
{
log_error ("inserting into duplicates table failed: %s\n",
gpg_strerror (rc));
goto leave;
}
if (!exists && count && !ctrl->create_pem)
{
log_info ("exporting more than one certificate "
"is not possible in binary mode\n");
log_info ("ignoring other certificates\n");
break;
}
if (!exists)
{
const unsigned char *image;
size_t imagelen;
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
if (count)
es_putc ('\n', stream);
print_short_info (cert, stream);
es_putc ('\n', stream);
}
count++;
if (!b64writer)
{
ctrl->pem_name = "CERTIFICATE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 :0)),
ctrl->pem_name, stream, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
rc = ksba_writer_write (writer, image, imagelen);
if (rc)
{
log_error ("write error: %s\n", gpg_strerror (rc));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
}
ksba_cert_release (cert);
cert = NULL;
}
if (rc && rc != -1)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
else if (b64writer)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
}
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
destroy_duptable (dtable);
}
/* Export a certificate and its private key. RAWMODE controls the
actual output:
0 - Private key and certifciate in PKCS#12 format
1 - Only unencrypted private key in PKCS#8 format
2 - Only unencrypted private key in PKCS#1 format
*/
void
gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode)
{
gpg_error_t err = 0;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cert_t cert = NULL;
const unsigned char *image;
size_t imagelen;
char *keygrip = NULL;
char *prompt;
void *data;
size_t datalen;
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
desc = xtrycalloc (1, sizeof *desc);
if (!desc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
err = classify_user_id (name, desc, 0);
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
/* Lookup the certificate and make sure that it is unique. */
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
err = keydb_get_cert (hd, &cert);
if (err)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (err));
goto leave;
}
next_ambiguous:
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
ksba_cert_t cert2 = NULL;
if (!keydb_get_cert (hd, &cert2))
{
if (gpgsm_certs_identical_p (cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
else if (err == -1 || gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
}
keygrip = gpgsm_get_keygrip_hexstring (cert);
if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip))
{
/* Note, that the !keygrip case indicates a bad certificate. */
err = gpg_error (GPG_ERR_NO_SECKEY);
log_error ("can't export key '%s': %s\n", name, gpg_strerror (err));
goto leave;
}
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
print_short_info (cert, stream);
es_putc ('\n', stream);
}
if (opt.p12_charset && ctrl->create_pem && !rawmode)
{
es_fprintf (stream, "The passphrase is %s encoded.\n\n",
opt.p12_charset);
}
if (rawmode == 0)
ctrl->pem_name = "PKCS12";
else if (rawmode == 1)
ctrl->pem_name = "PRIVATE KEY";
else
ctrl->pem_name = "RSA PRIVATE KEY";
err = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, stream, &writer);
if (err)
{
log_error ("can't create writer: %s\n", gpg_strerror (err));
goto leave;
}
prompt = gpgsm_format_keydesc (cert);
err = export_p12 (ctrl, image, imagelen, prompt, keygrip, rawmode,
&data, &datalen);
xfree (prompt);
if (err)
goto leave;
err = ksba_writer_write (writer, data, datalen);
xfree (data);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
err = gnupg_ksba_finish_writer (b64writer);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
ksba_cert_release (cert);
cert = NULL;
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
}
/* Print some info about the certifciate CERT to FP or STREAM */
static void
print_short_info (ksba_cert_t cert, estream_t stream)
{
char *p;
ksba_sexp_t sexp;
int idx;
for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs ((!idx
? "Issuer ...: "
: "\n aka ...: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
es_fputs ("Serial ...: ", stream);
sexp = ksba_cert_get_serial (cert);
if (sexp)
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
es_write_hexstring (stream, s+1, len, 0, NULL);
}
xfree (sexp);
}
es_putc ('\n', stream);
for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs ((!idx
? "Subject ..: "
: "\n aka ..: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
es_fprintf (stream, "Keygrip ..: %s\n", p);
xfree (p);
}
}
/* Parse a private key S-expression and return a malloced array with
the RSA parameters in pkcs#12 order. The caller needs to
deep-release this array. */
static gcry_mpi_t *
sexp_to_kparms (gcry_sexp_t sexp)
{
gcry_sexp_t list, l2;
const char *name;
const char *s;
size_t n;
int idx;
const char *elems;
gcry_mpi_t *array;
list = gcry_sexp_find_token (sexp, "private-key", 0 );
if(!list)
return NULL;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if(!name || n != 3 || memcmp (name, "rsa", 3))
{
gcry_sexp_release (list);
return NULL;
}
/* Parameter names used with RSA in the pkcs#12 order. */
elems = "nedqp--u";
array = xtrycalloc (strlen(elems) + 1, sizeof *array);
if (!array)
{
gcry_sexp_release (list);
return NULL;
}
for (idx=0, s=elems; *s; s++, idx++ )
{
if (*s == '-')
continue; /* Computed below */
l2 = gcry_sexp_find_token (list, s, 1);
if (l2)
{
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
}
if (!array[idx]) /* Required parameter not found or invalid. */
{
for (idx=0; array[idx]; idx++)
gcry_mpi_release (array[idx]);
xfree (array);
gcry_sexp_release (list);
return NULL;
}
}
gcry_sexp_release (list);
array[5] = gcry_mpi_snew (0); /* compute d mod (q-1) */
gcry_mpi_sub_ui (array[5], array[3], 1);
gcry_mpi_mod (array[5], array[2], array[5]);
array[6] = gcry_mpi_snew (0); /* compute d mod (p-1) */
gcry_mpi_sub_ui (array[6], array[4], 1);
gcry_mpi_mod (array[6], array[3], array[6]);
return array;
}
static gpg_error_t
export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip, int rawmode,
void **r_result, size_t *r_resultlen)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_skey = NULL;
gcry_mpi_t *kparms = NULL;
unsigned char *key = NULL;
size_t keylen;
char *passphrase = NULL;
unsigned char *result = NULL;
size_t resultlen;
int i;
*r_result = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Receive the wrapped key from the agent. */
err = gpgsm_agent_export_key (ctrl, keygrip, prompt,
&wrappedkey, &wrappedkeylen);
if (err)
goto leave;
/* Unwrap the key. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
xfree (wrappedkey);
wrappedkey = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Convert to a gcrypt S-expression. */
err = gcry_sexp_create (&s_skey, key, keylen, 0, xfree_fnc);
if (err)
goto leave;
key = NULL; /* Key is now owned by S_KEY. */
/* Get the parameters from the S-expression. */
kparms = sexp_to_kparms (s_skey);
gcry_sexp_release (s_skey);
s_skey = NULL;
if (!kparms)
{
log_error ("error converting key parameters\n");
err = GPG_ERR_BAD_SECKEY;
goto leave;
}
if (rawmode)
{
/* Export in raw mode, that is only the pkcs#1/#8 private key. */
result = p12_raw_build (kparms, rawmode, &resultlen);
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 ("Please enter the passphrase to protect the "
"new PKCS#12 object."),
1, &passphrase);
if (err)
goto leave;
result = p12_build (kparms, certimg, certimglen, passphrase,
opt.p12_charset, &resultlen);
xfree (passphrase);
passphrase = NULL;
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (key);
gcry_sexp_release (s_skey);
if (kparms)
{
for (i=0; kparms[i]; i++)
gcry_mpi_release (kparms[i]);
xfree (kparms);
}
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{
/* During export this is the passphrase used to unprotect the
key and not the pkcs#12 thing as in export. Therefore we can
issue the regular passphrase status. FIXME: replace the all
zero keyid by a regular one. */
gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000");
}
if (err)
{
xfree (result);
}
else
{
*r_result = result;
*r_resultlen = resultlen;
}
return err;
}
diff --git a/sm/fingerprint.c b/sm/fingerprint.c
index d8e840592..fbcec5883 100644
--- a/sm/fingerprint.c
+++ b/sm/fingerprint.c
@@ -1,345 +1,345 @@
/* fingerprint.c - Get the fingerprint
* Copyright (C) 2001 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
-#include "host2net.h"
+#include "../common/host2net.h"
/* Return the fingerprint of the certificate (we can't put this into
libksba because we need libgcrypt support). The caller must
provide an array of sufficient length or NULL so that the function
allocates the array. If r_len is not NULL, the length of the
digest is returned; well, this can also be done by using
gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used.
If there is a problem , the function does never return NULL but a
digest of all 0xff.
*/
unsigned char *
gpgsm_get_fingerprint (ksba_cert_t cert, int algo,
unsigned char *array, int *r_len)
{
gcry_md_hd_t md;
int rc, len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len);
if (!array)
array = xmalloc (len);
if (r_len)
*r_len = len;
/* Fist check whether we have cached the fingerprint. */
if (algo == GCRY_MD_SHA1)
{
size_t buflen;
assert (len >= 20);
if (!ksba_cert_get_user_data (cert, "sha1-fingerprint",
array, len, &buflen)
&& buflen == 20)
return array;
}
/* No, need to compute it. */
rc = gcry_md_open (&md, algo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
memset (array, 0xff, len); /* better return an invalid fpr than NULL */
return array;
}
rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (rc)
{
log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
memset (array, 0xff, len); /* better return an invalid fpr than NULL */
return array;
}
gcry_md_final (md);
memcpy (array, gcry_md_read(md, algo), len );
gcry_md_close (md);
/* Cache an SHA-1 fingerprint. */
if ( algo == GCRY_MD_SHA1 )
ksba_cert_set_user_data (cert, "sha1-fingerprint", array, 20);
return array;
}
/* Return an allocated buffer with the formatted fingerprint */
char *
gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo)
{
unsigned char digest[MAX_DIGEST_LEN];
char *buf;
int len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len <= MAX_DIGEST_LEN );
gpgsm_get_fingerprint (cert, algo, digest, NULL);
buf = xmalloc (len*3+1);
bin2hexcolon (digest, len, buf);
return buf;
}
/* Return an allocated buffer with the formatted fingerprint as one
large hexnumber */
char *
gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo)
{
unsigned char digest[MAX_DIGEST_LEN];
char *buf;
int len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len <= MAX_DIGEST_LEN );
gpgsm_get_fingerprint (cert, algo, digest, NULL);
buf = xmalloc (len*2+1);
bin2hex (digest, len, buf);
return buf;
}
/* Return a certificate ID. These are the last 4 bytes of the SHA-1
fingerprint. If R_HIGH is not NULL the next 4 bytes are stored
there. */
unsigned long
gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high)
{
unsigned char digest[20];
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL);
if (r_high)
*r_high = buf32_to_ulong (digest+12);
return buf32_to_ulong (digest + 16);
}
/* Return the so called KEYGRIP which is the SHA-1 hash of the public
key parameters expressed as an canoncial encoded S-Exp. ARRAY must
be 20 bytes long. Returns ARRAY or a newly allocated buffer if ARRAY was
given as NULL. May return NULL on error. */
unsigned char *
gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array)
{
gcry_sexp_t s_pkey;
int rc;
ksba_sexp_t p;
size_t n;
p = ksba_cert_get_public_key (cert);
if (!p)
return NULL; /* oops */
if (DBG_X509)
log_debug ("get_keygrip for public key\n");
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
return NULL;
}
rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n);
xfree (p);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
return NULL;
}
array = gcry_pk_get_keygrip (s_pkey, array);
gcry_sexp_release (s_pkey);
if (!array)
{
log_error ("can't calculate keygrip\n");
return NULL;
}
if (DBG_X509)
log_printhex ("keygrip=", array, 20);
return array;
}
/* Return an allocated buffer with the keygrip of CERT encoded as a
hexstring. NULL is returned in case of error. */
char *
gpgsm_get_keygrip_hexstring (ksba_cert_t cert)
{
unsigned char grip[20];
char *buf;
if (!gpgsm_get_keygrip (cert, grip))
return NULL;
buf = xtrymalloc (20*2+1);
if (buf)
bin2hex (grip, 20, buf);
return buf;
}
/* Return the PK algorithm used by CERT as well as the length in bits
of the public key at NBITS. */
int
gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits)
{
gcry_sexp_t s_pkey;
int rc;
ksba_sexp_t p;
size_t n;
gcry_sexp_t l1, l2;
const char *name;
char namebuf[128];
if (nbits)
*nbits = 0;
p = ksba_cert_get_public_key (cert);
if (!p)
return 0;
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
xfree (p);
return 0;
}
rc = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n);
xfree (p);
if (rc)
return 0;
if (nbits)
*nbits = gcry_pk_get_nbits (s_pkey);
/* Breaking the algorithm out of the S-exp is a bit of a challenge ... */
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
{
gcry_sexp_release (s_pkey);
return 0;
}
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l2;
name = gcry_sexp_nth_data (l1, 0, &n);
if (name)
{
if (n > sizeof namebuf -1)
n = sizeof namebuf -1;
memcpy (namebuf, name, n);
namebuf[n] = 0;
}
else
*namebuf = 0;
gcry_sexp_release (l1);
gcry_sexp_release (s_pkey);
return gcry_pk_map_name (namebuf);
}
/* For certain purposes we need a certificate id which has an upper
limit of the size. We use the hash of the issuer name and the
serial number for this. In most cases the serial number is not
that large and the resulting string can be passed on an assuan
command line. Everything is hexencoded with the serialnumber
delimited from the hash by a dot.
The caller must free the string.
*/
char *
gpgsm_get_certid (ksba_cert_t cert)
{
ksba_sexp_t serial;
char *p;
char *endp;
unsigned char hash[20];
unsigned long n;
char *certid;
int i;
p = ksba_cert_get_issuer (cert, 0);
if (!p)
return NULL; /* Ooops: No issuer */
gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p));
xfree (p);
serial = ksba_cert_get_serial (cert);
if (!serial)
return NULL; /* oops: no serial number */
p = (char *)serial;
if (*p != '(')
{
log_error ("Ooops: invalid serial number\n");
xfree (serial);
return NULL;
}
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p != ':')
{
log_error ("Ooops: invalid serial number (no colon)\n");
xfree (serial);
return NULL;
}
p++;
certid = xtrymalloc ( 40 + 1 + n*2 + 1);
if (!certid)
{
xfree (serial);
return NULL; /* out of core */
}
for (i=0, endp = certid; i < 20; i++, endp += 2 )
sprintf (endp, "%02X", hash[i]);
*endp++ = '.';
for (i=0; i < n; i++, endp += 2)
sprintf (endp, "%02X", ((unsigned char*)p)[i]);
*endp = 0;
xfree (serial);
return certid;
}
diff --git a/sm/gpgsm.c b/sm/gpgsm.c
index 34a9b966d..bcf479ac5 100644
--- a/sm/gpgsm.c
+++ b/sm/gpgsm.c
@@ -1,2227 +1,2227 @@
/* gpgsm.c - GnuPG for S/MIME
* Copyright (C) 2001-2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2008, 2010 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
/*#include <mcheck.h>*/
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "passphrase.h"
#include "../common/shareddefs.h"
#include "../kbx/keybox.h" /* malloc hooks */
-#include "i18n.h"
+#include "../common/i18n.h"
#include "keydb.h"
-#include "sysutils.h"
-#include "gc-opt-flags.h"
-#include "asshelp.h"
+#include "../common/sysutils.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/asshelp.h"
#include "../common/init.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
enum cmd_and_opt_values {
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
aListKeys = 'k',
aListSecretKeys = 'K',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
aSign = 's',
oUser = 'u',
oVerbose = 'v',
oBatch = 500,
aClearsign,
aKeygen,
aSignEncr,
aDeleteKey,
aImport,
aVerify,
aListExternalKeys,
aListChain,
aSendKeys,
aRecvKeys,
aExport,
aExportSecretKeyP12,
aExportSecretKeyP8,
aExportSecretKeyRaw,
aServer,
aLearnCard,
aCallDirmngr,
aCallProtectTool,
aPasswd,
aGPGConfList,
aGPGConfTest,
aDumpKeys,
aDumpChain,
aDumpSecretKeys,
aDumpExternalKeys,
aKeydbClearSomeCertFlags,
aFingerprint,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oDebugNoChainValidation,
oDebugIgnoreExpiration,
oLogFile,
oNoLogFile,
oAuditLog,
oHtmlAuditLog,
oEnableSpecialFilenames,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oPreferSystemDirmngr,
oDirmngrProgram,
oDisableDirmngr,
oProtectToolProgram,
oFakedSystemTime,
oPassphraseFD,
oPinentryMode,
oAssumeArmor,
oAssumeBase64,
oAssumeBinary,
oBase64,
oNoArmor,
oP12Charset,
oCompliance,
oDisableCRLChecks,
oEnableCRLChecks,
oDisableTrustedCertCRLCheck,
oEnableTrustedCertCRLCheck,
oForceCRLRefresh,
oDisableOCSP,
oEnableOCSP,
oIncludeCerts,
oPolicyFile,
oDisablePolicyChecks,
oEnablePolicyChecks,
oAutoIssuerKeyRetrieve,
oWithFingerprint,
oWithMD5Fingerprint,
oWithKeygrip,
oWithSecret,
oAnswerYes,
oAnswerNo,
oKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oStatusFD,
oCipherAlgo,
oDigestAlgo,
oExtraDigestAlgo,
oNoVerbose,
oNoSecmemWarn,
oNoDefKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oWithColons,
oWithKeyData,
oWithValidation,
oWithEphemeralKeys,
oSkipVerify,
oValidationModel,
oKeyServer,
oEncryptTo,
oNoEncryptTo,
oLoggerFD,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oIgnoreTimeConflict,
oNoRandomSeedFile,
oNoCommonCertsImport,
oIgnoreCertExtension,
oNoAutostart
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
/*ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ),*/
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
/*ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),*/
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aVerify, "verify", N_("verify a signature")),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListExternalKeys, "list-external-keys",
N_("list external keys")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")),
ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aDeleteKey, "delete-keys",
N_("remove keys from the public keyring")),
/*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/
/*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/
ARGPARSE_c (aImport, "import", N_("import certificates")),
ARGPARSE_c (aExport, "export", N_("export certificates")),
/* We use -raw and not -p1 for pkcs#1 secret key export so that it
won't accidentally be used in case -p12 was intended. */
ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"),
ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"),
ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"),
ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aCallDirmngr, "call-dirmngr",
N_("pass a command to the dirmngr")),
ARGPARSE_c (aCallProtectTool, "call-protect-tool",
N_("invoke gpg-protect-tool")),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aDumpKeys, "dump-cert", "@"),
ARGPARSE_c (aDumpKeys, "dump-keys", "@"),
ARGPARSE_c (aDumpChain, "dump-chain", "@"),
ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"),
ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"),
ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")),
ARGPARSE_s_s (oP12Charset, "p12-charset", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_s_n (oAssumeArmor, "assume-armor",
N_("assume input is in PEM format")),
ARGPARSE_s_n (oAssumeBase64, "assume-base64",
N_("assume input is in base-64 format")),
ARGPARSE_s_n (oAssumeBinary, "assume-binary",
N_("assume input is in binary format")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"),
ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks",
N_("never consult a CRL")),
ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"),
ARGPARSE_s_n (oDisableTrustedCertCRLCheck,
"disable-trusted-cert-crl-check", "@"),
ARGPARSE_s_n (oEnableTrustedCertCRLCheck,
"enable-trusted-cert-crl-check", "@"),
ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"),
ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"),
ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")),
ARGPARSE_s_s (oValidationModel, "validation-model", "@"),
ARGPARSE_s_i (oIncludeCerts, "include-certs",
N_("|N|number of certificates to include") ),
ARGPARSE_s_s (oPolicyFile, "policy-file",
N_("|FILE|take policy information from FILE")),
ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks",
N_("do not check certificate policies")),
ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"),
ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve",
N_("fetch missing issuer certificates")),
ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
ARGPARSE_s_s (oUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write a server mode log to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oAuditLog, "audit-log",
N_("|FILE|write an audit log to FILE")),
ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")),
ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")),
ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")),
ARGPARSE_s_s (oKeyring, "keyring",
N_("|FILE|add keyring to the list of keyrings")),
ARGPARSE_s_s (oDefaultKey, "default-key",
N_("|USER-ID|use USER-ID as default secret key")),
/* Not yet used: */
/* ARGPARSE_s_s (oDefRecipient, "default-recipient", */
/* N_("|NAME|use NAME as default recipient")), */
/* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */
/* N_("use the default key as default recipient")), */
/* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */
ARGPARSE_s_s (oKeyServer, "keyserver",
N_("|SPEC|use this keyserver to lookup keys")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"),
ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo",
N_("|NAME|use cipher algorithm NAME")),
ARGPARSE_s_s (oDigestAlgo, "digest-algo",
N_("|NAME|use message digest algorithm NAME")),
ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
/* Hidden options. */
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_n (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"),
ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithValidation, "with-validation", "@"),
ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"),
ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"),
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"),
ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
/* Command aliases. */
ARGPARSE_c (aListKeys, "list-key", "@"),
ARGPARSE_c (aListChain, "list-signatures", "@"),
ARGPARSE_c (aListChain, "list-sigs", "@"),
ARGPARSE_c (aListChain, "check-signatures", "@"),
ARGPARSE_c (aListChain, "check-sigs", "@"),
ARGPARSE_c (aDeleteKey, "delete-key", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* Global variable to keep an error count. */
int gpgsm_errors_seen = 0;
/* It is possible that we are currentlu running under setuid permissions */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug*/
static const char *debug_level;
static unsigned int debug_value;
/* Default value for include-certs. We need an extra macro for
gpgconf-list because the variable will be changed by the command
line option.
It is often cumbersome to locate intermediate certificates, thus by
default we include all certificates in the chain. However we leave
out the root certificate because that would make it too easy for
the recipient to import that root certificate. A root certificate
should be installed only after due checks and thus it won't help to
send it along with each message. */
#define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */
static int default_include_certs = DEFAULT_INCLUDE_CERTS;
/* Whether the chain mode shall be used for validation. */
static int default_validation_model;
/* The default cipher algo. */
#define DEFAULT_CIPHER_ALGO "AES"
static char *build_list (const char *text,
const char *(*mapf)(int), int (*chkf)(int));
static void set_cmd (enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void emergency_cleanup (void);
static int open_read (const char *filename);
static estream_t open_es_fread (const char *filename, const char *mode);
static estream_t open_es_fwrite (const char *filename);
static void run_protect_tool (int argc, char **argv);
static int
our_pk_test_algo (int algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_ECDSA:
return gcry_pk_test_algo (algo);
default:
return 1;
}
}
static int
our_cipher_test_algo (int algo)
{
switch (algo)
{
case GCRY_CIPHER_3DES:
case GCRY_CIPHER_AES128:
case GCRY_CIPHER_AES192:
case GCRY_CIPHER_AES256:
case GCRY_CIPHER_SERPENT128:
case GCRY_CIPHER_SERPENT192:
case GCRY_CIPHER_SERPENT256:
case GCRY_CIPHER_SEED:
case GCRY_CIPHER_CAMELLIA128:
case GCRY_CIPHER_CAMELLIA192:
case GCRY_CIPHER_CAMELLIA256:
return gcry_cipher_test_algo (algo);
default:
return 1;
}
}
static int
our_md_test_algo (int algo)
{
switch (algo)
{
case GCRY_MD_MD5:
case GCRY_MD_SHA1:
case GCRY_MD_RMD160:
case GCRY_MD_SHA224:
case GCRY_MD_SHA256:
case GCRY_MD_SHA384:
case GCRY_MD_SHA512:
case GCRY_MD_WHIRLPOOL:
return gcry_md_test_algo (algo);
default:
return 1;
}
}
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers;
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 11: p = "@GPGSM@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGSM@ [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGSM@ [options] [files]\n"
"Sign, check, encrypt or decrypt using the S/MIME protocol\n"
"Default operation depends on the input data\n");
break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!ciphers)
ciphers = build_list ("Cipher: ", gnupg_cipher_algo_name,
our_cipher_test_algo );
p = ciphers;
break;
case 35:
if (!pubkeys)
pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name,
our_pk_test_algo );
p = pubkeys;
break;
case 36:
if (!digests)
digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo );
p = digests;
break;
default: p = NULL; break;
}
return p;
}
static char *
build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int))
{
int i;
size_t n=strlen(text)+2;
char *list, *p;
if (maybe_setuid) {
gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */
}
for (i=1; i < 400; i++ )
if (!chkf(i))
n += strlen(mapf(i)) + 2;
list = xmalloc (21 + n);
*list = 0;
for (p=NULL, i=1; i < 400; i++)
{
if (!chkf(i))
{
if( !p )
p = stpcpy (list, text );
else
p = stpcpy (p, ", ");
p = stpcpy (p, mapf(i) );
}
}
if (p)
strcpy (p, "\n" );
return list;
}
/* Set the file pointer into binary mode if required. */
static void
set_binary (FILE *fp)
{
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (fp), O_BINARY);
#else
(void)fp;
#endif
}
static void
wrong_args (const char *text)
{
fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text);
gpgsm_exit (2);
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
gpgsm_exit (2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else if ( cmd == aSign && new_cmd == aEncr )
cmd = aSignEncr;
else if ( cmd == aEncr && new_cmd == aSign )
cmd = aSignEncr;
else if ( (cmd == aSign && new_cmd == aClearsign)
|| (cmd == aClearsign && new_cmd == aSign) )
cmd = aClearsign;
else
{
log_error(_("conflicting commands\n"));
gpgsm_exit(2);
}
*ret_cmd = cmd;
}
/* Helper to add recipients to a list. */
static void
do_add_recipient (ctrl_t ctrl, const char *name,
certlist_t *recplist, int is_encrypt_to, int recp_required)
{
int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to);
if (rc)
{
if (recp_required)
{
log_error ("can't encrypt to '%s': %s\n", name, gpg_strerror (rc));
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), name, NULL);
}
else
log_info (_("Note: won't be able to encrypt to '%s': %s\n"),
name, gpg_strerror (rc));
}
}
static void
parse_validation_model (const char *model)
{
int i = gpgsm_parse_validation_model (model);
if (i == -1)
log_error (_("unknown validation model '%s'\n"), model);
else
default_validation_model = i;
}
/* Release the list of SERVERS. As usual it is okay to call this
function with SERVERS passed as NULL. */
void
keyserver_list_free (struct keyserver_spec *servers)
{
while (servers)
{
struct keyserver_spec *tmp = servers->next;
xfree (servers->host);
xfree (servers->user);
if (servers->pass)
memset (servers->pass, 0, strlen (servers->pass));
xfree (servers->pass);
xfree (servers->base);
xfree (servers);
servers = tmp;
}
}
/* See also dirmngr ldapserver_parse_one(). */
struct keyserver_spec *
parse_keyserver_line (char *line,
const char *filename, unsigned int lineno)
{
char *p;
char *endp;
struct keyserver_spec *server;
int fieldno;
int fail = 0;
/* Parse the colon separated fields. */
server = xcalloc (1, sizeof *server);
for (fieldno = 1, p = line; p; p = endp, fieldno++ )
{
endp = strchr (p, ':');
if (endp)
*endp++ = '\0';
trim_spaces (p);
switch (fieldno)
{
case 1:
if (*p)
server->host = xstrdup (p);
else
{
log_error (_("%s:%u: no hostname given\n"),
filename, lineno);
fail = 1;
}
break;
case 2:
if (*p)
server->port = atoi (p);
break;
case 3:
if (*p)
server->user = xstrdup (p);
break;
case 4:
if (*p && !server->user)
{
log_error (_("%s:%u: password given without user\n"),
filename, lineno);
fail = 1;
}
else if (*p)
server->pass = xstrdup (p);
break;
case 5:
if (*p)
server->base = xstrdup (p);
break;
default:
/* (We silently ignore extra fields.) */
break;
}
}
if (fail)
{
log_info (_("%s:%u: skipping this line\n"), filename, lineno);
keyserver_list_free (server);
server = NULL;
}
return server;
}
int
main ( int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
/* char *username;*/
int may_coredump;
strlist_t sl, remusr= NULL, locusr=NULL;
strlist_t nrings=NULL;
int detached_sig = 0;
FILE *configfp = NULL;
char *configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int no_more_options = 0;
int default_config =1;
int default_keyring = 1;
char *logfile = NULL;
char *auditlog = NULL;
char *htmlauditlog = NULL;
int greeting = 0;
int nogreeting = 0;
int debug_wait = 0;
int use_random_seed = 1;
int no_common_certs_import = 0;
int with_fpr = 0;
const char *forced_digest_algo = NULL;
const char *extra_digest_algo = NULL;
enum cmd_and_opt_values cmd = 0;
struct server_control_s ctrl;
certlist_t recplist = NULL;
certlist_t signerlist = NULL;
int do_not_setup_keys = 0;
int recp_required = 0;
estream_t auditfp = NULL;
estream_t htmlauditfp = NULL;
struct assuan_malloc_hooks malloc_hooks;
int pwfd = -1;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (GPGSM_NAME);
/* trap_unaligned ();*/
gnupg_rl_initialize ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to secmem_init()
somewhere after the option parsing */
log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
/* Check that the libraries are suitable. Do it here because the
option parse may need services of the library */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lockfile cleanup. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* Note: If you change this default cipher algorithm , please
remember to update the Gpgconflist entry as well. */
opt.def_cipher_algoid = DEFAULT_CIPHER_ALGO;
/* First check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* yes there is one, so we do not try the default one but
read the config file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
{
default_config = 0; /* --no-options */
opt.no_homedir_creation = 1;
}
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
else if (pargs.r_opt == aCallProtectTool)
break; /* This break makes sure that --version and --help are
passed to the protect-tool. */
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
/* Setup a default control structure for command line mode */
memset (&ctrl, 0, sizeof ctrl);
gpgsm_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
ctrl.autodetect_encoding = 1;
/* Set the default option file */
if (default_config )
configname = make_filename (gnupg_homedir (),
GPGSM_NAME EXTSEP_S "conf", NULL);
/* Set the default policy file */
opt.policy_file = make_filename (gnupg_homedir (), "policies.txt", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* do not remove the args */
next_pass:
if (configname) {
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if (parse_debug)
log_info (_("Note: no default option file '%s'\n"), configname);
}
else
{
log_error (_("option file '%s': %s\n"), configname, strerror(errno));
gpgsm_exit(2);
}
xfree(configname);
configname = NULL;
}
if (parse_debug && configname)
log_info (_("reading options from '%s'\n"), configname);
default_config = 0;
}
while (!no_more_options
&& optfile_parse (configfp, configname, &configlineno, &pargs, opts))
{
switch (pargs.r_opt)
{
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
do_not_setup_keys = 1;
nogreeting = 1;
break;
case aServer:
opt.batch = 1;
set_cmd (&cmd, aServer);
break;
case aCallDirmngr:
opt.batch = 1;
set_cmd (&cmd, aCallDirmngr);
do_not_setup_keys = 1;
break;
case aCallProtectTool:
opt.batch = 1;
set_cmd (&cmd, aCallProtectTool);
no_more_options = 1; /* Stop parsing. */
do_not_setup_keys = 1;
break;
case aDeleteKey:
set_cmd (&cmd, aDeleteKey);
/*greeting=1;*/
do_not_setup_keys = 1;
break;
case aDetachedSign:
detached_sig = 1;
set_cmd (&cmd, aSign );
break;
case aKeygen:
set_cmd (&cmd, aKeygen);
greeting=1;
do_not_setup_keys = 1;
break;
case aImport:
case aSendKeys:
case aRecvKeys:
case aExport:
case aExportSecretKeyP12:
case aExportSecretKeyP8:
case aExportSecretKeyRaw:
case aDumpKeys:
case aDumpChain:
case aDumpExternalKeys:
case aDumpSecretKeys:
case aListKeys:
case aListExternalKeys:
case aListSecretKeys:
case aListChain:
case aLearnCard:
case aPasswd:
case aKeydbClearSomeCertFlags:
do_not_setup_keys = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aEncr:
recp_required = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aSym:
case aDecrypt:
case aSign:
case aClearsign:
case aVerify:
set_cmd (&cmd, pargs.r_opt);
break;
/* Output encoding selection. */
case oArmor:
ctrl.create_pem = 1;
break;
case oBase64:
ctrl.create_pem = 0;
ctrl.create_base64 = 1;
break;
case oNoArmor:
ctrl.create_pem = 0;
ctrl.create_base64 = 0;
break;
case oP12Charset:
opt.p12_charset = pargs.r.ret_str;
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
/* Input encoding selection. */
case oAssumeArmor:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 1;
ctrl.is_base64 = 0;
break;
case oAssumeBase64:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 1;
break;
case oAssumeBinary:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 0;
break;
case oDisableCRLChecks:
opt.no_crl_check = 1;
break;
case oEnableCRLChecks:
opt.no_crl_check = 0;
break;
case oDisableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 1;
break;
case oEnableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 0;
break;
case oForceCRLRefresh:
opt.force_crl_refresh = 1;
break;
case oDisableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 0;
break;
case oEnableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 1;
break;
case oIncludeCerts:
ctrl.include_certs = default_include_certs = pargs.r.ret_int;
break;
case oPolicyFile:
xfree (opt.policy_file);
if (*pargs.r.ret_str)
opt.policy_file = xstrdup (pargs.r.ret_str);
else
opt.policy_file = NULL;
break;
case oDisablePolicyChecks:
opt.no_policy_check = 1;
break;
case oEnablePolicyChecks:
opt.no_policy_check = 0;
break;
case oAutoIssuerKeyRetrieve:
opt.auto_issuer_key_retrieve = 1;
break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: /* fixme:tty_no_terminal(1);*/ break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oAuditLog: auditlog = pargs.r.ret_str; break;
case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break;
case oBatch:
opt.batch = 1;
greeting = 0;
break;
case oNoBatch: opt.batch = 0; break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oDebugNoChainValidation: opt.no_chain_validation = 1; break;
case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break;
case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
case oWithMD5Fingerprint:
opt.with_md5_fingerprint=1; /*fall through*/
case oWithFingerprint:
with_fpr=1; /*fall through*/
case aFingerprint:
opt.fingerprint++;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup (pargs.r.ret_str);
goto next_pass;
}
break;
case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oPreferSystemDirmngr: /* Obsolete */; break;
case oProtectToolProgram:
opt.protect_tool_program = pargs.r.ret_str;
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoDefKeyring: default_keyring = 0; break;
case oNoGreeting: nogreeting = 1; break;
case oDefaultKey:
if (*pargs.r.ret_str)
{
xfree (opt.local_user);
opt.local_user = xstrdup (pargs.r.ret_str);
}
break;
case oDefRecipient:
if (*pargs.r.ret_str)
opt.def_recipient = xstrdup (pargs.r.ret_str);
break;
case oDefRecipientSelf:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 1;
break;
case oNoDefRecipient:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 0;
break;
case oWithKeyData: opt.with_key_data=1; /* fall through */
case oWithColons: ctrl.with_colons = 1; break;
case oWithSecret: ctrl.with_secret = 1; break;
case oWithValidation: ctrl.with_validation=1; break;
case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oNoEncryptTo: opt.no_encrypt_to = 1; break;
case oEncryptTo: /* Store the recipient in the second list */
sl = add_to_strlist (&remusr, pargs.r.ret_str);
sl->flags = 1;
break;
case oRecipient: /* store the recipient */
add_to_strlist ( &remusr, pargs.r.ret_str);
break;
case oUser: /* Store the local users, the first one is the default */
if (!opt.local_user)
opt.local_user = xstrdup (pargs.r.ret_str);
add_to_strlist (&locusr, pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oCipherAlgo:
opt.def_cipher_algoid = pargs.r.ret_str;
break;
case oDisableCipherAlgo:
{
int algo = gcry_cipher_map_name (pargs.r.ret_str);
gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oDisablePubkeyAlgo:
{
int algo = gcry_pk_map_name (pargs.r.ret_str);
gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo );
}
break;
case oDigestAlgo:
forced_digest_algo = pargs.r.ret_str;
break;
case oExtraDigestAlgo:
extra_digest_algo = pargs.r.ret_str;
break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oNoCommonCertsImport: no_common_certs_import = 1; break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oValidationModel: parse_validation_model (pargs.r.ret_str); break;
case oKeyServer:
{
struct keyserver_spec *keyserver;
keyserver = parse_keyserver_line (pargs.r.ret_str,
configname, configlineno);
if (! keyserver)
log_error (_("could not parse keyserver\n"));
else
{
/* FIXME: Keep last next pointer. */
struct keyserver_spec **next_p = &opt.keyserver;
while (*next_p)
next_p = &(*next_p)->next;
*next_p = keyserver;
}
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str);
break;
case oNoAutostart: opt.autostart = 0; break;
case oCompliance:
/* Dummy option for now. */
break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the config filename. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
GPGSM_NAME EXTSEP_S "conf",
NULL);
if (log_get_errorcount(0))
gpgsm_exit(2);
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
/* Now that we have the options parsed we need to update the default
control structure. */
gpgsm_init_default_ctrl (&ctrl);
if (nogreeting)
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
# ifdef IS_DEVELOPMENT_VERSION
if (!opt.batch)
{
log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n");
log_info ("It is only intended for test purposes and should NOT be\n");
log_info ("used in a production environment or with production keys!\n");
}
# endif
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
/* if (opt.qualsig_approval && !opt.quiet) */
/* log_info (_("This software has officially been approved to " */
/* "create and verify\n" */
/* "qualified signatures according to German law.\n")); */
if (logfile && cmd == aServer)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
/*FIXME if (opt.batch) */
/* tty_batchmode (1); */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
set_debug ();
/* Although we always use gpgsm_exit, we better install a regualr
exit handler so that at least the secure memory gets wiped
out. */
if (atexit (emergency_cleanup))
{
log_error ("atexit failed\n");
gpgsm_exit (2);
}
/* Must do this after dropping setuid, because the mapping functions
may try to load an module and we may have disabled an algorithm.
We remap the commonly used algorithms to the OIDs for
convenience. We need to work with the OIDs because they are used
to check whether the encryption mode is actually available. */
if (!strcmp (opt.def_cipher_algoid, "3DES") )
opt.def_cipher_algoid = "1.2.840.113549.3.7";
else if (!strcmp (opt.def_cipher_algoid, "AES")
|| !strcmp (opt.def_cipher_algoid, "AES128"))
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2";
else if (!strcmp (opt.def_cipher_algoid, "AES192") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22";
else if (!strcmp (opt.def_cipher_algoid, "AES256") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT")
|| !strcmp (opt.def_cipher_algoid, "SERPENT128") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT256") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42";
else if (!strcmp (opt.def_cipher_algoid, "SEED") )
opt.def_cipher_algoid = "1.2.410.200004.1.4";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA")
|| !strcmp (opt.def_cipher_algoid, "CAMELLIA128") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4";
if (cmd != aGPGConfList)
{
if ( !gcry_cipher_map_name (opt.def_cipher_algoid)
|| !gcry_cipher_mode_from_oid (opt.def_cipher_algoid))
log_error (_("selected cipher algorithm is invalid\n"));
if (forced_digest_algo)
{
opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo);
if (our_md_test_algo(opt.forced_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
if (extra_digest_algo)
{
opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo);
if (our_md_test_algo (opt.extra_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
}
if (log_get_errorcount(0))
gpgsm_exit(2);
/* Set the random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
if (!cmd && opt.fingerprint && !with_fpr)
set_cmd (&cmd, aListKeys);
/* Add default keybox. */
if (!nrings && default_keyring)
{
int created;
keydb_add_resource (&ctrl, "pubring.kbx", 0, &created);
if (created && !no_common_certs_import)
{
/* Import the standard certificates for a new default keybox. */
char *filelist[2];
filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL);
filelist[1] = NULL;
if (!access (filelist[0], F_OK))
{
log_info (_("importing common certificates '%s'\n"),
filelist[0]);
gpgsm_import_files (&ctrl, 1, filelist, open_read);
}
xfree (filelist[0]);
}
}
for (sl = nrings; sl; sl = sl->next)
keydb_add_resource (&ctrl, sl->d, 0, NULL);
FREE_STRLIST(nrings);
/* Prepare the audit log feature for certain commands. */
if (auditlog || htmlauditlog)
{
switch (cmd)
{
case aEncr:
case aSign:
case aDecrypt:
case aVerify:
audit_release (ctrl.audit);
ctrl.audit = audit_new ();
if (auditlog)
auditfp = open_es_fwrite (auditlog);
if (htmlauditlog)
htmlauditfp = open_es_fwrite (htmlauditlog);
break;
default:
break;
}
}
if (!do_not_setup_keys)
{
for (sl = locusr; sl ; sl = sl->next)
{
int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0);
if (rc)
{
log_error (_("can't sign using '%s': %s\n"),
sl->d, gpg_strerror (rc));
gpgsm_status2 (&ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), sl->d, NULL);
gpgsm_status2 (&ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), sl->d, NULL);
}
}
/* Build the recipient list. We first add the regular ones and then
the encrypt-to ones because the underlying function will silently
ignore duplicates and we can't allow keeping a duplicate which is
flagged as encrypt-to as the actually encrypt function would then
complain about no (regular) recipients. */
for (sl = remusr; sl; sl = sl->next)
if (!(sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required);
if (!opt.no_encrypt_to)
{
for (sl = remusr; sl; sl = sl->next)
if ((sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required);
}
}
if (log_get_errorcount(0))
gpgsm_exit(1); /* Must stop for invalid recipients. */
/* Dispatch command. */
switch (cmd)
{
case aGPGConfList:
{ /* List options and default values in the GPG Conf format. */
char *config_filename_esc = percent_escape (opt.config_filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPGSM_NAME,
GC_OPT_FLAG_DEFAULT, config_filename_esc);
xfree (config_filename_esc);
es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("enable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-trusted-cert-crl-check:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("enable-ocsp:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_INCLUDE_CERTS);
es_printf ("disable-policy-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-issuer-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-dirmngr:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_CIPHER_ALGO);
es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE);
/* The next one is an info only item and should match what
proc_parameters actually implements. */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
"RSA-2048");
}
break;
case aGPGConfTest:
/* This is merely a dummy command to test whether the
configuration file is valid. */
break;
case aServer:
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
gpgsm_server (recplist);
break;
case aCallDirmngr:
if (!argc)
wrong_args ("--call-dirmngr <command> {args}");
else
if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1))
gpgsm_exit (1);
break;
case aCallProtectTool:
run_protect_tool (argc, argv);
break;
case aEncr: /* Encrypt the given file. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc) /* Source is stdin. */
gpgsm_encrypt (&ctrl, recplist, 0, fp);
else if (argc == 1) /* Source is the given file. */
gpgsm_encrypt (&ctrl, recplist, open_read (*argv), fp);
else
wrong_args ("--encrypt [datafile]");
es_fclose (fp);
}
break;
case aSign: /* Sign the given file. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
/* Fixme: We should also allow concatenation of multiple files for
signing because that is what gpg does.*/
set_binary (stdin);
if (!argc) /* Create from stdin. */
gpgsm_sign (&ctrl, signerlist, 0, detached_sig, fp);
else if (argc == 1) /* From file. */
gpgsm_sign (&ctrl, signerlist,
open_read (*argv), detached_sig, fp);
else
wrong_args ("--sign [datafile]");
es_fclose (fp);
}
break;
case aSignEncr: /* sign and encrypt the given file */
log_error ("this command has not yet been implemented\n");
break;
case aClearsign: /* make a clearsig */
log_error ("this command has not yet been implemented\n");
break;
case aVerify:
{
estream_t fp = NULL;
set_binary (stdin);
if (argc == 2 && opt.outfile)
log_info ("option --output ignored for a detached signature\n");
else if (opt.outfile)
fp = open_es_fwrite (opt.outfile);
if (!argc)
gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */
else if (argc == 1)
gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */
else if (argc == 2) /* detached signature (sig, detached) */
gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL);
else
wrong_args ("--verify [signature [detached_data]]");
es_fclose (fp);
}
break;
case aDecrypt:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc)
gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */
else if (argc == 1)
gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */
else
wrong_args ("--decrypt [filename]");
es_fclose (fp);
}
break;
case aDeleteKey:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_delete (&ctrl, sl);
free_strlist(sl);
break;
case aListChain:
case aDumpChain:
ctrl.with_chain = 1;
case aListKeys:
case aDumpKeys:
case aListExternalKeys:
case aDumpExternalKeys:
case aListSecretKeys:
case aDumpSecretKeys:
{
unsigned int mode;
estream_t fp;
switch (cmd)
{
case aListChain:
case aListKeys: mode = (0 | 0 | (1<<6)); break;
case aDumpChain:
case aDumpKeys: mode = (256 | 0 | (1<<6)); break;
case aListExternalKeys: mode = (0 | 0 | (1<<7)); break;
case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break;
case aListSecretKeys: mode = (0 | 2 | (1<<6)); break;
case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break;
default: BUG();
}
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_list_keys (&ctrl, sl, fp, mode);
free_strlist(sl);
es_fclose (fp);
}
break;
case aKeygen: /* Generate a key; well kind of. */
{
estream_t fpin = NULL;
estream_t fpout;
if (opt.batch)
{
if (!argc) /* Create from stdin. */
fpin = open_es_fread ("-", "r");
else if (argc == 1) /* From file. */
fpin = open_es_fread (*argv, "r");
else
wrong_args ("--generate-key --batch [parmfile]");
}
fpout = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (fpin)
gpgsm_genkey (&ctrl, fpin, fpout);
else
gpgsm_gencertreq_tty (&ctrl, fpout);
es_fclose (fpout);
}
break;
case aImport:
gpgsm_import_files (&ctrl, argc, argv, open_read);
break;
case aExport:
{
estream_t fp;
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_export (&ctrl, sl, fp);
free_strlist(sl);
es_fclose (fp);
}
break;
case aExportSecretKeyP12:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 0);
else
wrong_args ("--export-secret-key-p12 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyP8:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 1);
else
wrong_args ("--export-secret-key-p8 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyRaw:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 2);
else
wrong_args ("--export-secret-key-raw KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aSendKeys:
case aRecvKeys:
log_error ("this command has not yet been implemented\n");
break;
case aLearnCard:
if (argc)
wrong_args ("--learn-card");
else
{
int rc = gpgsm_agent_learn (&ctrl);
if (rc)
log_error ("error learning card: %s\n", gpg_strerror (rc));
}
break;
case aPasswd:
if (argc != 1)
wrong_args ("--change-passphrase <key-Id>");
else
{
int rc;
ksba_cert_t cert = NULL;
char *grip = NULL;
rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert);
if (rc)
;
else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
rc = gpg_error (GPG_ERR_BUG);
else
{
char *desc = gpgsm_format_keydesc (cert);
rc = gpgsm_agent_passwd (&ctrl, grip, desc);
xfree (desc);
}
if (rc)
log_error ("error changing passphrase: %s\n", gpg_strerror (rc));
xfree (grip);
ksba_cert_release (cert);
}
break;
case aKeydbClearSomeCertFlags:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
keydb_clear_some_cert_flags (&ctrl, sl);
free_strlist(sl);
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
break;
}
/* Print the audit result if needed. */
if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp))
{
if (auditlog && auditfp)
audit_print_result (ctrl.audit, auditfp, 0);
if (htmlauditlog && htmlauditfp)
audit_print_result (ctrl.audit, htmlauditfp, 1);
audit_release (ctrl.audit);
ctrl.audit = NULL;
es_fclose (auditfp);
es_fclose (htmlauditfp);
}
/* cleanup */
keyserver_list_free (opt.keyserver);
opt.keyserver = NULL;
gpgsm_release_certlist (recplist);
gpgsm_release_certlist (signerlist);
FREE_STRLIST (remusr);
FREE_STRLIST (locusr);
gpgsm_exit(0);
return 8; /*NOTREACHED*/
}
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
gpgsm_exit (int rc)
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0;
exit (rc);
}
void
gpgsm_init_default_ctrl (struct server_control_s *ctrl)
{
ctrl->include_certs = default_include_certs;
ctrl->use_ocsp = opt.enable_ocsp;
ctrl->validation_model = default_validation_model;
ctrl->offline = opt.disable_dirmngr;
}
int
gpgsm_parse_validation_model (const char *model)
{
if (!ascii_strcasecmp (model, "shell") )
return 0;
else if ( !ascii_strcasecmp (model, "chain") )
return 1;
else if ( !ascii_strcasecmp (model, "steed") )
return 2;
else
return -1;
}
/* Open the FILENAME for read and return the file descriptor. Stop
with an error message in case of problems. "-" denotes stdin and
if special filenames are allowed the given fd is opened instead. */
static int
open_read (const char *filename)
{
int fd;
if (filename[0] == '-' && !filename[1])
{
set_binary (stdin);
return 0; /* stdin */
}
fd = check_special_filename (filename, 0, 0);
if (fd != -1)
return fd;
fd = open (filename, O_RDONLY | O_BINARY);
if (fd == -1)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fd;
}
/* Same as open_read but return an estream_t. */
static estream_t
open_es_fread (const char *filename, const char *mode)
{
int fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
fd = fileno (stdin);
else
fd = check_special_filename (filename, 0, 0);
if (fd != -1)
{
fp = es_fdopen_nc (fd, mode);
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, mode);
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
/* Open FILENAME for fwrite and return an extended stream. Stop with
an error message in case of problems. "-" denotes stdout and if
special filenames are allowed the given fd is opened instead.
Caller must close the returned stream. */
static estream_t
open_es_fwrite (const char *filename)
{
int fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
{
fflush (stdout);
fp = es_fdopen_nc (fileno(stdout), "wb");
return fp;
}
fd = check_special_filename (filename, 1, 0);
if (fd != -1)
{
fp = es_fdopen_nc (fd, "wb");
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, "wb");
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
static void
run_protect_tool (int argc, char **argv)
{
#ifdef HAVE_W32_SYSTEM
(void)argc;
(void)argv;
#else
const char *pgm;
char **av;
int i;
if (!opt.protect_tool_program || !*opt.protect_tool_program)
pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL);
else
pgm = opt.protect_tool_program;
av = xcalloc (argc+2, sizeof *av);
av[0] = strrchr (pgm, '/');
if (!av[0])
av[0] = xstrdup (pgm);
for (i=1; argc; i++, argc--, argv++)
av[i] = *argv;
av[i] = NULL;
execv (pgm, av);
log_error ("error executing '%s': %s\n", pgm, strerror (errno));
#endif /*!HAVE_W32_SYSTEM*/
gpgsm_exit (2);
}
diff --git a/sm/import.c b/sm/import.c
index b284b5125..c7b65ad14 100644
--- a/sm/import.c
+++ b/sm/import.c
@@ -1,939 +1,939 @@
/* import.c - Import certificates
* Copyright (C) 2001, 2003, 2004, 2009, 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <unistd.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "exechelp.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/exechelp.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/membuf.h"
#include "minip12.h"
/* The arbitrary limit of one PKCS#12 object. */
#define MAX_P12OBJ_SIZE 128 /*kb*/
struct stats_s {
unsigned long count;
unsigned long imported;
unsigned long unchanged;
unsigned long not_imported;
unsigned long secret_read;
unsigned long secret_imported;
unsigned long secret_dups;
};
struct rsa_secret_key_s
{
gcry_mpi_t n; /* public modulus */
gcry_mpi_t e; /* public exponent */
gcry_mpi_t d; /* exponent */
gcry_mpi_t p; /* prime p. */
gcry_mpi_t q; /* prime q. */
gcry_mpi_t u; /* inverse of p mod q. */
};
static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader,
struct stats_s *stats);
static void
print_imported_status (ctrl_t ctrl, ksba_cert_t cert, int new_cert)
{
char *fpr;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (new_cert)
gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL);
gpgsm_status2 (ctrl, STATUS_IMPORT_OK,
new_cert? "1":"0", fpr, NULL);
xfree (fpr);
}
/* Print an IMPORT_PROBLEM status. REASON is one of:
0 := "No specific reason given".
1 := "Invalid Certificate".
2 := "Issuer Certificate missing".
3 := "Certificate Chain too long".
4 := "Error storing certificate".
*/
static void
print_import_problem (ctrl_t ctrl, ksba_cert_t cert, int reason)
{
char *fpr = NULL;
char buf[25];
int i;
sprintf (buf, "%d", reason);
if (cert)
{
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
/* detetect an error (all high) value */
for (i=0; fpr[i] == 'F'; i++)
;
if (!fpr[i])
{
xfree (fpr);
fpr = NULL;
}
}
gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL);
xfree (fpr);
}
void
print_imported_summary (ctrl_t ctrl, struct stats_s *stats)
{
char buf[14*25];
if (!opt.quiet)
{
log_info (_("total number processed: %lu\n"), stats->count);
if (stats->imported)
{
log_info (_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged)
log_info (_(" unchanged: %lu\n"), stats->unchanged);
if (stats->secret_read)
log_info (_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported)
log_info (_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups)
log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported)
log_info (_(" not imported: %lu\n"), stats->not_imported);
}
sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count,
0l /*stats->no_user_id*/,
stats->imported,
0l /*stats->imported_rsa*/,
stats->unchanged,
0l /*stats->n_uids*/,
0l /*stats->n_subk*/,
0l /*stats->n_sigs*/,
0l /*stats->n_revoc*/,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
0l /*stats->skipped_new_keys*/,
stats->not_imported
);
gpgsm_status (ctrl, STATUS_IMPORT_RES, buf);
}
static void
check_and_store (ctrl_t ctrl, struct stats_s *stats,
ksba_cert_t cert, int depth)
{
int rc;
if (stats)
stats->count++;
if ( depth >= 50 )
{
log_error (_("certificate chain too long\n"));
if (stats)
stats->not_imported++;
print_import_problem (ctrl, cert, 3);
return;
}
/* Some basic checks, but don't care about missing certificates;
this is so that we are able to import entire certificate chains
w/o requiring a special order (i.e. root-CA first). This used
to be different but because gpgsm_verify even imports
certificates without any checks, it doesn't matter much and the
code gets much cleaner. A housekeeping function to remove
certificates w/o an anchor would be nice, though.
Optionally we do a full validation in addition to the basic test.
*/
rc = gpgsm_basic_cert_check (ctrl, cert);
if (!rc && ctrl->with_validation)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL);
if (!rc || (!ctrl->with_validation
&& (gpg_err_code (rc) == GPG_ERR_MISSING_CERT
|| gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT)))
{
int existed;
if (!keydb_store_cert (ctrl, cert, 0, &existed))
{
ksba_cert_t next = NULL;
if (!existed)
{
print_imported_status (ctrl, cert, 1);
if (stats)
stats->imported++;
}
else
{
print_imported_status (ctrl, cert, 0);
if (stats)
stats->unchanged++;
}
if (opt.verbose > 1 && existed)
{
if (depth)
log_info ("issuer certificate already in DB\n");
else
log_info ("certificate already in DB\n");
}
else if (opt.verbose && !existed)
{
if (depth)
log_info ("issuer certificate imported\n");
else
log_info ("certificate imported\n");
}
/* Now lets walk up the chain and import all certificates up
the chain. This is required in case we already stored
parent certificates in the ephemeral keybox. Do not
update the statistics, though. */
if (!gpgsm_walk_cert_chain (ctrl, cert, &next))
{
check_and_store (ctrl, NULL, next, depth+1);
ksba_cert_release (next);
}
}
else
{
log_error (_("error storing certificate\n"));
if (stats)
stats->not_imported++;
print_import_problem (ctrl, cert, 4);
}
}
else
{
log_error (_("basic certificate checks failed - not imported\n"));
if (stats)
stats->not_imported++;
/* We keep the test for GPG_ERR_MISSING_CERT only in case
GPG_ERR_MISSING_CERT has been used instead of the newer
GPG_ERR_MISSING_ISSUER_CERT. */
print_import_problem
(ctrl, cert,
gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT? 2 :
gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 :
gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0);
}
}
static int
import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd)
{
int rc;
gnupg_ksba_io_t b64reader = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
ksba_cms_t cms = NULL;
estream_t fp = NULL;
ksba_content_type_t ct;
int any = 0;
fp = es_fdopen_nc (in_fd, "rb");
if (!fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)
| GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
/* We need to loop here to handle multiple PEM objects in one
file. */
do
{
ksba_cms_release (cms); cms = NULL;
ksba_cert_release (cert); cert = NULL;
ct = ksba_cms_identify (reader);
if (ct == KSBA_CT_SIGNED_DATA)
{ /* This is probably a signed-only message - import the certs */
ksba_stop_reason_t stopreason;
int i;
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, NULL);
if (rc)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
log_info ("not a certs-only message\n");
}
while (stopreason != KSBA_SR_READY);
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
check_and_store (ctrl, stats, cert, 0);
ksba_cert_release (cert);
cert = NULL;
}
if (!i)
log_error ("no certificate found\n");
else
any = 1;
}
else if (ct == KSBA_CT_PKCS12)
{
/* This seems to be a pkcs12 message. */
rc = parse_p12 (ctrl, reader, stats);
if (!rc)
any = 1;
}
else if (ct == KSBA_CT_NONE)
{ /* Failed to identify this message - assume a certificate */
rc = ksba_cert_new (&cert);
if (rc)
goto leave;
rc = ksba_cert_read_der (cert, reader);
if (rc)
goto leave;
check_and_store (ctrl, stats, cert, 0);
any = 1;
}
else
{
log_error ("can't extract certificates from input\n");
rc = gpg_error (GPG_ERR_NO_DATA);
}
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (b64reader));
leave:
if (any && gpg_err_code (rc) == GPG_ERR_EOF)
rc = 0;
ksba_cms_release (cms);
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (b64reader);
es_fclose (fp);
return rc;
}
/* Re-import certifciates. IN_FD is a list of linefeed delimited
fingerprints t re-import. The actual re-import is done by clearing
the ephemeral flag. */
static int
reimport_one (ctrl_t ctrl, struct stats_s *stats, int in_fd)
{
gpg_error_t err = 0;
estream_t fp = NULL;
char line[100]; /* Sufficient for a fingerprint. */
KEYDB_HANDLE kh;
KEYDB_SEARCH_DESC desc;
ksba_cert_t cert = NULL;
unsigned int flags;
kh = keydb_new ();
if (!kh)
{
err = gpg_error (GPG_ERR_ENOMEM);;
log_error (_("failed to allocate keyDB handle\n"));
goto leave;
}
keydb_set_ephemeral (kh, 1);
fp = es_fdopen_nc (in_fd, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("es_fdopen(%d) failed: %s\n", in_fd, gpg_strerror (err));
goto leave;
}
while (es_fgets (line, DIM(line)-1, fp) )
{
if (*line && line[strlen(line)-1] != '\n')
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
goto leave;
}
trim_spaces (line);
if (!*line)
continue;
stats->count++;
err = classify_user_id (line, &desc, 0);
if (err)
{
print_import_problem (ctrl, NULL, 0);
stats->not_imported++;
continue;
}
keydb_search_reset (kh);
err = keydb_search (ctrl, kh, &desc, 1);
if (err)
{
print_import_problem (ctrl, NULL, 0);
stats->not_imported++;
continue;
}
ksba_cert_release (cert);
cert = NULL;
err = keydb_get_cert (kh, &cert);
if (err)
{
log_error ("keydb_get_cert() failed: %s\n", gpg_strerror (err));
print_import_problem (ctrl, NULL, 1);
stats->not_imported++;
continue;
}
err = keydb_get_flags (kh, KEYBOX_FLAG_BLOB, 0, &flags);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
print_imported_status (ctrl, cert, 0);
stats->not_imported++;
continue;
}
if ( !(flags & KEYBOX_FLAG_BLOB_EPHEMERAL) )
{
print_imported_status (ctrl, cert, 0);
stats->unchanged++;
continue;
}
err = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (err)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (err));
print_import_problem (ctrl, cert, 0);
stats->not_imported++;
continue;
}
print_imported_status (ctrl, cert, 1);
stats->imported++;
}
err = 0;
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading fd %d: %s\n", in_fd, gpg_strerror (err));
goto leave;
}
leave:
ksba_cert_release (cert);
keydb_release (kh);
es_fclose (fp);
return err;
}
int
gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode)
{
int rc;
struct stats_s stats;
memset (&stats, 0, sizeof stats);
if (reimport_mode)
rc = reimport_one (ctrl, &stats, in_fd);
else
rc = import_one (ctrl, &stats, in_fd);
print_imported_summary (ctrl, &stats);
/* If we never printed an error message do it now so that a command
line invocation will return with an error (log_error keeps a
global errorcount) */
if (rc && !log_get_errorcount (0))
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
return rc;
}
int
gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
int (*of)(const char *fname))
{
int rc = 0;
struct stats_s stats;
memset (&stats, 0, sizeof stats);
if (!nfiles)
rc = import_one (ctrl, &stats, 0);
else
{
for (; nfiles && !rc ; nfiles--, files++)
{
int fd = of (*files);
rc = import_one (ctrl, &stats, fd);
close (fd);
if (rc == -1)
rc = 0;
}
}
print_imported_summary (ctrl, &stats);
/* If we never printed an error message do it now so that a command
line invocation will return with an error (log_error keeps a
global errorcount) */
if (rc && !log_get_errorcount (0))
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
return rc;
}
/* Check that the RSA secret key SKEY is valid. Swap parameters to
the libgcrypt standard. */
static gpg_error_t
rsa_key_check (struct rsa_secret_key_s *skey)
{
int err = 0;
gcry_mpi_t t = gcry_mpi_snew (0);
gcry_mpi_t t1 = gcry_mpi_snew (0);
gcry_mpi_t t2 = gcry_mpi_snew (0);
gcry_mpi_t phi = gcry_mpi_snew (0);
/* Check that n == p * q. */
gcry_mpi_mul (t, skey->p, skey->q);
if (gcry_mpi_cmp( t, skey->n) )
{
log_error ("RSA oops: n != p * q\n");
err++;
}
/* Check that p is less than q. */
if (gcry_mpi_cmp (skey->p, skey->q) > 0)
{
gcry_mpi_t tmp;
log_info ("swapping secret primes\n");
tmp = gcry_mpi_copy (skey->p);
gcry_mpi_set (skey->p, skey->q);
gcry_mpi_set (skey->q, tmp);
gcry_mpi_release (tmp);
/* Recompute u. */
gcry_mpi_invm (skey->u, skey->p, skey->q);
}
/* Check that e divides neither p-1 nor q-1. */
gcry_mpi_sub_ui (t, skey->p, 1 );
gcry_mpi_div (NULL, t, t, skey->e, 0);
if (!gcry_mpi_cmp_ui( t, 0) )
{
log_error ("RSA oops: e divides p-1\n");
err++;
}
gcry_mpi_sub_ui (t, skey->q, 1);
gcry_mpi_div (NULL, t, t, skey->e, 0);
if (!gcry_mpi_cmp_ui( t, 0))
{
log_info ("RSA oops: e divides q-1\n" );
err++;
}
/* Check that d is correct. */
gcry_mpi_sub_ui (t1, skey->p, 1);
gcry_mpi_sub_ui (t2, skey->q, 1);
gcry_mpi_mul (phi, t1, t2);
gcry_mpi_invm (t, skey->e, phi);
if (gcry_mpi_cmp (t, skey->d))
{
/* No: try universal exponent. */
gcry_mpi_gcd (t, t1, t2);
gcry_mpi_div (t, NULL, phi, t, 0);
gcry_mpi_invm (t, skey->e, t);
if (gcry_mpi_cmp (t, skey->d))
{
log_error ("RSA oops: bad secret exponent\n");
err++;
}
}
/* Check for correctness of u. */
gcry_mpi_invm (t, skey->p, skey->q);
if (gcry_mpi_cmp (t, skey->u))
{
log_info ("RSA oops: bad u parameter\n");
err++;
}
if (err)
log_info ("RSA secret key check failed\n");
gcry_mpi_release (t);
gcry_mpi_release (t1);
gcry_mpi_release (t2);
gcry_mpi_release (phi);
return err? gpg_error (GPG_ERR_BAD_SECKEY):0;
}
/* Object passed to store_cert_cb. */
struct store_cert_parm_s
{
gpg_error_t err; /* First error seen. */
struct stats_s *stats; /* The stats object. */
ctrl_t ctrl; /* The control object. */
};
/* Helper to store the DER encoded certificate CERTDATA of length
CERTDATALEN. */
static void
store_cert_cb (void *opaque,
const unsigned char *certdata, size_t certdatalen)
{
struct store_cert_parm_s *parm = opaque;
gpg_error_t err;
ksba_cert_t cert;
err = ksba_cert_new (&cert);
if (err)
{
if (!parm->err)
parm->err = err;
return;
}
err = ksba_cert_init_from_mem (cert, certdata, certdatalen);
if (err)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
if (!parm->err)
parm->err = err;
}
else
check_and_store (parm->ctrl, parm->stats, cert, 0);
ksba_cert_release (cert);
}
/* Assume that the reader is at a pkcs#12 message and try to import
certificates from that stupid format. We will transfer secret
keys to the agent. */
static gpg_error_t
parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats)
{
gpg_error_t err = 0;
char buffer[1024];
size_t ntotal, nread;
membuf_t p12mbuf;
char *p12buffer = NULL;
size_t p12buflen;
size_t p12bufoff;
gcry_mpi_t *kparms = NULL;
struct rsa_secret_key_s sk;
char *passphrase = NULL;
unsigned char *key = NULL;
size_t keylen;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_key = NULL;
unsigned char grip[20];
int bad_pass = 0;
int i;
struct store_cert_parm_s store_cert_parm;
memset (&store_cert_parm, 0, sizeof store_cert_parm);
store_cert_parm.ctrl = ctrl;
store_cert_parm.stats = stats;
init_membuf (&p12mbuf, 4096);
ntotal = 0;
while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread)))
{
if (ntotal >= MAX_P12OBJ_SIZE*1024)
{
/* Arbitrary limit to avoid DoS attacks. */
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE);
break;
}
put_membuf (&p12mbuf, buffer, nread);
ntotal += nread;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (!err)
{
p12buffer = get_membuf (&p12mbuf, &p12buflen);
if (!p12buffer)
err = gpg_error_from_syserror ();
}
if (err)
{
log_error (_("error reading input: %s\n"), gpg_strerror (err));
goto leave;
}
/* GnuPG 2.0.4 accidentally created binary P12 files with the string
"The passphrase is %s encoded.\n\n" prepended to the ASN.1 data.
We fix that here. */
if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18))
{
for (p12bufoff=18;
p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n';
p12bufoff++)
;
p12bufoff++;
if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n')
p12bufoff++;
}
else
p12bufoff = 0;
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 ("Please enter the passphrase to unprotect the PKCS#12 object."),
0, &passphrase);
if (err)
goto leave;
kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff,
passphrase, store_cert_cb, &store_cert_parm, &bad_pass);
xfree (passphrase);
passphrase = NULL;
if (!kparms)
{
log_error ("error parsing or decrypting the PKCS#12 file\n");
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* print_mpi (" n", kparms[0]); */
/* print_mpi (" e", kparms[1]); */
/* print_mpi (" d", kparms[2]); */
/* print_mpi (" p", kparms[3]); */
/* print_mpi (" q", kparms[4]); */
/* print_mpi ("dmp1", kparms[5]); */
/* print_mpi ("dmq1", kparms[6]); */
/* print_mpi (" u", kparms[7]); */
sk.n = kparms[0];
sk.e = kparms[1];
sk.d = kparms[2];
sk.q = kparms[3];
sk.p = kparms[4];
sk.u = kparms[7];
err = rsa_key_check (&sk);
if (err)
goto leave;
/* print_mpi (" n", sk.n); */
/* print_mpi (" e", sk.e); */
/* print_mpi (" d", sk.d); */
/* print_mpi (" p", sk.p); */
/* print_mpi (" q", sk.q); */
/* print_mpi (" u", sk.u); */
/* Create an S-expresion from the parameters. */
err = gcry_sexp_build (&s_key, NULL,
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL);
for (i=0; i < 8; i++)
gcry_mpi_release (kparms[i]);
gcry_free (kparms);
kparms = NULL;
if (err)
{
log_error ("failed to create S-expression from key: %s\n",
gpg_strerror (err));
goto leave;
}
/* Compute the keygrip. */
if (!gcry_pk_get_keygrip (s_key, grip))
{
err = gpg_error (GPG_ERR_GENERAL);
log_error ("can't calculate keygrip\n");
goto leave;
}
log_printhex ("keygrip=", grip, 20);
/* Convert to canonical encoding using a function which pads it to a
multiple of 64 bits. We need this padding for AESWRAP. */
err = make_canon_sexp_pad (s_key, 1, &key, &keylen);
if (err)
{
log_error ("error creating canonical S-expression\n");
goto leave;
}
gcry_sexp_release (s_key);
s_key = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Send the wrapped key to the agent. */
err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen);
if (!err)
{
stats->count++;
stats->secret_read++;
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
err = 0;
stats->count++;
stats->secret_read++;
stats->secret_dups++;
}
/* If we did not get an error from storing the secret key we return
a possible error from parsing the certificates. We do this after
storing the secret keys so that a bad certificate does not
inhibit our chance to store the secret key. */
if (!err && store_cert_parm.err)
err = store_cert_parm.err;
leave:
if (kparms)
{
for (i=0; i < 8; i++)
gcry_mpi_release (kparms[i]);
gcry_free (kparms);
kparms = NULL;
}
xfree (key);
gcry_sexp_release (s_key);
xfree (passphrase);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
xfree (get_membuf (&p12mbuf, NULL));
xfree (p12buffer);
if (bad_pass)
{
/* We only write a plain error code and not direct
BAD_PASSPHRASE because the pkcs12 parser might issue this
message multiple times, BAD_PASSPHRASE in general requires a
keyID and parts of the import might actually succeed so that
IMPORT_PROBLEM is also not appropriate. */
gpgsm_status_with_err_code (ctrl, STATUS_ERROR,
"import.parsep12", GPG_ERR_BAD_PASSPHRASE);
}
return err;
}
diff --git a/sm/keydb.c b/sm/keydb.c
index 75f83ee2d..87fc12d0e 100644
--- a/sm/keydb.c
+++ b/sm/keydb.c
@@ -1,1361 +1,1361 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
* Copyright (C) 2014 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpgsm.h"
#include "../kbx/keybox.h"
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
static int active_handles;
typedef enum {
KEYDB_RESOURCE_TYPE_NONE = 0,
KEYDB_RESOURCE_TYPE_KEYBOX
} KeydbResourceType;
#define MAX_KEYDB_RESOURCES 20
struct resource_item {
KeydbResourceType type;
union {
KEYBOX_HANDLE kr;
} u;
void *token;
dotlock_t lockhandle;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* Whether we have successfully registered any resource. */
static int any_registered;
struct keydb_handle {
int locked;
int found;
int saved_found;
int current;
int is_ephemeral;
int used; /* items in active */
struct resource_item active[MAX_KEYDB_RESOURCES];
};
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
static void
try_make_homedir (const char *fname)
{
const char *defhome = standard_homedir ();
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we do compare only the suffix if we see that the
default homedir does start with a tilde. */
if ( opt.dry_run || opt.no_homedir_creation )
return;
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (fname, defhome) )
#else
( *defhome == '~'
&& (strlen(fname) >= strlen (defhome+1)
&& !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) ))
|| (*defhome != '~' && !compare_filenames( fname, defhome ) )
#endif
)
{
if (gnupg_mkdir (fname, "-rwx"))
log_info (_("can't create directory '%s': %s\n"),
fname, strerror(errno) );
else if (!opt.quiet )
log_info (_("directory '%s' created\n"), fname);
}
}
/* Handle the creation of a keybox if it does not yet exist. Take
into acount that other processes might have the keybox already
locked. This lock check does not work if the directory itself is
not yet available. If R_CREATED is not NULL it will be set to true
if the function created a new keybox. */
static gpg_error_t
maybe_create_keybox (char *filename, int force, int *r_created)
{
dotlock_t lockhd = NULL;
FILE *fp;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
int save_slash;
if (r_created)
*r_created = 0;
/* A quick test whether the filename already exists. */
if (!access (filename, F_OK))
return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for some home
directory name patterns. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if (access (filename, F_OK))
{
rc = gpg_error_from_syserror ();
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keybox (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s'\n", filename );
if (!force)
return gpg_error (GPG_ERR_ENOENT);
else
return gpg_error (GPG_ERR_GENERAL);
}
if ( dotlock_take (lockhd, -1) )
{
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s'\n", filename);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Now the real test while we are locked. */
if (!access(filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
fp = fopen (filename, "w");
if (!fp)
{
rc = gpg_error_from_syserror ();
umask (oldmask);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
umask (oldmask);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic for OpenPGP keyboxes works the next time
it is used. */
rc = _keybox_write_header_blob (fp, 0);
if (rc)
{
fclose (fp);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
if (r_created)
*r_created = 1;
fclose (fp);
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return rc;
}
/*
* Register a resource (which currently may only be a keybox file).
* The first keybox which is added by this function is created if it
* does not exist. If AUTO_CREATED is not NULL it will be set to true
* if the function has created a new keybox.
*/
gpg_error_t
keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created)
{
const char *resname = url;
char *filename = NULL;
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
if (auto_created)
*auto_created = 0;
/* Do we have an URL?
gnupg-kbx:filename := this is a plain keybox
filename := See what it is, but create as plain keybox.
*/
if (strlen (resname) > 10)
{
if (!strncmp (resname, "gnupg-kbx:", 10) )
{
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
resname += 10;
}
#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
else if (strchr (resname, ':'))
{
log_error ("invalid key resource URL '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
}
if (*resname != DIRSEP_C )
{ /* do tilde expansion etc */
if (strchr(resname, DIRSEP_C) )
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
if (!force)
force = !any_registered;
/* see whether we can determine the filetype */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
FILE *fp = fopen( filename, "rb" );
if (fp)
{
u32 magic;
/* FIXME: check for the keybox magic */
if (fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - no more support */
else
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else /* maybe empty: assume keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
fclose (fp);
}
else /* no file yet: create keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = maybe_create_keybox (filename, force, auto_created);
if (err)
goto leave;
/* Now register the file */
{
void *token;
err = keybox_register_file (filename, 0, &token);
if (gpg_err_code (err) == GPG_ERR_EEXIST)
; /* Already registered - ignore. */
else if (err)
; /* Other error. */
else if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
all_resources[used_resources].lockhandle
= dotlock_create (filename, 0);
if (!all_resources[used_resources].lockhandle)
log_fatal ( _("can't create lock for '%s'\n"), filename);
/* Do a compress run if needed and the file is not locked. */
if (!dotlock_take (all_resources[used_resources].lockhandle, 0))
{
KEYBOX_HANDLE kbxhd = keybox_new_x509 (token, 0);
if (kbxhd)
{
keybox_compress (kbxhd);
keybox_release (kbxhd);
}
dotlock_release (all_resources[used_resources].lockhandle);
}
used_resources++;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
KEYDB_HANDLE
keydb_new (void)
{
KEYDB_HANDLE hd;
int i, j;
hd = xcalloc (1, sizeof *hd);
hd->found = -1;
hd->saved_found = -1;
assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].lockhandle = all_resources[i].lockhandle;
hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0);
if (!hd->active[j].u.kr)
{
xfree (hd);
return NULL; /* fixme: release all previously allocated handles*/
}
j++;
break;
}
}
hd->used = j;
active_handles++;
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
int i;
if (!hd)
return;
assert (active_handles > 0);
active_handles--;
unlock_all (hd);
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kr);
break;
}
}
xfree (hd);
}
/* Return the name of the current resource. This is function first
looks for the last found found, then for the current search
position, and last returns the first available resource. The
returned string is only valid as long as the handle exists. This
function does only return NULL if no handle is specified, in all
other error cases an empty string is returned. */
const char *
keydb_get_resource_name (KEYDB_HANDLE hd)
{
int idx;
const char *s = NULL;
if (!hd)
return NULL;
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
idx = 0;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
s = NULL;
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kr);
break;
}
return s? s: "";
}
/* Switch the handle into ephemeral mode and return the original value. */
int
keydb_set_ephemeral (KEYDB_HANDLE hd, int yes)
{
int i;
if (!hd)
return 0;
yes = !!yes;
if (hd->is_ephemeral != yes)
{
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_set_ephemeral (hd->active[i].u.kr, yes);
break;
}
}
}
i = hd->is_ephemeral;
hd->is_ephemeral = yes;
return i;
}
/* If the keyring has not yet been locked, lock it now. This
operation is required before any update operation; it is optional
for an insert operation. The lock is released with
keydb_released. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (hd->locked)
return 0; /* Already locked. */
return lock_all (hd);
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
/* Fixme: This locking scheme may lead to deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem. */
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (hd->active[i].lockhandle)
rc = dotlock_take (hd->active[i].lockhandle, -1);
break;
}
if (rc)
break;
}
if (rc)
{
/* revert the already set locks */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (hd->active[i].lockhandle)
dotlock_release (hd->active[i].lockhandle);
break;
}
}
}
else
hd->locked = 1;
/* make_dotlock () does not yet guarantee that errno is set, thus
we can't rely on the error reason and will simply use
EACCES. */
return rc? gpg_error (GPG_ERR_EACCES) : 0;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (!hd->locked)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (hd->active[i].lockhandle)
dotlock_release (hd->active[i].lockhandle);
break;
}
}
hd->locked = 0;
}
/* Push the last found state if any. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kr);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
}
/* Pop the last found state. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kr);
break;
}
}
/*
Return the last found object. Caller must free it. The returned
keyblock has the kbode flag bit 0 set for the node with the public
key used to locate the keyblock or flag bit 1 set for the user ID
node. */
int
keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert)
{
int rc = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return -1; /* nothing found */
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert);
break;
}
return rc;
}
/* Return a flag of the last found object. WHICH is the flag requested;
it should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored at the address given by
VALUE. Return 0 on success or an error code. */
gpg_error_t
keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value)
{
int err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_NOTHING_FOUND);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
return err;
}
/* Set a flag of the last found object. WHICH is the flag to be set; it
should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored in the keybox. Note,
that some flag values can't be updated and thus may return an
error, some other flag values may be masked out before an update.
Returns 0 on success or an error code. */
gpg_error_t
keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value)
{
int err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (!hd->locked)
return gpg_error (GPG_ERR_NOT_LOCKED);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
return err;
}
/*
* Insert a new Certificate into one of the resources.
*/
int
keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
int rc = -1;
int idx;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (opt.dry_run)
return 0;
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
return gpg_error (GPG_ERR_GENERAL);
if (!hd->locked)
return gpg_error (GPG_ERR_NOT_LOCKED);
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
break;
}
unlock_all (hd);
return rc;
}
/* Update the current keyblock with KB. */
int
keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
int rc = 0;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return -1; /* nothing found */
if (opt.dry_run)
return 0;
rc = lock_all (hd);
if (rc)
return rc;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
break;
}
unlock_all (hd);
return rc;
}
/*
* The current keyblock or cert will be deleted.
*/
int
keydb_delete (KEYDB_HANDLE hd, int unlock)
{
int rc = -1;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return -1; /* nothing found */
if( opt.dry_run )
return 0;
if (!hd->locked)
return gpg_error (GPG_ERR_NOT_LOCKED);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_delete (hd->active[hd->found].u.kr);
break;
}
if (unlock)
unlock_all (hd);
return rc;
}
/*
* Locate the default writable key resource, so that the next
* operation (which is only relevant for inserts) will be done on this
* resource.
*/
int
keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
{
int rc;
(void)reserved;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG();
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (keybox_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
}
}
return -1;
}
/*
* Rebuild the caches of all key resources.
*/
void
keydb_rebuild_caches (void)
{
int i;
for (i=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* rc = keybox_rebuild_cache (all_resources[i].token); */
/* if (rc) */
/* log_error (_("failed to rebuild keybox cache: %s\n"), */
/* g10_errstr (rc)); */
break;
}
}
}
/*
* Start the next search on this handle right at the beginning
*/
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
int i;
gpg_error_t rc = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
hd->current = 0;
hd->found = -1;
/* and reset all resources */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_search_reset (hd->active[i].u.kr);
break;
}
}
return rc;
}
/*
* Search through all keydb resources, starting at the current position,
* for a keyblock which contains one of the keys described in the DESC array.
*/
int
keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd,
KEYDB_SEARCH_DESC *desc, size_t ndesc)
{
int rc = -1;
unsigned long skipped;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!any_registered)
{
gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search",
gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
while (rc == -1 && hd->current >= 0 && hd->current < hd->used)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG(); /* we should never see it here */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc,
KEYBOX_BLOBTYPE_X509,
NULL, &skipped);
break;
}
if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{ /* EOF -> switch to next resource */
hd->current++;
}
else if (!rc)
hd->found = hd->current;
}
return rc;
}
int
keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
(void)kid;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = kid[0];
desc.u.kid[1] = kid[1];
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, 20);
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd,
const char *issuer, ksba_const_sexp_t serial)
{
KEYDB_SEARCH_DESC desc;
int rc;
const unsigned char *s;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN;
s = serial;
if (*s !='(')
return gpg_error (GPG_ERR_INV_VALUE);
s++;
for (desc.snlen = 0; digitp (s); s++)
desc.snlen = 10*desc.snlen + atoi_1 (s);
if (*s !=':')
return gpg_error (GPG_ERR_INV_VALUE);
desc.sn = s+1;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *name)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_SUBJECT;
desc.u.name = name;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
/* Store the certificate in the key DB but make sure that it does not
already exists. We do this simply by comparing the fingerprint.
If EXISTED is not NULL it will be set to true if the certificate
was already in the DB. */
int
keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed)
{
KEYDB_HANDLE kh;
int rc;
unsigned char fpr[20];
if (existed)
*existed = 0;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
/* Set the ephemeral flag so that the search looks at all
records. */
keydb_set_ephemeral (kh, 1);
rc = lock_all (kh);
if (rc)
return rc;
rc = keydb_search_fpr (ctrl, kh, fpr);
if (rc != -1)
{
keydb_release (kh);
if (!rc)
{
if (existed)
*existed = 1;
if (!ephemeral)
{
/* Remove ephemeral flags from existing certificate to "store"
it permanently. */
rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (rc)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0; /* okay */
}
log_error (_("problem looking for existing certificate: %s\n"),
gpg_strerror (rc));
return rc;
}
/* Reset the ephemeral flag if not requested. */
if (!ephemeral)
keydb_set_ephemeral (kh, 0);
rc = keydb_locate_writable (kh, 0);
if (rc)
{
log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
rc = keydb_insert_cert (kh, cert);
if (rc)
{
log_error (_("error storing certificate: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
keydb_release (kh);
return 0;
}
/* This is basically keydb_set_flags but it implements a complete
transaction by locating the certificate in the DB and updating the
flags. */
gpg_error_t
keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int which, int idx,
unsigned int mask, unsigned int value)
{
KEYDB_HANDLE kh;
gpg_error_t err;
unsigned char fpr[20];
unsigned int old_value;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
if (ephemeral)
keydb_set_ephemeral (kh, 1);
err = keydb_lock (kh);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_search_fpr (ctrl, kh, fpr);
if (err)
{
if (err == -1)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
log_error (_("problem re-searching certificate: %s\n"),
gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_get_flags (kh, which, idx, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
value = ((old_value & ~mask) | (value & mask));
if (value != old_value)
{
err = keydb_set_flags (kh, which, idx, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
keydb_release (kh);
return 0;
}
/* Reset all the certificate flags we have stored with the certificates
for performance reasons. */
void
keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names)
{
gpg_error_t err;
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
strlist_t sl;
int rc=0;
unsigned int old_value, value;
(void)ctrl;
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc));
else
ndesc++;
}
}
err = keydb_lock (hd);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
goto leave;
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"),
gpg_strerror (err));
goto leave;
}
value = (old_value & ~VALIDITY_REVOKED);
if (value != old_value)
{
err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
goto leave;
}
}
}
if (rc && rc != -1)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
xfree (desc);
keydb_release (hd);
}
diff --git a/sm/keylist.c b/sm/keylist.c
index 6db42e3f6..d27d4f441 100644
--- a/sm/keylist.c
+++ b/sm/keylist.c
@@ -1,1586 +1,1586 @@
/* keylist.c - Print certificates in various formats.
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009,
* 2010, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
-#include "i18n.h"
-#include "tlv.h"
+#include "../common/i18n.h"
+#include "../common/tlv.h"
struct list_external_parm_s
{
ctrl_t ctrl;
estream_t fp;
int print_header;
int with_colons;
int with_chain;
int raw_mode;
};
/* This table is to map Extended Key Usage OIDs to human readable
names. */
struct
{
const char *oid;
const char *name;
} key_purpose_map[] = {
{ "1.3.6.1.5.5.7.3.1", "serverAuth" },
{ "1.3.6.1.5.5.7.3.2", "clientAuth" },
{ "1.3.6.1.5.5.7.3.3", "codeSigning" },
{ "1.3.6.1.5.5.7.3.4", "emailProtection" },
{ "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" },
{ "1.3.6.1.5.5.7.3.6", "ipsecTunnel" },
{ "1.3.6.1.5.5.7.3.7", "ipsecUser" },
{ "1.3.6.1.5.5.7.3.8", "timeStamping" },
{ "1.3.6.1.5.5.7.3.9", "ocspSigning" },
{ "1.3.6.1.5.5.7.3.10", "dvcs" },
{ "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" },
{ "1.3.6.1.5.5.7.3.13", "eapOverPPP" },
{ "1.3.6.1.5.5.7.3.14", "wlanSSID" },
{ "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */
{ "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */
{ "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck" },
{ NULL, NULL }
};
/* Do not print this extension in the list of extensions. This is set
for oids which are already available via ksba fucntions. */
#define OID_FLAG_SKIP 1
/* The extension is a simple UTF8String and should be printed. */
#define OID_FLAG_UTF8 2
/* A table mapping OIDs to a descriptive string. */
static struct
{
char *oid;
char *name;
unsigned int flag; /* A flag as described above. */
} oidtranstbl[] = {
/* Algorithms. */
{ "1.2.840.10040.4.1", "dsa" },
{ "1.2.840.10040.4.3", "dsaWithSha1" },
{ "1.2.840.113549.1.1.1", "rsaEncryption" },
{ "1.2.840.113549.1.1.2", "md2WithRSAEncryption" },
{ "1.2.840.113549.1.1.3", "md4WithRSAEncryption" },
{ "1.2.840.113549.1.1.4", "md5WithRSAEncryption" },
{ "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" },
{ "1.2.840.113549.1.1.7", "rsaOAEP" },
{ "1.2.840.113549.1.1.8", "rsaOAEP-MGF" },
{ "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" },
{ "1.2.840.113549.1.1.10", "rsaPSS" },
{ "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" },
{ "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" },
{ "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" },
{ "1.3.14.3.2.26", "sha1" },
{ "1.3.14.3.2.29", "sha-1WithRSAEncryption" },
{ "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" },
/* Telesec extensions. */
{ "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" },
{ "0.2.262.1.10.12.1", "telesecCertIdExt" },
{ "0.2.262.1.10.12.2", "telesecPolicyIdentifier" },
{ "0.2.262.1.10.12.3", "telesecPolicyQualifierID" },
{ "0.2.262.1.10.12.4", "telesecCRLFilteredExt" },
{ "0.2.262.1.10.12.5", "telesecCRLFilterExt"},
{ "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" },
#define OIDSTR_restriction \
"1.3.36.8.3.8"
{ OIDSTR_restriction, "restriction", OID_FLAG_UTF8 },
/* PKIX private extensions. */
{ "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" },
{ "1.3.6.1.5.5.7.1.2", "biometricInfo" },
{ "1.3.6.1.5.5.7.1.3", "qcStatements" },
{ "1.3.6.1.5.5.7.1.4", "acAuditIdentity" },
{ "1.3.6.1.5.5.7.1.5", "acTargeting" },
{ "1.3.6.1.5.5.7.1.6", "acAaControls" },
{ "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" },
{ "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" },
{ "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" },
{ "1.3.6.1.5.5.7.1.10", "acProxying" },
{ "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" },
{ "1.3.6.1.5.5.7.48.1", "ocsp" },
{ "1.3.6.1.5.5.7.48.2", "caIssuers" },
{ "1.3.6.1.5.5.7.48.3", "timeStamping" },
{ "1.3.6.1.5.5.7.48.5", "caRepository" },
/* X.509 id-ce */
{ "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.15", "keyUsage", OID_FLAG_SKIP},
{ "2.5.29.16", "privateKeyUsagePeriod" },
{ "2.5.29.17", "subjectAltName", OID_FLAG_SKIP},
{ "2.5.29.18", "issuerAltName", OID_FLAG_SKIP},
{ "2.5.29.19", "basicConstraints", OID_FLAG_SKIP},
{ "2.5.29.20", "cRLNumber" },
{ "2.5.29.21", "cRLReason" },
{ "2.5.29.22", "expirationDate" },
{ "2.5.29.23", "instructionCode" },
{ "2.5.29.24", "invalidityDate" },
{ "2.5.29.27", "deltaCRLIndicator" },
{ "2.5.29.28", "issuingDistributionPoint" },
{ "2.5.29.29", "certificateIssuer" },
{ "2.5.29.30", "nameConstraints" },
{ "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP},
{ "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP},
{ "2.5.29.32.0", "anyPolicy" },
{ "2.5.29.33", "policyMappings" },
{ "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.36", "policyConstraints" },
{ "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP},
{ "2.5.29.46", "freshestCRL" },
{ "2.5.29.54", "inhibitAnyPolicy" },
/* Netscape certificate extensions. */
{ "2.16.840.1.113730.1.1", "netscape-cert-type" },
{ "2.16.840.1.113730.1.2", "netscape-base-url" },
{ "2.16.840.1.113730.1.3", "netscape-revocation-url" },
{ "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" },
{ "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" },
{ "2.16.840.1.113730.1.8", "netscape-ca-policy-url" },
{ "2.16.840.1.113730.1.9", "netscape-homePage-url" },
{ "2.16.840.1.113730.1.10", "netscape-entitylogo" },
{ "2.16.840.1.113730.1.11", "netscape-userPicture" },
{ "2.16.840.1.113730.1.12", "netscape-ssl-server-name" },
{ "2.16.840.1.113730.1.13", "netscape-comment" },
/* GnuPG extensions */
{ "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" },
{ "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" },
{ "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" },
/* Extensions used by the Bundesnetzagentur. */
{ "1.3.6.1.4.1.8301.3.5", "validityModel" },
{ NULL }
};
/* Return the description for OID; if no description is available
NULL is returned. */
static const char *
get_oid_desc (const char *oid, unsigned int *flag)
{
int i;
if (oid)
for (i=0; oidtranstbl[i].oid; i++)
if (!strcmp (oidtranstbl[i].oid, oid))
{
if (flag)
*flag = oidtranstbl[i].flag;
return oidtranstbl[i].name;
}
if (flag)
*flag = 0;
return NULL;
}
static void
print_key_data (ksba_cert_t cert, estream_t fp)
{
#if 0
int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
int i;
for(i=0; i < n; i++ )
{
es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
mpi_print(stdout, pk->pkey[i], 1 );
putchar(':');
putchar('\n');
}
#else
(void)cert;
(void)fp;
#endif
}
static void
print_capabilities (ksba_cert_t cert, estream_t fp)
{
gpg_error_t err;
unsigned int use;
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!err && buflen)
{
if (*buffer)
es_putc ('q', fp);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
es_putc ('e', fp);
es_putc ('s', fp);
es_putc ('c', fp);
es_putc ('E', fp);
es_putc ('S', fp);
es_putc ('C', fp);
return;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
return;
}
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
es_putc ('e', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
es_putc ('s', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_putc ('c', fp);
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
es_putc ('E', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
es_putc ('S', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_putc ('C', fp);
es_putc (':', fp);
}
static void
print_time (gnupg_isotime_t t, estream_t fp)
{
if (!t || !*t)
;
else
es_fputs (t, fp);
}
/* Return an allocated string with the email address extracted from a
DN. Note hat we use this code also in ../kbx/keybox-blob.c. */
static char *
email_kludge (const char *name)
{
const char *p, *string;
unsigned char *buf;
int n;
string = name;
for (;;)
{
p = strstr (string, "1.2.840.113549.1.9.1=#");
if (!p)
return NULL;
if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\'))
{
name = p + 22;
break;
}
string = p + 22;
}
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
OpenSSL generated keys get a nicer and usable listing. */
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (!n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; hexdigitp (p); p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return (char*)buf;
}
/* List one certificate in colon mode */
static void
list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
estream_t fp, int have_secret)
{
int rc;
int idx;
char truststring[2];
char *p;
ksba_sexp_t sexp;
char *fpr;
ksba_isotime_t t;
gpg_error_t valerr;
int algo;
unsigned int nbits;
const char *chain_id;
char *chain_id_buffer = NULL;
int is_root = 0;
char *kludge_uid;
if (ctrl->with_validation)
valerr = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, NULL, 0, NULL);
else
valerr = 0;
/* We need to get the fingerprint and the chaining ID in advance. */
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
{
ksba_cert_t next;
rc = gpgsm_walk_cert_chain (ctrl, cert, &next);
if (!rc) /* We known the issuer's certificate. */
{
p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
chain_id_buffer = p;
chain_id = chain_id_buffer;
ksba_cert_release (next);
}
else if (rc == -1) /* We have reached the root certificate. */
{
chain_id = fpr;
is_root = 1;
}
else
chain_id = NULL;
}
es_fputs (have_secret? "crs:":"crt:", fp);
/* Note: We can't use multiple flags, like "ei", because the
validation check does only return one error. */
truststring[0] = 0;
truststring[1] = 0;
if ((validity & VALIDITY_REVOKED)
|| gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED)
*truststring = 'r';
else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED)
*truststring = 'e';
else
{
/* Lets also check whether the certificate under question
expired. This is merely a hack until we found a proper way
to store the expiration flag in the keybox. */
ksba_isotime_t current_time, not_after;
gnupg_get_isotime (current_time);
if (!opt.ignore_expiration
&& !ksba_cert_get_validity (cert, 1, not_after)
&& *not_after && strcmp (current_time, not_after) > 0 )
*truststring = 'e';
else if (valerr)
{
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else
*truststring = 'i';
}
else if (ctrl->with_validation && !is_root)
*truststring = 'f';
}
/* If we have no truststring yet (i.e. the certificate might be
good) and this is a root certificate, we ask the agent whether
this is a trusted root certificate. */
if (!*truststring && is_root)
{
struct rootca_flags_s dummy_flags;
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else
{
rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags);
if (!rc)
*truststring = 'u'; /* Yes, we trust this one (ultimately). */
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
*truststring = 'n'; /* No, we do not trust this one. */
/* (in case of an error we can't tell anything.) */
}
}
if (*truststring)
es_fputs (truststring, fp);
algo = gpgsm_get_key_algo_info (cert, &nbits);
es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24);
ksba_cert_get_validity (cert, 0, t);
print_time (t, fp);
es_putc (':', fp);
ksba_cert_get_validity (cert, 1, t);
print_time ( t, fp);
es_putc (':', fp);
/* Field 8, serial number: */
if ((sexp = ksba_cert_get_serial (cert)))
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
for (s++; len; len--, s++)
es_fprintf (fp,"%02X", *s);
}
xfree (sexp);
}
es_putc (':', fp);
/* Field 9, ownertrust - not used here */
es_putc (':', fp);
/* field 10, old user ID - we use it here for the issuer DN */
if ((p = ksba_cert_get_issuer (cert,0)))
{
es_write_sanitized (fp, p, strlen (p), ":", NULL);
xfree (p);
}
es_putc (':', fp);
/* Field 11, signature class - not used */
es_putc (':', fp);
/* Field 12, capabilities: */
print_capabilities (cert, fp);
/* Field 13, not used: */
es_putc (':', fp);
if (have_secret || ctrl->with_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn)
&& (cardsn || ctrl->with_secret))
{
/* Field 14, not used: */
es_putc (':', fp);
/* Field 15: Token serial number or secret key indicator. */
if (cardsn)
es_fputs (cardsn, fp);
else if (ctrl->with_secret)
es_putc ('+', fp);
es_putc (':', fp);
}
xfree (cardsn);
xfree (p);
}
es_putc ('\n', fp);
/* FPR record */
es_fprintf (fp, "fpr:::::::::%s:::", fpr);
/* Print chaining ID (field 13)*/
if (chain_id)
es_fputs (chain_id, fp);
es_putc (':', fp);
es_putc ('\n', fp);
xfree (fpr); fpr = NULL; chain_id = NULL;
xfree (chain_id_buffer); chain_id_buffer = NULL;
if (opt.with_key_data)
{
if ( (p = gpgsm_get_keygrip_hexstring (cert)))
{
es_fprintf (fp, "grp:::::::::%s:\n", p);
xfree (p);
}
print_key_data (cert, fp);
}
kludge_uid = NULL;
for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
{
/* In the case that the same email address is in the subject DN
as well as in an alternate subject name we avoid printing it
a second time. */
if (kludge_uid && !strcmp (kludge_uid, p))
continue;
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, p, strlen (p), ":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
if (!idx)
{
/* It would be better to get the faked email address from
the keydb. But as long as we don't have a way to pass
the meta data back, we just check it the same way as the
code used to create the keybox meta data does */
kludge_uid = email_kludge (p);
if (kludge_uid)
{
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, kludge_uid, strlen (kludge_uid),
":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
}
}
xfree (p);
}
xfree (kludge_uid);
}
static void
print_name_raw (estream_t fp, const char *string)
{
if (!string)
es_fputs ("[error]", fp);
else
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
}
static void
print_names_raw (estream_t fp, int indent, ksba_name_t name)
{
int idx;
const char *s;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
if (!name)
{
es_fputs ("none\n", fp);
return;
}
for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
{
char *p = ksba_name_get_uri (name, idx);
es_fprintf (fp, "%*s", idx||indent_all?indent:0, "");
es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL);
es_putc ('\n', fp);
xfree (p);
}
}
static void
print_utf8_extn_raw (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
if (indent < 0)
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der);
}
static void
print_utf8_extn (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s%s]\n",
indent_all? indent:0, "", _("Error - "), gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s\"", indent_all? indent:0, "");
/* Fixme: we should implement word wrapping */
es_write_sanitized (fp, der, objlen, "\"", NULL);
es_fputs ("\"\n", fp);
}
/* List one certificate in raw mode useful to have a closer look at
the certificate. This one does no beautification and only minimal
output sanitation. It is mainly useful for debugging. */
static void
list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
size_t off, len;
ksba_sexp_t sexp, keyid;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
const char *oid, *s;
ksba_name_t name, name2;
unsigned int reason;
const unsigned char *cert_der = NULL;
(void)have_secret;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_certid (cert);
es_fprintf (fp, " certid: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_keygrip_hexstring (cert);
es_fprintf (fp, " keygrip: %s\n", dn?dn:"error");
xfree (dn);
ksba_cert_get_validity (cert, 0, t);
es_fputs (" notBefore: ", fp);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
es_fputs (" notAfter: ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
oid = ksba_cert_get_digest_algo (cert);
s = get_oid_desc (oid, NULL);
es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":"");
{
const char *algoname;
unsigned int nbits;
algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
es_fprintf (fp, " keyType: %u bit %s\n",
nbits, algoname? algoname:"?");
}
/* subjectKeyIdentifier */
es_fputs (" subjKeyId: ", fp);
err = ksba_cert_get_subj_key_id (cert, NULL, &keyid);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
/* authorityKeyIdentifier */
es_fputs (" authKeyId: ", fp);
err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
print_names_raw (fp, -15, name);
ksba_name_release (name);
}
if (keyid)
{
es_fputs (" authKeyId.ki: ", fp);
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
es_fputs (" keyUsage:", fp);
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
else
es_fputs (" [none]\n", fp);
es_fputs (" extKeyUsage: ", fp);
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" policies: ", fp);
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (p, fp);
p = pend;
if (*p == 'C')
es_fputs (" (critical)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" chainLength: ", fp);
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "[none]");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
else
es_fputs ("not a CA\n", fp);
/* CRL distribution point */
for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2,
&reason)) ;idx++)
{
es_fputs (" crlDP: ", fp);
print_names_raw (fp, 15, name);
if (reason)
{
es_fputs (" reason: ", fp);
if ( (reason & KSBA_CRLREASON_UNSPECIFIED))
es_fputs (" unused", fp);
if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE))
es_fputs (" keyCompromise", fp);
if ( (reason & KSBA_CRLREASON_CA_COMPROMISE))
es_fputs (" caCompromise", fp);
if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED))
es_fputs (" affiliationChanged", fp);
if ( (reason & KSBA_CRLREASON_SUPERSEDED))
es_fputs (" superseded", fp);
if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION))
es_fputs (" cessationOfOperation", fp);
if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD))
es_fputs (" certificateHold", fp);
es_putc ('\n', fp);
}
es_fputs (" issuer: ", fp);
print_names_raw (fp, 23, name2);
ksba_name_release (name);
ksba_name_release (name2);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" crlDP: [error]\n", fp);
else if (!idx)
es_fputs (" crlDP: [none]\n", fp);
/* authorityInfoAccess. */
for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" authInfo: ", fp);
s = get_oid_desc (string, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" authInfo: [error]\n", fp);
else if (!idx)
es_fputs (" authInfo: [none]\n", fp);
/* subjectInfoAccess. */
for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" subjectInfo: ", fp);
s = get_oid_desc (string, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" subjInfo: [error]\n", fp);
else if (!idx)
es_fputs (" subjInfo: [none]\n", fp);
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &i, &off, &len));idx++)
{
unsigned int flag;
s = get_oid_desc (oid, &flag);
if ((flag & OID_FLAG_SKIP))
continue;
es_fprintf (fp, " %s: %s%s%s%s [%d octets]\n",
i? "critExtn":" extn",
oid, s?" (":"", s?s:"", s?")":"", (int)len);
if ((flag & OID_FLAG_UTF8))
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
assert (cert_der);
print_utf8_extn_raw (fp, -15, cert_der+off, len);
}
}
if (with_validation)
{
err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
if (hd)
{
unsigned int blobflags;
err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags);
if (err)
es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err));
else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
es_fprintf (fp, " [stored as ephemeral]\n");
}
}
/* List one certificate in standard mode */
static void
list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
ksba_sexp_t sexp;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
size_t off, len;
const char *oid;
const unsigned char *cert_der = NULL;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
ksba_cert_get_validity (cert, 0, t);
es_fputs (" validity: ", fp);
gpgsm_print_time (fp, t);
es_fputs (" through ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
{
const char *algoname;
unsigned int nbits;
algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
es_fprintf (fp, " key type: %u bit %s\n",
nbits, algoname? algoname:"?");
}
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" key usage:", fp);
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs ("ext key usage: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs (", ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
/* Print restrictions. */
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &len));idx++)
{
if (!strcmp (oid, OIDSTR_restriction) )
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
assert (cert_der);
es_fputs (" restriction: ", fp);
print_utf8_extn (fp, 15, cert_der+off, len);
}
}
/* Print policies. */
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" policies: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
for (p=string; *p; p++)
{
if (*p == '\n')
*p = ',';
}
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
xfree (string);
}
es_putc ('\n', fp);
}
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
es_fputs (" chain length: ", fp);
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "none");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
if (opt.with_md5_fingerprint)
{
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error");
xfree (dn);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " fingerprint: %s\n", dn?dn:"error");
xfree (dn);
if (opt.with_keygrip)
{
dn = gpgsm_get_keygrip_hexstring (cert);
if (dn)
{
es_fprintf (fp, " keygrip: %s\n", dn);
xfree (dn);
}
}
if (have_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn)
es_fprintf (fp, " card s/n: %s\n", cardsn);
xfree (cardsn);
xfree (p);
}
if (with_validation)
{
gpg_error_t tmperr;
size_t buflen;
char buffer[1];
err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
tmperr = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!tmperr && buflen)
{
if (*buffer)
es_fputs (" [qualified]\n", fp);
}
else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (tmperr));
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
}
/* Same as standard mode list all certifying certs too. */
static void
list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, int raw_mode,
estream_t fp, int with_validation)
{
ksba_cert_t next = NULL;
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, 0, with_validation);
else
list_cert_std (ctrl, cert, fp, 0, with_validation);
ksba_cert_ref (cert);
while (!gpgsm_walk_cert_chain (ctrl, cert, &next))
{
ksba_cert_release (cert);
es_fputs ("Certified by\n", fp);
if (raw_mode)
list_cert_raw (ctrl, hd, next, fp, 0, with_validation);
else
list_cert_std (ctrl, next, fp, 0, with_validation);
cert = next;
}
ksba_cert_release (cert);
es_putc ('\n', fp);
}
/* List all internal keys or just the keys given as NAMES. MODE is a
bit vector to specify what keys are to be included; see
gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw
output mode will be used instead of the standard beautified one.
*/
static gpg_error_t
list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode, int raw_mode)
{
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
strlist_t sl;
int ndesc;
ksba_cert_t cert = NULL;
ksba_cert_t lastcert = NULL;
gpg_error_t rc = 0;
const char *lastresname, *resname;
int have_secret;
int want_ephemeral = ctrl->with_ephemeral_keys;
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
rc = gpg_error_from_syserror ();
log_error ("out of core\n");
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are listed. */
if (!want_ephemeral && names && ndesc)
{
int i;
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR16
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
want_ephemeral = 1;
}
if (want_ephemeral)
keydb_set_ephemeral (hd, 1);
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have a
found flag for each entry in desc and to set this we must check
all those entries after a match to mark all matched one -
currently we stop at the first match. To do this we need an
extra flag to enable this feature so */
/* Suppress duplicates at least when they follow each other. */
lastresname = NULL;
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned int validity;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity);
if (rc)
{
log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
/* Skip duplicated certificates, at least if they follow each
others. This works best if a single key is searched for and
expected. FIXME: Non-sequential duplicates remain. */
if (gpgsm_certs_identical_p (cert, lastcert))
{
ksba_cert_release (cert);
cert = NULL;
continue;
}
resname = keydb_get_resource_name (hd);
if (lastresname != resname )
{
int i;
if (ctrl->no_server)
{
es_fprintf (fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc ('-', fp);
es_putc ('\n', fp);
lastresname = resname;
}
}
have_secret = 0;
if (mode)
{
char *p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
rc = gpgsm_agent_havekey (ctrl, p);
if (!rc)
have_secret = 1;
else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
goto leave;
rc = 0;
xfree (p);
}
}
if (!mode || ((mode & 1) && !have_secret)
|| ((mode & 2) && have_secret) )
{
if (ctrl->with_colons)
list_cert_colon (ctrl, cert, validity, fp, have_secret);
else if (ctrl->with_chain)
list_cert_chain (ctrl, hd, cert,
raw_mode, fp, ctrl->with_validation);
else
{
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, have_secret,
ctrl->with_validation);
else
list_cert_std (ctrl, cert, fp, have_secret,
ctrl->with_validation);
es_putc ('\n', fp);
}
}
ksba_cert_release (lastcert);
lastcert = cert;
cert = NULL;
}
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 )
rc = 0;
if (rc)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
ksba_cert_release (cert);
ksba_cert_release (lastcert);
xfree (desc);
keydb_release (hd);
return rc;
}
static void
list_external_cb (void *cb_value, ksba_cert_t cert)
{
struct list_external_parm_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing certificate as ephemeral\n");
if (parm->print_header)
{
const char *resname = "[external keys]";
int i;
es_fprintf (parm->fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc('-', parm->fp);
es_putc ('\n', parm->fp);
parm->print_header = 0;
}
if (parm->with_colons)
list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0);
else if (parm->with_chain)
list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0);
else
{
if (parm->raw_mode)
list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0);
else
list_cert_std (parm->ctrl, cert, parm->fp, 0, 0);
es_putc ('\n', parm->fp);
}
}
/* List external keys similar to internal one. Note: mode does not
make sense here because it would be unwise to list external secret
keys */
static gpg_error_t
list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode)
{
int rc;
struct list_external_parm_s parm;
parm.fp = fp;
parm.ctrl = ctrl,
parm.print_header = ctrl->no_server;
parm.with_colons = ctrl->with_colons;
parm.with_chain = ctrl->with_chain;
parm.raw_mode = raw_mode;
rc = gpgsm_dirmngr_lookup (ctrl, names, 0, list_external_cb, &parm);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0; /* "Not found" is not an error here. */
if (rc)
log_error ("listing external keys failed: %s\n", gpg_strerror (rc));
return rc;
}
/* List all keys or just the key given as NAMES.
MODE controls the operation mode:
Bit 0-2:
0 = list all public keys but don't flag secret ones
1 = list only public keys
2 = list only secret keys
3 = list secret and public keys
Bit 6: list internal keys
Bit 7: list external keys
Bit 8: Do a raw format dump.
*/
gpg_error_t
gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode)
{
gpg_error_t err = 0;
if ((mode & (1<<6)))
err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256));
if (!err && (mode & (1<<7)))
err = list_external_keys (ctrl, names, fp, (mode&256));
return err;
}
diff --git a/sm/misc.c b/sm/misc.c
index 40e989f4e..1e2465f84 100644
--- a/sm/misc.c
+++ b/sm/misc.c
@@ -1,218 +1,218 @@
/* misc.c - Miscellaneous fucntions
* Copyright (C) 2004, 2009, 2011 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpgsm.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "../common/tlv.h"
#include "../common/sexp-parse.h"
/* Setup the environment so that the pinentry is able to get all
required information. This is used prior to an exec of the
protect-tool. */
void
setup_pinentry_env (void)
{
#ifndef HAVE_W32_SYSTEM
char *lc;
const char *name, *value;
int iterator;
/* Try to make sure that GPG_TTY has been set. This is needed if we
call for example the protect-tools with redirected stdin and thus
it won't be able to ge a default by itself. Try to do it here
but print a warning. */
value = session_env_getenv (opt.session_env, "GPG_TTY");
if (value)
gnupg_setenv ("GPG_TTY", value, 1);
else if (!(lc=getenv ("GPG_TTY")) || !*lc)
{
log_error (_("GPG_TTY has not been set - "
"using maybe bogus default\n"));
lc = gnupg_ttyname (0);
if (!lc)
lc = "/dev/tty";
gnupg_setenv ("GPG_TTY", lc, 1);
}
if (opt.lc_ctype)
gnupg_setenv ("LC_CTYPE", opt.lc_ctype, 1);
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
else if ( (lc = setlocale (LC_CTYPE, "")) )
gnupg_setenv ("LC_CTYPE", lc, 1);
#endif
if (opt.lc_messages)
gnupg_setenv ("LC_MESSAGES", opt.lc_messages, 1);
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
else if ( (lc = setlocale (LC_MESSAGES, "")) )
gnupg_setenv ("LC_MESSAGES", lc, 1);
#endif
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
if (!strcmp (name, "GPG_TTY"))
continue; /* Already set. */
value = session_env_getenv (opt.session_env, name);
if (value)
gnupg_setenv (name, value, 1);
}
#endif /*!HAVE_W32_SYSTEM*/
}
/* Transform a sig-val style s-expression as returned by Libgcrypt to
one which includes an algorithm identifier encoding the public key
and the hash algorithm. The public key algorithm is taken directly
from SIGVAL and the hash algorithm is given by MDALGO. This is
required because X.509 merges the public key algorithm and the hash
algorithm into one OID but Libgcrypt is not aware of that. The
function ignores missing parameters so that it can also be used to
create an siginfo value as expected by ksba_certreq_set_siginfo.
To create a siginfo s-expression a public-key s-expression may be
used instead of a sig-val. We only support RSA for now. */
gpg_error_t
transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo,
unsigned char **r_newsigval, size_t *r_newsigvallen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
int is_pubkey = 0;
const unsigned char *rsa_s = NULL;
size_t rsa_s_len = 0;
const char *oid;
gcry_sexp_t sexp;
*r_newsigval = NULL;
if (r_newsigvallen)
*r_newsigvallen = 0;
buf = sigval;
buflen = sigvallen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 7 && !memcmp ("sig-val", tok, toklen))
;
else if (tok && toklen == 10 && !memcmp ("public-key", tok, toklen))
is_pubkey = 1;
else
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 's': mpi = &rsa_s; mpi_len = &rsa_s_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
/* Map the hash algorithm to an OID. */
switch (mdalgo)
{
case GCRY_MD_SHA1:
oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */
break;
case GCRY_MD_SHA256:
oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */
break;
case GCRY_MD_SHA384:
oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */
break;
case GCRY_MD_SHA512:
oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */
break;
default:
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
if (rsa_s && !is_pubkey)
err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))",
oid, (int)rsa_s_len, rsa_s);
else
err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid);
if (err)
return err;
err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen);
gcry_sexp_release (sexp);
return err;
}
diff --git a/sm/qualified.c b/sm/qualified.c
index 61b071cbc..718141edc 100644
--- a/sm/qualified.c
+++ b/sm/qualified.c
@@ -1,325 +1,325 @@
/* qualified.c - Routines related to qualified signatures
* Copyright (C) 2005, 2007 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include "gpgsm.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include <ksba.h>
/* We open the file only once and keep the open file pointer as well
as the name of the file here. Note that, a listname not equal to
NULL indicates that this module has been intialized and if the
LISTFP is also NULL, no list of qualified signatures exists. */
static char *listname;
static FILE *listfp;
/* Read the trustlist and return entry by entry. KEY must point to a
buffer of at least 41 characters. COUNTRY shall be a buffer of at
least 3 characters to receive the country code of that qualified
signature (i.e. "de" for German and "be" for Belgium).
Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF
and any other error condition is indicated by the appropriate error
code. */
static gpg_error_t
read_list (char *key, char *country, int *lnr)
{
gpg_error_t err;
int c, i, j;
char *p, line[256];
*key = 0;
*country = 0;
if (!listname)
{
listname = make_filename (gnupg_datadir (), "qualified.txt", NULL);
listfp = fopen (listname, "r");
if (!listfp && errno != ENOENT)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), listname, gpg_strerror (err));
return err;
}
}
if (!listfp)
return gpg_error (GPG_ERR_EOF);
do
{
if (!fgets (line, DIM(line)-1, listfp) )
{
if (feof (listfp))
return gpg_error (GPG_ERR_EOF);
return gpg_error_from_syserror ();
}
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=getc (listfp)) != EOF && c != '\n')
;
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
++*lnr;
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
key[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("invalid formatted fingerprint in '%s', line %d\n"),
listname, *lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
assert (p[i]);
i++;
while (spacep (p+i))
i++;
if ( p[i] >= 'a' && p[i] <= 'z'
&& p[i+1] >= 'a' && p[i+1] <= 'z'
&& (spacep (p+i+2) || p[i+2] == '\n'))
{
country[0] = p[i];
country[1] = p[i+1];
country[2] = 0;
}
else
{
log_error (_("invalid country code in '%s', line %d\n"), listname, *lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
return 0;
}
/* Check whether the certificate CERT is included in the list of
qualified certificates. This list is similar to the "trustlist.txt"
as maintained by gpg-agent and includes fingerprints of root
certificates to be used for qualified (legally binding like
handwritten) signatures. We keep this list system wide and not
per user because it is not a decision of the user.
Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it
is not in the list or any other error (e.g. if no list of
qualified signatures is available. If COUNTRY has not been passed
as NULL a string witha maximum length of 2 will be copied into it;
thus the caller needs to provide a buffer of length 3. */
gpg_error_t
gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country)
{
gpg_error_t err;
char *fpr;
char key[41];
char mycountry[3];
int lnr = 0;
(void)ctrl;
if (country)
*country = 0;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
return gpg_error (GPG_ERR_GENERAL);
if (listfp)
{
/* W32ce has no rewind, thus we use the equivalent code. */
fseek (listfp, 0, SEEK_SET);
clearerr (listfp);
}
while (!(err = read_list (key, mycountry, &lnr)))
{
if (!strcmp (key, fpr))
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err && country)
strcpy (country, mycountry);
xfree (fpr);
return err;
}
/* We know that CERT is a qualified certificate. Ask the user for
consent to actually create a signature using this certificate.
Returns: 0 for yes, GPG_ERR_CANCEL for no or any other error
code. */
gpg_error_t
gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert)
{
gpg_error_t err;
char *name, *subject, *buffer, *p;
const char *s;
char *orig_codeset = NULL;
name = ksba_cert_get_subject (cert, 0);
if (!name)
return gpg_error (GPG_ERR_GENERAL);
subject = gpgsm_format_name2 (name, 0);
ksba_free (name); name = NULL;
orig_codeset = i18n_switchto_utf8 ();
if (asprintf (&name,
_("You are about to create a signature using your "
"certificate:\n"
"\"%s\"\n"
"This will create a qualified signature by law "
"equated to a handwritten signature.\n\n%s%s"
"Are you really sure that you want to do this?"),
subject? subject:"?",
opt.qualsig_approval?
"":
_("Note, that this software is not officially approved "
"to create or verify such signatures.\n"),
opt.qualsig_approval? "":"\n"
) < 0 )
err = gpg_error_from_syserror ();
else
err = 0;
i18n_switchback (orig_codeset);
xfree (subject);
if (err)
return err;
buffer = p = xtrymalloc (strlen (name) * 3 + 1);
if (!buffer)
{
err = gpg_error_from_syserror ();
free (name);
return err;
}
for (s=name; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *(unsigned char *)s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
free (name);
err = gpgsm_agent_get_confirmation (ctrl, buffer);
xfree (buffer);
return err;
}
/* Popup a prompt to inform the user that the signature created is not
a qualified one. This is of course only done if we know that we
have been approved. */
gpg_error_t
gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert)
{
gpg_error_t err;
char *name, *subject, *buffer, *p;
const char *s;
char *orig_codeset;
if (!opt.qualsig_approval)
return 0;
name = ksba_cert_get_subject (cert, 0);
if (!name)
return gpg_error (GPG_ERR_GENERAL);
subject = gpgsm_format_name2 (name, 0);
ksba_free (name); name = NULL;
orig_codeset = i18n_switchto_utf8 ();
if (asprintf (&name,
_("You are about to create a signature using your "
"certificate:\n"
"\"%s\"\n"
"Note, that this certificate will NOT create a "
"qualified signature!"),
subject? subject:"?") < 0 )
err = gpg_error_from_syserror ();
else
err = 0;
i18n_switchback (orig_codeset);
xfree (subject);
if (err)
return err;
buffer = p = xtrymalloc (strlen (name) * 3 + 1);
if (!buffer)
{
err = gpg_error_from_syserror ();
free (name);
return err;
}
for (s=name; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *(unsigned char *)s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
free (name);
err = gpgsm_agent_get_confirmation (ctrl, buffer);
xfree (buffer);
return err;
}
diff --git a/sm/server.c b/sm/server.c
index 0fadcadd9..37d66e22c 100644
--- a/sm/server.c
+++ b/sm/server.c
@@ -1,1496 +1,1496 @@
/* server.c - Server mode and main entry point
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include "gpgsm.h"
#include <assuan.h>
-#include "sysutils.h"
-#include "server-help.h"
+#include "../common/sysutils.h"
+#include "../common/server-help.h"
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* The filepointer for status message used in non-server mode */
static FILE *statusfp;
/* Data used to assuciate an Assuan context with local server data */
struct server_local_s {
assuan_context_t assuan_ctx;
int message_fd;
int list_internal;
int list_external;
int list_to_output; /* Write keylistings to the output fd. */
int enable_audit_log; /* Use an audit log. */
certlist_t recplist;
certlist_t signerlist;
certlist_t default_recplist; /* As set by main() - don't release. */
int allow_pinentry_notify; /* Set if pinentry notifications should
be passed back to the client. */
int no_encrypt_to; /* Local version of option. */
};
/* Cookie definition for assuan data line output. */
static gpgrt_ssize_t data_line_cookie_write (void *cookie,
const void *buffer, size_t size);
static int data_line_cookie_close (void *cookie);
static es_cookie_io_functions_t data_line_cookie_functions =
{
NULL,
data_line_cookie_write,
NULL,
data_line_cookie_close
};
static int command_has_option (const char *cmd, const char *cmdopt);
/* Note that it is sufficient to allocate the target string D as
long as the source string S, i.e.: strlen(s)+1; */
static void
strcpy_escaped_plus (char *d, const char *s)
{
while (*s)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d++ = xtoi_2 (s);
s += 2;
}
else if (*s == '+')
*d++ = ' ', s++;
else
*d++ = *s++;
}
*d = 0;
}
/* A write handler used by es_fopencookie to write assuan data
lines. */
static gpgrt_ssize_t
data_line_cookie_write (void *cookie, const void *buffer, size_t size)
{
assuan_context_t ctx = cookie;
if (assuan_send_data (ctx, buffer, size))
{
gpg_err_set_errno (EIO);
return -1;
}
return (gpgrt_ssize_t)size;
}
static int
data_line_cookie_close (void *cookie)
{
assuan_context_t ctx = cookie;
if (assuan_send_data (ctx, NULL, 0))
{
gpg_err_set_errno (EIO);
return -1;
}
return 0;
}
static void
close_message_fd (ctrl_t ctrl)
{
if (ctrl->server_local->message_fd != -1)
{
#ifdef HAVE_W32CE_SYSTEM
#warning Is this correct for W32/W32CE?
#endif
close (ctrl->server_local->message_fd);
ctrl->server_local->message_fd = -1;
}
}
/* Start a new audit session if this has been enabled. */
static gpg_error_t
start_audit_session (ctrl_t ctrl)
{
audit_release (ctrl->audit);
ctrl->audit = NULL;
if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) )
return gpg_error_from_syserror ();
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (opt.session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (opt.session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
err = session_env_setenv (opt.session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
err = session_env_setenv (opt.session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
xfree (opt.lc_ctype);
opt.lc_ctype = xtrystrdup (value);
if (!opt.lc_ctype)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "lc-messages"))
{
xfree (opt.lc_messages);
opt.lc_messages = xtrystrdup (value);
if (!opt.lc_messages)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "include-certs"))
{
int i = *value? atoi (value) : -1;
if (ctrl->include_certs < -2)
err = gpg_error (GPG_ERR_ASS_PARAMETER);
else
ctrl->include_certs = i;
}
else if (!strcmp (key, "list-mode"))
{
int i = *value? atoi (value) : 0;
if (!i || i == 1) /* default and mode 1 */
{
ctrl->server_local->list_internal = 1;
ctrl->server_local->list_external = 0;
}
else if (i == 2)
{
ctrl->server_local->list_internal = 0;
ctrl->server_local->list_external = 1;
}
else if (i == 3)
{
ctrl->server_local->list_internal = 1;
ctrl->server_local->list_external = 1;
}
else
err = gpg_error (GPG_ERR_ASS_PARAMETER);
}
else if (!strcmp (key, "list-to-output"))
{
int i = *value? atoi (value) : 0;
ctrl->server_local->list_to_output = i;
}
else if (!strcmp (key, "with-validation"))
{
int i = *value? atoi (value) : 0;
ctrl->with_validation = i;
}
else if (!strcmp (key, "with-secret"))
{
int i = *value? atoi (value) : 0;
ctrl->with_secret = i;
}
else if (!strcmp (key, "validation-model"))
{
int i = gpgsm_parse_validation_model (value);
if ( i >= 0 && i <= 2 )
ctrl->validation_model = i;
else
err = gpg_error (GPG_ERR_ASS_PARAMETER);
}
else if (!strcmp (key, "with-key-data"))
{
opt.with_key_data = 1;
}
else if (!strcmp (key, "enable-audit-log"))
{
int i = *value? atoi (value) : 0;
ctrl->server_local->enable_audit_log = i;
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
ctrl->server_local->allow_pinentry_notify = 1;
}
else if (!strcmp (key, "with-ephemeral-keys"))
{
int i = *value? atoi (value) : 0;
ctrl->with_ephemeral_keys = i;
}
else if (!strcmp (key, "no-encrypt-to"))
{
ctrl->server_local->no_encrypt_to = 1;
}
else if (!strcmp (key, "offline"))
{
/* We ignore this option if gpgsm has been started with
--disable-dirmngr (which also sets offline). */
if (!opt.disable_dirmngr)
{
int i = *value? !!atoi (value) : 1;
ctrl->offline = i;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
gpgsm_release_certlist (ctrl->server_local->recplist);
gpgsm_release_certlist (ctrl->server_local->signerlist);
ctrl->server_local->recplist = NULL;
ctrl->server_local->signerlist = NULL;
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static gpg_error_t
input_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
ctrl->autodetect_encoding = 0;
ctrl->is_pem = 0;
ctrl->is_base64 = 0;
if (strstr (line, "--armor"))
ctrl->is_pem = 1;
else if (strstr (line, "--base64"))
ctrl->is_base64 = 1;
else if (strstr (line, "--binary"))
;
else
ctrl->autodetect_encoding = 1;
return 0;
}
static gpg_error_t
output_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
ctrl->create_pem = 0;
ctrl->create_base64 = 0;
if (strstr (line, "--armor"))
ctrl->create_pem = 1;
else if (strstr (line, "--base64"))
ctrl->create_base64 = 1; /* just the raw output */
return 0;
}
static const char hlp_recipient[] =
"RECIPIENT <userID>\n"
"\n"
"Set the recipient for the encryption. USERID shall be the\n"
"internal representation of the key; the server may accept any other\n"
"way of specification [we will support this]. If this is a valid and\n"
"trusted recipient the server does respond with OK, otherwise the\n"
"return is an ERR with the reason why the recipient can't be used,\n"
"the encryption will then not be done for this recipient. If the\n"
"policy is not to encrypt at all if not all recipients are valid, the\n"
"client has to take care of this. All RECIPIENT commands are\n"
"cumulative until a RESET or an successful ENCRYPT command.";
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
if (!ctrl->audit)
rc = start_audit_session (ctrl);
else
rc = 0;
if (!rc)
rc = gpgsm_add_to_certlist (ctrl, line, 0,
&ctrl->server_local->recplist, 0);
if (rc)
{
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), line, NULL);
}
return rc;
}
static const char hlp_signer[] =
"SIGNER <userID>\n"
"\n"
"Set the signer's keys for the signature creation. USERID should\n"
"be the internal representation of the key; the server may accept any\n"
"other way of specification [we will support this]. If this is a\n"
"valid and usable signing key the server does respond with OK,\n"
"otherwise it returns an ERR with the reason why the key can't be\n"
"used, the signing will then not be done for this key. If the policy\n"
"is not to sign at all if not all signer keys are valid, the client\n"
"has to take care of this. All SIGNER commands are cumulative until\n"
"a RESET but they are *not* reset by an SIGN command because it can\n"
"be expected that set of signers are used for more than one sign\n"
"operation.";
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
rc = gpgsm_add_to_certlist (ctrl, line, 1,
&ctrl->server_local->signerlist, 0);
if (rc)
{
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), line, NULL);
/* For compatibiliy reasons we also issue the old code after the
new one. */
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), line, NULL);
}
return rc;
}
static const char hlp_encrypt[] =
"ENCRYPT \n"
"\n"
"Do the actual encryption process. Takes the plaintext from the INPUT\n"
"command, writes to the ciphertext to the file descriptor set with\n"
"the OUTPUT command, take the recipients form all the recipients set\n"
"so far. If this command fails the clients should try to delete all\n"
"output currently done or otherwise mark it as invalid. GPGSM does\n"
"ensure that there won't be any security problem with leftover data\n"
"on the output in this case.\n"
"\n"
"This command should in general not fail, as all necessary checks\n"
"have been done while setting the recipients. The input and output\n"
"pipes are closed.";
static gpg_error_t
cmd_encrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
certlist_t cl;
int inp_fd, out_fd;
estream_t out_fp;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
/* Now add all encrypt-to marked recipients from the default
list. */
rc = 0;
if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to)
{
for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next)
if (cl->is_encrypt_to)
rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert,
&ctrl->server_local->recplist, 1);
}
if (!rc)
rc = ctrl->audit? 0 : start_audit_session (ctrl);
if (!rc)
rc = gpgsm_encrypt (assuan_get_pointer (ctx),
ctrl->server_local->recplist,
inp_fd, out_fp);
es_fclose (out_fp);
gpgsm_release_certlist (ctrl->server_local->recplist);
ctrl->server_local->recplist = NULL;
/* Close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_decrypt[] =
"DECRYPT\n"
"\n"
"This performs the decrypt operation after doing some check on the\n"
"internal state. (e.g. that only needed data has been set). Because\n"
"it utilizes the GPG-Agent for the session key decryption, there is\n"
"no need to ask the client for a protecting passphrase - GPG-Agent\n"
"does take care of this by requesting this from the user.";
static gpg_error_t
cmd_decrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t out_fp;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_decrypt (ctrl, inp_fd, out_fp);
es_fclose (out_fp);
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_verify[] =
"VERIFY\n"
"\n"
"This does a verify operation on the message send to the input FD.\n"
"The result is written out using status lines. If an output FD was\n"
"given, the signed text will be written to that.\n"
"\n"
"If the signature is a detached one, the server will inquire about\n"
"the signed material and the client must provide it.";
static gpg_error_t
cmd_verify (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
estream_t out_fp = NULL;
(void)line;
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
if (out_fd != -1)
{
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_verify (assuan_get_pointer (ctx), fd,
ctrl->server_local->message_fd, out_fp);
es_fclose (out_fp);
/* Close and reset the fd. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_sign[] =
"SIGN [--detached]\n"
"\n"
"Sign the data set with the INPUT command and write it to the sink\n"
"set by OUTPUT. With \"--detached\", a detached signature is\n"
"created (surprise).";
static gpg_error_t
cmd_sign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t out_fp;
int detached;
int rc;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
detached = has_option (line, "--detached");
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed");
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist,
inp_fd, detached, out_fp);
es_fclose (out_fp);
/* close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_import[] =
"IMPORT [--re-import]\n"
"\n"
"Import the certificates read form the input-fd, return status\n"
"message for each imported one. The import checks the validity of\n"
"the certificate but not of the entire chain. It is possible to\n"
"import expired certificates.\n"
"\n"
"With the option --re-import the input data is expected to a be a LF\n"
"separated list of fingerprints. The command will re-import these\n"
"certificates, meaning that they are made permanent by removing\n"
"their ephemeral flag.";
static gpg_error_t
cmd_import (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
int reimport = has_option (line, "--re-import");
(void)line;
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport);
/* close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_export[] =
"EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] <pattern>\n"
"\n"
"Export the certificates selected by PATTERN. With --data the output\n"
"is returned using Assuan D lines; the default is to use the sink given\n"
"by the last \"OUTPUT\" command. The options --armor or --base64 encode \n"
"the output using the PEM respective a plain base-64 format; the default\n"
"is a binary format which is only suitable for a single certificate.\n"
"With --secret the secret key is exported using the PKCS#8 format,\n"
"with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container.";
static gpg_error_t
cmd_export (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *p;
strlist_t list, sl;
int use_data;
int opt_secret;
int opt_raw = 0;
int opt_pkcs12 = 0;
use_data = has_option (line, "--data");
if (use_data)
{
/* We need to override any possible setting done by an OUTPUT command. */
ctrl->create_pem = has_option (line, "--armor");
ctrl->create_base64 = has_option (line, "--base64");
}
opt_secret = has_option (line, "--secret");
if (opt_secret)
{
opt_raw = has_option (line, "--raw");
opt_pkcs12 = has_option (line, "--pkcs12");
}
line = skip_options (line);
/* Break the line down into an strlist_t. */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
free_strlist (list);
return out_of_core ();
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
if (opt_secret)
{
if (!list || !*list->d)
return set_error (GPG_ERR_NO_DATA, "No key given");
if (list->next)
return set_error (GPG_ERR_TOO_MANY, "Only one key allowed");
}
if (use_data)
{
estream_t stream;
stream = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!stream)
{
free_strlist (list);
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
if (opt_secret)
gpgsm_p12_export (ctrl, list->d, stream,
opt_raw? 2 : opt_pkcs12 ? 0 : 1);
else
gpgsm_export (ctrl, list, stream);
es_fclose (stream);
}
else
{
int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
estream_t out_fp;
if (fd == -1)
{
free_strlist (list);
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
}
out_fp = es_fdopen_nc (fd, "w");
if (!out_fp)
{
free_strlist (list);
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
if (opt_secret)
gpgsm_p12_export (ctrl, list->d, out_fp,
opt_raw? 2 : opt_pkcs12 ? 0 : 1);
else
gpgsm_export (ctrl, list, out_fp);
es_fclose (out_fp);
}
free_strlist (list);
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_delkeys[] =
"DELKEYS <patterns>\n"
"\n"
"Delete the certificates specified by PATTERNS. Each pattern shall be\n"
"a percent-plus escaped certificate specification. Usually a\n"
"fingerprint will be used for this.";
static gpg_error_t
cmd_delkeys (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *p;
strlist_t list, sl;
int rc;
/* break the line down into an strlist_t */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
free_strlist (list);
return out_of_core ();
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
rc = gpgsm_delete (ctrl, list);
free_strlist (list);
/* close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_output[] =
"OUTPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to write the output data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used. See also the\n"
"\"INPUT\" and \"MESSAGE\" commands.";
static const char hlp_input[] =
"INPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to read the input data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used. See also the\n"
"\"MESSAGE\" and \"OUTPUT\" commands.";
static const char hlp_message[] =
"MESSAGE FD[=<n>]\n"
"\n"
"Set the file descriptor to read the message for a detached\n"
"signatures to N. If N is not given and the operating system\n"
"supports file descriptor passing, the file descriptor currently in\n"
"flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands.";
static gpg_error_t
cmd_message (assuan_context_t ctx, char *line)
{
int rc;
gnupg_fd_t sysfd;
int fd;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = assuan_command_parse_fd (ctx, line, &sysfd);
if (rc)
return rc;
#ifdef HAVE_W32CE_SYSTEM
sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0);
if (sysfd == INVALID_HANDLE_VALUE)
return set_error (gpg_err_code_from_syserror (),
"rvid conversion failed");
#endif
fd = translate_sys2libc_fd (sysfd, 0);
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
ctrl->server_local->message_fd = fd;
return 0;
}
static const char hlp_listkeys[] =
"LISTKEYS [<patterns>]\n"
"LISTSECRETKEYS [<patterns>]\n"
"DUMPKEYS [<patterns>]\n"
"DUMPSECRETKEYS [<patterns>]\n"
"\n"
"List all certificates or only those specified by PATTERNS. Each\n"
"pattern shall be a percent-plus escaped certificate specification.\n"
"The \"SECRET\" versions of the command filter the output to include\n"
"only certificates where the secret key is available or a corresponding\n"
"smartcard has been registered. The \"DUMP\" versions of the command\n"
"are only useful for debugging. The output format is a percent escaped\n"
"colon delimited listing as described in the manual.\n"
"\n"
"These \"OPTION\" command keys effect the output::\n"
"\n"
" \"list-mode\" set to 0: List only local certificates (default).\n"
" 1: Ditto.\n"
" 2: List only external certificates.\n"
" 3: List local and external certificates.\n"
"\n"
" \"with-validation\" set to true: Validate each certificate.\n"
"\n"
" \"with-ephemeral-key\" set to true: Always include ephemeral\n"
" certificates.\n"
"\n"
" \"list-to-output\" set to true: Write output to the file descriptor\n"
" given by the last \"OUTPUT\" command.";
static int
do_listkeys (assuan_context_t ctx, char *line, int mode)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
estream_t fp;
char *p;
strlist_t list, sl;
unsigned int listmode;
gpg_error_t err;
/* Break the line down into an strlist. */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
free_strlist (list);
return out_of_core ();
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
if (ctrl->server_local->list_to_output)
{
int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if ( outfd == -1 )
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
fp = es_fdopen_nc (outfd, "w");
if (!fp)
return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed");
}
else
{
fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!fp)
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
ctrl->with_colons = 1;
listmode = mode;
if (ctrl->server_local->list_internal)
listmode |= (1<<6);
if (ctrl->server_local->list_external)
listmode |= (1<<7);
err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode);
free_strlist (list);
es_fclose (fp);
if (ctrl->server_local->list_to_output)
assuan_close_output_fd (ctx);
return err;
}
static gpg_error_t
cmd_listkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 3);
}
static gpg_error_t
cmd_dumpkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 259);
}
static gpg_error_t
cmd_listsecretkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 2);
}
static gpg_error_t
cmd_dumpsecretkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 258);
}
static const char hlp_genkey[] =
"GENKEY\n"
"\n"
"Read the parameters in native format from the input fd and write a\n"
"certificate request to the output.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t in_stream, out_stream;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
in_stream = es_fdopen_nc (inp_fd, "r");
if (!in_stream)
return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed");
out_stream = es_fdopen_nc (out_fd, "w");
if (!out_stream)
{
es_fclose (in_stream);
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
rc = gpgsm_genkey (ctrl, in_stream, out_stream);
es_fclose (out_stream);
es_fclose (in_stream);
/* close and reset the fds */
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_getauditlog[] =
"GETAUDITLOG [--data] [--html]\n"
"\n"
"If --data is used, the output is send using D-lines and not to the\n"
"file descriptor given by an OUTPUT command.\n"
"\n"
"If --html is used the output is formatted as an XHTML block. This is\n"
"designed to be incorporated into a HTML document.";
static gpg_error_t
cmd_getauditlog (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int out_fd;
estream_t out_stream;
int opt_data, opt_html;
int rc;
opt_data = has_option (line, "--data");
opt_html = has_option (line, "--html");
/* Not needed: line = skip_options (line); */
if (!ctrl->audit)
return gpg_error (GPG_ERR_NO_DATA);
if (opt_data)
{
out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!out_stream)
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
else
{
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_stream = es_fdopen_nc (out_fd, "w");
if (!out_stream)
{
return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed");
}
}
audit_print_result (ctrl->audit, out_stream, opt_html);
rc = 0;
es_fclose (out_stream);
/* Close and reset the fd. */
if (!opt_data)
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" agent-check - Return success if the agent is running.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if the command CMD implements the option OPT.\n"
" offline - Returns OK if the connection is in offline mode.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "agent-check"))
{
rc = gpgsm_agent_send_nop (ctrl);
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_GENERAL);
}
}
}
}
else if (!strcmp (line, "offline"))
{
rc = ctrl->offline? 0 : gpg_error (GPG_ERR_GENERAL);
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_passwd[] =
"PASSWD <userID>\n"
"\n"
"Change the passphrase of the secret key for USERID.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
char *grip = NULL;
line = skip_options (line);
err = gpgsm_find_cert (ctrl, line, NULL, &cert);
if (err)
;
else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
err = gpg_error (GPG_ERR_INTERNAL);
else
{
char *desc = gpgsm_format_keydesc (cert);
err = gpgsm_agent_passwd (ctrl, grip, desc);
xfree (desc);
}
xfree (grip);
ksba_cert_release (cert);
return err;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "IMPORT"))
{
if (!strcmp (cmdopt, "re-import"))
return 1;
}
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "RECIPIENT", cmd_recipient, hlp_recipient },
{ "SIGNER", cmd_signer, hlp_signer },
{ "ENCRYPT", cmd_encrypt, hlp_encrypt },
{ "DECRYPT", cmd_decrypt, hlp_decrypt },
{ "VERIFY", cmd_verify, hlp_verify },
{ "SIGN", cmd_sign, hlp_sign },
{ "IMPORT", cmd_import, hlp_import },
{ "EXPORT", cmd_export, hlp_export },
{ "INPUT", NULL, hlp_input },
{ "OUTPUT", NULL, hlp_output },
{ "MESSAGE", cmd_message, hlp_message },
{ "LISTKEYS", cmd_listkeys, hlp_listkeys },
{ "DUMPKEYS", cmd_dumpkeys, hlp_listkeys },
{ "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys },
{ "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "DELKEYS", cmd_delkeys, hlp_delkeys },
{ "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Startup the server. DEFAULT_RECPLIST is the list of recipients as
set from the command line or config file. We only require those
marked as encrypt-to. */
void
gpgsm_server (certlist_t default_recplist)
{
int rc;
assuan_fd_t filedes[2];
assuan_context_t ctx;
struct server_control_s ctrl;
static const char hello[] = ("GNU Privacy Guard's S/M server "
VERSION " ready");
memset (&ctrl, 0, sizeof ctrl);
gpgsm_init_default_ctrl (&ctrl);
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FILEDES in this case. */
#ifdef HAVE_W32CE_SYSTEM
#define SERVER_STDIN es_fileno(es_stdin)
#define SERVER_STDOUT es_fileno(es_stdout)
#else
#define SERVER_STDIN 0
#define SERVER_STDOUT 1
#endif
filedes[0] = assuan_fdopen (SERVER_STDIN);
filedes[1] = assuan_fdopen (SERVER_STDOUT);
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
gpgsm_exit (2);
}
rc = assuan_init_pipe_server (ctx, filedes);
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror (rc));
gpgsm_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror(rc));
gpgsm_exit (2);
}
if (opt.verbose || opt.debug)
{
char *tmp;
/* Fixme: Use the really used socket name. */
if (asprintf (&tmp,
"Home: %s\n"
"Config: %s\n"
"DirmngrInfo: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename,
dirmngr_socket_name (),
hello) > 0)
{
assuan_set_hello_line (ctx, tmp);
free (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_input_notify (ctx, input_notify);
assuan_register_output_notify (ctx, output_notify);
assuan_register_option_handler (ctx, option_handler);
assuan_set_pointer (ctx, &ctrl);
ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
ctrl.server_local->assuan_ctx = ctx;
ctrl.server_local->message_fd = -1;
ctrl.server_local->list_internal = 1;
ctrl.server_local->list_external = 0;
ctrl.server_local->default_recplist = default_recplist;
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
gpgsm_release_certlist (ctrl.server_local->recplist);
ctrl.server_local->recplist = NULL;
gpgsm_release_certlist (ctrl.server_local->signerlist);
ctrl.server_local->signerlist = NULL;
xfree (ctrl.server_local);
audit_release (ctrl.audit);
ctrl.audit = NULL;
assuan_release (ctx);
}
gpg_error_t
gpgsm_status2 (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (ctrl->no_server && ctrl->status_fd == -1)
; /* No status wanted. */
else if (ctrl->no_server)
{
if (!statusfp)
{
if (ctrl->status_fd == 1)
statusfp = stdout;
else if (ctrl->status_fd == 2)
statusfp = stderr;
else
statusfp = fdopen (ctrl->status_fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
ctrl->status_fd, strerror(errno));
}
}
fputs ("[GNUPG:] ", statusfp);
fputs (get_status_string (no), statusfp);
while ( (text = va_arg (arg_ptr, const char*) ))
{
putc ( ' ', statusfp );
for (; *text; text++)
{
if (*text == '\n')
fputs ( "\\n", statusfp );
else if (*text == '\r')
fputs ( "\\r", statusfp );
else
putc ( *(const byte *)text, statusfp );
}
}
putc ('\n', statusfp);
fflush (statusfp);
}
else
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
char buf[950], *p;
size_t n;
p = buf;
n = 0;
while ( (text = va_arg (arg_ptr, const char *)) )
{
if (n)
{
*p++ = ' ';
n++;
}
for ( ; *text && n < DIM (buf)-2; n++)
*p++ = *text++;
}
*p = 0;
err = assuan_write_status (ctx, get_status_string (no), buf);
}
va_end (arg_ptr);
return err;
}
gpg_error_t
gpgsm_status (ctrl_t ctrl, int no, const char *text)
{
return gpgsm_status2 (ctrl, no, text, NULL);
}
gpg_error_t
gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text,
gpg_err_code_t ec)
{
char buf[30];
sprintf (buf, "%u", (unsigned int)ec);
if (text)
return gpgsm_status2 (ctrl, no, text, buf, NULL);
else
return gpgsm_status2 (ctrl, no, buf, NULL);
}
gpg_error_t
gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text,
gpg_error_t err)
{
char buf[30];
snprintf (buf, sizeof buf, "%u", err);
if (text)
return gpgsm_status2 (ctrl, no, text, buf, NULL);
else
return gpgsm_status2 (ctrl, no, buf, NULL);
}
/* Helper to notify the client about Pinentry events. Because that
might disturb some older clients, this is only done when enabled
via an option. Returns an gpg error code. */
gpg_error_t
gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
diff --git a/sm/sign.c b/sm/sign.c
index 0ca575b2a..a153b511f 100644
--- a/sm/sign.c
+++ b/sm/sign.c
@@ -1,788 +1,788 @@
/* sign.c - Sign a message
* Copyright (C) 2001, 2002, 2003, 2008,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
/* Hash the data and return if something was hashed. Return -1 on error. */
static int
hash_data (int fd, gcry_md_hd_t md)
{
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return -1;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
gcry_md_write (md, buffer, nread);
}
while (nread);
if (es_ferror (fp))
{
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
rc = -1;
}
es_fclose (fp);
return rc;
}
static int
hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer)
{
gpg_error_t err;
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
int any = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return tmperr;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
if (nread)
{
any = 1;
gcry_md_write (md, buffer, nread);
err = ksba_writer_write_octet_string (writer, buffer, nread, 0);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
rc = err;
}
}
}
while (nread && !rc);
if (es_ferror (fp))
{
rc = gpg_error_from_syserror ();
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
}
es_fclose (fp);
if (!any)
{
/* We can't allow signing an empty message because it does not
make much sense and more seriously, ksba_cms_build has
already written the tag for data and now expects an octet
string and an octet string of size 0 is illegal. */
log_error ("cannot sign an empty message\n");
rc = gpg_error (GPG_ERR_NO_DATA);
}
if (!rc)
{
err = ksba_writer_write_octet_string (writer, NULL, 0, 1);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
rc = err;
}
}
return rc;
}
/* Get the default certificate which is defined as the first
certificate capable of signing returned by the keyDB and has a
secret key available. */
int
gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert)
{
KEYDB_HANDLE hd;
ksba_cert_t cert = NULL;
int rc;
char *p;
hd = keydb_new ();
if (!hd)
return gpg_error (GPG_ERR_GENERAL);
rc = keydb_search_first (ctrl, hd);
if (rc)
{
keydb_release (hd);
return rc;
}
do
{
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
keydb_release (hd);
return rc;
}
if (!gpgsm_cert_use_sign_p (cert))
{
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
if (!gpgsm_agent_havekey (ctrl, p))
{
xfree (p);
keydb_release (hd);
*r_cert = cert;
return 0; /* got it */
}
xfree (p);
}
}
ksba_cert_release (cert);
cert = NULL;
}
while (!(rc = keydb_search_next (ctrl, hd)));
if (rc && rc != -1)
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
ksba_cert_release (cert);
keydb_release (hd);
return rc;
}
static ksba_cert_t
get_default_signer (ctrl_t ctrl)
{
KEYDB_SEARCH_DESC desc;
ksba_cert_t cert = NULL;
KEYDB_HANDLE kh = NULL;
int rc;
if (!opt.local_user)
{
rc = gpgsm_get_default_cert (ctrl, &cert);
if (rc)
{
if (rc != -1)
log_debug ("failed to find default certificate: %s\n",
gpg_strerror (rc));
return NULL;
}
return cert;
}
rc = classify_user_id (opt.local_user, &desc, 0);
if (rc)
{
log_error ("failed to find default signer: %s\n", gpg_strerror (rc));
return NULL;
}
kh = keydb_new ();
if (!kh)
return NULL;
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc)
{
log_debug ("failed to find default certificate: rc=%d\n", rc);
}
else
{
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_debug ("failed to get cert: rc=%d\n", rc);
}
}
keydb_release (kh);
return cert;
}
/* Depending on the options in CTRL add the certificate CERT as well as
other certificate up in the chain to the Root-CA to the CMS
object. */
static int
add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert)
{
gpg_error_t err;
int rc = 0;
ksba_cert_t next = NULL;
int n;
int not_root = 0;
ksba_cert_ref (cert);
n = ctrl->include_certs;
log_debug ("adding certificates at level %d\n", n);
if (n == -2)
{
not_root = 1;
n = -1;
}
if (n < 0 || n > 50)
n = 50; /* We better apply an upper bound */
/* First add my own certificate unless we don't want any certificate
included at all. */
if (n)
{
if (not_root && gpgsm_is_root_cert (cert))
err = 0;
else
err = ksba_cms_add_cert (cms, cert);
if (err)
goto ksba_failure;
if (n>0)
n--;
}
/* Walk the chain to include all other certificates. Note that a -1
used for N makes sure that there is no limit and all certs get
included. */
while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, cert, &next)) )
{
if (not_root && gpgsm_is_root_cert (next))
err = 0;
else
err = ksba_cms_add_cert (cms, next);
ksba_cert_release (cert);
cert = next; next = NULL;
if (err)
goto ksba_failure;
}
ksba_cert_release (cert);
return rc == -1? 0: rc;
ksba_failure:
ksba_cert_release (cert);
log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err));
return err;
}
/* Perform a sign operation.
Sign the data received on DATA-FD in embedded mode or in detached
mode when DETACHED is true. Write the signature to OUT_FP. The
keys used to sign are taken from SIGNERLIST or the default one will
be used if the value of this argument is NULL. */
int
gpgsm_sign (ctrl_t ctrl, certlist_t signerlist,
int data_fd, int detached, estream_t out_fp)
{
int i, rc;
gpg_error_t err;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh = NULL;
gcry_md_hd_t data_md = NULL;
int signer;
const char *algoid;
int algo;
ksba_isotime_t signed_at;
certlist_t cl;
int release_signerlist = 0;
audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
ctrl->pem_name = "SIGNED MESSAGE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
err = ksba_cms_new (&cms);
if (err)
{
rc = err;
goto leave;
}
err = ksba_cms_set_reader_writer (cms, NULL, writer);
if (err)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* We are going to create signed data with data as encap. content */
err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA);
if (!err)
err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA);
if (err)
{
log_debug ("ksba_cms_set_content_type failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* If no list of signers is given, use the default certificate. */
if (!signerlist)
{
ksba_cert_t cert = get_default_signer (ctrl);
if (!cert)
{
log_error ("no default signer found\n");
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Although we don't check for ambigious specification we will
check that the signer's certificate is usable and valid. */
rc = gpgsm_cert_use_sign_p (cert);
if (!rc)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL);
if (rc)
{
char *tmpfpr;
tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0);
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), tmpfpr, NULL);
xfree (tmpfpr);
goto leave;
}
/* That one is fine - create signerlist. */
signerlist = xtrycalloc (1, sizeof *signerlist);
if (!signerlist)
{
rc = out_of_core ();
ksba_cert_release (cert);
goto leave;
}
signerlist->cert = cert;
release_signerlist = 1;
}
/* Figure out the hash algorithm to use. We do not want to use the
one for the certificate but if possible an OID for the plain
algorithm. */
if (opt.forced_digest_algo && opt.verbose)
log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo);
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
{
const char *oid;
if (opt.forced_digest_algo)
{
oid = NULL;
cl->hash_algo = opt.forced_digest_algo;
}
else
{
oid = ksba_cert_get_digest_algo (cl->cert);
cl->hash_algo = oid ? gcry_md_map_name (oid) : 0;
}
switch (cl->hash_algo)
{
case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break;
case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break;
case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break;
case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break;
case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break;
case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break;
/* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */
case GCRY_MD_MD5: /* We don't want to use MD5. */
case 0: /* No algorithm found in cert. */
default: /* Other algorithms. */
log_info (_("hash algorithm %d (%s) for signer %d not supported;"
" using %s\n"),
cl->hash_algo, oid? oid: "?", i,
gcry_md_algo_name (GCRY_MD_SHA1));
cl->hash_algo = GCRY_MD_SHA1;
oid = "1.3.14.3.2.26";
break;
}
cl->hash_algo_oid = oid;
}
if (opt.verbose)
{
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
log_info (_("hash algorithm used for signer %d: %s (%s)\n"),
i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid);
}
/* Gather certificates of signers and store them in the CMS object. */
for (cl=signerlist; cl; cl = cl->next)
{
rc = gpgsm_cert_use_sign_p (cl->cert);
if (rc)
goto leave;
err = ksba_cms_add_signer (cms, cl->cert);
if (err)
{
log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
rc = add_certificate_list (ctrl, cms, cl->cert);
if (rc)
{
log_error ("failed to store list of certificates: %s\n",
gpg_strerror(rc));
goto leave;
}
/* Set the hash algorithm we are going to use */
err = ksba_cms_add_digest_algo (cms, cl->hash_algo_oid);
if (err)
{
log_debug ("ksba_cms_add_digest_algo failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Check whether one of the certificates is qualified. Note that we
already validated the certificate and thus the user data stored
flag must be available. */
if (!opt.no_chain_validation)
{
for (cl=signerlist; cl; cl = cl->next)
{
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cl->cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (err || !buflen)
{
log_error (_("checking for qualified certificate failed: %s\n"),
gpg_strerror (err));
rc = err;
goto leave;
}
if (*buffer)
err = gpgsm_qualified_consent (ctrl, cl->cert);
else
err = gpgsm_not_qualified_warning (ctrl, cl->cert);
if (err)
{
rc = err;
goto leave;
}
}
}
/* Prepare hashing (actually we are figuring out what we have set
above). */
rc = gcry_md_open (&data_md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (data_md, "sign.data");
for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++)
{
algo = gcry_md_map_name (algoid);
if (!algo)
{
log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
gcry_md_enable (data_md, algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
if (detached)
{ /* We hash the data right now so that we can store the message
digest. ksba_cms_build() takes this as an flag that detached
data is expected. */
unsigned char *digest;
size_t digest_len;
if (!hash_data (data_fd, data_md))
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer, digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
gnupg_get_isotime (signed_at);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
err = ksba_cms_set_signing_time (cms, signer, signed_at);
if (err)
{
log_error ("ksba_cms_set_signing_time failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* We need to write at least a minimal list of our capabilities to
try to convince some MUAs to use 3DES and not the crippled
RC2. Our list is:
aes128-CBC
des-EDE3-CBC
*/
err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0);
if (!err)
err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0);
if (err)
{
log_error ("ksba_cms_add_smime_capability failed: %s\n",
gpg_strerror (err));
goto leave;
}
/* Main building loop. */
do
{
err = ksba_cms_build (cms, &stopreason);
if (err)
{
log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
{
/* Hash the data and store the message digest. */
unsigned char *digest;
size_t digest_len;
assert (!detached);
rc = hash_and_copy_data (data_fd, data_md, writer);
if (rc)
goto leave;
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer,
digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
else if (stopreason == KSBA_SR_NEED_SIG)
{
/* Compute the signature for all signers. */
gcry_md_hd_t md;
rc = gcry_md_open (&md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (md, "sign.attr");
ksba_cms_set_hash_function (cms, HASH_FNC, md);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
unsigned char *sigval = NULL;
char *buf, *fpr;
audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer);
if (signer)
gcry_md_reset (md);
{
certlist_t cl_tmp;
for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next)
{
gcry_md_enable (md, cl_tmp->hash_algo);
audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO,
cl_tmp->hash_algo);
}
}
rc = ksba_cms_hash_signed_attrs (cms, signer);
if (rc)
{
log_debug ("hashing signed attrs failed: %s\n",
gpg_strerror (rc));
gcry_md_close (md);
goto leave;
}
rc = gpgsm_create_cms_signature (ctrl, cl->cert,
md, cl->hash_algo, &sigval);
if (rc)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc);
gcry_md_close (md);
goto leave;
}
err = ksba_cms_set_sig_val (cms, signer, sigval);
xfree (sigval);
if (err)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err);
log_error ("failed to store the signature: %s\n",
gpg_strerror (err));
rc = err;
gcry_md_close (md);
goto leave;
}
/* write a status message */
fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1);
if (!fpr)
{
rc = gpg_error (GPG_ERR_ENOMEM);
gcry_md_close (md);
goto leave;
}
rc = 0;
{
int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL);
buf = xtryasprintf ("%c %d %d 00 %s %s",
detached? 'D':'S',
pkalgo,
cl->hash_algo,
signed_at,
fpr);
if (!buf)
rc = gpg_error_from_syserror ();
}
xfree (fpr);
if (rc)
{
gcry_md_close (md);
goto leave;
}
gpgsm_status (ctrl, STATUS_SIG_CREATED, buf);
xfree (buf);
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0);
}
gcry_md_close (md);
}
}
while (stopreason != KSBA_SR_READY);
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SIGNING_DONE);
log_info ("signature created\n");
leave:
if (rc)
log_error ("error creating signature: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc) );
if (release_signerlist)
gpgsm_release_certlist (signerlist);
ksba_cms_release (cms);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
gcry_md_close (data_md);
return rc;
}
diff --git a/sm/verify.c b/sm/verify.c
index 1ac97cb03..6c034e692 100644
--- a/sm/verify.c
+++ b/sm/verify.c
@@ -1,668 +1,668 @@
/* verify.c - Verify a messages signature
* Copyright (C) 2001, 2002, 2003, 2007,
* 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
static char *
strtimestamp_r (ksba_isotime_t atime)
{
char *buffer = xmalloc (15);
if (!atime || !*atime)
strcpy (buffer, "none");
else
sprintf (buffer, "%.4s-%.2s-%.2s", atime, atime+4, atime+6);
return buffer;
}
/* Hash the data for a detached signature. Returns 0 on success. */
static gpg_error_t
hash_data (int fd, gcry_md_hd_t md)
{
gpg_error_t err = 0;
estream_t fp;
char buffer[4096];
int nread;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("fdopen(%d) failed: %s\n", fd, gpg_strerror (err));
return err;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
gcry_md_write (md, buffer, nread);
}
while (nread);
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("read error on fd %d: %s\n", fd, gpg_strerror (err));
}
es_fclose (fp);
return err;
}
/* Perform a verify operation. To verify detached signatures, DATA_FD
must be different than -1. With OUT_FP given and a non-detached
signature, the signed material is written to that stream. */
int
gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp)
{
int i, rc;
gnupg_ksba_io_t b64reader = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_reader_t reader;
ksba_writer_t writer = NULL;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
ksba_cert_t cert;
KEYDB_HANDLE kh;
gcry_md_hd_t data_md = NULL;
int signer;
const char *algoid;
int algo;
int is_detached;
estream_t in_fp = NULL;
char *p;
audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
in_fp = es_fdopen_nc (in_fd, "rb");
if (!in_fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)),
in_fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
if (out_fp)
{
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, writer);
if (rc)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
rc = gcry_md_open (&data_md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (data_md, "vrfy.data");
audit_log (ctrl->audit, AUDIT_SETUP_READY);
is_detached = 0;
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_NEED_HASH)
{
is_detached = 1;
audit_log (ctrl->audit, AUDIT_DETACHED_SIGNATURE);
if (opt.verbose)
log_info ("detached signature\n");
}
if (stopreason == KSBA_SR_NEED_HASH
|| stopreason == KSBA_SR_BEGIN_DATA)
{
audit_log (ctrl->audit, AUDIT_GOT_DATA);
/* We are now able to enable the hash algorithms */
for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++)
{
algo = gcry_md_map_name (algoid);
if (!algo)
{
log_error ("unknown hash algorithm '%s'\n",
algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
audit_log_s (ctrl->audit, AUDIT_BAD_DATA_HASH_ALGO, algoid);
}
else
{
if (DBG_X509)
log_debug ("enabling hash algorithm %d (%s)\n",
algo, algoid? algoid:"");
gcry_md_enable (data_md, algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
}
}
if (opt.extra_digest_algo)
{
if (DBG_X509)
log_debug ("enabling extra hash algorithm %d\n",
opt.extra_digest_algo);
gcry_md_enable (data_md, opt.extra_digest_algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO,
opt.extra_digest_algo);
}
if (is_detached)
{
if (data_fd == -1)
{
log_info ("detached signature w/o data "
"- assuming certs-only\n");
audit_log (ctrl->audit, AUDIT_CERT_ONLY_SIG);
}
else
audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING,
hash_data (data_fd, data_md));
}
else
{
ksba_cms_set_hash_function (cms, HASH_FNC, data_md);
}
}
else if (stopreason == KSBA_SR_END_DATA)
{ /* The data bas been hashed */
audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, 0);
}
}
while (stopreason != KSBA_SR_READY);
if (b64writer)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
audit_log_ok (ctrl->audit, AUDIT_WRITE_ERROR, rc);
goto leave;
}
}
if (data_fd != -1 && !is_detached)
{
log_error ("data given for a non-detached signature\n");
rc = gpg_error (GPG_ERR_CONFLICT);
audit_log (ctrl->audit, AUDIT_USAGE_ERROR);
goto leave;
}
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
/* Fixme: it might be better to check the validity of the
certificate first before entering it into the DB. This way
we would avoid cluttering the DB with invalid
certificates. */
audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert,
keydb_store_cert (ctrl, cert, 0, NULL));
ksba_cert_release (cert);
}
cert = NULL;
for (signer=0; ; signer++)
{
char *issuer = NULL;
ksba_sexp_t sigval = NULL;
ksba_isotime_t sigtime, keyexptime;
ksba_sexp_t serial;
char *msgdigest = NULL;
size_t msgdigestlen;
char *ctattr;
int sigval_hash_algo;
int info_pkalgo;
unsigned int verifyflags;
rc = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial);
if (!signer && gpg_err_code (rc) == GPG_ERR_NO_DATA
&& data_fd == -1 && is_detached)
{
log_info ("certs-only message accepted\n");
rc = 0;
break;
}
if (rc)
{
if (signer && rc == -1)
rc = 0;
break;
}
gpgsm_status (ctrl, STATUS_NEWSIG, NULL);
audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer);
if (DBG_X509)
{
log_debug ("signer %d - issuer: '%s'\n",
signer, issuer? issuer:"[NONE]");
log_debug ("signer %d - serial: ", signer);
gpgsm_dump_serial (serial);
log_printf ("\n");
}
if (ctrl->audit)
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_SIG_NAME, tmpstr);
xfree (tmpstr);
}
rc = ksba_cms_get_signing_time (cms, signer, sigtime);
if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
*sigtime = 0;
else if (rc)
{
log_error ("error getting signing time: %s\n", gpg_strerror (rc));
*sigtime = 0; /* (we can't encode an error in the time string.) */
}
rc = ksba_cms_get_message_digest (cms, signer,
&msgdigest, &msgdigestlen);
if (!rc)
{
size_t is_enabled;
algoid = ksba_cms_get_digest_algo (cms, signer);
algo = gcry_md_map_name (algoid);
if (DBG_X509)
log_debug ("signer %d - digest algo: %d\n", signer, algo);
is_enabled = sizeof algo;
if ( gcry_md_info (data_md, GCRYCTL_IS_ALGO_ENABLED,
&algo, &is_enabled)
|| !is_enabled)
{
log_error ("digest algo %d (%s) has not been enabled\n",
algo, algoid?algoid:"");
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "unsupported");
goto next_signer;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
{
assert (!msgdigest);
rc = 0;
algoid = NULL;
algo = 0;
}
else /* real error */
{
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
break;
}
rc = ksba_cms_get_sigattr_oids (cms, signer,
"1.2.840.113549.1.9.3", &ctattr);
if (!rc)
{
const char *s;
if (DBG_X509)
log_debug ("signer %d - content-type attribute: %s",
signer, ctattr);
s = ksba_cms_get_content_oid (cms, 1);
if (!s || strcmp (ctattr, s))
{
log_error ("content-type attribute does not match "
"actual content-type\n");
ksba_free (ctattr);
ctattr = NULL;
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
ksba_free (ctattr);
ctattr = NULL;
}
else if (rc != -1)
{
log_error ("error getting content-type attribute: %s\n",
gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
rc = 0;
sigval = ksba_cms_get_sig_val (cms, signer);
if (!sigval)
{
log_error ("no signature value available\n");
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
sigval_hash_algo = hash_algo_from_sigval (sigval);
if (DBG_X509)
{
log_debug ("signer %d - signature available (sigval hash=%d)",
signer, sigval_hash_algo);
/* log_printhex ("sigval ", sigval, */
/* gcry_sexp_canon_len (sigval, 0, NULL, NULL)); */
}
if (!sigval_hash_algo)
sigval_hash_algo = algo; /* Fallback used e.g. with old libksba. */
/* Find the certificate of the signer */
keydb_search_reset (kh);
rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial);
if (rc)
{
if (rc == -1)
{
log_error ("certificate not found\n");
rc = gpg_error (GPG_ERR_NO_PUBKEY);
}
else
log_error ("failed to find the certificate: %s\n",
gpg_strerror(rc));
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey",
numbuf, NULL);
}
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "no-cert");
goto next_signer;
}
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("failed to get cert: %s\n", gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
log_info (_("Signature made "));
if (*sigtime)
dump_isotime (sigtime);
else
log_printf (_("[date not given]"));
log_printf (_(" using certificate ID 0x%08lX\n"),
gpgsm_get_short_fingerprint (cert, NULL));
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
if (msgdigest)
{ /* Signed attributes are available. */
gcry_md_hd_t md;
unsigned char *s;
/* Check that the message digest in the signed attributes
matches the one we calculated on the data. */
s = gcry_md_read (data_md, algo);
if ( !s || !msgdigestlen
|| gcry_md_get_algo_dlen (algo) != msgdigestlen
|| memcmp (s, msgdigest, msgdigestlen) )
{
char *fpr;
log_error (_("invalid signature: message digest attribute "
"does not match computed one\n"));
if (DBG_X509)
{
if (msgdigest)
log_printhex ("message: ", msgdigest, msgdigestlen);
if (s)
log_printhex ("computed: ",
s, gcry_md_get_algo_dlen (algo));
}
fpr = gpgsm_fpr_and_name_for_status (cert);
gpgsm_status (ctrl, STATUS_BADSIG, fpr);
xfree (fpr);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, sigval_hash_algo);
rc = gcry_md_open (&md, sigval_hash_algo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
if (DBG_HASHING)
gcry_md_debug (md, "vrfy.attr");
ksba_cms_set_hash_function (cms, HASH_FNC, md);
rc = ksba_cms_hash_signed_attrs (cms, signer);
if (rc)
{
log_error ("hashing signed attrs failed: %s\n",
gpg_strerror (rc));
gcry_md_close (md);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
rc = gpgsm_check_cms_signature (cert, sigval, md,
sigval_hash_algo, &info_pkalgo);
gcry_md_close (md);
}
else
{
rc = gpgsm_check_cms_signature (cert, sigval, data_md,
algo, &info_pkalgo);
}
if (rc)
{
char *fpr;
log_error ("invalid signature: %s\n", gpg_strerror (rc));
fpr = gpgsm_fpr_and_name_for_status (cert);
gpgsm_status (ctrl, STATUS_BADSIG, fpr);
xfree (fpr);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/
if (rc)
{
gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage",
gpg_err_code (rc));
rc = 0;
}
if (DBG_X509)
log_debug ("signature okay - checking certs\n");
audit_log (ctrl->audit, AUDIT_VALIDATE_CHAIN);
rc = gpgsm_validate_chain (ctrl, cert,
*sigtime? sigtime : "19700101T000000",
keyexptime, 0,
NULL, 0, &verifyflags);
{
char *fpr, *buf, *tstr;
fpr = gpgsm_fpr_and_name_for_status (cert);
if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
{
gpgsm_status (ctrl, STATUS_EXPKEYSIG, fpr);
rc = 0;
}
else
gpgsm_status (ctrl, STATUS_GOODSIG, fpr);
xfree (fpr);
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
tstr = strtimestamp_r (sigtime);
buf = xasprintf ("%s %s %s %s 0 0 %d %d 00", fpr, tstr,
*sigtime? sigtime : "0",
*keyexptime? keyexptime : "0",
info_pkalgo, algo);
xfree (tstr);
xfree (fpr);
gpgsm_status (ctrl, STATUS_VALIDSIG, buf);
xfree (buf);
}
audit_log_ok (ctrl->audit, AUDIT_CHAIN_STATUS, rc);
if (rc) /* of validate_chain */
{
log_error ("invalid certification chain: %s\n", gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN
|| gpg_err_code (rc) == GPG_ERR_BAD_CERT
|| gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT
|| gpg_err_code (rc) == GPG_ERR_CERT_REVOKED)
gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL,
gpg_err_code (rc));
else
gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL,
gpg_err_code (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "good");
for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
{
log_info (!i? _("Good signature from")
: _(" aka"));
log_printf (" \"");
gpgsm_es_print_name (log_get_stream (), p);
log_printf ("\"\n");
ksba_free (p);
}
/* Print a note if this is a qualified signature. */
{
size_t qualbuflen;
char qualbuffer[1];
rc = ksba_cert_get_user_data (cert, "is_qualified", &qualbuffer,
sizeof (qualbuffer), &qualbuflen);
if (!rc && qualbuflen)
{
if (*qualbuffer)
{
log_info (_("This is a qualified signature\n"));
if (!opt.qualsig_approval)
log_info
(_("Note, that this software is not officially approved "
"to create or verify such signatures.\n"));
}
}
else if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (rc));
}
gpgsm_status (ctrl, STATUS_TRUST_FULLY,
(verifyflags & VALIDATE_FLAG_STEED)?
"0 steed":
(verifyflags & VALIDATE_FLAG_CHAIN_MODEL)?
"0 chain": "0 shell");
next_signer:
rc = 0;
xfree (issuer);
xfree (serial);
xfree (sigval);
xfree (msgdigest);
ksba_cert_release (cert);
cert = NULL;
}
rc = 0;
leave:
ksba_cms_release (cms);
gnupg_ksba_destroy_reader (b64reader);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
gcry_md_close (data_md);
es_fclose (in_fp);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc );
gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave",
numbuf, NULL);
}
return rc;
}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 38abd7c8c..0c828a7bd 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,179 +1,179 @@
# Makefile.am - Tools directory
# Copyright (C) 2003, 2007 Free Software Foundation, Inc.
#
# 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 <https://www.gnu.org/licenses/>.
EXTRA_DIST = \
Manifest watchgnupg.c no-libgcrypt.c \
addgnupghome applygnupgdefaults \
lspgpot mail-signed-keys convert-from-106 sockprox.c \
ccidmon.c ChangeLog-2011 gpg-connect-agent-w32info.rc
-AM_CPPFLAGS = -I$(top_srcdir)/common
+AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
if HAVE_W32_SYSTEM
resource_objs += gpg-connect-agent-w32info.o
endif
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS)
sbin_SCRIPTS = addgnupghome applygnupgdefaults
if HAVE_USTAR
# bin_SCRIPTS += gpg-zip
noinst_SCRIPTS = gpg-zip
endif
if BUILD_SYMCRYPTRUN
symcryptrun = symcryptrun
else
symcryptrun =
endif
if BUILD_WKS_TOOLS
gpg_wks_server = gpg-wks-server
else
gpg_wks_server =
endif
libexec_PROGRAMS = gpg-wks-client
bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun}
if !HAVE_W32_SYSTEM
bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server}
endif
if !DISABLE_REGEX
libexec_PROGRAMS += gpg-check-pattern
endif
if !HAVE_W32CE_SYSTEM
noinst_PROGRAMS = clean-sat make-dns-cert gpgsplit
endif
if !HAVE_W32CE_SYSTEM
if BUILD_GPGTAR
bin_PROGRAMS += gpgtar
else
noinst_PROGRAMS += gpgtar
endif
endif
common_libs = $(libcommon)
commonpth_libs = $(libcommonpth)
# Some modules require PTH under W32CE.
if HAVE_W32CE_SYSTEM
maybe_commonpth_libs = $(commonpth_libs)
else
maybe_commonpth_libs = $(common_libs)
endif
if HAVE_W32CE_SYSTEM
pwquery_libs =
else
pwquery_libs = ../common/libsimple-pwquery.a
endif
if HAVE_W32CE_SYSTEM
opt_libassuan_libs = $(LIBASSUAN_LIBS)
endif
gpgsplit_LDADD = $(common_libs) \
$(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c
# common sucks in gpg-error, will they, nil they (some compilers
# do not eliminate the supposed-to-be-unused-inline-functions).
gpgconf_LDADD = $(maybe_commonpth_libs) $(opt_libassuan_libs) \
$(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \
$(LIBICONV) $(W32SOCKLIBS)
gpgconf_LDFLAGS = $(extra_bin_ldflags)
gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h
gpgparsemail_LDADD =
symcryptrun_SOURCES = symcryptrun.c
symcryptrun_LDADD = $(LIBUTIL_LIBS) $(common_libs) $(pwquery_libs) \
$(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) \
$(LIBICONV) $(NETLIBS) $(W32SOCKLIBS) $(LIBASSUAN_LIBS)
watchgnupg_SOURCES = watchgnupg.c
watchgnupg_LDADD = $(NETLIBS)
gpg_connect_agent_SOURCES = gpg-connect-agent.c
gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \
$(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
$(GPG_ERROR_LIBS) \
$(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
$(resource_objs)
if !DISABLE_REGEX
gpg_check_pattern_SOURCES = gpg-check-pattern.c
gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV)
gpg_check_pattern_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \
$(LIBICONV)
endif
gpgtar_SOURCES = \
gpgtar.c gpgtar.h \
gpgtar-create.c \
gpgtar-extract.c \
gpgtar-list.c
gpgtar_CFLAGS = $(GPG_ERROR_CFLAGS)
gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS)
gpg_wks_server_SOURCES = \
gpg-wks-server.c \
gpg-wks.h \
wks-util.c \
wks-receive.c \
rfc822parse.c rfc822parse.h \
mime-parser.c mime-parser.h \
mime-maker.c mime-maker.h \
send-mail.c send-mail.h
gpg_wks_server_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV)
gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(LIBICONV)
gpg_wks_client_SOURCES = \
gpg-wks-client.c \
gpg-wks.h \
wks-util.c \
wks-receive.c \
rfc822parse.c rfc822parse.h \
mime-parser.c mime-parser.h \
mime-maker.h mime-maker.c \
send-mail.c send-mail.h \
call-dirmngr.c call-dirmngr.h
gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV)
gpg_wks_client_LDADD = $(libcommon) \
$(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(LIBICONV)
# Make sure that all libs are build before we use them. This is
# important for things like make -j2.
$(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a
diff --git a/tools/call-dirmngr.c b/tools/call-dirmngr.c
index 51f1fa146..9e3e493ee 100644
--- a/tools/call-dirmngr.c
+++ b/tools/call-dirmngr.c
@@ -1,312 +1,312 @@
/* call-dirmngr.c - Interact with the Dirmngr.
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include <assuan.h>
-#include "util.h"
-#include "i18n.h"
-#include "asshelp.h"
-#include "mbox-util.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/mbox-util.h"
#include "./call-dirmngr.h"
static struct
{
int verbose;
int debug_ipc;
int autostart;
} opt;
void
set_dirmngr_options (int verbose, int debug_ipc, int autostart)
{
opt.verbose = verbose;
opt.debug_ipc = debug_ipc;
opt.autostart = autostart;
}
/* Connect to the Dirmngr and return an assuan context. */
static gpg_error_t
connect_dirmngr (assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
NULL,
opt.autostart, opt.verbose, opt.debug_ipc,
NULL, NULL);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no dirmngr running in this session\n"));
}
}
if (err)
assuan_release (ctx);
else
{
*r_ctx = ctx;
}
return err;
}
/* Parameter structure used with the WKD_GET command. */
struct wkd_get_parm_s
{
estream_t memfp;
};
/* Data callback for the WKD_GET command. */
static gpg_error_t
wkd_get_data_cb (void *opaque, const void *data, size_t datalen)
{
struct wkd_get_parm_s *parm = opaque;
gpg_error_t err = 0;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (!parm->memfp)
return 0; /* Data is not required. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Status callback for the WKD_GET command. */
static gpg_error_t
wkd_get_status_cb (void *opaque, const char *line)
{
struct wkd_get_parm_s *parm = opaque;
gpg_error_t err = 0;
(void)line;
(void)parm;
return err;
}
/* Ask the dirmngr for the submission address of a WKD server for the
* mail address ADDRSPEC. On success the submission address is stored
* at R_ADDRSPEC. */
gpg_error_t
wkd_get_submission_address (const char *addrspec, char **r_addrspec)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
void *vp;
char *buffer = NULL;
char *p;
memset (&parm, 0, sizeof parm);
*r_addrspec = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET --submission-address -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_fputc (0, parm.memfp);
if (es_fclose_snatch (parm.memfp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
buffer = vp;
parm.memfp = NULL;
p = strchr (buffer, '\n');
if (p)
*p = 0;
trim_spaces (buffer);
if (!is_valid_mailbox (buffer))
{
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
*r_addrspec = xtrystrdup (buffer);
if (!*r_addrspec)
err = gpg_error_from_syserror ();
leave:
es_free (buffer);
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
/* Ask the dirmngr for the policy flags and return them as an estream
* memory stream. If no policy flags are set, NULL is stored at
* R_BUFFER. */
gpg_error_t
wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
char *buffer = NULL;
memset (&parm, 0, sizeof parm);
*r_buffer = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET --policy-flags -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_buffer = parm.memfp;
parm.memfp = 0;
leave:
es_free (buffer);
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
/* Ask the dirmngr for the key for ADDRSPEC. On success a stream with
* the key is stored at R_KEY. */
gpg_error_t
wkd_get_key (const char *addrspec, estream_t *r_key)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
leave:
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
index a3224ab9d..719734090 100644
--- a/tools/gpg-check-pattern.c
+++ b/tools/gpg-check-pattern.c
@@ -1,494 +1,494 @@
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
# include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <regex.h>
#include <ctype.h>
-#include "util.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oVerbose = 'v',
oArmor = 'a',
oPassphrase = 'P',
oProtect = 'p',
oUnprotect = 'u',
oNull = '0',
oNoVerbose = 500,
oCheck,
oHomedir
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, N_("@Options:\n ") },
{ oVerbose, "verbose", 0, "verbose" },
{ oHomedir, "homedir", 2, "@" },
{ oCheck, "check", 0, "run only a syntax check on the patternfile" },
{ oNull, "null", 0, "input is expected to be null delimited" },
{0}
};
/* Global options are accessed through the usual OPT structure. */
static struct
{
int verbose;
const char *homedir;
int checkonly;
int null;
} opt;
enum {
PAT_NULL, /* Indicates end of the array. */
PAT_STRING, /* The pattern is a simple string. */
PAT_REGEX /* The pattern is an extended regualr expression. */
};
/* An object to decibe an item of our pattern table. */
struct pattern_s
{
int type;
unsigned int lineno; /* Line number of the pattern file. */
union {
struct {
const char *string; /* Pointer to the actual string (nul termnated). */
size_t length; /* The length of this string (strlen). */
} s; /*PAT_STRING*/
struct {
/* We allocate the regex_t because this type is larger than what
we need for PAT_STRING and we expect only a few regex in a
patternfile. It would be a waste of core to have so many
unused stuff in the table. */
regex_t *regex;
} r; /*PAT_REGEX*/
} u;
};
typedef struct pattern_s pattern_t;
/*** Local prototypes ***/
static char *read_file (const char *fname, size_t *r_length);
static pattern_t *parse_pattern_file (char *data, size_t datalen);
static void process (FILE *fp, pattern_t *patarray);
/* Info function for usage(). */
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpg-check-pattern (@GnuPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
break;
case 41:
p = _("Syntax: gpg-check-pattern [options] patternfile\n"
"Check a passphrase given on stdin against the patternfile\n");
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
char *raw_pattern;
size_t raw_pattern_length;
pattern_t *patternarray;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-check-pattern", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* (do not remove the args) */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oCheck: opt.checkonly = 1; break;
case oNull: opt.null = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
exit (2);
if (argc != 1)
usage (1);
/* We read the entire pattern file into our memory and parse it
using a separate function. This allows us to eventual do the
reading while running setuid so that the pattern file can be
hidden from regular users. I am not sure whether this makes
sense, but lets be prepared for it. */
raw_pattern = read_file (*argv, &raw_pattern_length);
if (!raw_pattern)
exit (2);
patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
if (!patternarray)
exit (1);
if (opt.checkonly)
return 0;
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (stdin) , O_BINARY );
#endif
process (stdin, patternarray);
return log_get_errorcount(0)? 1 : 0;
}
/* Read a file FNAME into a buffer and return that malloced buffer.
Caller must free the buffer. On error NULL is returned, on success
the valid length of the buffer is stored at R_LENGTH. The returned
buffer is guarnteed to be nul terminated. */
static char *
read_file (const char *fname, size_t *r_length)
{
FILE *fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(fp) , O_BINARY );
#endif
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize+1);
else
buf = xrealloc (buf, bufsize+1);
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (fileno(fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
fclose (fp);
xfree (buf);
return NULL;
}
fclose (fp);
}
buf[buflen] = 0;
*r_length = buflen;
return buf;
}
static char *
get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
regerror (errcode, compiled, buffer, length);
return buffer;
}
/* Parse the pattern given in the memory aread DATA/DATALEN and return
a new pattern array. The end of the array is indicated by a NULL
entry. On error an error message is printed and the function
returns NULL. Note that the function modifies DATA and assumes
that data is nul terminated (even if this is one byte past
DATALEN). */
static pattern_t *
parse_pattern_file (char *data, size_t datalen)
{
char *p, *p2;
size_t n;
pattern_t *array;
size_t arraysize, arrayidx;
unsigned int lineno = 0;
/* Estimate the number of entries by counting the non-comment lines. */
arraysize = 0;
p = data;
for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
if (*p != '#')
arraysize++;
arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
array = xcalloc (arraysize, sizeof *array);
arrayidx = 0;
/* Loop over all lines. */
while (datalen && data)
{
lineno++;
p = data;
p2 = data = memchr (p, '\n', datalen);
if (p2)
{
*data++ = 0;
datalen -= data - p;
}
else
p2 = p + datalen;
assert (!*p2);
p2--;
while (isascii (*p) && isspace (*p))
p++;
if (*p == '#')
continue;
while (p2 > p && isascii (*p2) && isspace (*p2))
*p2-- = 0;
if (!*p)
continue;
assert (arrayidx < arraysize);
array[arrayidx].lineno = lineno;
if (*p == '/')
{
int rerr;
p++;
array[arrayidx].type = PAT_REGEX;
if (*p && p[strlen(p)-1] == '/')
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
rerr = regcomp (array[arrayidx].u.r.regex, p,
REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (rerr)
{
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
xfree (rerrbuf);
if (!opt.checkonly)
exit (1);
}
}
else
{
array[arrayidx].type = PAT_STRING;
array[arrayidx].u.s.string = p;
array[arrayidx].u.s.length = strlen (p);
}
arrayidx++;
}
assert (arrayidx < arraysize);
array[arrayidx].type = PAT_NULL;
return array;
}
/* Check whether string macthes any of the pattern in PATARRAY and
returns the matching pattern item or NULL. */
static pattern_t *
match_p (const char *string, pattern_t *patarray)
{
pattern_t *pat;
if (!*string)
{
if (opt.verbose)
log_info ("zero length input line - ignored\n");
return NULL;
}
for (pat = patarray; pat->type != PAT_NULL; pat++)
{
if (pat->type == PAT_STRING)
{
if (!strcasecmp (pat->u.s.string, string))
return pat;
}
else if (pat->type == PAT_REGEX)
{
int rerr;
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
if (!rerr)
return pat;
else if (rerr != REG_NOMATCH)
{
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
log_error ("matching r.e. failed: %s\n", rerrbuf);
xfree (rerrbuf);
return pat; /* Better indicate a match on error. */
}
}
else
BUG ();
}
return NULL;
}
/* Actual processing of the input. This function does not return an
error code but exits as soon as a match has been found. */
static void
process (FILE *fp, pattern_t *patarray)
{
char buffer[2048];
size_t idx;
int c;
unsigned long lineno = 0;
pattern_t *pat;
idx = 0;
c = 0;
while (idx < sizeof buffer -1 && c != EOF )
{
if ((c = getc (fp)) != EOF)
buffer[idx] = c;
if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
{
lineno++;
if (!opt.null)
{
while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
idx--;
}
buffer[idx] = 0;
pat = match_p (buffer, patarray);
if (pat)
{
if (opt.verbose)
log_error ("input line %lu matches pattern at line %u"
" - rejected\n",
lineno, pat->lineno);
exit (1);
}
idx = 0;
}
else
idx++;
}
if (c != EOF)
{
log_error ("input line %lu too long - rejected\n", lineno+1);
exit (1);
}
if (ferror (fp))
{
log_error ("input read error at line %lu: %s - rejected\n",
lineno+1, strerror (errno));
exit (1);
}
if (opt.verbose)
log_info ("no input line matches the pattern - accepted\n");
}
diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c
index 59e219217..ef71d2733 100644
--- a/tools/gpg-connect-agent.c
+++ b/tools/gpg-connect-agent.c
@@ -1,2258 +1,2258 @@
/* gpg-connect-agent.c - Tool to connect to the agent.
* Copyright (C) 2005, 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <assuan.h>
#include <unistd.h>
#include <assert.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "../common/util.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "../common/membuf.h"
#include "../common/ttyio.h"
#ifdef HAVE_W32_SYSTEM
# include "../common/exechelp.h"
#endif
#include "../common/init.h"
#define CONTROL_D ('D' - 'A' + 1)
#define octdigitp(p) (*(p) >= '0' && *(p) <= '7')
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRawSocket = 'S',
oTcpSocket = 'T',
oExec = 'E',
oRun = 'r',
oSubst = 's',
oNoVerbose = 500,
oHomedir,
oAgentProgram,
oDirmngrProgram,
oHex,
oDecode,
oNoExtConnect,
oDirmngr,
oUIServer,
oNoAutostart,
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")),
ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")),
ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")),
ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")),
ARGPARSE_s_n (oUIServer, "uiserver", "@"),
ARGPARSE_s_s (oRawSocket, "raw-socket",
N_("|NAME|connect to Assuan socket NAME")),
ARGPARSE_s_s (oTcpSocket, "tcp-socket",
N_("|ADDR|connect to Assuan server at ADDR")),
ARGPARSE_s_n (oExec, "exec",
N_("run the Assuan server given on the command line")),
ARGPARSE_s_n (oNoExtConnect, "no-ext-connect",
N_("do not use extended connect mode")),
ARGPARSE_s_s (oRun, "run",
N_("|FILE|run commands from FILE on startup")),
ARGPARSE_s_n (oSubst, "subst", N_("run /subst on startup")),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@" ),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_end ()
};
/* We keep all global options in the structure OPT. */
struct
{
int verbose; /* Verbosity level. */
int quiet; /* Be extra quiet. */
int autostart; /* Start the server if not running. */
const char *homedir; /* Configuration directory name */
const char *agent_program; /* Value of --agent-program. */
const char *dirmngr_program; /* Value of --dirmngr-program. */
int hex; /* Print data lines in hex format. */
int decode; /* Decode received data lines. */
int use_dirmngr; /* Use the dirmngr and not gpg-agent. */
int use_uiserver; /* Use the standard UI server. */
const char *raw_socket; /* Name of socket to connect in raw mode. */
const char *tcp_socket; /* Name of server to connect in tcp mode. */
int exec; /* Run the pgm given on the command line. */
unsigned int connect_flags; /* Flags used for connecting. */
int enable_varsubst; /* Set if variable substitution is enabled. */
int trim_leading_spaces;
} opt;
/* Definitions for /definq commands and a global linked list with all
the definitions. */
struct definq_s
{
struct definq_s *next;
char *name; /* Name of inquiry or NULL for any name. */
int is_var; /* True if FILE is a variable name. */
int is_prog; /* True if FILE is a program to run. */
char file[1]; /* Name of file or program. */
};
typedef struct definq_s *definq_t;
static definq_t definq_list;
static definq_t *definq_list_tail = &definq_list;
/* Variable definitions and glovbal table. */
struct variable_s
{
struct variable_s *next;
char *value; /* Malloced value - always a string. */
char name[1]; /* Name of the variable. */
};
typedef struct variable_s *variable_t;
static variable_t variable_table;
/* To implement loops we store entire lines in a linked list. */
struct loopline_s
{
struct loopline_s *next;
char line[1];
};
typedef struct loopline_s *loopline_t;
/* This is used to store the pid of the server. */
static pid_t server_pid = (pid_t)(-1);
/* The current datasink file or NULL. */
static FILE *current_datasink;
/* A list of open file descriptors. */
static struct
{
int inuse;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#endif
} open_fd_table[256];
/*-- local prototypes --*/
static char *substitute_line_copy (const char *buffer);
static int read_and_print_response (assuan_context_t ctx, int withhash,
int *r_goterr);
static assuan_context_t start_agent (void);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPG@-connect-agent (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPG@-connect-agent [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPG@-connect-agent [options]\n"
"Connect to a running agent and send commands\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
case 33: p = "\n"; break;
default: p = NULL; break;
}
return p;
}
/* Unescape STRING and returned the malloced result. The surrounding
quotes must already be removed from STRING. */
static char *
unescape_string (const char *string)
{
const unsigned char *s;
int esc;
size_t n;
char *buffer;
unsigned char *d;
n = 0;
for (s = (const unsigned char*)string, esc=0; *s; s++)
{
if (esc)
{
switch (*s)
{
case 'b':
case 't':
case 'v':
case 'n':
case 'f':
case 'r':
case '"':
case '\'':
case '\\': n++; break;
case 'x':
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
n++;
break;
default:
if (s[1] && s[2]
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
n++;
break;
}
esc = 0;
}
else if (*s == '\\')
esc = 1;
else
n++;
}
buffer = xmalloc (n+1);
d = (unsigned char*)buffer;
for (s = (const unsigned char*)string, esc=0; *s; s++)
{
if (esc)
{
switch (*s)
{
case 'b': *d++ = '\b'; break;
case 't': *d++ = '\t'; break;
case 'v': *d++ = '\v'; break;
case 'n': *d++ = '\n'; break;
case 'f': *d++ = '\f'; break;
case 'r': *d++ = '\r'; break;
case '"': *d++ = '\"'; break;
case '\'': *d++ = '\''; break;
case '\\': *d++ = '\\'; break;
case 'x':
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
{
s++;
*d++ = xtoi_2 (s);
s++;
}
break;
default:
if (s[1] && s[2]
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
{
*d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2);
s += 2;
}
break;
}
esc = 0;
}
else if (*s == '\\')
esc = 1;
else
*d++ = *s;
}
*d = 0;
return buffer;
}
/* Do the percent unescaping and return a newly malloced string.
If WITH_PLUS is set '+' characters will be changed to space. */
static char *
unpercent_string (const char *string, int with_plus)
{
const unsigned char *s;
unsigned char *buffer, *p;
size_t n;
n = 0;
for (s=(const unsigned char *)string; *s; s++)
{
if (*s == '%' && s[1] && s[2])
{
s++;
n++;
s++;
}
else if (with_plus && *s == '+')
n++;
else
n++;
}
buffer = xmalloc (n+1);
p = buffer;
for (s=(const unsigned char *)string; *s; s++)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*p++ = xtoi_2 (s);
s++;
}
else if (with_plus && *s == '+')
*p++ = ' ';
else
*p++ = *s;
}
*p = 0;
return (char*)buffer;
}
static const char *
set_var (const char *name, const char *value)
{
variable_t var;
for (var = variable_table; var; var = var->next)
if (!strcmp (var->name, name))
break;
if (!var)
{
var = xmalloc (sizeof *var + strlen (name));
var->value = NULL;
strcpy (var->name, name);
var->next = variable_table;
variable_table = var;
}
xfree (var->value);
var->value = value? xstrdup (value) : NULL;
return var->value;
}
static void
set_int_var (const char *name, int value)
{
char numbuf[35];
snprintf (numbuf, sizeof numbuf, "%d", value);
set_var (name, numbuf);
}
/* Return the value of a variable. That value is valid until a
variable of the name is changed. Return NULL if not found. Note
that envvars are copied to our variable list at the first access
and not at oprogram start. */
static const char *
get_var (const char *name)
{
variable_t var;
const char *s;
if (!*name)
return "";
for (var = variable_table; var; var = var->next)
if (!strcmp (var->name, name))
break;
if (!var && (s = getenv (name)))
return set_var (name, s);
if (!var || !var->value)
return NULL;
return var->value;
}
/* Perform some simple arithmetic operations. Caller must release
the return value. On error the return value is NULL. */
static char *
arithmetic_op (int operator, const char *operands)
{
long result, value;
char numbuf[35];
while ( spacep (operands) )
operands++;
if (!*operands)
return NULL;
result = strtol (operands, NULL, 0);
while (*operands && !spacep (operands) )
operands++;
if (operator == '!')
result = !result;
while (*operands)
{
while ( spacep (operands) )
operands++;
if (!*operands)
break;
value = strtol (operands, NULL, 0);
while (*operands && !spacep (operands) )
operands++;
switch (operator)
{
case '+': result += value; break;
case '-': result -= value; break;
case '*': result *= value; break;
case '/':
if (!value)
return NULL;
result /= value;
break;
case '%':
if (!value)
return NULL;
result %= value;
break;
case '!': result = !value; break;
case '|': result = result || value; break;
case '&': result = result && value; break;
default:
log_error ("unknown arithmetic operator '%c'\n", operator);
return NULL;
}
}
snprintf (numbuf, sizeof numbuf, "%ld", result);
return xstrdup (numbuf);
}
/* Extended version of get_var. This returns a malloced string and
understand the function syntax: "func args".
Defined functions are
get - Return a value described by the next argument:
cwd - The current working directory.
homedir - The gnupg homedir.
sysconfdir - GnuPG's system configuration directory.
bindir - GnuPG's binary directory.
libdir - GnuPG's library directory.
libexecdir - GnuPG's library directory for executable files.
datadir - GnuPG's data directory.
serverpid - The PID of the current server.
unescape ARGS
Remove C-style escapes from string. Note that "\0" and
"\x00" terminate the string implictly. Use "\x7d" to
represent the closing brace. The args start right after
the first space after the function name.
unpercent ARGS
unpercent+ ARGS
Remove percent style ecaping from string. Note that "%00
terminates the string implicitly. Use "%7d" to represetn
the closing brace. The args start right after the first
space after the function name. "unpercent+" also maps '+'
to space.
percent ARGS
percent+ ARGS
Escape the args using the percent style. Tabs, formfeeds,
linefeeds, carriage return, and the plus sign are also
escaped. "percent+" also maps spaces to plus characters.
errcode ARG
Assuming ARG is an integer, return the gpg-error code.
errsource ARG
Assuming ARG is an integer, return the gpg-error source.
errstring ARG
Assuming ARG is an integer return a formatted fpf error string.
Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg"
*/
static char *
get_var_ext (const char *name)
{
static int recursion_count;
const char *s;
char *result;
char *p;
char *free_me = NULL;
int intvalue;
if (recursion_count > 50)
{
log_error ("variables nested too deeply\n");
return NULL;
}
recursion_count++;
free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL;
if (free_me)
name = free_me;
for (s=name; *s && !spacep (s); s++)
;
if (!*s)
{
s = get_var (name);
result = s? xstrdup (s): NULL;
}
else if ( (s - name) == 3 && !strncmp (name, "get", 3))
{
while ( spacep (s) )
s++;
if (!strcmp (s, "cwd"))
{
result = gnupg_getcwd ();
if (!result)
log_error ("getcwd failed: %s\n", strerror (errno));
}
else if (!strcmp (s, "homedir"))
result = xstrdup (gnupg_homedir ());
else if (!strcmp (s, "sysconfdir"))
result = xstrdup (gnupg_sysconfdir ());
else if (!strcmp (s, "bindir"))
result = xstrdup (gnupg_bindir ());
else if (!strcmp (s, "libdir"))
result = xstrdup (gnupg_libdir ());
else if (!strcmp (s, "libexecdir"))
result = xstrdup (gnupg_libexecdir ());
else if (!strcmp (s, "datadir"))
result = xstrdup (gnupg_datadir ());
else if (!strcmp (s, "serverpid"))
result = xasprintf ("%d", (int)server_pid);
else
{
log_error ("invalid argument '%s' for variable function 'get'\n", s);
log_info ("valid are: cwd, "
"{home,bin,lib,libexec,data}dir, serverpid\n");
result = NULL;
}
}
else if ( (s - name) == 8 && !strncmp (name, "unescape", 8))
{
s++;
result = unescape_string (s);
}
else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9))
{
s++;
result = unpercent_string (s, 0);
}
else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10))
{
s++;
result = unpercent_string (s, 1);
}
else if ( (s - name) == 7 && !strncmp (name, "percent", 7))
{
s++;
result = percent_escape (s, "+\t\r\n\f\v");
}
else if ( (s - name) == 8 && !strncmp (name, "percent+", 8))
{
s++;
result = percent_escape (s, "+\t\r\n\f\v");
for (p=result; *p; p++)
if (*p == ' ')
*p = '+';
}
else if ( (s - name) == 7 && !strncmp (name, "errcode", 7))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%d", gpg_err_code (intvalue));
}
else if ( (s - name) == 9 && !strncmp (name, "errsource", 9))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%d", gpg_err_source (intvalue));
}
else if ( (s - name) == 9 && !strncmp (name, "errstring", 9))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%s <%s>",
gpg_strerror (intvalue), gpg_strsource (intvalue));
}
else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name))
{
result = arithmetic_op (*name, s+1);
}
else
{
log_error ("unknown variable function '%.*s'\n", (int)(s-name), name);
result = NULL;
}
xfree (free_me);
recursion_count--;
return result;
}
/* Substitute variables in LINE and return a new allocated buffer if
required. The function might modify LINE if the expanded version
fits into it. */
static char *
substitute_line (char *buffer)
{
char *line = buffer;
char *p, *pend;
const char *value;
size_t valuelen, n;
char *result = NULL;
char *freeme = NULL;
while (*line)
{
p = strchr (line, '$');
if (!p)
return result; /* No more variables. */
if (p[1] == '$') /* Escaped dollar sign. */
{
memmove (p, p+1, strlen (p+1)+1);
line = p + 1;
continue;
}
if (p[1] == '{')
{
int count = 0;
for (pend=p+2; *pend; pend++)
{
if (*pend == '{')
count++;
else if (*pend == '}')
{
if (--count < 0)
break;
}
}
if (!*pend)
return result; /* Unclosed - don't substitute. */
}
else
{
for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++)
;
}
if (p[1] == '{' && *pend == '}')
{
int save = *pend;
*pend = 0;
freeme = get_var_ext (p+2);
value = freeme;
*pend++ = save;
}
else if (*pend)
{
int save = *pend;
*pend = 0;
value = get_var (p+1);
*pend = save;
}
else
value = get_var (p+1);
if (!value)
value = "";
valuelen = strlen (value);
if (valuelen <= pend - p)
{
memcpy (p, value, valuelen);
p += valuelen;
n = pend - p;
if (n)
memmove (p, p+n, strlen (p+n)+1);
line = p;
}
else
{
char *src = result? result : buffer;
char *dst;
dst = xmalloc (strlen (src) + valuelen + 1);
n = p - src;
memcpy (dst, src, n);
memcpy (dst + n, value, valuelen);
n += valuelen;
strcpy (dst + n, pend);
line = dst + n;
xfree (result);
result = dst;
}
xfree (freeme);
freeme = NULL;
}
return result;
}
/* Same as substitute_line but do not modify BUFFER. */
static char *
substitute_line_copy (const char *buffer)
{
char *result, *p;
p = xstrdup (buffer?buffer:"");
result = substitute_line (p);
if (!result)
result = p;
else
xfree (p);
return result;
}
static void
assign_variable (char *line, int syslet)
{
char *name, *p, *tmp, *free_me, *buffer;
/* Get the name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
if (!*p)
set_var (name, NULL); /* Remove variable. */
else if (syslet)
{
free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL;
if (free_me)
p = free_me;
buffer = xmalloc (4 + strlen (p) + 1);
strcpy (stpcpy (buffer, "get "), p);
tmp = get_var_ext (buffer);
xfree (buffer);
set_var (name, tmp);
xfree (tmp);
xfree (free_me);
}
else
{
tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL;
if (tmp)
{
set_var (name, tmp);
xfree (tmp);
}
else
set_var (name, p);
}
}
static void
show_variables (void)
{
variable_t var;
for (var = variable_table; var; var = var->next)
if (var->value)
printf ("%-20s %s\n", var->name, var->value);
}
/* Store an inquire response pattern. Note, that this function may
change the content of LINE. We assume that leading white spaces
are already removed. */
static void
add_definq (char *line, int is_var, int is_prog)
{
definq_t d;
char *name, *p;
/* Get name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
d = xmalloc (sizeof *d + strlen (p) );
strcpy (d->file, p);
d->is_var = is_var;
d->is_prog = is_prog;
if ( !strcmp (name, "*"))
d->name = NULL;
else
d->name = xstrdup (name);
d->next = NULL;
*definq_list_tail = d;
definq_list_tail = &d->next;
}
/* Show all inquiry defintions. */
static void
show_definq (void)
{
definq_t d;
for (d=definq_list; d; d = d->next)
if (d->name)
printf ("%-20s %c %s\n",
d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file);
for (d=definq_list; d; d = d->next)
if (!d->name)
printf ("%-20s %c %s\n", "*",
d->is_var? 'v': d->is_prog? 'p':'f', d->file);
}
/* Clear all inquiry definitions. */
static void
clear_definq (void)
{
while (definq_list)
{
definq_t tmp = definq_list->next;
xfree (definq_list->name);
xfree (definq_list);
definq_list = tmp;
}
definq_list_tail = &definq_list;
}
static void
do_sendfd (assuan_context_t ctx, char *line)
{
FILE *fp;
char *name, *mode, *p;
int rc, fd;
/* Get file name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get mode. */
mode = p;
if (!*mode)
mode = "r";
else
{
for (p=mode; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
}
/* Open and send. */
fp = fopen (name, mode);
if (!fp)
{
log_error ("can't open '%s' in \"%s\" mode: %s\n",
name, mode, strerror (errno));
return;
}
fd = fileno (fp);
if (opt.verbose)
log_error ("file '%s' opened in \"%s\" mode, fd=%d\n",
name, mode, fd);
rc = assuan_sendfd (ctx, INT2FD (fd) );
if (rc)
log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc));
fclose (fp);
}
static void
do_recvfd (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
log_info ("This command has not yet been implemented\n");
}
static void
do_open (char *line)
{
FILE *fp;
char *varname, *name, *mode, *p;
int fd;
#ifdef HAVE_W32_SYSTEM
if (server_pid == (pid_t)(-1))
{
log_error ("the pid of the server is unknown\n");
log_info ("use command \"/serverpid\" first\n");
return;
}
#endif
/* Get variable name. */
varname = line;
for (p=varname; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get file name. */
name = p;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get mode. */
mode = p;
if (!*mode)
mode = "r";
else
{
for (p=mode; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
}
/* Open and send. */
fp = fopen (name, mode);
if (!fp)
{
log_error ("can't open '%s' in \"%s\" mode: %s\n",
name, mode, strerror (errno));
return;
}
fd = fileno (fp);
if (fd >= 0 && fd < DIM (open_fd_table))
{
open_fd_table[fd].inuse = 1;
#ifdef HAVE_W32CE_SYSTEM
# warning fixme: implement our pipe emulation.
#endif
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
{
HANDLE prochandle, handle, newhandle;
handle = (void*)_get_osfhandle (fd);
prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid);
if (!prochandle)
{
log_error ("failed to open the server process\n");
close (fd);
return;
}
if (!DuplicateHandle (GetCurrentProcess(), handle,
prochandle, &newhandle, 0,
TRUE, DUPLICATE_SAME_ACCESS ))
{
log_error ("failed to duplicate the handle\n");
close (fd);
CloseHandle (prochandle);
return;
}
CloseHandle (prochandle);
open_fd_table[fd].handle = newhandle;
}
if (opt.verbose)
log_info ("file '%s' opened in \"%s\" mode, fd=%d (libc=%d)\n",
name, mode, (int)open_fd_table[fd].handle, fd);
set_int_var (varname, (int)open_fd_table[fd].handle);
#else
if (opt.verbose)
log_info ("file '%s' opened in \"%s\" mode, fd=%d\n",
name, mode, fd);
set_int_var (varname, fd);
#endif
}
else
{
log_error ("can't put fd %d into table\n", fd);
close (fd);
}
}
static void
do_close (char *line)
{
int fd = atoi (line);
#ifdef HAVE_W32_SYSTEM
int i;
for (i=0; i < DIM (open_fd_table); i++)
if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd)
break;
if (i < DIM (open_fd_table))
fd = i;
else
{
log_error ("given fd (system handle) has not been opened\n");
return;
}
#endif
if (fd < 0 || fd >= DIM (open_fd_table))
{
log_error ("invalid fd\n");
return;
}
if (!open_fd_table[fd].inuse)
{
log_error ("given fd has not been opened\n");
return;
}
#ifdef HAVE_W32_SYSTEM
CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */
#endif
close (fd);
open_fd_table[fd].inuse = 0;
}
static void
do_showopen (void)
{
int i;
for (i=0; i < DIM (open_fd_table); i++)
if (open_fd_table[i].inuse)
{
#ifdef HAVE_W32_SYSTEM
printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i);
#else
printf ("%-15d\n", i);
#endif
}
}
static gpg_error_t
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* Get the pid of the server and store it locally. */
static void
do_serverpid (assuan_context_t ctx)
{
int rc;
membuf_t mb;
char *buffer;
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
log_error ("command \"%s\" failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
else
{
server_pid = (pid_t)strtoul (buffer, NULL, 10);
if (opt.verbose)
log_info ("server's PID is %lu\n", (unsigned long)server_pid);
}
xfree (buffer);
}
/* Return true if the command is either "HELP" or "SCD HELP". */
static int
help_cmd_p (const char *line)
{
if (!ascii_strncasecmp (line, "SCD", 3)
&& (spacep (line+3) || !line[3]))
{
for (line += 3; spacep (line); line++)
;
}
return (!ascii_strncasecmp (line, "HELP", 4)
&& (spacep (line+4) || !line[4]));
}
/* gpg-connect-agent's entry point. */
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int no_more_options = 0;
assuan_context_t ctx;
char *line, *p;
char *tmpline;
size_t linesize;
int rc;
int cmderr;
const char *opt_run = NULL;
gpgrt_stream_t script_fp = NULL;
int use_tty, keep_line;
struct {
int collecting;
loopline_t head;
loopline_t *tail;
loopline_t current;
unsigned int nestlevel;
int oneshot;
char *condition;
} loopstack[20];
int loopidx;
char **cmdline_commands = NULL;
early_system_init ();
gnupg_rl_initialize ();
set_strusage (my_strusage);
log_set_prefix ("gpg-connect-agent", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (0);
opt.autostart = 1;
opt.connect_flags = 1;
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oNoAutostart: opt.autostart = 0; break;
case oHex: opt.hex = 1; break;
case oDecode: opt.decode = 1; break;
case oDirmngr: opt.use_dirmngr = 1; break;
case oUIServer: opt.use_uiserver = 1; break;
case oRawSocket: opt.raw_socket = pargs.r.ret_str; break;
case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break;
case oExec: opt.exec = 1; break;
case oNoExtConnect: opt.connect_flags &= ~(1); break;
case oRun: opt_run = pargs.r.ret_str; break;
case oSubst:
opt.enable_varsubst = 1;
opt.trim_leading_spaces = 1;
break;
default: pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
exit (2);
/* --uiserver is a shortcut for a specific raw socket. This comes
in particular handy on Windows. */
if (opt.use_uiserver)
{
opt.raw_socket = make_absfilename (gnupg_homedir (), "S.uiserver", NULL);
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
use_tty = (gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)));
if (opt.exec)
{
if (!argc)
{
log_error (_("option \"%s\" requires a program "
"and optional arguments\n"), "--exec" );
exit (1);
}
}
else if (argc)
cmdline_commands = argv;
if (opt.exec && opt.raw_socket)
{
opt.raw_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--raw-socket", "--exec");
}
if (opt.exec && opt.tcp_socket)
{
opt.tcp_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--tcp-socket", "--exec");
}
if (opt.tcp_socket && opt.raw_socket)
{
opt.tcp_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--tcp-socket", "--raw-socket");
}
if (opt_run && !(script_fp = gpgrt_fopen (opt_run, "r")))
{
log_error ("cannot open run file '%s': %s\n",
opt_run, strerror (errno));
exit (1);
}
if (opt.exec)
{
assuan_fd_t no_close[3];
no_close[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr));
no_close[1] = assuan_fd_from_posix_fd (log_get_fd ());
no_close[2] = ASSUAN_INVALID_FD;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_pipe_connect
(ctx, *argv, (const char **)argv, no_close, NULL, NULL,
(opt.connect_flags & 1) ? ASSUAN_PIPE_CONNECT_FDPASSING : 0);
if (rc)
{
log_error ("assuan_pipe_connect_ext failed: %s\n",
gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("server '%s' started\n", *argv);
}
else if (opt.raw_socket)
{
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_socket_connect
(ctx, opt.raw_socket, 0,
(opt.connect_flags & 1) ? ASSUAN_SOCKET_CONNECT_FDPASSING : 0);
if (rc)
{
log_error ("can't connect to socket '%s': %s\n",
opt.raw_socket, gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("connection to socket '%s' established\n", opt.raw_socket);
}
else if (opt.tcp_socket)
{
char *url;
url = xstrconcat ("assuan://", opt.tcp_socket, NULL);
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_socket_connect (ctx, opt.tcp_socket, 0, 0);
if (rc)
{
log_error ("can't connect to server '%s': %s\n",
opt.tcp_socket, gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("connection to socket '%s' established\n", url);
xfree (url);
}
else
ctx = start_agent ();
/* See whether there is a line pending from the server (in case
assuan did not run the initial handshaking). */
if (assuan_pending_line (ctx))
{
rc = read_and_print_response (ctx, 0, &cmderr);
if (rc)
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
}
for (loopidx=0; loopidx < DIM (loopstack); loopidx++)
loopstack[loopidx].collecting = 0;
loopidx = -1;
line = NULL;
linesize = 0;
keep_line = 1;
for (;;)
{
int n;
size_t maxlength = 2048;
assert (loopidx < (int)DIM (loopstack));
if (loopidx >= 0 && loopstack[loopidx].current)
{
keep_line = 0;
xfree (line);
line = xstrdup (loopstack[loopidx].current->line);
n = strlen (line);
/* Never go beyond of the final /end. */
if (loopstack[loopidx].current->next)
loopstack[loopidx].current = loopstack[loopidx].current->next;
else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
;
else
log_fatal ("/end command vanished\n");
}
else if (cmdline_commands && *cmdline_commands && !script_fp)
{
keep_line = 0;
xfree (line);
line = xstrdup (*cmdline_commands);
cmdline_commands++;
n = strlen (line);
if (n >= maxlength)
maxlength = 0;
}
else if (use_tty && !script_fp)
{
keep_line = 0;
xfree (line);
line = tty_get ("> ");
n = strlen (line);
if (n==1 && *line == CONTROL_D)
n = 0;
if (n >= maxlength)
maxlength = 0;
}
else
{
if (!keep_line)
{
xfree (line);
line = NULL;
linesize = 0;
keep_line = 1;
}
n = gpgrt_read_line (script_fp ? script_fp : gpgrt_stdin,
&line, &linesize, &maxlength);
}
if (n < 0)
{
log_error (_("error reading input: %s\n"), strerror (errno));
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
log_error ("stopping script execution\n");
continue;
}
exit (1);
}
if (!n)
{
/* EOF */
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
if (opt.verbose)
log_info ("end of script\n");
continue;
}
break;
}
if (!maxlength)
{
log_error (_("line too long - skipped\n"));
continue;
}
if (memchr (line, 0, n))
log_info (_("line shortened due to embedded Nul character\n"));
if (line[n-1] == '\n')
line[n-1] = 0;
if (opt.trim_leading_spaces)
{
const char *s = line;
while (spacep (s))
s++;
if (s != line)
{
for (p=line; *s;)
*p++ = *s++;
*p = 0;
n = p - line;
}
}
if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting)
{
loopline_t ll;
ll = xmalloc (sizeof *ll + strlen (line));
ll->next = NULL;
strcpy (ll->line, line);
*loopstack[loopidx+1].tail = ll;
loopstack[loopidx+1].tail = &ll->next;
if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
loopstack[loopidx+1].nestlevel--;
else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6)))
loopstack[loopidx+1].nestlevel++;
if (loopstack[loopidx+1].nestlevel)
continue;
/* We reached the corresponding /end. */
loopstack[loopidx+1].collecting = 0;
loopidx++;
}
if (*line == '/')
{
/* Handle control commands. */
char *cmd = line+1;
for (p=cmd; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
if (!strcmp (cmd, "let"))
{
assign_variable (p, 0);
}
else if (!strcmp (cmd, "slet"))
{
/* Deprecated - never used in a released version. */
assign_variable (p, 1);
}
else if (!strcmp (cmd, "showvar"))
{
show_variables ();
}
else if (!strcmp (cmd, "definq"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 1, 0);
xfree (tmpline);
}
else
add_definq (p, 1, 0);
}
else if (!strcmp (cmd, "definqfile"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 0, 0);
xfree (tmpline);
}
else
add_definq (p, 0, 0);
}
else if (!strcmp (cmd, "definqprog"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 0, 1);
xfree (tmpline);
}
else
add_definq (p, 0, 1);
}
else if (!strcmp (cmd, "datafile"))
{
const char *fname;
if (current_datasink)
{
if (current_datasink != stdout)
fclose (current_datasink);
current_datasink = NULL;
}
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
fname = tmpline? tmpline : p;
if (fname && !strcmp (fname, "-"))
current_datasink = stdout;
else if (fname && *fname)
{
current_datasink = fopen (fname, "wb");
if (!current_datasink)
log_error ("can't open '%s': %s\n",
fname, strerror (errno));
}
xfree (tmpline);
}
else if (!strcmp (cmd, "showdef"))
{
show_definq ();
}
else if (!strcmp (cmd, "cleardef"))
{
clear_definq ();
}
else if (!strcmp (cmd, "echo"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
puts (tmpline);
xfree (tmpline);
}
else
puts (p);
}
else if (!strcmp (cmd, "sendfd"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_sendfd (ctx, tmpline);
xfree (tmpline);
}
else
do_sendfd (ctx, p);
continue;
}
else if (!strcmp (cmd, "recvfd"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_recvfd (ctx, tmpline);
xfree (tmpline);
}
else
do_recvfd (ctx, p);
continue;
}
else if (!strcmp (cmd, "open"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_open (tmpline);
xfree (tmpline);
}
else
do_open (p);
}
else if (!strcmp (cmd, "close"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_close (tmpline);
xfree (tmpline);
}
else
do_close (p);
}
else if (!strcmp (cmd, "showopen"))
{
do_showopen ();
}
else if (!strcmp (cmd, "serverpid"))
{
do_serverpid (ctx);
}
else if (!strcmp (cmd, "hex"))
opt.hex = 1;
else if (!strcmp (cmd, "nohex"))
opt.hex = 0;
else if (!strcmp (cmd, "decode"))
opt.decode = 1;
else if (!strcmp (cmd, "nodecode"))
opt.decode = 0;
else if (!strcmp (cmd, "subst"))
{
opt.enable_varsubst = 1;
opt.trim_leading_spaces = 1;
}
else if (!strcmp (cmd, "nosubst"))
opt.enable_varsubst = 0;
else if (!strcmp (cmd, "run"))
{
char *p2;
for (p2=p; *p2 && !spacep (p2); p2++)
;
if (*p2)
*p2++ = 0;
while (spacep (p2))
p++;
if (*p2)
{
log_error ("syntax error in run command\n");
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
}
}
else if (script_fp)
{
log_error ("cannot nest run commands - stop\n");
gpgrt_fclose (script_fp);
script_fp = NULL;
}
else if (!(script_fp = gpgrt_fopen (p, "r")))
{
log_error ("cannot open run file '%s': %s\n",
p, strerror (errno));
}
else if (opt.verbose)
log_info ("running commands from '%s'\n", p);
}
else if (!strcmp (cmd, "while"))
{
if (loopidx+2 >= (int)DIM(loopstack))
{
log_error ("blocks are nested too deep\n");
/* We should better die or break all loop in this
case as recovering from this error won't be
easy. */
}
else
{
loopstack[loopidx+1].head = NULL;
loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
loopstack[loopidx+1].current = NULL;
loopstack[loopidx+1].nestlevel = 1;
loopstack[loopidx+1].oneshot = 0;
loopstack[loopidx+1].condition = xstrdup (p);
loopstack[loopidx+1].collecting = 1;
}
}
else if (!strcmp (cmd, "if"))
{
if (loopidx+2 >= (int)DIM(loopstack))
{
log_error ("blocks are nested too deep\n");
}
else
{
/* Note that we need to evaluate the condition right
away and not just at the end of the block as we
do with a WHILE. */
loopstack[loopidx+1].head = NULL;
loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
loopstack[loopidx+1].current = NULL;
loopstack[loopidx+1].nestlevel = 1;
loopstack[loopidx+1].oneshot = 1;
loopstack[loopidx+1].condition = substitute_line_copy (p);
loopstack[loopidx+1].collecting = 1;
}
}
else if (!strcmp (cmd, "end"))
{
if (loopidx < 0)
log_error ("stray /end command encountered - ignored\n");
else
{
char *tmpcond;
const char *value;
long condition;
/* Evaluate the condition. */
tmpcond = xstrdup (loopstack[loopidx].condition);
if (loopstack[loopidx].oneshot)
{
xfree (loopstack[loopidx].condition);
loopstack[loopidx].condition = xstrdup ("0");
}
tmpline = substitute_line (tmpcond);
value = tmpline? tmpline : tmpcond;
/* "true" or "yes" are commonly used to mean TRUE;
all other strings will evaluate to FALSE due to
the strtoul. */
if (!ascii_strcasecmp (value, "true")
|| !ascii_strcasecmp (value, "yes"))
condition = 1;
else
condition = strtol (value, NULL, 0);
xfree (tmpline);
xfree (tmpcond);
if (condition)
{
/* Run loop. */
loopstack[loopidx].current = loopstack[loopidx].head;
}
else
{
/* Cleanup. */
while (loopstack[loopidx].head)
{
loopline_t tmp = loopstack[loopidx].head->next;
xfree (loopstack[loopidx].head);
loopstack[loopidx].head = tmp;
}
loopstack[loopidx].tail = NULL;
loopstack[loopidx].current = NULL;
loopstack[loopidx].nestlevel = 0;
loopstack[loopidx].collecting = 0;
loopstack[loopidx].oneshot = 0;
xfree (loopstack[loopidx].condition);
loopstack[loopidx].condition = NULL;
loopidx--;
}
}
}
else if (!strcmp (cmd, "bye"))
{
break;
}
else if (!strcmp (cmd, "sleep"))
{
gnupg_sleep (1);
}
else if (!strcmp (cmd, "help"))
{
puts (
"Available commands:\n"
"/echo ARGS Echo ARGS.\n"
"/let NAME VALUE Set variable NAME to VALUE.\n"
"/showvar Show all variables.\n"
"/definq NAME VAR Use content of VAR for inquiries with NAME.\n"
"/definqfile NAME FILE Use content of FILE for inquiries with NAME.\n"
"/definqprog NAME PGM Run PGM for inquiries with NAME.\n"
"/datafile [NAME] Write all D line content to file NAME.\n"
"/showdef Print all definitions.\n"
"/cleardef Delete all definitions.\n"
"/sendfd FILE MODE Open FILE and pass descriptor to server.\n"
"/recvfd Receive FD from server and print.\n"
"/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n"
"/close FD Close file with descriptor FD.\n"
"/showopen Show descriptors of all open files.\n"
"/serverpid Retrieve the pid of the server.\n"
"/[no]hex Enable hex dumping of received data lines.\n"
"/[no]decode Enable decoding of received data lines.\n"
"/[no]subst Enable variable substitution.\n"
"/run FILE Run commands from FILE.\n"
"/if VAR Begin conditional block controlled by VAR.\n"
"/while VAR Begin loop controlled by VAR.\n"
"/end End loop or condition\n"
"/bye Terminate gpg-connect-agent.\n"
"/help Print this help.");
}
else
log_error (_("unknown command '%s'\n"), cmd );
continue;
}
if (opt.verbose && script_fp)
puts (line);
tmpline = opt.enable_varsubst? substitute_line (line) : NULL;
if (tmpline)
{
rc = assuan_write_line (ctx, tmpline);
xfree (tmpline);
}
else
rc = assuan_write_line (ctx, line);
if (rc)
{
log_info (_("sending line failed: %s\n"), gpg_strerror (rc) );
break;
}
if (*line == '#' || !*line)
continue; /* Don't expect a response for a comment line. */
rc = read_and_print_response (ctx, help_cmd_p (line), &cmderr);
if (rc)
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
if ((rc || cmderr) && script_fp)
{
log_error ("stopping script execution\n");
gpgrt_fclose (script_fp);
script_fp = NULL;
}
/* FIXME: If the last command was BYE or the server died for
some other reason, we won't notice until we get the next
input command. Probing the connection with a non-blocking
read could help to notice termination or other problems
early. */
}
if (opt.verbose)
log_info ("closing connection to agent\n");
/* XXX: We would like to release the context here, but libassuan
nicely says good bye to the server, which results in a SIGPIPE if
the server died. Unfortunately, libassuan does not ignore
SIGPIPE when used with UNIX sockets, hence we simply leak the
context here. */
if (0)
assuan_release (ctx);
else
gpgrt_annotate_leaked_object (ctx);
xfree (line);
return 0;
}
/* Handle an Inquire from the server. Return False if it could not be
handled; in this case the caller shll complete the operation. LINE
is the complete line as received from the server. This function
may change the content of LINE. */
static int
handle_inquire (assuan_context_t ctx, char *line)
{
const char *name;
definq_t d;
FILE *fp = NULL;
char buffer[1024];
int rc, n;
/* Skip the command and trailing spaces. */
for (; *line && !spacep (line); line++)
;
while (spacep (line))
line++;
/* Get the name. */
name = line;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
/* Now match it against our list. The second loop is there to
detect the match-all entry. */
for (d=definq_list; d; d = d->next)
if (d->name && !strcmp (d->name, name))
break;
if (!d)
for (d=definq_list; d; d = d->next)
if (!d->name)
break;
if (!d)
{
if (opt.verbose)
log_info ("no handler for inquiry '%s' found\n", name);
return 0;
}
if (d->is_var)
{
char *tmpvalue = get_var_ext (d->file);
if (tmpvalue)
rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue));
else
rc = assuan_send_data (ctx, "", 0);
xfree (tmpvalue);
if (rc)
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
}
else
{
if (d->is_prog)
{
#ifdef HAVE_W32CE_SYSTEM
fp = NULL;
#else
fp = popen (d->file, "r");
#endif
if (!fp)
log_error ("error executing '%s': %s\n",
d->file, strerror (errno));
else if (opt.verbose)
log_error ("handling inquiry '%s' by running '%s'\n",
name, d->file);
}
else
{
fp = fopen (d->file, "rb");
if (!fp)
log_error ("error opening '%s': %s\n", d->file, strerror (errno));
else if (opt.verbose)
log_error ("handling inquiry '%s' by returning content of '%s'\n",
name, d->file);
}
if (!fp)
return 0;
while ( (n = fread (buffer, 1, sizeof buffer, fp)) )
{
rc = assuan_send_data (ctx, buffer, n);
if (rc)
{
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
break;
}
}
if (ferror (fp))
log_error ("error reading from '%s': %s\n", d->file, strerror (errno));
}
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
if (d->is_var)
;
else if (d->is_prog)
{
#ifndef HAVE_W32CE_SYSTEM
if (pclose (fp))
log_error ("error running '%s': %s\n", d->file, strerror (errno));
#endif
}
else
fclose (fp);
return 1;
}
/* Read all response lines from server and print them. Returns 0 on
success or an assuan error code. If WITHHASH istrue, comment lines
are printed. Sets R_GOTERR to true if the command did not returned
OK. */
static int
read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr)
{
char *line;
size_t linelen;
gpg_error_t rc;
int i, j;
int need_lf = 0;
*r_goterr = 0;
for (;;)
{
do
{
rc = assuan_read_line (ctx, &line, &linelen);
if (rc)
return rc;
if ((withhash || opt.verbose > 1) && *line == '#')
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
while (*line == '#' || !linelen);
if (linelen >= 1
&& line[0] == 'D' && line[1] == ' ')
{
if (current_datasink)
{
const unsigned char *s;
int c = 0;
for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
{
if (*s == '%' && j+2 < linelen)
{
s++; j++;
c = xtoi_2 ( s );
s++; j++;
}
else
c = *s;
putc (c, current_datasink);
}
}
else if (opt.hex)
{
for (i=2; i < linelen; )
{
int save_i = i;
printf ("D[%04X] ", i-2);
for (j=0; j < 16 ; j++, i++)
{
if (j == 8)
putchar (' ');
if (i < linelen)
printf (" %02X", ((unsigned char*)line)[i]);
else
fputs (" ", stdout);
}
fputs (" ", stdout);
i= save_i;
for (j=0; j < 16; j++, i++)
{
unsigned int c = ((unsigned char*)line)[i];
if ( i >= linelen )
putchar (' ');
else if (isascii (c) && isprint (c) && !iscntrl (c))
putchar (c);
else
putchar ('.');
}
putchar ('\n');
}
}
else if (opt.decode)
{
const unsigned char *s;
int need_d = 1;
int c = 0;
for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
{
if (need_d)
{
fputs ("D ", stdout);
need_d = 0;
}
if (*s == '%' && j+2 < linelen)
{
s++; j++;
c = xtoi_2 ( s );
s++; j++;
}
else
c = *s;
if (c == '\n')
need_d = 1;
putchar (c);
}
need_lf = (c != '\n');
}
else
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
else
{
if (need_lf)
{
if (!current_datasink || current_datasink != stdout)
putchar ('\n');
need_lf = 0;
}
if (linelen >= 1
&& line[0] == 'S'
&& (line[1] == '\0' || line[1] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
else if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
set_int_var ("?", 0);
return 0;
}
else if (linelen >= 3
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
&& (line[3] == '\0' || line[3] == ' '))
{
int errval;
errval = strtol (line+3, NULL, 10);
if (!errval)
errval = -1;
set_int_var ("?", errval);
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
*r_goterr = 1;
return 0;
}
else if (linelen >= 7
&& line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
&& line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
&& line[6] == 'E'
&& (line[7] == '\0' || line[7] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
if (!handle_inquire (ctx, line))
assuan_write_line (ctx, "CANCEL");
}
else if (linelen >= 3
&& line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
&& (line[3] == '\0' || line[3] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
/* Received from server, thus more responses are expected. */
}
else
return gpg_error (GPG_ERR_ASS_INV_RESPONSE);
}
}
}
/* Connect to the agent and send the standard options. */
static assuan_context_t
start_agent (void)
{
gpg_error_t err;
assuan_context_t ctx;
session_env_t session_env;
session_env = session_env_new ();
if (!session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
if (opt.use_dirmngr)
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program,
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
else
err = start_new_gpg_agent (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
NULL, NULL,
session_env,
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
session_env_release (session_env);
if (err)
{
if (!opt.autostart
&& (gpg_err_code (err)
== opt.use_dirmngr? GPG_ERR_NO_DIRMNGR : GPG_ERR_NO_AGENT))
{
/* In the no-autostart case we don't make gpg-connect-agent
fail on a missing server. */
log_info (opt.use_dirmngr?
_("no dirmngr running in this session\n"):
_("no gpg-agent running in this session\n"));
exit (0);
}
else
{
log_error (_("error sending standard options: %s\n"),
gpg_strerror (err));
exit (1);
}
}
return ctx;
}
diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c
index c31e3a1b0..c57810543 100644
--- a/tools/gpg-wks-client.c
+++ b/tools/gpg-wks-client.c
@@ -1,1221 +1,1221 @@
/* gpg-wks-client.c - A client for the Web Key Service protocols.
* Copyright (C) 2016 Werner Koch
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
-#include "status.h"
-#include "i18n.h"
-#include "sysutils.h"
-#include "init.h"
-#include "asshelp.h"
-#include "userids.h"
-#include "ccparray.h"
-#include "exectool.h"
-#include "mbox-util.h"
-#include "name-value.h"
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/init.h"
+#include "../common/asshelp.h"
+#include "../common/userids.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
+#include "../common/mbox-util.h"
+#include "../common/name-value.h"
#include "call-dirmngr.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oDebug = 500,
aSupported,
aCheck,
aCreate,
aReceive,
aRead,
oGpgProgram,
oSend,
oFakeSubmissionAddr,
oStatusFD,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aSupported, "supported",
("check whether provider supports WKS")),
ARGPARSE_c (aCheck, "check",
("check whether a key is available")),
ARGPARSE_c (aCreate, "create",
("create a publication request")),
ARGPARSE_c (aReceive, "receive",
("receive a MIME confirmation request")),
ARGPARSE_c (aRead, "read",
("receive a plain text confirmation request")),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MIME_VALUE , "mime" },
{ DBG_PARSER_VALUE , "parser" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* Value of the option --fake-submission-addr. */
const char *fake_submission_addr;
static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
static gpg_error_t command_supported (char *userid);
static gpg_error_t command_check (char *userid);
static gpg_error_t command_send (const char *fingerprint, char *userid);
static gpg_error_t encrypt_response (estream_t *r_output, estream_t input,
const char *addrspec,
const char *fingerprint);
static gpg_error_t read_confirmation_request (estream_t msg);
static gpg_error_t command_receive_cb (void *opaque,
const char *mediatype, estream_t fp,
unsigned int flags);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-wks-client (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-wks-client [command] [options] [args]\n"
"Client for the Web Key Service\n");
break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
exit (2);
}
/* Command line parsing. */
static enum cmd_and_opt_values
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
enum cmd_and_opt_values cmd = 0;
int no_more_options = 0;
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
case oSend:
opt.use_sendmail = 1;
break;
case oOutput:
opt.output = pargs->r.ret_str;
break;
case oFakeSubmissionAddr:
fake_submission_addr = pargs->r.ret_str;
break;
case oStatusFD:
wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case aSupported:
case aCreate:
case aReceive:
case aRead:
case aCheck:
cmd = pargs->r_opt;
break;
default: pargs->err = 2; break;
}
}
return cmd;
}
/* gpg-wks-client main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
enum cmd_and_opt_values cmd;
gnupg_reopen_std ("gpg-wks-client");
set_strusage (my_strusage);
log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
cmd = parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
/* Tell call-dirmngr what options we want. */
set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
/* Run the selected command. */
switch (cmd)
{
case aSupported:
if (argc != 1)
wrong_args ("--supported USER-ID");
err = command_supported (argv[0]);
if (err && gpg_err_code (err) != GPG_ERR_FALSE)
log_error ("checking support failed: %s\n", gpg_strerror (err));
break;
case aCreate:
if (argc != 2)
wrong_args ("--create FINGERPRINT USER-ID");
err = command_send (argv[0], argv[1]);
if (err)
log_error ("creating request failed: %s\n", gpg_strerror (err));
break;
case aReceive:
if (argc)
wrong_args ("--receive < MIME-DATA");
err = wks_receive (es_stdin, command_receive_cb, NULL);
if (err)
log_error ("processing mail failed: %s\n", gpg_strerror (err));
break;
case aRead:
if (argc)
wrong_args ("--read < WKS-DATA");
err = read_confirmation_request (es_stdin);
if (err)
log_error ("processing mail failed: %s\n", gpg_strerror (err));
break;
case aCheck:
if (argc != 1)
wrong_args ("--check USER-ID");
err = command_check (argv[0]);
break;
default:
usage (1);
err = 0;
break;
}
if (err)
wks_write_status (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
wks_write_status (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
struct get_key_status_parm_s
{
const char *fpr;
int found;
int count;
};
static void
get_key_status_cb (void *opaque, const char *keyword, char *args)
{
struct get_key_status_parm_s *parm = opaque;
/*log_debug ("%s: %s\n", keyword, args);*/
if (!strcmp (keyword, "EXPORTED"))
{
parm->count++;
if (!ascii_strcasecmp (args, parm->fpr))
parm->found = 1;
}
}
/* Get a key by fingerprint from gpg's keyring and make sure that the
* mail address ADDRSPEC is included in the key. The key is returned
* as a new memory stream at R_KEY.
*
* Fixme: After we have implemented import and export filters for gpg
* this function shall only return a key with just this user id. */
static gpg_error_t
get_key (estream_t *r_key, const char *fingerprint, const char *addrspec)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
estream_t key = NULL;
struct get_key_status_parm_s parm;
char *filterexp = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
key = es_fopenmem (0, "w+b");
if (!key)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
/* Prefix the key with the MIME content type. */
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", key);
filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--export-options=export-minimal");
ccparray_put (&ccp, "--export-filter");
ccparray_put (&ccp, filterexp);
ccparray_put (&ccp, "--export");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
parm.fpr = fingerprint;
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, key,
get_key_status_cb, &parm);
if (!err && parm.count > 1)
err = gpg_error (GPG_ERR_TOO_MANY);
else if (!err && !parm.found)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("export failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (key);
*r_key = key;
key = NULL;
leave:
es_fclose (key);
xfree (argv);
xfree (filterexp);
return err;
}
struct decrypt_stream_parm_s
{
char *fpr;
char *mainfpr;
int otrust;
};
static void
decrypt_stream_status_cb (void *opaque, const char *keyword, char *args)
{
struct decrypt_stream_parm_s *decinfo = opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr)
{
char *fields[3];
if (split_fields (args, fields, DIM (fields)) >= 3)
{
decinfo->fpr = xstrdup (fields[0]);
decinfo->mainfpr = xstrdup (fields[1]);
decinfo->otrust = *fields[2];
}
}
}
/* Decrypt the INPUT stream to a new stream which is stored at success
* at R_OUTPUT. */
static gpg_error_t
decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo,
estream_t input)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
*r_output = NULL;
memset (decinfo, 0, sizeof *decinfo);
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
/* We limit the output to 64 KiB to avoid DoS using compression
* tricks. A regular client will anyway only send a minimal key;
* that is one w/o key signatures and attribute packets. */
ccparray_put (&ccp, "--max-output=0x10000");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--decrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
decrypt_stream_status_cb, decinfo);
if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust))
err = gpg_error (GPG_ERR_INV_ENGINE);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
else if (opt.verbose)
log_info ("decryption succeeded\n");
es_rewind (output);
*r_output = output;
output = NULL;
leave:
if (err)
{
xfree (decinfo->fpr);
xfree (decinfo->mainfpr);
memset (decinfo, 0, sizeof *decinfo);
}
es_fclose (output);
xfree (argv);
return err;
}
/* Check whether the provider supports the WKS protocol. */
static gpg_error_t
command_supported (char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
char *submission_to = NULL;
addrspec = mailbox_from_userid (userid);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
/* Get the submission address. */
err = wkd_get_submission_address (addrspec, &submission_to);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)
{
if (opt.verbose)
log_info ("provider for '%s' does NOT support WKS (%s)\n",
addrspec, gpg_strerror (err));
err = gpg_error (GPG_ERR_FALSE);
log_inc_errorcount ();
}
goto leave;
}
if (opt.verbose)
log_info ("provider for '%s' supports WKS\n", addrspec);
leave:
xfree (submission_to);
xfree (addrspec);
return err;
}
/* Check whether the key for USERID is available in the WKD. */
static gpg_error_t
command_check (char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
estream_t key = NULL;
char *fpr = NULL;
strlist_t mboxes = NULL;
strlist_t sl;
int found = 0;
addrspec = mailbox_from_userid (userid);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
/* Get the submission address. */
err = wkd_get_key (addrspec, &key);
switch (gpg_err_code (err))
{
case 0:
if (opt.verbose)
log_info ("public key for '%s' found via WKD\n", addrspec);
/* Fixme: Check that the key contains the user id. */
break;
case GPG_ERR_NO_DATA: /* No such key. */
if (opt.verbose)
log_info ("public key for '%s' NOT found via WKD\n", addrspec);
err = gpg_error (GPG_ERR_NO_PUBKEY);
log_inc_errorcount ();
break;
case GPG_ERR_UNKNOWN_HOST:
if (opt.verbose)
log_info ("error looking up '%s' via WKD: %s\n",
addrspec, gpg_strerror (err));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
break;
default:
log_error ("error looking up '%s' via WKD: %s\n",
addrspec, gpg_strerror (err));
break;
}
if (err)
goto leave;
/* Look closer at the key. */
err = wks_list_key (key, &fpr, &mboxes);
if (err || !fpr)
{
log_error ("error parsing key: %s\n",
err? gpg_strerror (err) : "no fingerprint found");
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
if (opt.verbose)
log_info ("fingerprint: %s\n", fpr);
for (sl = mboxes; sl; sl = sl->next)
{
if (!strcmp (sl->d, addrspec))
found = 1;
if (opt.verbose)
log_info (" addr-spec: %s\n", sl->d);
}
if (!found)
{
log_error ("public key for '%s' has no user id with the mail address\n",
addrspec);
err = gpg_error (GPG_ERR_CERT_REVOKED);
}
leave:
xfree (fpr);
free_strlist (mboxes);
es_fclose (key);
xfree (addrspec);
return err;
}
/* Locate the key by fingerprint and userid and send a publication
* request. */
static gpg_error_t
command_send (const char *fingerprint, char *userid)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char *addrspec = NULL;
estream_t key = NULL;
estream_t keyenc = NULL;
char *submission_to = NULL;
mime_maker_t mime = NULL;
struct policy_flags_s policy;
memset (&policy, 0, sizeof policy);
if (classify_user_id (fingerprint, &desc, 1)
|| !(desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_FPR20))
{
log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
addrspec = mailbox_from_userid (userid);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
err = get_key (&key, fingerprint, addrspec);
if (err)
goto leave;
/* Get the submission address. */
if (fake_submission_addr)
{
submission_to = xstrdup (fake_submission_addr);
err = 0;
}
else
err = wkd_get_submission_address (addrspec, &submission_to);
if (err)
{
char *domain = strchr (addrspec, '@');
if (domain)
domain = domain + 1;
log_error (_("looking up WKS submission address for %s: %s\n"),
domain ? domain : addrspec, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
log_error (_("this domain probably doesn't support WKS.\n"));
goto leave;
}
log_info ("submitting request to '%s'\n", submission_to);
/* Get the policy flags. */
if (!fake_submission_addr)
{
estream_t mbuf;
err = wkd_get_policy_flags (addrspec, &mbuf);
if (err && gpg_err_code (err) != GPG_ERR_NO_DATA)
{
log_error ("error reading policy flags for '%s': %s\n",
submission_to, gpg_strerror (err));
goto leave;
}
if (mbuf)
{
err = wks_parse_policy (&policy, mbuf, 1);
es_fclose (mbuf);
if (err)
goto leave;
}
}
if (policy.auth_submit)
log_info ("no confirmation required for '%s'\n", addrspec);
/* Encrypt the key part. */
es_rewind (key);
err = encrypt_response (&keyenc, key, submission_to, fingerprint);
if (err)
goto leave;
es_fclose (key);
key = NULL;
/* Send the key. */
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", addrspec);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", submission_to);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Key publishing request");
if (err)
goto leave;
/* Tell server which draft we support. */
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &keyenc);
if (err)
goto leave;
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
xfree (submission_to);
es_fclose (keyenc);
es_fclose (key);
xfree (addrspec);
return err;
}
static void
encrypt_response_status_cb (void *opaque, const char *keyword, char *args)
{
gpg_error_t *failure = opaque;
char *fields[2];
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
if (!strcmp (keyword, "FAILURE"))
{
if (split_fields (args, fields, DIM (fields)) >= 2
&& !strcmp (fields[0], "encrypt"))
*failure = strtoul (fields[1], NULL, 10);
}
}
/* Encrypt the INPUT stream to a new stream which is stored at success
* at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT
* (so that the sent message may later be inspected by the user). We
* currently retrieve that key from the WKD, DANE, or from "local".
* "local" is last to prefer the latest key version but use a local
* copy in case we are working offline. It might be useful for the
* server to send the fingerprint of its encryption key - or even the
* entire key back. */
static gpg_error_t
encrypt_response (estream_t *r_output, estream_t input, const char *addrspec,
const char *fingerprint)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
gpg_error_t gpg_err = 0;
*r_output = NULL;
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
if (fake_submission_addr)
ccparray_put (&ccp, "--auto-key-locate=clear,local");
else
ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local");
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, addrspec);
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, "--encrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
encrypt_response_status_cb, &gpg_err);
if (err)
{
if (gpg_err)
err = gpg_err;
log_error ("encryption failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (output);
*r_output = output;
output = NULL;
leave:
es_fclose (output);
xfree (argv);
return err;
}
static gpg_error_t
send_confirmation_response (const char *sender, const char *address,
const char *nonce, int encrypt,
const char *fingerprint)
{
gpg_error_t err;
estream_t body = NULL;
estream_t bodyenc = NULL;
mime_maker_t mime = NULL;
body = es_fopenmem (0, "w+b");
if (!body)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
/* It is fine to use 8 bit encoding because that is encrypted and
* only our client will see it. */
if (encrypt)
{
es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
"Content-Transfer-Encoding: 8bit\n"
"\n",
body);
}
es_fprintf (body, ("type: confirmation-response\n"
"sender: %s\n"
"address: %s\n"
"nonce: %s\n"),
sender,
address,
nonce);
es_rewind (body);
if (encrypt)
{
err = encrypt_response (&bodyenc, body, sender, fingerprint);
if (err)
goto leave;
es_fclose (body);
body = NULL;
}
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", address);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", sender);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
if (encrypt)
{
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &bodyenc);
if (err)
goto leave;
}
else
{
err = mime_maker_add_header (mime, "Content-Type",
"application/vnd.gnupg.wks");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &body);
if (err)
goto leave;
}
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
es_fclose (bodyenc);
es_fclose (body);
return err;
}
/* Reply to a confirmation request. The MSG has already been
* decrypted and we only need to send the nonce back. MAINFPR is
* either NULL or the primary key fingerprint of the key used to
* decrypt the request. */
static gpg_error_t
process_confirmation_request (estream_t msg, const char *mainfpr)
{
gpg_error_t err;
nvc_t nvc;
nve_t item;
const char *value, *sender, *address, *fingerprint, *nonce;
err = nvc_parse (&nvc, NULL, msg);
if (err)
{
log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_MIME)
{
log_debug ("request follows:\n");
nvc_write (nvc, log_get_stream ());
}
/* Check that this is a confirmation request. */
if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
&& !strcmp (value, "confirmation-request")))
{
if (item && value)
log_error ("received unexpected wks message '%s'\n", value);
else
log_error ("received invalid wks message: %s\n", "'type' missing");
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
goto leave;
}
/* Get the fingerprint. */
if (!((item = nvc_lookup (nvc, "fingerprint:"))
&& (value = nve_value (item))
&& strlen (value) >= 40))
{
log_error ("received invalid wks message: %s\n",
"'fingerprint' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
fingerprint = value;
/* Check that the fingerprint matches the key used to decrypt the
* message. In --read mode or with the old format we don't have the
* decryption key; thus we can't bail out. */
if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint))
{
log_info ("target fingerprint: %s\n", fingerprint);
log_info ("but decrypted with: %s\n", mainfpr);
log_error ("confirmation request not decrypted with target key\n");
if (mainfpr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
/* Get the address. */
if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'address' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
address = value;
/* FIXME: Check that the "address" matches the User ID we want to
* publish. */
/* Get the sender. */
if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'sender' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
sender = value;
/* FIXME: Check that the "sender" matches the From: address. */
/* Get the nonce. */
if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
&& strlen (value) > 16))
{
log_error ("received invalid wks message: %s\n",
"'nonce' missing or too short");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
nonce = value;
/* Send the confirmation. If no key was found, try again without
* encryption. */
err = send_confirmation_response (sender, address, nonce, 1, fingerprint);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
{
log_info ("no encryption key found - sending response in the clear\n");
err = send_confirmation_response (sender, address, nonce, 0, NULL);
}
leave:
nvc_release (nvc);
return err;
}
/* Read a confirmation request and decrypt it if needed. This
* function may not be used with a mail or MIME message but only with
* the actual encrypted or plaintext WKS data. */
static gpg_error_t
read_confirmation_request (estream_t msg)
{
gpg_error_t err;
int c;
estream_t plaintext = NULL;
/* We take a really simple approach to check whether MSG is
* encrypted: We know that an encrypted message is always armored
* and thus starts with a few dashes. It is even sufficient to
* check for a single dash, because that can never be a proper first
* WKS data octet. We need to skip leading spaces, though. */
while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n')
;
if (c == EOF)
{
log_error ("can't process an empty message\n");
return gpg_error (GPG_ERR_INV_DATA);
}
if (es_ungetc (c, msg) != c)
{
log_error ("error ungetting octet from message\n");
return gpg_error (GPG_ERR_INTERNAL);
}
if (c != '-')
err = process_confirmation_request (msg, NULL);
else
{
struct decrypt_stream_parm_s decinfo;
err = decrypt_stream (&plaintext, &decinfo, msg);
if (err)
log_error ("decryption failed: %s\n", gpg_strerror (err));
else if (decinfo.otrust != 'u')
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
log_error ("key used to decrypt the confirmation request"
" was not generated by us\n");
}
else
err = process_confirmation_request (plaintext, decinfo.mainfpr);
xfree (decinfo.fpr);
xfree (decinfo.mainfpr);
}
es_fclose (plaintext);
return err;
}
/* Called from the MIME receiver to process the plain text data in MSG. */
static gpg_error_t
command_receive_cb (void *opaque, const char *mediatype,
estream_t msg, unsigned int flags)
{
gpg_error_t err;
(void)opaque;
(void)flags;
if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
err = read_confirmation_request (msg);
else
{
log_info ("ignoring unexpected message of type '%s'\n", mediatype);
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
}
return err;
}
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index 0c939e5e0..105521281 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1,4162 +1,4162 @@
/* gpgconf-comp.c - Configuration utility for GnuPG.
* Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
* Copyright (C) 2016 Werner Koch
*
* 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 GnuPG; if not, see <https://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <ctype.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN 1
# include <windows.h>
#else
# include <pwd.h>
# include <grp.h>
#endif
/* For log_logv(), asctimestamp(), gnupg_get_time (). */
-#include "util.h"
-#include "i18n.h"
-#include "exechelp.h"
-#include "sysutils.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/exechelp.h"
+#include "../common/sysutils.h"
-#include "gc-opt-flags.h"
+#include "../common/gc-opt-flags.h"
#include "gpgconf.h"
/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
returns a plain filename without escaping. As long as we have not
fixed that we need to use gpg2. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
#define GPGNAME "gpg2"
#else
#define GPGNAME GPG_NAME
#endif
/* TODO:
Components: Add more components and their options.
Robustness: Do more validation. Call programs to do validation for us.
Add options to change backend binary path.
Extract binary path for some backends from gpgsm/gpg config.
*/
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ))
void gc_error (int status, int errnum, const char *fmt, ...) \
__attribute__ ((format (printf, 3, 4)));
#endif
/* Output a diagnostic message. If ERRNUM is not 0, then the output
is followed by a colon, a white space, and the error string for the
error number ERRNUM. In any case the output is finished by a
newline. The message is prepended by the program name, a colon,
and a whitespace. The output may be further formatted or
redirected by the jnlib logging facility. */
void
gc_error (int status, int errnum, const char *fmt, ...)
{
va_list arg_ptr;
va_start (arg_ptr, fmt);
log_logv (GPGRT_LOG_ERROR, fmt, arg_ptr);
va_end (arg_ptr);
if (errnum)
log_printf (": %s\n", strerror (errnum));
else
log_printf ("\n");
if (status)
{
log_printf (NULL);
log_printf ("fatal error (exit status %i)\n", status);
exit (status);
}
}
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
static void dirmngr_runtime_change (int killflag);
/* Backend configuration. Backends are used to decide how the default
and current value of an option can be determined, and how the
option can be changed. To every option in every component belongs
exactly one backend that controls and determines the option. Some
backends are programs from the GPG system. Others might be
implemented by GPGConf itself. If you change this enum, don't
forget to update GC_BACKEND below. */
typedef enum
{
/* Any backend, used for find_option (). */
GC_BACKEND_ANY,
/* The Gnu Privacy Guard. */
GC_BACKEND_GPG,
/* The Gnu Privacy Guard for S/MIME. */
GC_BACKEND_GPGSM,
/* The GPG Agent. */
GC_BACKEND_GPG_AGENT,
/* The GnuPG SCDaemon. */
GC_BACKEND_SCDAEMON,
/* The GnuPG directory manager. */
GC_BACKEND_DIRMNGR,
/* The LDAP server list file for the director manager. */
GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST,
/* The Pinentry (not a part of GnuPG, proper). */
GC_BACKEND_PINENTRY,
/* The number of the above entries. */
GC_BACKEND_NR
} gc_backend_t;
/* To be able to implement generic algorithms for the various
backends, we collect all information about them in this struct. */
static struct
{
/* The name of the backend. */
const char *name;
/* The name of the program that acts as the backend. Some backends
don't have an associated program, but are implemented directly by
GPGConf. In this case, PROGRAM is NULL. */
char *program;
/* The module name (GNUPG_MODULE_NAME_foo) as defined by
../common/util.h. This value is used to get the actual installed
path of the program. 0 is used if no backend program is
available. */
char module_name;
/* The runtime change callback. If KILLFLAG is true the component
is killed and not just reloaded. */
void (*runtime_change) (int killflag);
/* The option name for the configuration filename of this backend.
This must be an absolute filename. It can be an option from a
different backend (but then ordering of the options might
matter). Note: This must be unique among all components. */
const char *option_config_filename;
/* If this is a file backend rather than a program backend, then
this is the name of the option associated with the file. */
const char *option_name;
} gc_backend[GC_BACKEND_NR] =
{
{ NULL }, /* GC_BACKEND_ANY dummy entry. */
{ GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG,
NULL, GPGCONF_NAME "-" GPG_NAME ".conf" },
{ GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM,
NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" },
{ GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT,
gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" },
{ SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON,
scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" },
{ DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR,
dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" },
{ DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0,
NULL, "ldapserverlist-file", "LDAP Server" },
{ "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY,
NULL, GPGCONF_NAME "-pinentry.conf" },
};
/* Option configuration. */
/* An option might take an argument, or not. Argument types can be
basic or complex. Basic types are generic and easy to validate.
Complex types provide more specific information about the intended
use, but can be difficult to validate. If you add to this enum,
don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE
NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL
INTERFACE. */
typedef enum
{
/* Basic argument types. */
/* No argument. */
GC_ARG_TYPE_NONE = 0,
/* A String argument. */
GC_ARG_TYPE_STRING = 1,
/* A signed integer argument. */
GC_ARG_TYPE_INT32 = 2,
/* An unsigned integer argument. */
GC_ARG_TYPE_UINT32 = 3,
/* ADD NEW BASIC TYPE ENTRIES HERE. */
/* Complex argument types. */
/* A complete filename. */
GC_ARG_TYPE_FILENAME = 32,
/* An LDAP server in the format
HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */
GC_ARG_TYPE_LDAP_SERVER = 33,
/* A 40 character fingerprint. */
GC_ARG_TYPE_KEY_FPR = 34,
/* A user ID or key ID or fingerprint for a certificate. */
GC_ARG_TYPE_PUB_KEY = 35,
/* A user ID or key ID or fingerprint for a certificate with a key. */
GC_ARG_TYPE_SEC_KEY = 36,
/* A alias list made up of a key, an equal sign and a space
separated list of values. */
GC_ARG_TYPE_ALIAS_LIST = 37,
/* ADD NEW COMPLEX TYPE ENTRIES HERE. */
/* The number of the above entries. */
GC_ARG_TYPE_NR
} gc_arg_type_t;
/* For every argument, we record some information about it in the
following struct. */
static struct
{
/* For every argument type exists a basic argument type that can be
used as a fallback for input and validation purposes. */
gc_arg_type_t fallback;
/* Human-readable name of the type. */
const char *name;
} gc_arg_type[GC_ARG_TYPE_NR] =
{
/* The basic argument types have their own types as fallback. */
{ GC_ARG_TYPE_NONE, "none" },
{ GC_ARG_TYPE_STRING, "string" },
{ GC_ARG_TYPE_INT32, "int32" },
{ GC_ARG_TYPE_UINT32, "uint32" },
/* Reserved basic type entries for future extension. */
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
/* The complex argument types have a basic type as fallback. */
{ GC_ARG_TYPE_STRING, "filename" },
{ GC_ARG_TYPE_STRING, "ldap server" },
{ GC_ARG_TYPE_STRING, "key fpr" },
{ GC_ARG_TYPE_STRING, "pub key" },
{ GC_ARG_TYPE_STRING, "sec key" },
{ GC_ARG_TYPE_STRING, "alias list" },
};
/* Every option has an associated expert level, than can be used to
hide advanced and expert options from beginners. If you add to
this list, don't forget to update GC_LEVEL below. YOU MUST NOT
CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE
EXTERNAL INTERFACE. */
typedef enum
{
/* The basic options should always be displayed. */
GC_LEVEL_BASIC,
/* The advanced options may be hidden from beginners. */
GC_LEVEL_ADVANCED,
/* The expert options should only be displayed to experts. */
GC_LEVEL_EXPERT,
/* The invisible options should normally never be displayed. */
GC_LEVEL_INVISIBLE,
/* The internal options are never exported, they mark options that
are recorded for internal use only. */
GC_LEVEL_INTERNAL,
/* ADD NEW ENTRIES HERE. */
/* The number of the above entries. */
GC_LEVEL_NR
} gc_expert_level_t;
/* A description for each expert level. */
static struct
{
const char *name;
} gc_level[] =
{
{ "basic" },
{ "advanced" },
{ "expert" },
{ "invisible" },
{ "internal" }
};
/* Option flags. The flags which are used by the backends are defined
by gc-opt-flags.h, included above.
YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
PART OF THE EXTERNAL INTERFACE. */
/* Some entries in the option list are not options, but mark the
beginning of a new group of options. These entries have the GROUP
flag set. */
#define GC_OPT_FLAG_GROUP (1UL << 0)
/* The ARG_OPT flag for an option indicates that the argument is
optional. This is never set for GC_ARG_TYPE_NONE options. */
#define GC_OPT_FLAG_ARG_OPT (1UL << 1)
/* The LIST flag for an option indicates that the option can occur
several times. A comma separated list of arguments is used as the
argument value. */
#define GC_OPT_FLAG_LIST (1UL << 2)
/* A human-readable description for each flag. */
static struct
{
const char *name;
} gc_flag[] =
{
{ "group" },
{ "optional arg" },
{ "list" },
{ "runtime" },
{ "default" },
{ "default desc" },
{ "no arg desc" },
{ "no change" }
};
/* To each option, or group marker, the information in the GC_OPTION
struct is provided. If you change this, don't forget to update the
option list of each component. */
struct gc_option
{
/* If this is NULL, then this is a terminator in an array of unknown
length. Otherwise, if this entry is a group marker (see FLAGS),
then this is the name of the group described by this entry.
Otherwise it is the name of the option described by this
entry. The name must not contain a colon. */
const char *name;
/* The option flags. If the GROUP flag is set, then this entry is a
group marker, not an option, and only the fields LEVEL,
DESC_DOMAIN and DESC are valid. In all other cases, this entry
describes a new option and all fields are valid. */
unsigned long flags;
/* The expert level. This field is valid for options and groups. A
group has the expert level of the lowest-level option in the
group. */
gc_expert_level_t level;
/* A gettext domain in which the following description can be found.
If this is NULL, then DESC is not translated. Valid for groups
and options.
Note that we try to keep the description of groups within the
gnupg domain.
IMPORTANT: If you add a new domain please make sure to add a code
set switching call to the function my_dgettext further below. */
const char *desc_domain;
/* A gettext description for this group or option. If it starts
with a '|', then the string up to the next '|' describes the
argument, and the description follows the second '|'.
In general enclosing these description in N_() is not required
because the description should be identical to the one in the
help menu of the respective program. */
const char *desc;
/* The following fields are only valid for options. */
/* The type of the option argument. */
gc_arg_type_t arg_type;
/* The backend that implements this option. */
gc_backend_t backend;
/* The following fields are set to NULL at startup (because all
option's are declared as static variables). They are at the end
of the list so that they can be omitted from the option
declarations. */
/* This is true if the option is supported by this version of the
backend. */
int active;
/* The default value for this option. This is NULL if the option is
not present in the backend, the empty string if no default is
available, and otherwise a quoted string. */
char *default_value;
/* The default argument is only valid if the "optional arg" flag is
set, and specifies the default argument (value) that is used if
the argument is omitted. */
char *default_arg;
/* The current value of this option. */
char *value;
/* The new flags for this option. The only defined flag is actually
GC_OPT_FLAG_DEFAULT, and it means that the option should be
deleted. In this case, NEW_VALUE is NULL. */
unsigned long new_flags;
/* The new value of this option. */
char *new_value;
};
typedef struct gc_option gc_option_t;
/* Use this macro to terminate an option list. */
#define GC_OPTION_NULL { NULL }
#ifndef BUILD_WITH_AGENT
#define gc_options_gpg_agent NULL
#else
/* The options of the GC_COMPONENT_GPG_AGENT component. */
static gc_option_t gc_options_gpg_agent[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-" GPG_AGENT_NAME ".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not use the SCdaemon",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable ssh support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable putty support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg",
"|N|expire cached PINs after N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|expire SSH keys after N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum PIN cache lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum SSH key lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED,
"gnupg", "allow passphrase to be prompted through Emacs",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-external-cache", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg", "disallow caller to override the pinentry",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not grab keyboard and mouse",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Passphrase policy",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options enforcing a passphrase policy") },
{ "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow bypassing the passphrase policy"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|require at least N non-alpha characters for a new passphrase"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT,
"gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "max-passphrase-days", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|expire the passphrase after N days"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "enable-passphrase-history", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow the reuse of old passphrases"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "pinentry-timeout", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set the Pinentry timeout to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_AGENT*/
#ifndef BUILD_WITH_SCDAEMON
#define gc_options_scdaemon NULL
#else
/* The options of the GC_COMPONENT_SCDAEMON component. */
static gc_option_t gc_options_scdaemon[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"SCDAEMON_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|connect to reader at port N",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as ct-API driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as PC/SC driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not use the internal CCID driver",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "do not use a reader's pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "enable-pinpad-varlen",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "use variable length input for pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|disconnect the card after N seconds of inactivity",
GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write a log to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "deny the use of admin card commands",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_SCDAEMON*/
#ifndef BUILD_WITH_GPG
#define gc_options_gpg NULL
#else
/* The options of the GC_COMPONENT_GPG component. */
static gc_option_t gc_options_gpg[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPG_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
"gnupg", N_("|SPEC|set up email aliases"),
GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "trust-model",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
/* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */
/* NULL, NULL, */
/* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("|URL|use keyserver at URL"), /* Deprecated - use dirmngr */
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("allow PKA lookups (DNS requests)"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPG*/
#ifndef BUILD_WITH_GPGSM
#define gc_options_gpgsm NULL
#else
/* The options of the GC_COMPONENT_GPGSM component. */
static gc_option_t gc_options_gpgsm[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPGSM_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "use system's dirmngr if available",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("|SPEC|use this keyserver to lookup keys"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "never consult a CRL",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("do not check CRLs for root certificates"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "check validity using OCSP",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|N|number of certificates to include",
GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM },
{ "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not check certificate policies",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "fetch missing issuer certificates",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use cipher algorithm NAME",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPGSM*/
#ifndef BUILD_WITH_DIRMNGR
#define gc_options_dirmngr NULL
#else
/* The options of the GC_COMPONENT_DIRMNGR component. */
static gc_option_t gc_options_dirmngr[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"DIRMNGR_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"dirmngr", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Format",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the format of the output") },
{ "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "sh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "csh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"dirmngr", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR },
{ "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"dirmngr", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not detach from the console",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "Enforcement",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the interactivity and enforcement") },
{ "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "run without asking a user",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "force loading of outdated CRLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow online software version check",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Tor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the use of Tor") },
{ "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "route all network traffic via TOR",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|URL|use keyserver at URL"),
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "HTTP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for HTTP servers") },
{ "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of HTTP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore HTTP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|redirect all HTTP requests to URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("use system's HTTP proxy setting"),
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "LDAP",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration of LDAP servers to use") },
{ "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of LDAP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore LDAP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|HOST|use HOST for LDAP queries",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not use fallback hosts with --ldap-proxy",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "add new servers discovered in CRL distribution points"
" to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|set LDAP timeout to N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
/* The following entry must not be removed, as it is required for
the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */
{ "ldapserverlist-file",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
"dirmngr", "|FILE|read LDAP server list from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
/* This entry must come after at least one entry for
GC_BACKEND_DIRMNGR in this component, so that the entry for
"ldapserverlist-file will be initialized before this one. */
{ "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("LDAP server list"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST },
{ "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|do not return more than N items in one query",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "OCSP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for OCSP") },
{ "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow sending OCSP requests",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore certificate contained OCSP service URLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|use OCSP responder at URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|FPR|OCSP response signed by FPR",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_DIRMNGR*/
/* The options of the GC_COMPONENT_PINENTRY component. */
static gc_option_t gc_options_pinentry[] =
{
/* A dummy option to allow gc_component_list_components to find the
pinentry backend. Needs to be a conf file. */
{ GPGCONF_NAME"-pinentry.conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY },
GC_OPTION_NULL
};
/* Component system. Each component is a set of options that can be
configured at the same time. If you change this, don't forget to
update GC_COMPONENT below. */
typedef enum
{
/* The classic GPG for OpenPGP. */
GC_COMPONENT_GPG,
/* The GPG Agent. */
GC_COMPONENT_GPG_AGENT,
/* The Smardcard Daemon. */
GC_COMPONENT_SCDAEMON,
/* GPG for S/MIME. */
GC_COMPONENT_GPGSM,
/* The LDAP Directory Manager for CRLs. */
GC_COMPONENT_DIRMNGR,
/* The external Pinentry. */
GC_COMPONENT_PINENTRY,
/* The number of components. */
GC_COMPONENT_NR
} gc_component_t;
/* The information associated with each component. */
static struct
{
/* The name of this component. Must not contain a colon (':')
character. */
const char *name;
/* The gettext domain for the description DESC. If this is NULL,
then the description is not translated. */
const char *desc_domain;
/* The description for this domain. */
const char *desc;
/* The list of options for this component, terminated by
GC_OPTION_NULL. */
gc_option_t *options;
} gc_component[] =
{
{ "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg },
{ "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent },
{ "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon },
{ "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm },
{ "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr },
{ "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry }
};
/* Structure used to collect error output of the backend programs. */
struct error_line_s;
typedef struct error_line_s *error_line_t;
struct error_line_s
{
error_line_t next; /* Link to next item. */
const char *fname; /* Name of the config file (points into BUFFER). */
unsigned int lineno; /* Line number of the config file. */
const char *errtext; /* Text of the error message (points into BUFFER). */
char buffer[1]; /* Helper buffer. */
};
/* Initialization and finalization. */
static void
gc_option_free (gc_option_t *o)
{
if (o == NULL || o->name == NULL)
return;
xfree (o->value);
gc_option_free (o + 1);
}
static void
gc_components_free (void)
{
int i;
for (i = 0; i < DIM (gc_component); i++)
gc_option_free (gc_component[i].options);
}
void
gc_components_init (void)
{
atexit (gc_components_free);
}
/* Engine specific support. */
static void
gpg_agent_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[5];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
err = gpg_error_from_syserror ();
argv[i++] = "--homedir";
argv[i++] = abs_homedir;
}
argv[i++] = "--no-autostart";
argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[1], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
scdaemon_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[9];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
err = gpg_error_from_syserror ();
argv[i++] = "--homedir";
argv[i++] = abs_homedir;
}
argv[i++] = "-s";
argv[i++] = "--no-autostart";
argv[i++] = "GETINFO scd_running";
argv[i++] = "/if ${! $?}";
argv[i++] = "scd killscd";
argv[i++] = "/end";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[4], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
dirmngr_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[6];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "--no-autostart";
argv[1] = "--dirmngr";
argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR";
if (gnupg_default_homedir_p ())
argv[3] = NULL;
else
{
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
err = gpg_error_from_syserror ();
argv[3] = "--homedir";
argv[4] = abs_homedir;
argv[5] = NULL;
}
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[2], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
/* Launch the gpg-agent or the dirmngr if not already running. */
gpg_error_t
gc_component_launch (int component)
{
gpg_error_t err;
const char *pgmname;
const char *argv[3];
int i;
pid_t pid;
if (component < 0)
{
err = gc_component_launch (GC_COMPONENT_GPG_AGENT);
if (!err)
err = gc_component_launch (GC_COMPONENT_DIRMNGR);
return err;
}
if (!(component == GC_COMPONENT_GPG_AGENT
|| component == GC_COMPONENT_DIRMNGR))
{
es_fputs (_("Component not suitable for launching"), es_stderr);
es_putc ('\n', es_stderr);
exit (1);
}
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
i = 0;
if (component == GC_COMPONENT_DIRMNGR)
argv[i++] = "--dirmngr";
argv[i++] = "NOP";
argv[i] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s%s%s': %s",
pgmname,
component == GC_COMPONENT_DIRMNGR? " --dirmngr":"",
" NOP",
gpg_strerror (err));
gnupg_release_process (pid);
return err;
}
/* Unconditionally restart COMPONENT. */
void
gc_component_kill (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the restart for the selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (1);
}
}
/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */
void
gc_component_reload (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the reload for all selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
}
/* More or less Robust version of dgettext. It has the side effect of
switching the codeset to utf-8 because this is what we want to
output. In theory it is posible to keep the original code set and
switch back for regular disgnostic output (redefine "_(" for that)
but given the natur of this tool, being something invoked from
other pograms, it does not make much sense. */
static const char *
my_dgettext (const char *domain, const char *msgid)
{
#ifdef USE_SIMPLE_GETTEXT
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
gettext_use_utf8 (1);
}
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
/* FIXME: we have no dgettext, thus we can't switch. */
text = (char*)gettext (msgid);
return text ? text : msgid;
}
else
return msgid;
#elif defined(ENABLE_NLS)
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
bind_textdomain_codeset (PACKAGE_GT, "utf-8");
bindtextdomain (DIRMNGR_NAME, LOCALEDIR);
bind_textdomain_codeset (DIRMNGR_NAME, "utf-8");
}
/* Note: This is a hack to actually use the gnupg2 domain as
long we are in a transition phase where gnupg 1.x and 1.9 may
coexist. */
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
text = dgettext (domain, msgid);
return text ? text : msgid;
}
else
return msgid;
#else
(void)domain;
return msgid;
#endif
}
/* Percent-Escape special characters. The string is valid until the
next invocation of the function. */
char *
gc_percent_escape (const char *src)
{
static char *esc_str;
static int esc_str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (esc_str_len < new_len)
{
char *new_esc_str = realloc (esc_str, new_len);
if (!new_esc_str)
gc_error (1, errno, "can not escape string");
esc_str = new_esc_str;
esc_str_len = new_len;
}
dst = esc_str;
while (*src)
{
if (*src == '%')
{
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = '5';
}
else if (*src == ':')
{
/* The colon is used as field separator. */
*(dst++) = '%';
*(dst++) = '3';
*(dst++) = 'a';
}
else if (*src == ',')
{
/* The comma is used as list separator. */
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = 'c';
}
else if (*src == '\n')
{
/* The newline is problematic in a line-based format. */
*(dst++) = '%';
*(dst++) = '0';
*(dst++) = 'a';
}
else
*(dst++) = *(src);
src++;
}
*dst = '\0';
return esc_str;
}
/* Percent-Deescape special characters. The string is valid until the
next invocation of the function. */
static char *
percent_deescape (const char *src)
{
static char *str;
static int str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (str_len < new_len)
{
char *new_str = realloc (str, new_len);
if (!new_str)
gc_error (1, errno, "can not deescape string");
str = new_str;
str_len = new_len;
}
dst = str;
while (*src)
{
if (*src == '%')
{
int val = hextobyte (src + 1);
if (val < 0)
gc_error (1, 0, "malformed end of string %s", src);
*(dst++) = (char) val;
src += 3;
}
else
*(dst++) = *(src++);
}
*dst = '\0';
return str;
}
/* List all components that are available. */
void
gc_component_list_components (estream_t out)
{
gc_component_t component;
gc_option_t *option;
gc_backend_t backend;
int backend_seen[GC_BACKEND_NR];
const char *desc;
const char *pgmname;
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
if (option)
{
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
pgmname = "";
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program
&& !gc_backend[backend].module_name)
continue;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
break;
}
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fprintf (out, "%s\n", gc_percent_escape (pgmname));
}
}
}
static int
all_digits_p (const char *p, size_t len)
{
if (!len)
return 0; /* No. */
for (; len; len--, p++)
if (!isascii (*p) || !isdigit (*p))
return 0; /* No. */
return 1; /* Yes. */
}
/* Collect all error lines from stream FP. Only lines prefixed with
TAG are considered. Returns a list of error line items (which may
be empty). There is no error return. */
static error_line_t
collect_error_output (estream_t fp, const char *tag)
{
char buffer[1024];
char *p, *p2, *p3;
int c, cont_line;
unsigned int pos;
error_line_t eitem, errlines, *errlines_tail;
size_t taglen = strlen (tag);
errlines = NULL;
errlines_tail = &errlines;
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (cont_line)
; /*Ignore continuations of previous line. */
else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':')
{
/* "gpgsm: foo:4: bla" */
/* Yep, we are interested in this line. */
p = buffer + taglen + 1;
while (*p == ' ' || *p == '\t')
p++;
trim_trailing_spaces (p); /* Get rid of extra CRs. */
if (!*p)
; /* Empty lines are ignored. */
else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
&& all_digits_p (p2+1, p3 - (p2+1)))
{
/* Line in standard compiler format. */
p3++;
while (*p3 == ' ' || *p3 == '\t')
p3++;
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = eitem->buffer;
eitem->buffer[p2-p] = 0;
eitem->errtext = eitem->buffer + (p3 - p);
/* (we already checked that there are only ascii
digits followed by a colon) */
eitem->lineno = 0;
for (p2++; isdigit (*p2); p2++)
eitem->lineno = eitem->lineno*10 + (*p2 - '0');
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
else
{
/* Other error output. */
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = NULL;
eitem->errtext = eitem->buffer;
eitem->lineno = 0;
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
}
pos = 0;
/* If this was not a complete line mark that we are in a
continuation. */
cont_line = (c != '\n');
}
}
/* We ignore error lines not terminated by a LF. */
return errlines;
}
/* Check the options of a single component. Returns 0 if everything
is OK. */
int
gc_component_check_options (int component, estream_t out, const char *conf_file)
{
gpg_error_t err;
unsigned int result;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
const char *pgmname;
const char *argv[4];
int i;
pid_t pid;
int exitcode;
estream_t errfp;
error_line_t errlines;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
option = gc_component[component].options;
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (!gc_backend[backend].program)
continue;
if (!gc_backend[backend].module_name)
continue;
break;
}
if (! option || ! option->name)
return 0;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
i = 0;
if (conf_file)
{
argv[i++] = "--options";
argv[i++] = conf_file;
}
if (component == GC_COMPONENT_PINENTRY)
argv[i++] = "--version";
else
argv[i++] = "--gpgconf-test";
argv[i++] = NULL;
result = 0;
errlines = NULL;
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, NULL, &errfp, &pid);
if (err)
result |= 1; /* Program could not be run. */
else
{
errlines = collect_error_output (errfp,
gc_component[component].name);
if (gnupg_wait_process (pgmname, pid, 1, &exitcode))
{
if (exitcode == -1)
result |= 1; /* Program could not be run or it
terminated abnormally. */
result |= 2; /* Program returned an error. */
}
gnupg_release_process (pid);
es_fclose (errfp);
}
/* If the program could not be run, we can't tell whether
the config file is good. */
if (result & 1)
result |= 2;
if (out)
{
const char *desc;
error_line_t errptr;
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fputs (gc_percent_escape (pgmname), out);
es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
for (errptr = errlines; errptr; errptr = errptr->next)
{
if (errptr != errlines)
es_fputs ("\n:::::", out); /* Continuation line. */
if (errptr->fname)
es_fputs (gc_percent_escape (errptr->fname), out);
es_putc (':', out);
if (errptr->fname)
es_fprintf (out, "%u", errptr->lineno);
es_putc (':', out);
es_fputs (gc_percent_escape (errptr->errtext), out);
es_putc (':', out);
}
es_putc ('\n', out);
}
while (errlines)
{
error_line_t tmp = errlines->next;
xfree (errlines);
errlines = tmp;
}
return result;
}
/* Check all components that are available. */
void
gc_check_programs (estream_t out)
{
gc_component_t component;
for (component = 0; component < GC_COMPONENT_NR; component++)
gc_component_check_options (component, out, NULL);
}
/* Find the component with the name NAME. Returns -1 if not
found. */
int
gc_component_find (const char *name)
{
gc_component_t idx;
for (idx = 0; idx < GC_COMPONENT_NR; idx++)
{
if (gc_component[idx].options
&& !strcmp (name, gc_component[idx].name))
return idx;
}
return -1;
}
/* List the option OPTION. */
static void
list_one_option (const gc_option_t *option, estream_t out)
{
const char *desc = NULL;
char *arg_name = NULL;
if (option->desc)
{
desc = my_dgettext (option->desc_domain, option->desc);
if (*desc == '|')
{
const char *arg_tail = strchr (&desc[1], '|');
if (arg_tail)
{
int arg_len = arg_tail - &desc[1];
arg_name = xmalloc (arg_len + 1);
memcpy (arg_name, &desc[1], arg_len);
arg_name[arg_len] = '\0';
desc = arg_tail + 1;
}
}
}
/* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS
PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY
FIELDS. */
/* The name field. */
es_fprintf (out, "%s", option->name);
/* The flags field. */
es_fprintf (out, ":%lu", option->flags);
if (opt.verbose)
{
es_putc (' ', out);
if (!option->flags)
es_fprintf (out, "none");
else
{
unsigned long flags = option->flags;
unsigned long flag = 0;
unsigned long first = 1;
while (flags)
{
if (flags & 1)
{
if (first)
first = 0;
else
es_putc (',', out);
es_fprintf (out, "%s", gc_flag[flag].name);
}
flags >>= 1;
flag++;
}
}
}
/* The level field. */
es_fprintf (out, ":%u", option->level);
if (opt.verbose)
es_fprintf (out, " %s", gc_level[option->level].name);
/* The description field. */
es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : "");
/* The type field. */
es_fprintf (out, ":%u", option->arg_type);
if (opt.verbose)
es_fprintf (out, " %s", gc_arg_type[option->arg_type].name);
/* The alternate type field. */
es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback);
if (opt.verbose)
es_fprintf (out, " %s",
gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
/* The argument name field. */
es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : "");
xfree (arg_name);
/* The default value field. */
es_fprintf (out, ":%s", option->default_value ? option->default_value : "");
/* The default argument field. */
es_fprintf (out, ":%s", option->default_arg ? option->default_arg : "");
/* The value field. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST)
&& option->value)
/* The special format "1,1,1,1,...,1" is converted to a number
here. */
es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
else
es_fprintf (out, ":%s", option->value ? option->value : "");
/* ADD NEW FIELDS HERE. */
es_putc ('\n', out);
}
/* List all options of the component COMPONENT. */
void
gc_component_list_options (int component, estream_t out)
{
const gc_option_t *option = gc_component[component].options;
while (option && option->name)
{
/* Do not output unknown or internal options. */
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& (!option->active || option->level == GC_LEVEL_INTERNAL))
{
option++;
continue;
}
if (option->flags & GC_OPT_FLAG_GROUP)
{
const gc_option_t *group_option = option + 1;
gc_expert_level_t level = GC_LEVEL_NR;
/* The manual states that the group level is always the
minimum of the levels of all contained options. Due to
different active options, and because it is hard to
maintain manually, we calculate it here. The value in
the global static table is ignored. */
while (group_option->name)
{
if (group_option->flags & GC_OPT_FLAG_GROUP)
break;
if (group_option->level < level)
level = group_option->level;
group_option++;
}
/* Check if group is empty. */
if (level != GC_LEVEL_NR)
{
gc_option_t opt_copy;
/* Fix up the group level. */
memcpy (&opt_copy, option, sizeof (opt_copy));
opt_copy.level = level;
list_one_option (&opt_copy, out);
}
}
else
list_one_option (option, out);
option++;
}
}
/* Find the option NAME in component COMPONENT, for the backend
BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */
static gc_option_t *
find_option (gc_component_t component, const char *name,
gc_backend_t backend)
{
gc_option_t *option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& !strcmp (option->name, name)
&& (backend == GC_BACKEND_ANY || option->backend == backend))
break;
option++;
}
return option->name ? option : NULL;
}
/* Determine the configuration filename for the component COMPONENT
and backend BACKEND. */
static char *
get_config_filename (gc_component_t component, gc_backend_t backend)
{
char *filename = NULL;
gc_option_t *option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
assert (option);
assert (option->arg_type == GC_ARG_TYPE_FILENAME);
assert (!(option->flags & GC_OPT_FLAG_LIST));
if (!option->active || !option->default_value)
gc_error (1, 0, "Option %s, needed by backend %s, was not initialized",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
if (option->value && *option->value)
filename = percent_deescape (&option->value[1]);
else if (option->default_value && *option->default_value)
filename = percent_deescape (&option->default_value[1]);
else
filename = "";
#if HAVE_W32CE_SYSTEM
if (!(filename[0] == '/' || filename[0] == '\\'))
#elif defined(HAVE_DOSISH_SYSTEM)
if (!(filename[0]
&& filename[1] == ':'
&& (filename[2] == '/' || filename[2] == '\\')))
#else
if (filename[0] != '/')
#endif
gc_error (1, 0, "Option %s, needed by backend %s, is not absolute",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
return filename;
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is a program-type backend. */
static void
retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
int exitcode;
pid_t pid;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t config;
char *config_filename;
pgmname = (gc_backend[backend].module_name
? gnupg_module_name (gc_backend[backend].module_name)
: gc_backend[backend].program );
argv[0] = "--gpgconf-list";
argv[1] = NULL;
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, &outfp, NULL, &pid);
if (err)
{
gc_error (1, 0, "could not gather active options from '%s': %s",
pgmname, gpg_strerror (err));
}
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
gc_option_t *option;
char *linep;
unsigned long flags = 0;
char *default_value = NULL;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s from %s",
line, pgmname);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s from %s",
line, pgmname);
linep = end;
}
/* Extract default value, if present. Default to empty if
not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
if (flags & GC_OPT_FLAG_DEFAULT)
default_value = linep;
linep = end;
}
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
if (option->active)
gc_error (1, errno, "option %s returned twice from %s",
line, pgmname);
option->active = 1;
option->flags |= flags;
if (default_value && *default_value)
option->default_value = xstrdup (default_value);
}
}
if (length < 0 || es_ferror (outfp))
gc_error (1, errno, "error reading from %s", pgmname);
if (es_fclose (outfp))
gc_error (1, errno, "error closing %s", pgmname);
err = gnupg_wait_process (pgmname, pid, 1, &exitcode);
if (err)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
gnupg_release_process (pid);
/* At this point, we can parse the configuration file. */
config_filename = get_config_filename (component, backend);
config = es_fopen (config_filename, "r");
if (!config)
{
if (errno != ENOENT)
gc_error (0, errno, "warning: can not open config file %s",
config_filename);
}
else
{
while ((length = es_read_line (config, &line, &line_len, NULL)) > 0)
{
char *name;
char *value;
gc_option_t *option;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
value = name;
while (*value && *value != ' ' && *value != '\t'
&& *value != '#' && *value != '\r' && *value != '\n')
value++;
if (*value == ' ' || *value == '\t')
{
char *end;
*(value++) = '\0';
while (*value == ' ' || *value == '\t')
value++;
end = value;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
while (end > value && (end[-1] == ' ' || end[-1] == '\t'))
end--;
*end = '\0';
}
else
*value = '\0';
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
char *opt_value;
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
if (*value)
gc_error (0, 0,
"warning: ignoring argument %s for option %s",
value, name);
opt_value = xstrdup ("1");
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
opt_value = xasprintf ("\"%s", gc_percent_escape (value));
else
{
/* FIXME: Verify that the number is sane. */
opt_value = xstrdup (value);
}
/* Now enter the option into the table. */
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (option->value)
xfree (option->value);
option->value = opt_value;
}
else
{
if (!option->value)
option->value = opt_value;
else
{
char *old = option->value;
option->value = xasprintf ("%s,%s", old, opt_value);
xfree (old);
xfree (opt_value);
}
}
}
}
if (length < 0 || es_ferror (config))
gc_error (1, errno, "error reading from %s", config_filename);
if (es_fclose (config))
gc_error (1, errno, "error closing %s", config_filename);
}
xfree (line);
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is of type file list. */
static void
retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
{
gc_option_t *list_option;
gc_option_t *config_option;
char *list_filename;
gpgrt_stream_t list_file;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
char *list = NULL;
list_option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (list_option);
assert (!list_option->active);
list_filename = get_config_filename (component, backend);
list_file = gpgrt_fopen (list_filename, "r");
if (!list_file)
gc_error (0, errno, "warning: can not open list file %s", list_filename);
else
{
while ((length = gpgrt_read_line (list_file, &line, &line_len, NULL)) > 0)
{
char *start;
char *end;
char *new_list;
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (!*start || *start == '#' || *start == '\r' || *start == '\n')
continue;
end = start;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
/* Walk back to skip trailing white spaces. Looks evil, but
works because of the conditions on START and END imposed
at this point (END is at least START + 1, and START is
not a whitespace character). */
while (*(end - 1) == ' ' || *(end - 1) == '\t')
end--;
*end = '\0';
/* FIXME: Oh, no! This is so lame! Should use realloc and
really append. */
if (list)
{
new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start));
xfree (list);
list = new_list;
}
else
list = xasprintf ("\"%s", gc_percent_escape (start));
}
if (length < 0 || gpgrt_ferror (list_file))
gc_error (1, errno, "can not read list file %s", list_filename);
}
list_option->active = 1;
list_option->value = list;
/* Fix up the read-only flag. */
config_option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
if (config_option->flags & GC_OPT_FLAG_NO_CHANGE)
list_option->flags |= GC_OPT_FLAG_NO_CHANGE;
if (list_file && gpgrt_fclose (list_file))
gc_error (1, errno, "error closing %s", list_filename);
xfree (line);
}
/* Retrieve the currently active options and their defaults from all
involved backends for this component. Using -1 for component will
retrieve all options from all components. */
void
gc_component_retrieve_options (int component)
{
int process_all = 0;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
if (component == -1)
{
process_all = 1;
component = 0;
assert (component < GC_COMPONENT_NR);
}
do
{
if (component == GC_COMPONENT_PINENTRY)
continue; /* Skip this dummy component. */
option = gc_component[component].options;
while (option && option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP))
{
backend = option->backend;
if (backend_seen[backend])
{
option++;
continue;
}
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program)
retrieve_options_from_program (component, backend);
else
retrieve_options_from_file (component, backend);
}
option++;
}
}
while (process_all && ++component < GC_COMPONENT_NR);
}
/* Perform a simple validity check based on the type. Return in
* NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
* type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode
* is used. */
static void
option_check_validity (gc_option_t *option, unsigned long flags,
char *new_value, unsigned long *new_value_nr,
int verbatim)
{
char *arg;
if (!option->active)
gc_error (1, 0, "option %s not supported by backend %s",
option->name, gc_backend[option->backend].name);
if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name);
if (flags & GC_OPT_FLAG_DEFAULT)
{
if (*new_value)
gc_error (1, 0, "argument %s provided for deleted option %s",
new_value, option->name);
return;
}
/* GC_ARG_TYPE_NONE options have special list treatment. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
char *tail;
gpg_err_set_errno (0);
*new_value_nr = strtoul (new_value, &tail, 0);
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*tail)
gc_error (1, 0, "garbage after argument for option %s",
option->name);
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (*new_value_nr != 1)
gc_error (1, 0, "argument for non-list option %s of type 0 "
"(none) must be 1", option->name);
}
else
{
if (*new_value_nr == 0)
gc_error (1, 0, "argument for option %s of type 0 (none) "
"must be positive", option->name);
}
return;
}
arg = new_value;
do
{
if (*arg == '\0' || (*arg == ',' && !verbatim))
{
if (!(option->flags & GC_OPT_FLAG_ARG_OPT))
gc_error (1, 0, "argument required for option %s", option->name);
if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST))
gc_error (1, 0, "list found for non-list option %s", option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
{
if (*arg != '"' && !verbatim)
gc_error (1, 0, "string argument for option %s must begin "
"with a quote (\") character", option->name);
/* FIXME: We do not allow empty string arguments for now, as
we do not quote arguments in configuration files, and
thus no argument is indistinguishable from the empty
string. */
if (arg[1] == '\0' || (arg[1] == ',' && !verbatim))
gc_error (1, 0, "empty string argument for option %s is "
"currently not allowed. Please report this!",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
{
long res;
gpg_err_set_errno (0);
res = strtol (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32)
{
unsigned long res;
gpg_err_set_errno (0);
res = strtoul (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
arg = verbatim? strchr (arg, ',') : NULL;
if (arg)
arg++;
}
while (arg && *arg);
}
#ifdef HAVE_W32_SYSTEM
int
copy_file (const char *src_name, const char *dst_name)
{
#define BUF_LEN 4096
char buffer[BUF_LEN];
int len;
gpgrt_stream_t src;
gpgrt_stream_t dst;
src = gpgrt_fopen (src_name, "r");
if (src == NULL)
return -1;
dst = gpgrt_fopen (dst_name, "w");
if (dst == NULL)
{
int saved_err = errno;
gpgrt_fclose (src);
gpg_err_set_errno (saved_err);
return -1;
}
do
{
int written;
len = gpgrt_fread (buffer, 1, BUF_LEN, src);
if (len == 0)
break;
written = gpgrt_fwrite (buffer, 1, len, dst);
if (written != len)
break;
}
while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst));
if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src))
{
int saved_errno = errno;
gpgrt_fclose (src);
gpgrt_fclose (dst);
unlink (dst_name);
gpg_err_set_errno (saved_errno);
return -1;
}
if (gpgrt_fclose (dst))
gc_error (1, errno, "error closing %s", dst_name);
if (gpgrt_fclose (src))
gc_error (1, errno, "error closing %s", src_name);
return 0;
}
#endif /* HAVE_W32_SYSTEM */
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. This
* function may store pointers to malloced strings in SRC_FILENAMEP,
* DEST_FILENAMEP, and ORIG_FILENAMEP. Those must be freed by the
* caller. The strings refer to three versions of the configuration
* file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_file (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
char *arg;
char *cur_arg = NULL;
option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (option);
assert (option->active);
assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE);
/* FIXME. Throughout the function, do better error reporting. */
/* Note that get_config_filename() calls percent_deescape(), so we
call this before processing the arguments. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
arg = option->new_value;
if (arg && arg[0] == '\0')
arg = NULL;
else if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_file_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char *endp;
char saved_end;
endp = start;
end = endp;
/* Search for the end of the line. */
while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n')
{
endp++;
if (*endp && *endp != ' ' && *endp != '\t'
&& *endp != '\r' && *endp != '\n' && *endp != '#')
end = endp + 1;
}
saved_end = *end;
*end = '\0';
if ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| !cur_arg || strcmp (start, cur_arg))
disable = 1;
else
{
/* Find next argument. */
if (arg)
{
char *arg_end;
arg++;
arg_end = strchr (arg, ',');
if (arg_end)
*arg_end = '\0';
cur_arg = percent_deescape (arg);
if (arg_end)
{
*arg_end = ',';
arg = arg_end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
*end = saved_end;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the arguments we are going to add.
Now, dump the new arguments and write the end marker, possibly
followed by the rest of the original file. */
while (cur_arg)
{
gpgrt_fprintf (src_file, "%s\n", cur_arg);
/* Find next argument. */
if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_file_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. If
* VERBATIM is set the profile mode is used. This function may store
* pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and
* ORIG_FILENAMEP. Those must be freed by the caller. The strings
* refer to three versions of the configuration file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_program (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep,
int verbatim)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
/* Special hack for gpg, see below. */
int utf8strings_seen = 0;
/* FIXME. Throughout the function, do better error reporting. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
else if (backend == GC_BACKEND_GPG && in_marker
&& ! strcmp ("utf8-strings\n", line))
{
/* Strip duplicated entries. */
if (utf8strings_seen)
disable = 1;
else
utf8strings_seen = 1;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char saved_end;
end = start;
while (*end && *end != ' ' && *end != '\t'
&& *end != '\r' && *end != '\n' && *end != '#')
end++;
saved_end = *end;
*end = '\0';
option = find_option (component, start, backend);
*end = saved_end;
if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| option->new_value))
disable = 1;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the options we are going to change.
Now, dump the changed options (except for those we are going to
revert to their default), and write the end marker, possibly
followed by the rest of the original file. */
/* We have to turn on UTF8 strings for GnuPG. */
if (backend == GC_BACKEND_GPG && ! utf8strings_seen)
gpgrt_fprintf (src_file, "utf8-strings\n");
option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& option->backend == backend
&& option->new_value)
{
char *arg = option->new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_NONE)
{
assert (*arg == '1');
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
arg++;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
{
char *end;
if (!verbatim)
{
log_assert (*arg == '"');
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
}
else
end = NULL;
gpgrt_fprintf (src_file, "%s %s\n", option->name,
verbatim? arg : percent_deescape (arg));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
else
{
char *end;
end = strchr (arg, ',');
if (end)
*end = '\0';
gpgrt_fprintf (src_file, "%s %s\n", option->name, arg);
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
assert (arg == NULL || *arg == '\0' || *arg == ',');
if (arg && *arg == ',')
arg++;
}
while (arg && *arg);
}
option++;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Common code for gc_component_change_options and
* gc_process_gpgconf_conf. If VERBATIM is set the profile parsing
* mode is used. */
static void
change_one_value (gc_option_t *option, int *runtime,
unsigned long flags, char *new_value, int verbatim)
{
unsigned long new_value_nr = 0;
option_check_validity (option, flags, new_value, &new_value_nr, verbatim);
if (option->flags & GC_OPT_FLAG_RUNTIME)
runtime[option->backend] = 1;
option->new_flags = flags;
if (!(flags & GC_OPT_FLAG_DEFAULT))
{
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST))
{
char *str;
/* We convert the number to a list of 1's for convenient
list handling. */
assert (new_value_nr > 0);
option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
str = option->new_value;
*(str++) = '1';
while (--new_value_nr > 0)
{
*(str++) = ',';
*(str++) = '1';
}
*(str++) = '\0';
}
else
option->new_value = xstrdup (new_value);
}
}
/* Read the modifications from IN and apply them. If IN is NULL the
modifications are expected to already have been set to the global
table. If VERBATIM is set the profile mode is used. */
void
gc_component_change_options (int component, estream_t in, estream_t out,
int verbatim)
{
int err = 0;
int block = 0;
int runtime[GC_BACKEND_NR];
char *src_filename[GC_BACKEND_NR];
char *dest_filename[GC_BACKEND_NR];
char *orig_filename[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy component for now. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
runtime[backend] = 0;
src_filename[backend] = NULL;
dest_filename[backend] = NULL;
orig_filename[backend] = NULL;
}
if (in)
{
/* Read options from the file IN. */
while ((length = es_read_line (in, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *new_value = "";
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s", line);
linep = end;
}
/* Don't allow setting of the no change flag. */
flags &= ~GC_OPT_FLAG_NO_CHANGE;
/* Extract default value, if present. Default to empty if not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
new_value = linep;
linep = end;
}
option = find_option (component, line, GC_BACKEND_ANY);
if (!option)
gc_error (1, 0, "unknown option %s", line);
if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
{
gc_error (0, 0, "ignoring new value for option %s",
option->name);
continue;
}
change_one_value (option, runtime, flags, new_value, 0);
}
if (length < 0 || gpgrt_ferror (in))
gc_error (1, errno, "error reading stream 'in'");
}
/* Now that we have collected and locally verified the changes,
write them out to new configuration files, verify them
externally, and then commit them. */
option = gc_component[component].options;
while (option && option->name)
{
/* Go on if we have already seen this backend, or if there is
nothing to do. */
if (src_filename[option->backend]
|| !(option->new_flags || option->new_value))
{
option++;
continue;
}
if (gc_backend[option->backend].program)
{
err = change_options_program (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend],
verbatim);
if (! err)
{
/* External verification. */
err = gc_component_check_options (component, out,
src_filename[option->backend]);
if (err)
{
gc_error (0, 0,
_("External verification of component %s failed"),
gc_component[component].name);
gpg_err_set_errno (EINVAL);
}
}
}
else
err = change_options_file (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend]);
if (err)
break;
option++;
}
/* We are trying to atomically commit all changes. Unfortunately,
we cannot rely on gnupg_rename_file to manage the signals for us,
doing so would require us to pass NULL as BLOCK to any subsequent
call to it. Instead, we just manage the signal handling
manually. */
block = 1;
gnupg_block_all_signals ();
if (! err && ! opt.dry_run)
{
int i;
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* FIXME: Make a verification here. */
assert (dest_filename[i]);
if (orig_filename[i])
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
else
{
#ifdef HAVE_W32_SYSTEM
/* We skip the unlink if we expect the file not to
be there. */
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
#else /* HAVE_W32_SYSTEM */
/* This is a bit safer than rename() because we
expect DEST_FILENAME not to be there. If it
happens to be there, this will fail. */
err = link (src_filename[i], dest_filename[i]);
if (!err)
err = unlink (src_filename[i]);
#endif /* !HAVE_W32_SYSTEM */
}
if (err)
break;
xfree (src_filename[i]);
src_filename[i] = NULL;
}
}
}
if (err || opt.dry_run)
{
int i;
int saved_errno = errno;
/* An error occurred or a dry-run is requested. */
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* The change was not yet committed. */
unlink (src_filename[i]);
if (orig_filename[i])
unlink (orig_filename[i]);
}
else
{
/* The changes were already committed. FIXME: This is a
tad dangerous, as we don't know if we don't overwrite
a version of the file that is even newer than the one
we just installed. */
if (orig_filename[i])
gnupg_rename_file (orig_filename[i], dest_filename[i], NULL);
else
unlink (dest_filename[i]);
}
}
if (err)
gc_error (1, saved_errno, "could not commit changes");
/* Fall-through for dry run. */
goto leave;
}
/* If it all worked, notify the daemons of the changes. */
if (opt.runtime)
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
/* Move the per-process backup file into its place. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
if (orig_filename[backend])
{
char *backup_filename;
assert (dest_filename[backend]);
backup_filename = xasprintf ("%s.%s.bak",
dest_filename[backend], GPGCONF_NAME);
gnupg_rename_file (orig_filename[backend], backup_filename, NULL);
xfree (backup_filename);
}
leave:
if (block)
gnupg_unblock_all_signals ();
xfree (line);
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
xfree (src_filename[backend]);
xfree (dest_filename[backend]);
xfree (orig_filename[backend]);
}
}
/* Check whether USER matches the current user of one of its group.
This function may change USER. Returns true is there is a
match. */
static int
key_matches_user_or_group (char *user)
{
char *group;
if (*user == '*' && user[1] == 0)
return 1; /* A single asterisk matches all users. */
group = strchr (user, ':');
if (group)
*group++ = 0;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we don't support groups. */
if (group && *group)
gc_error (0, 0, _("Note that group specifications are ignored\n"));
#ifndef HAVE_W32CE_SYSTEM
if (*user)
{
static char *my_name;
if (!my_name)
{
char tmp[1];
DWORD size = 1;
GetUserNameA (tmp, &size);
my_name = xmalloc (size);
if (!GetUserNameA (my_name, &size))
gc_error (1,0, "error getting current user name: %s",
w32_strerror (-1));
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
#endif /*HAVE_W32CE_SYSTEM*/
#else /*!HAVE_W32_SYSTEM*/
/* First check whether the user matches. */
if (*user)
{
static char *my_name;
if (!my_name)
{
struct passwd *pw = getpwuid ( getuid () );
if (!pw)
gc_error (1, errno, "getpwuid failed for current user");
my_name = xstrdup (pw->pw_name);
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
/* If that failed, check whether a group matches. */
if (group && *group)
{
static char *my_group;
static char **my_supgroups;
int n;
if (!my_group)
{
struct group *gr = getgrgid ( getgid () );
if (!gr)
gc_error (1, errno, "getgrgid failed for current user");
my_group = xstrdup (gr->gr_name);
}
if (!strcmp (group, my_group))
return 1; /* Found. */
if (!my_supgroups)
{
int ngids;
gid_t *gids;
ngids = getgroups (0, NULL);
gids = xcalloc (ngids+1, sizeof *gids);
ngids = getgroups (ngids, gids);
if (ngids < 0)
gc_error (1, errno, "getgroups failed for current user");
my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
for (n=0; n < ngids; n++)
{
struct group *gr = getgrgid ( gids[n] );
if (!gr)
gc_error (1, errno, "getgrgid failed for supplementary group");
my_supgroups[n] = xstrdup (gr->gr_name);
}
xfree (gids);
}
for (n=0; my_supgroups[n]; n++)
if (!strcmp (group, my_supgroups[n]))
return 1; /* Found. */
}
#endif /*!HAVE_W32_SYSTEM*/
return 0; /* No match. */
}
/* Read and process the global configuration file for gpgconf. This
optional file is used to update our internal tables at runtime and
may also be used to set new default values. If FNAME is NULL the
default name will be used. With UPDATE set to true the internal
tables are actually updated; if not set, only a syntax check is
done. If DEFAULTS is true the global options are written to the
configuration files. If LISTFP is set, no changes are done but the
configuration file is printed to LISTFP in a colon separated format.
Returns 0 on success or if the config file is not present; -1 is
returned on error. */
int
gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
estream_t listfp)
{
int result = 0;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
gpgrt_stream_t config;
int lineno = 0;
int in_rule = 0;
int got_match = 0;
int runtime[GC_BACKEND_NR];
int backend_id, component_id;
char *fname;
if (fname_arg)
fname = xstrdup (fname_arg);
else
fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf",
NULL);
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
config = gpgrt_fopen (fname, "r");
if (!config)
{
/* Do not print an error if the file is not available, except
when running in syntax check mode. */
if (errno != ENOENT || !update)
{
gc_error (0, errno, "can not open global config file '%s'", fname);
result = -1;
}
xfree (fname);
return result;
}
while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0)
{
char *key, *component, *option, *flags, *value;
char *empty;
gc_option_t *option_info = NULL;
char *p;
int is_continuation;
lineno++;
key = line;
while (*key == ' ' || *key == '\t')
key++;
if (!*key || *key == '#' || *key == '\r' || *key == '\n')
continue;
is_continuation = (key != line);
/* Parse the key field. */
if (!is_continuation && got_match)
break; /* Finish after the first match. */
else if (!is_continuation)
{
in_rule = 0;
for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
;
if (!*p)
{
gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno);
result = -1;
continue;
}
*p++ = 0;
component = p;
}
else if (!in_rule)
{
gc_error (0, 0, "continuation but no rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
else
{
component = key;
key = NULL;
}
in_rule = 1;
/* Parse the component. */
while (*component == ' ' || *component == '\t')
component++;
for (p=component; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == component)
{
gc_error (0, 0, "missing component at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
empty = p;
*p++ = 0;
option = p;
component_id = gc_component_find (component);
if (component_id < 0)
{
gc_error (0, 0, "unknown component at '%s', line %d",
fname, lineno);
result = -1;
}
/* Parse the option name. */
while (*option == ' ' || *option == '\t')
option++;
for (p=option; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == option)
{
gc_error (0, 0, "missing option at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
*p++ = 0;
flags = p;
if ( component_id != -1)
{
option_info = find_option (component_id, option, GC_BACKEND_ANY);
if (!option_info)
{
gc_error (0, 0, "unknown option at '%s', line %d",
fname, lineno);
result = -1;
}
}
/* Parse the optional flags. */
while (*flags == ' ' || *flags == '\t')
flags++;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
gc_error (0, 0, "syntax error in rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
*p++ = 0;
value = p;
}
else /* No flags given. */
{
value = flags;
flags = NULL;
}
/* Parse the optional value. */
while (*value == ' ' || *value == '\t')
value++;
for (p=value; *p && !strchr ("\r\n", *p); p++)
;
if (p == value)
value = empty; /* No value given; let it point to an empty string. */
else
{
/* Strip trailing white space. */
*p = 0;
for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
*p = 0;
}
/* Check flag combinations. */
if (!flags)
;
else if (!strcmp (flags, "default"))
{
if (*value)
{
gc_error (0, 0, "flag \"default\" may not be combined "
"with a value at '%s', line %d",
fname, lineno);
result = -1;
}
}
else if (!strcmp (flags, "change"))
;
else if (!strcmp (flags, "no-change"))
;
else
{
gc_error (0, 0, "unknown flag at '%s', line %d",
fname, lineno);
result = -1;
}
/* In list mode we print out all records. */
if (listfp && !result)
{
/* If this is a new ruleset, print a key record. */
if (!is_continuation)
{
char *group = strchr (key, ':');
if (group)
{
*group++ = 0;
if ((p = strchr (group, ':')))
*p = 0; /* We better strip any extra stuff. */
}
es_fprintf (listfp, "k:%s:", gc_percent_escape (key));
es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):"");
}
/* All other lines are rule records. */
es_fprintf (listfp, "r:::%s:%s:%s:",
gc_component[component_id].name,
option_info->name? option_info->name : "",
flags? flags : "");
if (value != empty)
es_fprintf (listfp, "\"%s", gc_percent_escape (value));
es_putc ('\n', listfp);
}
/* Check whether the key matches but do this only if we are not
running in syntax check mode. */
if ( update
&& !result && !listfp
&& (got_match || (key && key_matches_user_or_group (key))) )
{
int newflags = 0;
got_match = 1;
/* Apply the flags from gpgconf.conf. */
if (!flags)
;
else if (!strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
else if (!strcmp (flags, "no-change"))
option_info->flags |= GC_OPT_FLAG_NO_CHANGE;
else if (!strcmp (flags, "change"))
option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE;
if (defaults)
{
/* Here we explicitly allow updating the value again. */
if (newflags)
{
option_info->new_flags = 0;
}
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 0);
}
}
}
if (length < 0 || gpgrt_ferror (config))
{
gc_error (0, errno, "error reading from '%s'", fname);
result = -1;
}
if (gpgrt_fclose (config))
gc_error (0, errno, "error closing '%s'", fname);
xfree (line);
/* If it all worked, process the options. */
if (!result && update && defaults && !listfp)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 0);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname);
return result;
}
/*
* Apply the profile FNAME to all known configure files.
*/
gpg_error_t
gc_apply_profile (const char *fname)
{
gpg_error_t err;
char *fname_buffer = NULL;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t fp;
int lineno = 0;
int runtime[GC_BACKEND_NR];
int backend_id;
int component_id = -1;
int skip_section = 0;
int error_count = 0;
int newflags;
if (!fname)
fname = "-";
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
if (!(!strcmp (fname, "-")
|| strchr (fname, '/')
#ifdef HAVE_W32_SYSTEM
|| strchr (fname, '\\')
#endif
|| strchr (fname, '.')))
{
/* FNAME looks like a standard profile name. Check whether one
* is installed and use that instead of the given file name. */
fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S,
fname, ".prf", NULL);
if (!access (fname_buffer, F_OK))
fname = fname_buffer;
}
fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info ("applying profile '%s'\n", fname);
err = 0;
while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0)
{
char *name, *flags, *value;
gc_option_t *option_info = NULL;
char *p;
lineno++;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
trim_trailing_spaces (name);
/* Check whether this is a new section. */
if (*name == '[')
{
name++;
skip_section = 0;
/* New section: Get the name of the component. */
p = strchr (name, ']');
if (!p)
{
error_count++;
log_info ("%s:%d:%d: error: syntax error in section tag\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
*p++ = 0;
if (*p)
log_info ("%s:%d:%d: warning: garbage after section tag\n",
fname, lineno, (int)(p - line));
trim_spaces (name);
component_id = gc_component_find (name);
if (component_id < 0)
{
log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n",
fname, lineno, (int)(name - line), name );
skip_section = 1;
}
continue;
}
if (skip_section)
continue;
if (component_id < 0)
{
error_count++;
log_info ("%s:%d:%d: error: not in a valid section\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
/* Parse the option name. */
for (p = name; *p && !spacep (p); p++)
;
*p++ = 0;
value = p;
option_info = find_option (component_id, name, GC_BACKEND_ANY);
if (!option_info)
{
error_count++;
log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n",
fname, lineno, (int)(name - line),
name, gc_component[component_id].name);
continue;
}
/* Parse the optional flags. */
trim_spaces (value);
flags = value;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
log_info ("%s:%d:%d: warning: invalid flag specification\n",
fname, lineno, (int)(p - line));
continue;
}
*p++ = 0;
value = p;
trim_spaces (value);
}
else /* No flags given. */
flags = NULL;
/* Set required defaults. */
if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE
&& !*value)
value = "1";
/* Check and save this option. */
newflags = 0;
if (flags && !strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
if (newflags)
option_info->new_flags = 0;
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 1);
}
if (length < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
error_count++;
log_error (_("%s:%u: read error: %s\n"),
fname, lineno, gpg_strerror (err));
}
if (es_fclose (fp))
log_error (_("error closing '%s'\n"), fname);
if (error_count)
log_error (_("error parsing '%s'\n"), fname);
xfree (line);
/* If it all worked, process the options. */
if (!err)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 1);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname_buffer);
return err;
}
diff --git a/tools/gpgconf.c b/tools/gpgconf.c
index a9f4607bf..c6fb24e61 100644
--- a/tools/gpgconf.c
+++ b/tools/gpgconf.c
@@ -1,771 +1,771 @@
/* gpgconf.c - Configuration utility for GnuPG
* Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "gpgconf.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "../common/init.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oVerbose = 'v',
oRuntime = 'r',
oComponent = 'c',
oNull = '0',
oNoVerbose = 500,
oHomedir,
oBuilddir,
aListComponents,
aCheckPrograms,
aListOptions,
aChangeOptions,
aCheckOptions,
aApplyDefaults,
aListConfig,
aCheckConfig,
aQuerySWDB,
aListDirs,
aLaunch,
aKill,
aCreateSocketDir,
aRemoveSocketDir,
aApplyProfile,
aReload
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] =
{
{ 300, NULL, 0, N_("@Commands:\n ") },
{ aListComponents, "list-components", 256, N_("list all components") },
{ aCheckPrograms, "check-programs", 256, N_("check all programs") },
{ aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
{ aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") },
{ aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") },
{ aApplyDefaults, "apply-defaults", 256,
N_("apply global default values") },
{ aApplyProfile, "apply-profile", 256,
N_("|FILE|update configuration files using FILE") },
{ aListDirs, "list-dirs", 256,
N_("get the configuration directories for @GPGCONF@") },
{ aListConfig, "list-config", 256,
N_("list global configuration file") },
{ aCheckConfig, "check-config", 256,
N_("check global configuration file") },
{ aQuerySWDB, "query-swdb", 256,
N_("query the software version database") },
{ aReload, "reload", 256, N_("reload all or a given component")},
{ aLaunch, "launch", 256, N_("launch a given component")},
{ aKill, "kill", 256, N_("kill a given component")},
{ aCreateSocketDir, "create-socketdir", 256, "@"},
{ aRemoveSocketDir, "remove-socketdir", 256, "@"},
{ 301, NULL, 0, N_("@\nOptions:\n ") },
{ oOutput, "output", 2, N_("use as output file") },
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("quiet") },
{ oDryRun, "dry-run", 0, N_("do not make any changes") },
{ oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") },
/* hidden options */
{ oHomedir, "homedir", 2, "@" },
{ oBuilddir, "build-prefix", 2, "@" },
{ oNull, "null", 0, "@" },
{ oNoVerbose, "no-verbose", 0, "@"},
{0}
};
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPGCONF@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGCONF@ [options]\n"
"Manage configuration options for tools of the @GNUPG@ system\n");
break;
default: p = NULL; break;
}
return p;
}
/* Return the fp for the output. This is usually stdout unless
--output has been used. In the latter case this function opens
that file. */
static estream_t
get_outfp (estream_t *fp)
{
if (!*fp)
{
if (opt.outfile)
{
*fp = es_fopen (opt.outfile, "w");
if (!*fp)
gc_error (1, errno, "can not open '%s'", opt.outfile);
}
else
*fp = es_stdout;
}
return *fp;
}
static void
list_dirs (estream_t fp, char **names)
{
static struct {
const char *name;
const char *(*fnc)(void);
const char *extra;
} list[] = {
{ "sysconfdir", gnupg_sysconfdir, NULL },
{ "bindir", gnupg_bindir, NULL },
{ "libexecdir", gnupg_libexecdir, NULL },
{ "libdir", gnupg_libdir, NULL },
{ "datadir", gnupg_datadir, NULL },
{ "localedir", gnupg_localedir, NULL },
{ "socketdir", gnupg_socketdir, NULL },
{ "dirmngr-socket", dirmngr_socket_name, NULL,},
{ "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME },
{ "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME },
{ "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME },
{ "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME },
{ "homedir", gnupg_homedir, NULL }
};
int idx, j;
char *tmp;
const char *s;
for (idx = 0; idx < DIM (list); idx++)
{
s = list[idx].fnc ();
if (list[idx].extra)
{
tmp = make_filename (s, list[idx].extra, NULL);
s = tmp;
}
else
tmp = NULL;
if (!names)
es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s));
else
{
for (j=0; names[j]; j++)
if (!strcmp (names[j], list[idx].name))
{
es_fputs (s, fp);
es_putc (opt.null? '\0':'\n', fp);
}
}
xfree (tmp);
}
}
/* Check whether NAME is valid argument for query_swdb(). Valid names
* start with a letter and contain only alphanumeric characters or an
* underscore. */
static int
valid_swdb_name_p (const char *name)
{
if (!name || !*name || !alphap (name))
return 0;
for (name++; *name; name++)
if (!alnump (name) && *name != '_')
return 0;
return 1;
}
/* Query the SWDB file. If necessary and possible this functions asks
* the dirmngr to load an updated version of that file. The caller
* needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and
* optional the currently installed version in CURRENT_VERSION. The
* output written to OUT is a colon delimited line with these fields:
*
* name :: The name of the package
* curvers:: The installed version if given.
* status :: This value tells the status of the software package
* '-' :: No information available
* (error or CURRENT_VERSION not given)
* '?' :: Unknown NAME
* 'u' :: Update available
* 'c' :: The version is Current
* 'n' :: The current version is already Newer than the
* available one.
* urgency :: If the value is greater than zero an urgent update is required.
* error :: 0 on success or an gpg_err_code_t
* Common codes seen:
* GPG_ERR_TOO_OLD :: The SWDB file is to old to be used.
* GPG_ERR_ENOENT :: The SWDB file is not available.
* GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file.
* filedate:: Date of the swdb file (yyyymmddThhmmss)
* verified:: Date we checked the validity of the file (yyyyymmddThhmmss)
* version :: The version string from the swdb.
* reldate :: Release date of that version (yyyymmddThhmmss)
* size :: Size of the package in bytes.
* hash :: SHA-2 hash of the package.
*
*/
static void
query_swdb (estream_t out, const char *name, const char *current_version)
{
gpg_error_t err;
const char *search_name;
char *fname = NULL;
estream_t fp = NULL;
char *line = NULL;
char *self_version = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char *fields[2];
char *p;
gnupg_isotime_t filedate = {0};
gnupg_isotime_t verified = {0};
char *value_ver = NULL;
gnupg_isotime_t value_date = {0};
char *value_size = NULL;
char *value_sha2 = NULL;
unsigned long value_size_ul = 0;
int status, i;
if (!valid_swdb_name_p (name))
{
log_error ("error in package name '%s': %s\n",
name, gpg_strerror (GPG_ERR_INV_NAME));
goto leave;
}
if (!strcmp (name, "gnupg"))
search_name = "gnupg21";
else if (!strcmp (name, "gnupg1"))
search_name = "gnupg1";
else
search_name = name;
if (!current_version && !strcmp (name, "gnupg"))
{
/* Use our own version but string a possible beta string. */
self_version = xstrdup (PACKAGE_VERSION);
p = strchr (self_version, '-');
if (p)
*p = 0;
current_version = self_version;
}
if (current_version && (strchr (current_version, ':')
|| compare_version_strings (current_version, NULL)))
{
log_error ("error in version string '%s': %s\n",
current_version, gpg_strerror (GPG_ERR_INV_ARG));
goto leave;
}
fname = make_filename (gnupg_homedir (), "swdb.lst", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Note that the parser uses the first occurance of a matching
* values and ignores possible duplicated values. */
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (split_fields (line, fields, DIM (fields)) < DIM(fields))
continue; /* Skip empty lines and names w/o a value. */
if (*fields[0] == '#')
continue; /* Skip comments. */
/* Record the meta data. */
if (!*filedate && !strcmp (fields[0], ".filedate"))
{
string2isotime (filedate, fields[1]);
continue;
}
if (!*verified && !strcmp (fields[0], ".verified"))
{
string2isotime (verified, fields[1]);
continue;
}
/* Tokenize the name. */
p = strrchr (fields[0], '_');
if (!p)
continue; /* Name w/o an underscore. */
*p++ = 0;
/* Wait for the requested name. */
if (!strcmp (fields[0], search_name))
{
if (!strcmp (p, "ver") && !value_ver)
value_ver = xstrdup (fields[1]);
else if (!strcmp (p, "date") && !*value_date)
string2isotime (value_date, fields[1]);
else if (!strcmp (p, "size") && !value_size)
value_size = xstrdup (fields[1]);
else if (!strcmp (p, "sha2") && !value_sha2)
value_sha2 = xstrdup (fields[1]);
}
}
if (len < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
if (!*filedate || !*verified)
{
err = gpg_error (GPG_ERR_INV_TIME);
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
goto leave;
}
if (!value_ver)
{
es_fprintf (out, "%s:%s:?:::::::::\n",
name,
current_version? current_version : "");
goto leave;
}
if (value_size)
{
gpg_err_set_errno (0);
value_size_ul = strtoul (value_size, &p, 10);
if (errno)
value_size_ul = 0;
else if (*p == 'k')
value_size_ul *= 1024;
}
err = 0;
status = '-';
if (compare_version_strings (value_ver, NULL))
err = gpg_error (GPG_ERR_INV_VALUE);
else if (!current_version)
;
else if (!(i = compare_version_strings (value_ver, current_version)))
status = 'c';
else if (i > 0)
status = 'u';
else
status = 'n';
es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n",
name,
current_version? current_version : "",
status,
err,
filedate,
verified,
value_ver,
value_date,
value_size_ul,
value_sha2? value_sha2 : "");
leave:
xfree (value_ver);
xfree (value_size);
xfree (value_sha2);
xfree (line);
es_fclose (fp);
xfree (fname);
xfree (self_version);
}
/* gpgconf main. */
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
const char *fname;
int no_more_options = 0;
enum cmd_and_opt_values cmd = 0;
estream_t outfp = NULL;
early_system_init ();
gnupg_reopen_std (GPGCONF_NAME);
set_strusage (my_strusage);
log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gc_components_init ();
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oRuntime:
opt.runtime = 1;
break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break;
case oNull: opt.null = 1; break;
case aListDirs:
case aListComponents:
case aCheckPrograms:
case aListOptions:
case aChangeOptions:
case aCheckOptions:
case aApplyDefaults:
case aApplyProfile:
case aListConfig:
case aCheckConfig:
case aQuerySWDB:
case aReload:
case aLaunch:
case aKill:
case aCreateSocketDir:
case aRemoveSocketDir:
cmd = pargs.r_opt;
break;
default: pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
fname = argc ? *argv : NULL;
switch (cmd)
{
case aListComponents:
default:
/* List all components. */
gc_component_list_components (get_outfp (&outfp));
break;
case aCheckPrograms:
/* Check all programs. */
gc_check_programs (get_outfp (&outfp));
break;
case aListOptions:
case aChangeOptions:
case aCheckOptions:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
exit (2);
}
else
{
int idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
exit (1);
}
if (cmd == aCheckOptions)
gc_component_check_options (idx, get_outfp (&outfp), NULL);
else
{
gc_component_retrieve_options (idx);
if (gc_process_gpgconf_conf (NULL, 1, 0, NULL))
exit (1);
if (cmd == aListOptions)
gc_component_list_options (idx, get_outfp (&outfp));
else if (cmd == aChangeOptions)
gc_component_change_options (idx, es_stdin,
get_outfp (&outfp), 0);
}
}
break;
case aLaunch:
case aKill:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
exit (2);
}
else if (!strcmp (fname, "all"))
{
if (cmd == aLaunch)
{
if (gc_component_launch (-1))
exit (1);
}
else
{
gc_component_kill (-1);
}
}
else
{
/* Launch/Kill a given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
exit (1);
}
else if (cmd == aLaunch)
{
if (gc_component_launch (idx))
exit (1);
}
else
{
/* We don't error out if the kill failed because this
command should do nothing if the component is not
running. */
gc_component_kill (idx);
}
}
break;
case aReload:
if (!fname || !strcmp (fname, "all"))
{
/* Reload all. */
gc_component_reload (-1);
}
else
{
/* Reload given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
exit (1);
}
else
{
gc_component_reload (idx);
}
}
break;
case aListConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp)))
exit (1);
break;
case aCheckConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, NULL))
exit (1);
break;
case aApplyDefaults:
if (fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("No argument allowed"), es_stderr);
es_putc ('\n', es_stderr);
exit (2);
}
gc_component_retrieve_options (-1);
if (gc_process_gpgconf_conf (NULL, 1, 1, NULL))
exit (1);
break;
case aApplyProfile:
gc_component_retrieve_options (-1);
if (gc_apply_profile (fname))
exit (1);
break;
case aListDirs:
/* Show the system configuration directories for gpgconf. */
get_outfp (&outfp);
list_dirs (outfp, argc? argv : NULL);
break;
case aQuerySWDB:
/* Query the software version database. */
if (!fname || argc > 2)
{
es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n",
GPGCONF_NAME);
exit (2);
}
get_outfp (&outfp);
query_swdb (outfp, fname, argc > 1? argv[1] : NULL);
break;
case aCreateSocketDir:
{
char *socketdir;
unsigned int flags;
/* Make sure that the top /run/user/UID/gnupg dir has been
* created. */
gnupg_socketdir ();
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 64) && !opt.dry_run)
{
/* No sub dir - create it. */
if (gnupg_mkdir (socketdir, "-rwx"))
gc_error (1, errno, "error creating '%s'", socketdir);
/* Try again. */
xfree (socketdir);
socketdir = _gnupg_socketdir_internal (1, &flags);
}
/* Give some info. */
if ( (flags & ~32) || opt.verbose || opt.dry_run)
{
log_info ("socketdir is '%s'\n", socketdir);
if ((flags & 1)) log_info ("\tgeneral error\n");
if ((flags & 2)) log_info ("\tno /run/user dir\n");
if ((flags & 4)) log_info ("\tbad permissions\n");
if ((flags & 8)) log_info ("\tbad permissions (subdir)\n");
if ((flags & 16)) log_info ("\tmkdir failed\n");
if ((flags & 32)) log_info ("\tnon-default homedir\n");
if ((flags & 64)) log_info ("\tno such subdir\n");
if ((flags & 128)) log_info ("\tusing homedir as fallback\n");
}
if ((flags & ~32) && !opt.dry_run)
gc_error (1, 0, "error creating socket directory");
xfree (socketdir);
}
break;
case aRemoveSocketDir:
{
char *socketdir;
unsigned int flags;
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 128))
log_info ("ignoring request to remove non /run/user socket dir\n");
else if (opt.dry_run)
;
else if (rmdir (socketdir))
gc_error (1, errno, "error removing '%s'", socketdir);
xfree (socketdir);
}
break;
}
if (outfp != es_stdout)
if (es_fclose (outfp))
gc_error (1, errno, "error closing '%s'", opt.outfile);
return 0;
}
diff --git a/tools/gpgsplit.c b/tools/gpgsplit.c
index 3b4bb15a9..ce2777d3a 100644
--- a/tools/gpgsplit.c
+++ b/tools/gpgsplit.c
@@ -1,890 +1,890 @@
/* gpgsplit.c - An OpenPGP packet splitting tool
* Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#ifdef HAVE_ZIP
# include <zlib.h>
#endif
#ifdef HAVE_BZIP2
# include <bzlib.h>
#endif /* HAVE_BZIP2 */
#if defined(__riscos__) && defined(USE_ZLIBRISCOS)
# include "zlib-riscos.h"
#endif
#define INCLUDED_BY_MAIN_MODULE 1
-#include "util.h"
-#include "openpgpdefs.h"
+#include "../common/util.h"
+#include "../common/openpgpdefs.h"
static int opt_verbose;
static const char *opt_prefix = "";
static int opt_uncompress;
static int opt_secret_to_public;
static int opt_no_split;
static void g10_exit( int rc );
static void split_packets (const char *fname);
enum cmd_and_opt_values {
aNull = 0,
oVerbose = 'v',
oPrefix = 'p',
oUncompress = 500,
oSecretToPublic,
oNoSplit,
aTest
};
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, "@Options:\n " },
{ oVerbose, "verbose", 0, "verbose" },
{ oPrefix, "prefix", 2, "|STRING|Prepend filenames with STRING" },
{ oUncompress, "uncompress", 0, "uncompress a packet"},
{ oSecretToPublic, "secret-to-public", 0, "convert secret keys to public keys"},
{ oNoSplit, "no-split", 0, "write to stdout and don't actually split"},
{0} };
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpgsplit (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = "Please report bugs to <@EMAIL@>.\n"; break;
case 1:
case 40: p =
"Usage: gpgsplit [options] [files] (-h for help)";
break;
case 41: p =
"Syntax: gpgsplit [options] [files]\n"
"Split an OpenPGP message into packets\n";
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
#ifdef HAVE_DOSISH_SYSTEM
setmode( fileno(stdin), O_BINARY );
setmode( fileno(stdout), O_BINARY );
#endif
log_set_prefix ("gpgsplit", GPGRT_LOG_WITH_PREFIX);
set_strusage (my_strusage);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
while (optfile_parse( NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oVerbose: opt_verbose = 1; break;
case oPrefix: opt_prefix = pargs.r.ret_str; break;
case oUncompress: opt_uncompress = 1; break;
case oSecretToPublic: opt_secret_to_public = 1; break;
case oNoSplit: opt_no_split = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
g10_exit (2);
if (!argc)
split_packets (NULL);
else
{
for ( ;argc; argc--, argv++)
split_packets (*argv);
}
g10_exit (0);
return 0;
}
static void
g10_exit (int rc)
{
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit(rc );
}
static const char *
pkttype_to_string (int pkttype)
{
const char *s;
switch (pkttype)
{
case PKT_PUBKEY_ENC : s = "pk_enc"; break;
case PKT_SIGNATURE : s = "sig"; break;
case PKT_SYMKEY_ENC : s = "sym_enc"; break;
case PKT_ONEPASS_SIG : s = "onepass_sig"; break;
case PKT_SECRET_KEY : s = "secret_key"; break;
case PKT_PUBLIC_KEY : s = "public_key"; break;
case PKT_SECRET_SUBKEY : s = "secret_subkey"; break;
case PKT_COMPRESSED :
s = opt_uncompress? "uncompressed":"compressed";
break;
case PKT_ENCRYPTED : s = "encrypted"; break;
case PKT_MARKER : s = "marker"; break;
case PKT_PLAINTEXT : s = "plaintext"; break;
case PKT_RING_TRUST : s = "ring_trust"; break;
case PKT_USER_ID : s = "user_id"; break;
case PKT_PUBLIC_SUBKEY : s = "public_subkey"; break;
case PKT_OLD_COMMENT : s = "old_comment"; break;
case PKT_ATTRIBUTE : s = "attribute"; break;
case PKT_ENCRYPTED_MDC : s = "encrypted_mdc"; break;
case PKT_MDC : s = "mdc"; break;
case PKT_COMMENT : s = "comment"; break;
case PKT_GPG_CONTROL : s = "gpg_control"; break;
default: s = "unknown"; break;
}
return s;
}
/*
* Create a new filename and a return a pointer to a statically
* allocated buffer
*/
static char *
create_filename (int pkttype)
{
static unsigned int partno = 0;
static char *name;
if (!name)
name = xmalloc (strlen (opt_prefix) + 100 );
assert (pkttype < 1000 && pkttype >= 0 );
partno++;
sprintf (name, "%s%06u-%03d" EXTSEP_S "%.40s",
opt_prefix, partno, pkttype, pkttype_to_string (pkttype));
return name;
}
static int
read_u16 (FILE *fp, size_t *rn)
{
int c;
if ( (c = getc (fp)) == EOF )
return -1;
*rn = c << 8;
if ( (c = getc (fp)) == EOF )
return -1;
*rn |= c;
return 0;
}
static int
read_u32 (FILE *fp, unsigned long *rn)
{
size_t tmp;
if (read_u16 (fp, &tmp))
return -1;
*rn = tmp << 16;
if (read_u16 (fp, &tmp))
return -1;
*rn |= tmp;
return 0;
}
static int
write_old_header (FILE *fp, int pkttype, unsigned int len)
{
int ctb = (0x80 | ((pkttype & 15)<<2));
if (len < 256)
;
else if (len < 65536)
ctb |= 1;
else
ctb |= 2;
if ( putc ( ctb, fp) == EOF )
return -1;
if ( (ctb & 2) )
{
if (putc ((len>>24), fp) == EOF)
return -1;
if (putc ((len>>16), fp) == EOF)
return -1;
}
if ( (ctb & 3) )
{
if (putc ((len>>8), fp) == EOF)
return -1;
}
if (putc ((len&0xff), fp) == EOF)
return -1;
return 0;
}
static int
write_new_header (FILE *fp, int pkttype, unsigned int len)
{
if ( putc ((0xc0 | (pkttype & 0x3f)), fp) == EOF )
return -1;
if (len < 192)
{
if (putc (len, fp) == EOF)
return -1;
}
else if (len < 8384)
{
len -= 192;
if (putc ((len/256)+192, fp) == EOF)
return -1;
if (putc ((len%256), fp) == EOF)
return -1;
}
else
{
if (putc ( 0xff, fp) == EOF)
return -1;
if (putc ( (len >> 24), fp) == EOF)
return -1;
if (putc ( (len >> 16), fp) == EOF)
return -1;
if (putc ( (len >> 8), fp) == EOF)
return -1;
if (putc ( (len & 0xff), fp) == EOF)
return -1;
}
return 0;
}
/* Return the length of the public key given BUF of BUFLEN with a
secret key. */
static int
public_key_length (const unsigned char *buf, size_t buflen)
{
const unsigned char *s;
int nmpis;
/* byte version number (3 or 4)
u32 creation time
[u16 valid days (version 3 only)]
byte algorithm
n MPIs (n and e) */
if (!buflen)
return 0;
if (buf[0] < 2 || buf[0] > 4)
return 0; /* wrong version number */
if (buflen < (buf[0] == 4? 6:8))
return 0;
s = buf + (buf[0] == 4? 6:8);
buflen -= (buf[0] == 4? 6:8);
switch (s[-1])
{
case 1:
case 2:
case 3:
nmpis = 2;
break;
case 16:
case 20:
nmpis = 3;
break;
case 17:
nmpis = 4;
break;
default:
return 0;
}
for (; nmpis; nmpis--)
{
unsigned int nbits, nbytes;
if (buflen < 2)
return 0;
nbits = (s[0] << 8) | s[1];
s += 2; buflen -= 2;
nbytes = (nbits+7) / 8;
if (buflen < nbytes)
return 0;
s += nbytes; buflen -= nbytes;
}
return s - buf;
}
#ifdef HAVE_ZIP
static int
handle_zlib(int algo,FILE *fpin,FILE *fpout)
{
z_stream zs;
byte *inbuf, *outbuf;
unsigned int inbufsize, outbufsize;
int c,zinit_done, zrc, nread, count;
size_t n;
memset (&zs, 0, sizeof zs);
inbufsize = 2048;
inbuf = xmalloc (inbufsize);
outbufsize = 8192;
outbuf = xmalloc (outbufsize);
zs.avail_in = 0;
zinit_done = 0;
do
{
if (zs.avail_in < inbufsize)
{
n = zs.avail_in;
if (!n)
zs.next_in = (Bytef *) inbuf;
count = inbufsize - n;
for (nread=0;
nread < count && (c=getc (fpin)) != EOF;
nread++)
inbuf[n+nread] = c;
n += nread;
if (nread < count && algo == 1)
{
inbuf[n] = 0xFF; /* chew dummy byte */
n++;
}
zs.avail_in = n;
}
zs.next_out = (Bytef *) outbuf;
zs.avail_out = outbufsize;
if (!zinit_done)
{
zrc = (algo == 1? inflateInit2 ( &zs, -13)
: inflateInit ( &zs ));
if (zrc != Z_OK)
{
log_fatal ("zlib problem: %s\n", zs.msg? zs.msg :
zrc == Z_MEM_ERROR ? "out of core" :
zrc == Z_VERSION_ERROR ?
"invalid lib version" :
"unknown error" );
}
zinit_done = 1;
}
else
{
#ifdef Z_SYNC_FLUSH
zrc = inflate (&zs, Z_SYNC_FLUSH);
#else
zrc = inflate (&zs, Z_PARTIAL_FLUSH);
#endif
if (zrc == Z_STREAM_END)
; /* eof */
else if (zrc != Z_OK && zrc != Z_BUF_ERROR)
{
if (zs.msg)
log_fatal ("zlib inflate problem: %s\n", zs.msg );
else
log_fatal ("zlib inflate problem: rc=%d\n", zrc );
}
for (n=0; n < outbufsize - zs.avail_out; n++)
{
if (putc (outbuf[n], fpout) == EOF )
return 1;
}
}
}
while (zrc != Z_STREAM_END && zrc != Z_BUF_ERROR);
{
int i;
fputs ("Left over bytes:", stderr);
for (i=0; i < zs.avail_in; i++)
fprintf (stderr, " %02X", zs.next_in[i]);
putc ('\n', stderr);
}
inflateEnd (&zs);
return 0;
}
#endif /*HAVE_ZIP*/
#ifdef HAVE_BZIP2
static int
handle_bzip2(int algo,FILE *fpin,FILE *fpout)
{
bz_stream bzs;
byte *inbuf, *outbuf;
unsigned int inbufsize, outbufsize;
int c,zinit_done, zrc, nread, count;
size_t n;
memset (&bzs, 0, sizeof bzs);
inbufsize = 2048;
inbuf = xmalloc (inbufsize);
outbufsize = 8192;
outbuf = xmalloc (outbufsize);
bzs.avail_in = 0;
zinit_done = 0;
do
{
if (bzs.avail_in < inbufsize)
{
n = bzs.avail_in;
if (!n)
bzs.next_in = inbuf;
count = inbufsize - n;
for (nread=0;
nread < count && (c=getc (fpin)) != EOF;
nread++)
inbuf[n+nread] = c;
n += nread;
if (nread < count && algo == 1)
{
inbuf[n] = 0xFF; /* chew dummy byte */
n++;
}
bzs.avail_in = n;
}
bzs.next_out = outbuf;
bzs.avail_out = outbufsize;
if (!zinit_done)
{
zrc = BZ2_bzDecompressInit(&bzs,0,0);
if (zrc != BZ_OK)
log_fatal ("bz2lib problem: %d\n",zrc);
zinit_done = 1;
}
else
{
zrc = BZ2_bzDecompress(&bzs);
if (zrc == BZ_STREAM_END)
; /* eof */
else if (zrc != BZ_OK && zrc != BZ_PARAM_ERROR)
log_fatal ("bz2lib inflate problem: %d\n", zrc );
for (n=0; n < outbufsize - bzs.avail_out; n++)
{
if (putc (outbuf[n], fpout) == EOF )
return 1;
}
}
}
while (zrc != BZ_STREAM_END && zrc != BZ_PARAM_ERROR);
BZ2_bzDecompressEnd(&bzs);
return 0;
}
#endif /* HAVE_BZIP2 */
/* hdr must point to a buffer large enough to hold all header bytes */
static int
write_part (FILE *fpin, unsigned long pktlen,
int pkttype, int partial, unsigned char *hdr, size_t hdrlen)
{
FILE *fpout;
int c, first;
unsigned char *p;
const char *outname = create_filename (pkttype);
#if defined(__riscos__) && defined(USE_ZLIBRISCOS)
static int initialized = 0;
if (!initialized)
initialized = riscos_load_module("ZLib", zlib_path, 1);
#endif
if (opt_no_split)
fpout = stdout;
else
{
if (opt_verbose)
log_info ("writing '%s'\n", outname);
fpout = fopen (outname, "wb");
if (!fpout)
{
log_error ("error creating '%s': %s\n", outname, strerror(errno));
/* stop right now, otherwise we would mess up the sequence
of the part numbers */
g10_exit (1);
}
}
if (opt_secret_to_public
&& (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY))
{
unsigned char *blob = xmalloc (pktlen);
int i, len;
pkttype = pkttype == PKT_SECRET_KEY? PKT_PUBLIC_KEY:PKT_PUBLIC_SUBKEY;
for (i=0; i < pktlen; i++)
{
c = getc (fpin);
if (c == EOF)
goto read_error;
blob[i] = c;
}
len = public_key_length (blob, pktlen);
if (!len)
{
log_error ("error calculating public key length\n");
g10_exit (1);
}
if ( (hdr[0] & 0x40) )
{
if (write_new_header (fpout, pkttype, len))
goto write_error;
}
else
{
if (write_old_header (fpout, pkttype, len))
goto write_error;
}
for (i=0; i < len; i++)
{
if ( putc (blob[i], fpout) == EOF )
goto write_error;
}
goto ready;
}
if (!opt_uncompress)
{
for (p=hdr; hdrlen; p++, hdrlen--)
{
if ( putc (*p, fpout) == EOF )
goto write_error;
}
}
first = 1;
while (partial)
{
size_t partlen;
if (partial == 1)
{ /* openpgp */
if (first )
{
c = pktlen;
assert( c >= 224 && c < 255 );
first = 0;
}
else if ((c = getc (fpin)) == EOF )
goto read_error;
else
hdr[hdrlen++] = c;
if (c < 192)
{
pktlen = c;
partial = 0; /* (last segment may follow) */
}
else if (c < 224 )
{
pktlen = (c - 192) * 256;
if ((c = getc (fpin)) == EOF)
goto read_error;
hdr[hdrlen++] = c;
pktlen += c + 192;
partial = 0;
}
else if (c == 255)
{
if (read_u32 (fpin, &pktlen))
goto read_error;
hdr[hdrlen++] = pktlen >> 24;
hdr[hdrlen++] = pktlen >> 16;
hdr[hdrlen++] = pktlen >> 8;
hdr[hdrlen++] = pktlen;
partial = 0;
}
else
{ /* next partial body length */
for (p=hdr; hdrlen; p++, hdrlen--)
{
if ( putc (*p, fpout) == EOF )
goto write_error;
}
partlen = 1 << (c & 0x1f);
for (; partlen; partlen--)
{
if ((c = getc (fpin)) == EOF)
goto read_error;
if ( putc (c, fpout) == EOF )
goto write_error;
}
}
}
else if (partial == 2)
{ /* old gnupg */
assert (!pktlen);
if ( read_u16 (fpin, &partlen) )
goto read_error;
hdr[hdrlen++] = partlen >> 8;
hdr[hdrlen++] = partlen;
for (p=hdr; hdrlen; p++, hdrlen--)
{
if ( putc (*p, fpout) == EOF )
goto write_error;
}
if (!partlen)
partial = 0; /* end of packet */
for (; partlen; partlen--)
{
c = getc (fpin);
if (c == EOF)
goto read_error;
if ( putc (c, fpout) == EOF )
goto write_error;
}
}
else
{ /* compressed: read to end */
pktlen = 0;
partial = 0;
hdrlen = 0;
if (opt_uncompress)
{
if ((c = getc (fpin)) == EOF)
goto read_error;
if (0)
;
#ifdef HAVE_ZIP
else if(c==1 || c==2)
{
if(handle_zlib(c,fpin,fpout))
goto write_error;
}
#endif /* HAVE_ZIP */
#ifdef HAVE_BZIP2
else if(c==3)
{
if(handle_bzip2(c,fpin,fpout))
goto write_error;
}
#endif /* HAVE_BZIP2 */
else
{
log_error("invalid compression algorithm (%d)\n",c);
goto read_error;
}
}
else
{
while ( (c=getc (fpin)) != EOF )
{
if ( putc (c, fpout) == EOF )
goto write_error;
}
}
if (!feof (fpin))
goto read_error;
}
}
for (p=hdr; hdrlen; p++, hdrlen--)
{
if ( putc (*p, fpout) == EOF )
goto write_error;
}
/* standard packet or last segment of partial length encoded packet */
for (; pktlen; pktlen--)
{
c = getc (fpin);
if (c == EOF)
goto read_error;
if ( putc (c, fpout) == EOF )
goto write_error;
}
ready:
if ( !opt_no_split && fclose (fpout) )
log_error ("error closing '%s': %s\n", outname, strerror (errno));
return 0;
write_error:
log_error ("error writing '%s': %s\n", outname, strerror (errno));
if (!opt_no_split)
fclose (fpout);
return 2;
read_error:
if (!opt_no_split)
{
int save = errno;
fclose (fpout);
errno = save;
}
return -1;
}
static int
do_split (FILE *fp)
{
int c, ctb, pkttype;
unsigned long pktlen = 0;
int partial = 0;
unsigned char header[20];
int header_idx = 0;
ctb = getc (fp);
if (ctb == EOF)
return 3; /* ready */
header[header_idx++] = ctb;
if (!(ctb & 0x80))
{
log_error("invalid CTB %02x\n", ctb );
return 1;
}
if ( (ctb & 0x40) )
{ /* new CTB */
pkttype = (ctb & 0x3f);
if( (c = getc (fp)) == EOF )
return -1;
header[header_idx++] = c;
if ( c < 192 )
pktlen = c;
else if ( c < 224 )
{
pktlen = (c - 192) * 256;
if( (c = getc (fp)) == EOF )
return -1;
header[header_idx++] = c;
pktlen += c + 192;
}
else if ( c == 255 )
{
if (read_u32 (fp, &pktlen))
return -1;
header[header_idx++] = pktlen >> 24;
header[header_idx++] = pktlen >> 16;
header[header_idx++] = pktlen >> 8;
header[header_idx++] = pktlen;
}
else
{ /* partial body length */
pktlen = c;
partial = 1;
}
}
else
{
int lenbytes;
pkttype = (ctb>>2)&0xf;
lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3));
if (!lenbytes )
{
pktlen = 0; /* don't know the value */
if( pkttype == PKT_COMPRESSED )
partial = 3;
else
partial = 2; /* the old GnuPG partial length encoding */
}
else
{
for ( ; lenbytes; lenbytes-- )
{
pktlen <<= 8;
if( (c = getc (fp)) == EOF )
return -1;
header[header_idx++] = c;
pktlen |= c;
}
}
}
return write_part (fp, pktlen, pkttype, partial, header, header_idx);
}
static void
split_packets (const char *fname)
{
FILE *fp;
int rc;
if (!fname || !strcmp (fname, "-"))
{
fp = stdin;
fname = "-";
}
else if ( !(fp = fopen (fname,"rb")) )
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return;
}
while ( !(rc = do_split (fp)) )
;
if ( rc > 0 )
; /* error already handled */
else if ( ferror (fp) )
log_error ("error reading '%s': %s\n", fname, strerror (errno));
else
log_error ("premature EOF while reading '%s'\n", fname );
if ( fp != stdin )
fclose (fp);
}
diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c
index ffd239f42..c622a6672 100644
--- a/tools/gpgtar-create.c
+++ b/tools/gpgtar-create.c
@@ -1,978 +1,978 @@
/* gpgtar-create.c - Create a TAR archive
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <unistd.h>
# include <pwd.h>
# include <grp.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <assert.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "../common/exectool.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
#include "gpgtar.h"
#ifndef HAVE_LSTAT
#define lstat(a,b) stat ((a), (b))
#endif
/* Object to control the file scanning. */
struct scanctrl_s;
typedef struct scanctrl_s *scanctrl_t;
struct scanctrl_s
{
tar_header_t flist;
tar_header_t *flist_tail;
int nestlevel;
};
/* Given a fresh header object HDR with only the name field set, try
to gather all available info. This is the W32 version. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
fillup_entry_w32 (tar_header_t hdr)
{
char *p;
wchar_t *wfname;
WIN32_FILE_ATTRIBUTE_DATA fad;
DWORD attr;
for (p=hdr->name; *p; p++)
if (*p == '/')
*p = '\\';
wfname = native_to_wchar (hdr->name);
for (p=hdr->name; *p; p++)
if (*p == '\\')
*p = '/';
if (!wfname)
{
log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1));
return gpg_error_from_syserror ();
}
if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad))
{
log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1));
xfree (wfname);
return gpg_error_from_syserror ();
}
xfree (wfname);
attr = fad.dwFileAttributes;
if ((attr & FILE_ATTRIBUTE_NORMAL))
hdr->typeflag = TF_REGULAR;
else if ((attr & FILE_ATTRIBUTE_DIRECTORY))
hdr->typeflag = TF_DIRECTORY;
else if ((attr & FILE_ATTRIBUTE_DEVICE))
hdr->typeflag = TF_NOTSUP;
else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY)))
hdr->typeflag = TF_NOTSUP;
else
hdr->typeflag = TF_REGULAR;
/* Map some attributes to USTAR defined mode bits. */
hdr->mode = 0640; /* User may read and write, group only read. */
if ((attr & FILE_ATTRIBUTE_DIRECTORY))
hdr->mode |= 0110; /* Dirs are user and group executable. */
if ((attr & FILE_ATTRIBUTE_READONLY))
hdr->mode &= ~0200; /* Clear the user write bit. */
if ((attr & FILE_ATTRIBUTE_HIDDEN))
hdr->mode &= ~0707; /* Clear all user and other bits. */
if ((attr & FILE_ATTRIBUTE_SYSTEM))
hdr->mode |= 0004; /* Make it readable by other. */
/* Only set the size for a regular file. */
if (hdr->typeflag == TF_REGULAR)
hdr->size = (fad.nFileSizeHigh * (unsigned long long)(MAXDWORD+1)
+ fad.nFileSizeLow);
hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32)
| fad.ftLastWriteTime.dwLowDateTime);
if (!hdr->mtime)
hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32)
| fad.ftCreationTime.dwLowDateTime);
hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */
return 0;
}
#endif /*HAVE_W32_SYSTEM*/
/* Given a fresh header object HDR with only the name field set, try
to gather all available info. This is the POSIX version. */
#ifndef HAVE_W32_SYSTEM
static gpg_error_t
fillup_entry_posix (tar_header_t hdr)
{
gpg_error_t err;
struct stat sbuf;
if (lstat (hdr->name, &sbuf))
{
err = gpg_error_from_syserror ();
log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err));
return err;
}
if (S_ISREG (sbuf.st_mode))
hdr->typeflag = TF_REGULAR;
else if (S_ISDIR (sbuf.st_mode))
hdr->typeflag = TF_DIRECTORY;
else if (S_ISCHR (sbuf.st_mode))
hdr->typeflag = TF_CHARDEV;
else if (S_ISBLK (sbuf.st_mode))
hdr->typeflag = TF_BLOCKDEV;
else if (S_ISFIFO (sbuf.st_mode))
hdr->typeflag = TF_FIFO;
else if (S_ISLNK (sbuf.st_mode))
hdr->typeflag = TF_SYMLINK;
else
hdr->typeflag = TF_NOTSUP;
/* FIXME: Save DEV and INO? */
/* Set the USTAR defined mode bits using the system macros. */
if (sbuf.st_mode & S_IRUSR)
hdr->mode |= 0400;
if (sbuf.st_mode & S_IWUSR)
hdr->mode |= 0200;
if (sbuf.st_mode & S_IXUSR)
hdr->mode |= 0100;
if (sbuf.st_mode & S_IRGRP)
hdr->mode |= 0040;
if (sbuf.st_mode & S_IWGRP)
hdr->mode |= 0020;
if (sbuf.st_mode & S_IXGRP)
hdr->mode |= 0010;
if (sbuf.st_mode & S_IROTH)
hdr->mode |= 0004;
if (sbuf.st_mode & S_IWOTH)
hdr->mode |= 0002;
if (sbuf.st_mode & S_IXOTH)
hdr->mode |= 0001;
#ifdef S_IXUID
if (sbuf.st_mode & S_IXUID)
hdr->mode |= 04000;
#endif
#ifdef S_IXGID
if (sbuf.st_mode & S_IXGID)
hdr->mode |= 02000;
#endif
#ifdef S_ISVTX
if (sbuf.st_mode & S_ISVTX)
hdr->mode |= 01000;
#endif
hdr->nlink = sbuf.st_nlink;
hdr->uid = sbuf.st_uid;
hdr->gid = sbuf.st_gid;
/* Only set the size for a regular file. */
if (hdr->typeflag == TF_REGULAR)
hdr->size = sbuf.st_size;
hdr->mtime = sbuf.st_mtime;
return 0;
}
#endif /*!HAVE_W32_SYSTEM*/
/* Add a new entry. The name of a director entry is ENTRYNAME; if
that is NULL, DNAME is the name of the directory itself. Under
Windows ENTRYNAME shall have backslashes replaced by standard
slashes. */
static gpg_error_t
add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl)
{
gpg_error_t err;
tar_header_t hdr;
char *p;
size_t dnamelen = strlen (dname);
assert (dnamelen);
hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1
+ (entryname? strlen (entryname) : 0) + 1);
if (!hdr)
return gpg_error_from_syserror ();
p = stpcpy (hdr->name, dname);
if (entryname)
{
if (dname[dnamelen-1] != '/')
*p++ = '/';
strcpy (p, entryname);
}
else
{
if (hdr->name[dnamelen-1] == '/')
hdr->name[dnamelen-1] = 0;
}
#ifdef HAVE_DOSISH_SYSTEM
err = fillup_entry_w32 (hdr);
#else
err = fillup_entry_posix (hdr);
#endif
if (err)
xfree (hdr);
else
{
if (opt.verbose)
gpgtar_print_header (hdr, log_get_stream ());
*scanctrl->flist_tail = hdr;
scanctrl->flist_tail = &hdr->next;
}
return 0;
}
static gpg_error_t
scan_directory (const char *dname, scanctrl_t scanctrl)
{
gpg_error_t err = 0;
#ifdef HAVE_W32_SYSTEM
WIN32_FIND_DATAW fi;
HANDLE hd = INVALID_HANDLE_VALUE;
char *p;
if (!*dname)
return 0; /* An empty directory name has no entries. */
{
char *fname;
wchar_t *wfname;
fname = xtrymalloc (strlen (dname) + 2 + 2 + 1);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!strcmp (dname, "/"))
strcpy (fname, "/*"); /* Trailing slash is not allowed. */
else if (!strcmp (dname, "."))
strcpy (fname, "*");
else if (*dname && dname[strlen (dname)-1] == '/')
strcpy (stpcpy (fname, dname), "*");
else if (*dname && dname[strlen (dname)-1] != '*')
strcpy (stpcpy (fname, dname), "/*");
else
strcpy (fname, dname);
for (p=fname; *p; p++)
if (*p == '/')
*p = '\\';
wfname = native_to_wchar (fname);
xfree (fname);
if (!wfname)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, gpg_strerror (err));
goto leave;
}
hd = FindFirstFileW (wfname, &fi);
if (hd == INVALID_HANDLE_VALUE)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, w32_strerror (-1));
xfree (wfname);
goto leave;
}
xfree (wfname);
}
do
{
char *fname = wchar_to_native (fi.cFileName);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("error converting filename: %s\n", w32_strerror (-1));
break;
}
for (p=fname; *p; p++)
if (*p == '\\')
*p = '/';
if (!strcmp (fname, "." ) || !strcmp (fname, ".."))
err = 0; /* Skip self and parent dir entry. */
else if (!strncmp (dname, "./", 2) && dname[2])
err = add_entry (dname+2, fname, scanctrl);
else
err = add_entry (dname, fname, scanctrl);
xfree (fname);
}
while (!err && FindNextFileW (hd, &fi));
if (err)
;
else if (GetLastError () == ERROR_NO_MORE_FILES)
err = 0;
else
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, w32_strerror (-1));
}
leave:
if (hd != INVALID_HANDLE_VALUE)
FindClose (hd);
#else /*!HAVE_W32_SYSTEM*/
DIR *dir;
struct dirent *de;
if (!*dname)
return 0; /* An empty directory name has no entries. */
dir = opendir (dname);
if (!dir)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, gpg_strerror (err));
return err;
}
while ((de = readdir (dir)))
{
if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, ".."))
continue; /* Skip self and parent dir entry. */
err = add_entry (dname, de->d_name, scanctrl);
if (err)
goto leave;
}
leave:
closedir (dir);
#endif /*!HAVE_W32_SYSTEM*/
return err;
}
static gpg_error_t
scan_recursive (const char *dname, scanctrl_t scanctrl)
{
gpg_error_t err = 0;
tar_header_t hdr, *start_tail, *stop_tail;
if (scanctrl->nestlevel > 200)
{
log_error ("directories too deeply nested\n");
return gpg_error (GPG_ERR_RESOURCE_LIMIT);
}
scanctrl->nestlevel++;
assert (scanctrl->flist_tail);
start_tail = scanctrl->flist_tail;
scan_directory (dname, scanctrl);
stop_tail = scanctrl->flist_tail;
hdr = *start_tail;
for (; hdr && hdr != *stop_tail; hdr = hdr->next)
if (hdr->typeflag == TF_DIRECTORY)
{
if (opt.verbose > 1)
log_info ("scanning directory '%s'\n", hdr->name);
scan_recursive (hdr->name, scanctrl);
}
scanctrl->nestlevel--;
return err;
}
/* Returns true if PATTERN is acceptable. */
static int
pattern_valid_p (const char *pattern)
{
if (!*pattern)
return 0;
if (*pattern == '.' && pattern[1] == '.')
return 0;
if (*pattern == '/'
#ifdef HAVE_DOSISH_SYSTEM
|| *pattern == '\\'
#endif
)
return 0; /* Absolute filenames are not supported. */
#ifdef HAVE_DRIVE_LETTERS
if (((*pattern >= 'a' && *pattern <= 'z')
|| (*pattern >= 'A' && *pattern <= 'Z'))
&& pattern[1] == ':')
return 0; /* Drive letter are not allowed either. */
#endif /*HAVE_DRIVE_LETTERS*/
return 1; /* Okay. */
}
static void
store_xoctal (char *buffer, size_t length, unsigned long long value)
{
char *p, *pend;
size_t n;
unsigned long long v;
assert (length > 1);
v = value;
n = length;
p = pend = buffer + length;
*--p = 0; /* Nul byte. */
n--;
do
{
*--p = '0' + (v % 8);
v /= 8;
n--;
}
while (v && n);
if (!v)
{
/* Pad. */
for ( ; n; n--)
*--p = '0';
}
else /* Does not fit into the field. Store as binary number. */
{
v = value;
n = length;
p = pend = buffer + length;
do
{
*--p = v;
v /= 256;
n--;
}
while (v && n);
if (!v)
{
/* Pad. */
for ( ; n; n--)
*--p = 0;
if (*p & 0x80)
BUG ();
*p |= 0x80; /* Set binary flag. */
}
else
BUG ();
}
}
static void
store_uname (char *buffer, size_t length, unsigned long uid)
{
static int initialized;
static unsigned long lastuid;
static char lastuname[32];
if (!initialized || uid != lastuid)
{
#ifdef HAVE_W32_SYSTEM
mem2str (lastuname, uid? "user":"root", sizeof lastuname);
#else
struct passwd *pw = getpwuid (uid);
lastuid = uid;
initialized = 1;
if (pw)
mem2str (lastuname, pw->pw_name, sizeof lastuname);
else
{
log_info ("failed to get name for uid %lu\n", uid);
*lastuname = 0;
}
#endif
}
mem2str (buffer, lastuname, length);
}
static void
store_gname (char *buffer, size_t length, unsigned long gid)
{
static int initialized;
static unsigned long lastgid;
static char lastgname[32];
if (!initialized || gid != lastgid)
{
#ifdef HAVE_W32_SYSTEM
mem2str (lastgname, gid? "users":"root", sizeof lastgname);
#else
struct group *gr = getgrgid (gid);
lastgid = gid;
initialized = 1;
if (gr)
mem2str (lastgname, gr->gr_name, sizeof lastgname);
else
{
log_info ("failed to get name for gid %lu\n", gid);
*lastgname = 0;
}
#endif
}
mem2str (buffer, lastgname, length);
}
static gpg_error_t
build_header (void *record, tar_header_t hdr)
{
gpg_error_t err;
struct ustar_raw_header *raw = record;
size_t namelen, n;
unsigned long chksum;
unsigned char *p;
memset (record, 0, RECORDSIZE);
/* Store name and prefix. */
namelen = strlen (hdr->name);
if (namelen < sizeof raw->name)
memcpy (raw->name, hdr->name, namelen);
else
{
n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix;
for (n--; n ; n--)
if (hdr->name[n] == '/')
break;
if (namelen - n < sizeof raw->name)
{
/* Note that the N is < sizeof prefix and that the
delimiting slash is not stored. */
memcpy (raw->prefix, hdr->name, n);
memcpy (raw->name, hdr->name+n+1, namelen - n);
}
else
{
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("error storing file '%s': %s\n",
hdr->name, gpg_strerror (err));
return err;
}
}
store_xoctal (raw->mode, sizeof raw->mode, hdr->mode);
store_xoctal (raw->uid, sizeof raw->uid, hdr->uid);
store_xoctal (raw->gid, sizeof raw->gid, hdr->gid);
store_xoctal (raw->size, sizeof raw->size, hdr->size);
store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime);
switch (hdr->typeflag)
{
case TF_REGULAR: raw->typeflag[0] = '0'; break;
case TF_HARDLINK: raw->typeflag[0] = '1'; break;
case TF_SYMLINK: raw->typeflag[0] = '2'; break;
case TF_CHARDEV: raw->typeflag[0] = '3'; break;
case TF_BLOCKDEV: raw->typeflag[0] = '4'; break;
case TF_DIRECTORY: raw->typeflag[0] = '5'; break;
case TF_FIFO: raw->typeflag[0] = '6'; break;
default: return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
memcpy (raw->magic, "ustar", 6);
raw->version[0] = '0';
raw->version[1] = '0';
store_uname (raw->uname, sizeof raw->uname, hdr->uid);
store_gname (raw->gname, sizeof raw->gname, hdr->gid);
#ifndef HAVE_W32_SYSTEM
if (hdr->typeflag == TF_SYMLINK)
{
int nread;
nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1);
if (nread < 0)
{
err = gpg_error_from_syserror ();
log_error ("error reading symlink '%s': %s\n",
hdr->name, gpg_strerror (err));
return err;
}
raw->linkname[nread] = 0;
}
#endif /*HAVE_W32_SYSTEM*/
/* Compute the checksum. */
memset (raw->checksum, ' ', sizeof raw->checksum);
chksum = 0;
p = record;
for (n=0; n < RECORDSIZE; n++)
chksum += *p++;
store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum);
raw->checksum[7] = ' ';
return 0;
}
static gpg_error_t
write_file (estream_t stream, tar_header_t hdr)
{
gpg_error_t err;
char record[RECORDSIZE];
estream_t infp;
size_t nread, nbytes;
int any;
err = build_header (record, hdr);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
{
log_info ("skipping unsupported file '%s'\n", hdr->name);
err = 0;
}
return err;
}
if (hdr->typeflag == TF_REGULAR)
{
infp = es_fopen (hdr->name, "rb");
if (!infp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s - skipped\n",
hdr->name, gpg_strerror (err));
return err;
}
}
else
infp = NULL;
err = write_record (stream, record);
if (err)
goto leave;
if (hdr->typeflag == TF_REGULAR)
{
hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE;
any = 0;
while (hdr->nrecords--)
{
nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE);
if (!nbytes)
nbytes = RECORDSIZE;
nread = es_fread (record, 1, nbytes, infp);
if (nread != nbytes)
{
err = gpg_error_from_syserror ();
log_error ("error reading file '%s': %s%s\n",
hdr->name, gpg_strerror (err),
any? " (file shrunk?)":"");
goto leave;
}
any = 1;
err = write_record (stream, record);
if (err)
goto leave;
}
nread = es_fread (record, 1, 1, infp);
if (nread)
log_info ("note: file '%s' has grown\n", hdr->name);
}
leave:
if (err)
es_fclose (infp);
else if ((err = es_fclose (infp)))
log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err));
return err;
}
static gpg_error_t
write_eof_mark (estream_t stream)
{
gpg_error_t err;
char record[RECORDSIZE];
memset (record, 0, sizeof record);
err = write_record (stream, record);
if (!err)
err = write_record (stream, record);
return err;
}
/* Create a new tarball using the names in the array INPATTERN. If
INPATTERN is NULL take the pattern as null terminated strings from
stdin. */
gpg_error_t
gpgtar_create (char **inpattern, int encrypt, int sign)
{
gpg_error_t err = 0;
struct scanctrl_s scanctrl_buffer;
scanctrl_t scanctrl = &scanctrl_buffer;
tar_header_t hdr, *start_tail;
estream_t outstream = NULL;
estream_t cipher_stream = NULL;
int eof_seen = 0;
if (!inpattern)
es_set_binary (es_stdin);
memset (scanctrl, 0, sizeof *scanctrl);
scanctrl->flist_tail = &scanctrl->flist;
while (!eof_seen)
{
char *pat, *p;
int skip_this = 0;
if (inpattern)
{
const char *pattern = *inpattern;
if (!pattern)
break; /* End of array. */
inpattern++;
if (!*pattern)
continue;
pat = xtrystrdup (pattern);
}
else /* Read null delimited pattern from stdin. */
{
int c;
char namebuf[4096];
size_t n = 0;
for (;;)
{
if ((c = es_getc (es_stdin)) == EOF)
{
if (es_ferror (es_stdin))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n",
"[stdin]", strerror (errno));
goto leave;
}
/* Note: The Nul is a delimiter and not a terminator. */
c = 0;
eof_seen = 1;
}
if (n >= sizeof namebuf - 1)
{
if (!skip_this)
{
skip_this = 1;
log_error ("error reading '%s': %s\n",
"[stdin]", "filename too long");
}
}
else
namebuf[n++] = c;
if (!c)
{
namebuf[n] = 0;
break;
}
}
if (skip_this || n < 2)
continue;
pat = xtrystrdup (namebuf);
}
if (!pat)
{
err = gpg_error_from_syserror ();
log_error ("memory allocation problem: %s\n", gpg_strerror (err));
goto leave;
}
for (p=pat; *p; p++)
if (*p == '\\')
*p = '/';
if (opt.verbose > 1)
log_info ("scanning '%s'\n", pat);
start_tail = scanctrl->flist_tail;
if (skip_this || !pattern_valid_p (pat))
log_error ("skipping invalid name '%s'\n", pat);
else if (!add_entry (pat, NULL, scanctrl)
&& *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY))
scan_recursive (pat, scanctrl);
xfree (pat);
}
if (opt.outfile)
{
if (!strcmp (opt.outfile, "-"))
outstream = es_stdout;
else
outstream = es_fopen (opt.outfile, "wb");
if (!outstream)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else
{
outstream = es_stdout;
}
if (outstream == es_stdout)
es_set_binary (es_stdout);
if (encrypt || sign)
{
cipher_stream = outstream;
outstream = es_fopenmem (0, "rwb");
if (! outstream)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
for (hdr = scanctrl->flist; hdr; hdr = hdr->next)
{
err = write_file (outstream, hdr);
if (err)
goto leave;
}
err = write_eof_mark (outstream);
if (err)
goto leave;
if (encrypt || sign)
{
strlist_t arg;
ccparray_t ccp;
const char **argv;
err = es_fseek (outstream, 0, SEEK_SET);
if (err)
goto leave;
/* '--encrypt' may be combined with '--symmetric', but 'encrypt'
is set either way. Clear it if no recipients are specified.
XXX: Fix command handling. */
if (opt.symmetric && opt.recipients == NULL)
encrypt = 0;
ccparray_init (&ccp, 0);
if (encrypt)
ccparray_put (&ccp, "--encrypt");
if (sign)
ccparray_put (&ccp, "--sign");
if (opt.user)
{
ccparray_put (&ccp, "--local-user");
ccparray_put (&ccp, opt.user);
}
if (opt.symmetric)
ccparray_put (&ccp, "--symmetric");
for (arg = opt.recipients; arg; arg = arg->next)
{
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, arg->d);
}
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv,
outstream, NULL, cipher_stream, NULL, NULL);
xfree (argv);
if (err)
goto leave;
}
leave:
if (!err)
{
gpg_error_t first_err;
if (outstream != es_stdout)
first_err = es_fclose (outstream);
else
first_err = es_fflush (outstream);
outstream = NULL;
if (cipher_stream != es_stdout)
err = es_fclose (cipher_stream);
else
err = es_fflush (cipher_stream);
cipher_stream = NULL;
if (! err)
err = first_err;
}
if (err)
{
log_error ("creating tarball '%s' failed: %s\n",
opt.outfile ? opt.outfile : "-", gpg_strerror (err));
if (outstream && outstream != es_stdout)
es_fclose (outstream);
if (cipher_stream && cipher_stream != es_stdout)
es_fclose (cipher_stream);
if (opt.outfile)
gnupg_remove (opt.outfile);
}
scanctrl->flist_tail = NULL;
while ( (hdr = scanctrl->flist) )
{
scanctrl->flist = hdr->next;
xfree (hdr);
}
return err;
}
diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c
index f9a50e747..b0e17cb10 100644
--- a/tools/gpgtar-extract.c
+++ b/tools/gpgtar-extract.c
@@ -1,401 +1,401 @@
/* gpgtar-extract.c - Extract from a TAR archive
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "../common/exectool.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
#include "gpgtar.h"
static gpg_error_t
extract_regular (estream_t stream, const char *dirname,
tar_header_t hdr)
{
gpg_error_t err;
char record[RECORDSIZE];
size_t n, nbytes, nwritten;
char *fname;
estream_t outfp = NULL;
fname = strconcat (dirname, "/", hdr->name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("error creating filename: %s\n", gpg_strerror (err));
goto leave;
}
else
err = 0;
if (opt.dry_run)
outfp = es_fopenmem (0, "wb");
else
outfp = es_fopen (fname, "wb");
if (!outfp)
{
err = gpg_error_from_syserror ();
log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
for (n=0; n < hdr->nrecords;)
{
err = read_record (stream, record);
if (err)
goto leave;
n++;
if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
nbytes = RECORDSIZE;
else
nbytes = (hdr->size % RECORDSIZE);
nwritten = es_fwrite (record, 1, nbytes, outfp);
if (nwritten != nbytes)
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
/* Fixme: Set permissions etc. */
leave:
if (!err && opt.verbose)
log_info ("extracted '%s'\n", fname);
es_fclose (outfp);
if (err && fname && outfp)
{
if (gnupg_remove (fname))
log_error ("error removing incomplete file '%s': %s\n",
fname, gpg_strerror (gpg_error_from_syserror ()));
}
xfree (fname);
return err;
}
static gpg_error_t
extract_directory (const char *dirname, tar_header_t hdr)
{
gpg_error_t err;
char *fname;
size_t prefixlen;
prefixlen = strlen (dirname) + 1;
fname = strconcat (dirname, "/", hdr->name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("error creating filename: %s\n", gpg_strerror (err));
goto leave;
}
else
err = 0;
if (fname[strlen (fname)-1] == '/')
fname[strlen (fname)-1] = 0;
if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------"))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EEXIST)
{
/* Ignore existing directories while extracting. */
err = 0;
}
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* Try to create the directory with parents but keep the
original error code in case of a failure. */
char *p;
int rc = 0;
for (p = fname+prefixlen; (p = strchr (p, '/')); p++)
{
*p = 0;
rc = gnupg_mkdir (fname, "-rwx------");
*p = '/';
if (rc)
break;
}
if (!rc && !gnupg_mkdir (fname, "-rwx------"))
err = 0;
}
if (err)
log_error ("error creating directory '%s': %s\n",
fname, gpg_strerror (err));
}
leave:
if (!err && opt.verbose)
log_info ("created '%s/'\n", fname);
xfree (fname);
return err;
}
static gpg_error_t
extract (estream_t stream, const char *dirname, tar_header_t hdr)
{
gpg_error_t err;
size_t n;
n = strlen (hdr->name);
#ifdef HAVE_DOSISH_SYSTEM
if (strchr (hdr->name, '\\'))
{
log_error ("filename '%s' contains a backslash - "
"can't extract on this system\n", hdr->name);
return gpg_error (GPG_ERR_INV_NAME);
}
#endif /*HAVE_DOSISH_SYSTEM*/
if (!n
|| strstr (hdr->name, "//")
|| strstr (hdr->name, "/../")
|| !strncmp (hdr->name, "../", 3)
|| (n >= 3 && !strcmp (hdr->name+n-3, "/.." )))
{
log_error ("filename '%s' as suspicious parts - not extracting\n",
hdr->name);
return gpg_error (GPG_ERR_INV_NAME);
}
if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
err = extract_regular (stream, dirname, hdr);
else if (hdr->typeflag == TF_DIRECTORY)
err = extract_directory (dirname, hdr);
else
{
char record[RECORDSIZE];
log_info ("unsupported file type %d for '%s' - skipped\n",
(int)hdr->typeflag, hdr->name);
for (err = 0, n=0; !err && n < hdr->nrecords; n++)
err = read_record (stream, record);
}
return err;
}
/* Create a new directory to be used for extracting the tarball.
Returns the name of the directory which must be freed by the
caller. In case of an error a diagnostic is printed and NULL
returned. */
static char *
create_directory (const char *dirprefix)
{
gpg_error_t err = 0;
char *prefix_buffer = NULL;
char *dirname = NULL;
size_t n;
int idx;
/* Remove common suffixes. */
n = strlen (dirprefix);
if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG)
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e")))
{
prefix_buffer = xtrystrdup (dirprefix);
if (!prefix_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
prefix_buffer[n-4] = 0;
dirprefix = prefix_buffer;
}
for (idx=1; idx < 5000; idx++)
{
xfree (dirname);
dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!gnupg_mkdir (dirname, "-rwx------"))
goto leave; /* Ready. */
if (errno != EEXIST && errno != ENOTDIR)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = gpg_error (GPG_ERR_LIMIT_REACHED);
leave:
if (err)
{
log_error ("error creating an extract directory: %s\n",
gpg_strerror (err));
xfree (dirname);
dirname = NULL;
}
xfree (prefix_buffer);
return dirname;
}
gpg_error_t
gpgtar_extract (const char *filename, int decrypt)
{
gpg_error_t err;
estream_t stream;
estream_t cipher_stream = NULL;
tar_header_t header = NULL;
const char *dirprefix = NULL;
char *dirname = NULL;
if (filename)
{
if (!strcmp (filename, "-"))
stream = es_stdin;
else
stream = es_fopen (filename, "rb");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
return err;
}
}
else
stream = es_stdin;
if (stream == es_stdin)
es_set_binary (es_stdin);
if (decrypt)
{
strlist_t arg;
ccparray_t ccp;
const char **argv;
cipher_stream = stream;
stream = es_fopenmem (0, "rwb");
if (! stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--decrypt");
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv,
cipher_stream, NULL, stream, NULL, NULL);
xfree (argv);
if (err)
goto leave;
err = es_fseek (stream, 0, SEEK_SET);
if (err)
goto leave;
}
if (opt.directory)
dirname = xtrystrdup (opt.directory);
else
{
if (filename)
{
dirprefix = strrchr (filename, '/');
if (dirprefix)
dirprefix++;
else
dirprefix = filename;
}
else if (opt.filename)
{
dirprefix = strrchr (opt.filename, '/');
if (dirprefix)
dirprefix++;
else
dirprefix = opt.filename;
}
if (!dirprefix || !*dirprefix)
dirprefix = "GPGARCH";
dirname = create_directory (dirprefix);
if (!dirname)
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
if (opt.verbose)
log_info ("extracting to '%s/'\n", dirname);
for (;;)
{
err = gpgtar_read_header (stream, &header);
if (err || header == NULL)
goto leave;
err = extract (stream, dirname, header);
if (err)
goto leave;
xfree (header);
header = NULL;
}
leave:
xfree (header);
xfree (dirname);
if (stream != es_stdin)
es_fclose (stream);
if (stream != cipher_stream)
es_fclose (cipher_stream);
return err;
}
diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c
index 8286d0861..0e10be8a0 100644
--- a/tools/gpgtar-list.c
+++ b/tools/gpgtar-list.c
@@ -1,375 +1,375 @@
/* gpgtar-list.c - List a TAR archive
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
-#include "i18n.h"
+#include "../common/i18n.h"
#include "gpgtar.h"
#include "../common/exectool.h"
#include "../common/ccparray.h"
static unsigned long long
parse_xoctal (const void *data, size_t length, const char *filename)
{
const unsigned char *p = data;
unsigned long long value;
if (!length)
value = 0;
else if ( (*p & 0x80))
{
/* Binary format. */
value = (*p++ & 0x7f);
while (--length)
{
value <<= 8;
value |= *p++;
}
}
else
{
/* Octal format */
value = 0;
/* Skip leading spaces and zeroes. */
for (; length && (*p == ' ' || *p == '0'); length--, p++)
;
for (; length && *p; length--, p++)
{
if (*p >= '0' && *p <= '7')
{
value <<= 3;
value += (*p - '0');
}
else
{
log_error ("%s: invalid octal number encountered - assuming 0\n",
filename);
value = 0;
break;
}
}
}
return value;
}
static tar_header_t
parse_header (const void *record, const char *filename)
{
const struct ustar_raw_header *raw = record;
size_t n, namelen, prefixlen;
tar_header_t header;
int use_prefix;
use_prefix = (!memcmp (raw->magic, "ustar", 5)
&& (raw->magic[5] == ' ' || !raw->magic[5]));
for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
;
if (namelen == sizeof raw->name)
log_info ("%s: warning: name not terminated by a nul byte\n", filename);
for (n=namelen+1; n < sizeof raw->name; n++)
if (raw->name[n])
{
log_info ("%s: warning: garbage after name\n", filename);
break;
}
if (use_prefix && raw->prefix[0])
{
for (prefixlen=0; (prefixlen < sizeof raw->prefix
&& raw->prefix[prefixlen]); prefixlen++)
;
if (prefixlen == sizeof raw->prefix)
log_info ("%s: warning: prefix not terminated by a nul byte\n",
filename);
for (n=prefixlen+1; n < sizeof raw->prefix; n++)
if (raw->prefix[n])
{
log_info ("%s: warning: garbage after prefix\n", filename);
break;
}
}
else
prefixlen = 0;
header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
if (!header)
{
log_error ("%s: error allocating header: %s\n",
filename, gpg_strerror (gpg_error_from_syserror ()));
return NULL;
}
if (prefixlen)
{
n = prefixlen;
memcpy (header->name, raw->prefix, n);
if (raw->prefix[n-1] != '/')
header->name[n++] = '/';
}
else
n = 0;
memcpy (header->name+n, raw->name, namelen);
header->name[n+namelen] = 0;
header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename);
header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename);
header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename);
header->size = parse_xoctal (raw->size, sizeof raw->size, filename);
header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
/* checksum = */
switch (raw->typeflag[0])
{
case '0': header->typeflag = TF_REGULAR; break;
case '1': header->typeflag = TF_HARDLINK; break;
case '2': header->typeflag = TF_SYMLINK; break;
case '3': header->typeflag = TF_CHARDEV; break;
case '4': header->typeflag = TF_BLOCKDEV; break;
case '5': header->typeflag = TF_DIRECTORY; break;
case '6': header->typeflag = TF_FIFO; break;
case '7': header->typeflag = TF_RESERVED; break;
default: header->typeflag = TF_UNKNOWN; break;
}
/* Compute the number of data records following this header. */
if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN)
header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
else
header->nrecords = 0;
return header;
}
/* Read the next block, assming it is a tar header. Returns a header
object on success in R_HEADER, or an error. If the stream is
consumed, R_HEADER is set to NULL. In case of an error an error
message has been printed. */
static gpg_error_t
read_header (estream_t stream, tar_header_t *r_header)
{
gpg_error_t err;
char record[RECORDSIZE];
int i;
err = read_record (stream, record);
if (err)
return err;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i == RECORDSIZE)
{
/* All zero header - check whether it is the first part of an
end of archive mark. */
err = read_record (stream, record);
if (err)
return err;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i != RECORDSIZE)
log_info ("%s: warning: skipping empty header\n",
es_fname_get (stream));
else
{
/* End of archive - FIXME: we might want to check for garbage. */
*r_header = NULL;
return 0;
}
}
*r_header = parse_header (record, es_fname_get (stream));
return *r_header ? 0 : gpg_error_from_syserror ();
}
/* Skip the data records according to HEADER. Prints an error message
on error and return -1. */
static int
skip_data (estream_t stream, tar_header_t header)
{
char record[RECORDSIZE];
unsigned long long n;
for (n=0; n < header->nrecords; n++)
{
if (read_record (stream, record))
return -1;
}
return 0;
}
static void
print_header (tar_header_t header, estream_t out)
{
unsigned long mask;
char modestr[10+1];
int i;
*modestr = '?';
switch (header->typeflag)
{
case TF_REGULAR: *modestr = '-'; break;
case TF_HARDLINK: *modestr = 'h'; break;
case TF_SYMLINK: *modestr = 'l'; break;
case TF_CHARDEV: *modestr = 'c'; break;
case TF_BLOCKDEV: *modestr = 'b'; break;
case TF_DIRECTORY:*modestr = 'd'; break;
case TF_FIFO: *modestr = 'f'; break;
case TF_RESERVED: *modestr = '='; break;
case TF_UNKNOWN: break;
case TF_NOTSUP: break;
}
for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
if ((header->typeflag & 04000))
modestr[3] = modestr[3] == 'x'? 's':'S';
if ((header->typeflag & 02000))
modestr[6] = modestr[6] == 'x'? 's':'S';
if ((header->typeflag & 01000))
modestr[9] = modestr[9] == 'x'? 't':'T';
modestr[10] = 0;
es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n",
modestr, header->nlink, header->uid, header->gid, header->size,
isotimestamp (header->mtime), header->name);
}
/* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
from stdin. */
gpg_error_t
gpgtar_list (const char *filename, int decrypt)
{
gpg_error_t err;
estream_t stream;
estream_t cipher_stream = NULL;
tar_header_t header = NULL;
if (filename)
{
if (!strcmp (filename, "-"))
stream = es_stdin;
else
stream = es_fopen (filename, "rb");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
return err;
}
}
else
stream = es_stdin;
if (stream == es_stdin)
es_set_binary (es_stdin);
if (decrypt)
{
strlist_t arg;
ccparray_t ccp;
const char **argv;
cipher_stream = stream;
stream = es_fopenmem (0, "rwb");
if (! stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--decrypt");
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv,
cipher_stream, NULL, stream, NULL, NULL);
xfree (argv);
if (err)
goto leave;
err = es_fseek (stream, 0, SEEK_SET);
if (err)
goto leave;
}
for (;;)
{
err = read_header (stream, &header);
if (err || header == NULL)
goto leave;
print_header (header, es_stdout);
if (skip_data (stream, header))
goto leave;
xfree (header);
header = NULL;
}
leave:
xfree (header);
if (stream != es_stdin)
es_fclose (stream);
if (stream != cipher_stream)
es_fclose (cipher_stream);
return err;
}
gpg_error_t
gpgtar_read_header (estream_t stream, tar_header_t *r_header)
{
return read_header (stream, r_header);
}
void
gpgtar_print_header (tar_header_t header, estream_t out)
{
if (header && out)
print_header (header, out);
}
diff --git a/tools/gpgtar.c b/tools/gpgtar.c
index 3dff176e1..2757ab011 100644
--- a/tools/gpgtar.c
+++ b/tools/gpgtar.c
@@ -1,597 +1,597 @@
/* gpgtar.c - A simple TAR implementation mainly useful for Windows.
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* GnuPG comes with a shell script gpg-zip which creates archive files
in the same format as PGP Zip, which is actually a USTAR format.
That is fine and works nicely on all Unices but for Windows we
don't have a compatible shell and the supply of tar programs is
limited. Given that we need just a few tar option and it is an
open question how many Unix concepts are to be mapped to Windows,
we might as well write our own little tar customized for use with
gpg. So here we go. */
#include <config.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
-#include "util.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
#include "../common/openpgpdefs.h"
#include "../common/init.h"
#include "../common/strlist.h"
#include "gpgtar.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
aCreate = 600,
aExtract,
aEncrypt = 'e',
aDecrypt = 'd',
aSign = 's',
aList = 't',
oSymmetric = 'c',
oRecipient = 'r',
oUser = 'u',
oOutput = 'o',
oDirectory = 'C',
oQuiet = 'q',
oVerbose = 'v',
oFilesFrom = 'T',
oNoVerbose = 500,
aSignEncrypt,
oGpgProgram,
oSkipCrypto,
oOpenPGP,
oCMS,
oSetFilename,
oNull,
/* Compatibility with gpg-zip. */
oGpgArgs,
oTarArgs,
/* Debugging. */
oDryRun,
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aCreate, "create", N_("create an archive")),
ARGPARSE_c (aExtract, "extract", N_("extract an archive")),
ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")),
ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")),
ARGPARSE_c (aSign, "sign", N_("create a signed archive")),
ARGPARSE_c (aList, "list-archive", N_("list an archive")),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oOpenPGP, "openpgp", "@"),
ARGPARSE_s_n (oCMS, "cms", "@"),
ARGPARSE_group (302, N_("@\nTar options:\n ")),
ARGPARSE_s_s (oDirectory, "directory",
N_("|DIRECTORY|extract files into DIRECTORY")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"),
ARGPARSE_s_s (oTarArgs, "tar-args", "@"),
ARGPARSE_end ()
};
/* The list of commands and options for tar that we understand. */
static ARGPARSE_OPTS tar_opts[] = {
ARGPARSE_s_s (oDirectory, "directory",
N_("|DIRECTORY|extract files into DIRECTORY")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
ARGPARSE_end ()
};
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPGTAR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpgtar [options] [files] [directories] (-h for help)");
break;
case 41:
p = _("Syntax: gpgtar [options] [files] [directories]\n"
"Encrypt or sign files into an archive\n");
break;
default: p = NULL; break;
}
return p;
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else if (cmd == aSign && new_cmd == aEncrypt)
cmd = aSignEncrypt;
else if (cmd == aEncrypt && new_cmd == aSign)
cmd = aSignEncrypt;
else
{
log_error (_("conflicting commands\n"));
exit (2);
}
*ret_cmd = cmd;
}
/* Shell-like argument splitting.
For compatibility with gpg-zip we accept arguments for GnuPG and
tar given as a string argument to '--gpg-args' and '--tar-args'.
gpg-zip was implemented as a Bourne Shell script, and therefore, we
need to split the string the same way the shell would. */
static int
shell_parse_stringlist (const char *str, strlist_t *r_list)
{
strlist_t list = NULL;
const char *s = str;
char quoted = 0;
char arg[1024];
char *p = arg;
#define addchar(c) \
do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0)
#define addargument() \
do { \
if (p > arg) \
{ \
*p = 0; \
append_to_strlist (&list, arg); \
p = arg; \
} \
} while (0)
#define unquoted 0
#define singlequote '\''
#define doublequote '"'
for (; *s; s++)
{
switch (quoted)
{
case unquoted:
if (isspace (*s))
addargument ();
else if (*s == singlequote || *s == doublequote)
quoted = *s;
else
addchar (*s);
break;
case singlequote:
if (*s == singlequote)
quoted = unquoted;
else
addchar (*s);
break;
case doublequote:
assert (s > str || !"cannot be quoted at first char");
if (*s == doublequote && *(s - 1) != '\\')
quoted = unquoted;
else
addchar (*s);
break;
default:
assert (! "reached");
}
}
/* Append the last argument. */
addargument ();
#undef doublequote
#undef singlequote
#undef unquoted
#undef addargument
#undef addchar
*r_list = list;
return 0;
}
/* Like shell_parse_stringlist, but returns an argv vector
instead of a strlist. */
static int
shell_parse_argv (const char *s, int *r_argc, char ***r_argv)
{
int i;
strlist_t list;
if (shell_parse_stringlist (s, &list))
return 1;
*r_argc = strlist_length (list);
*r_argv = xtrycalloc (*r_argc, sizeof **r_argv);
if (*r_argv == NULL)
return 1;
for (i = 0; list; i++)
{
gpgrt_annotate_leaked_object (list);
(*r_argv)[i] = list->d;
list = list->next;
}
gpgrt_annotate_leaked_object (*r_argv);
return 0;
}
/* Global flags. */
enum cmd_and_opt_values cmd = 0;
int skip_crypto = 0;
const char *files_from = NULL;
int null_names = 0;
/* Command line parsing. */
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
int no_more_options = 0;
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oOutput: opt.outfile = pargs->r.ret_str; break;
case oDirectory: opt.directory = pargs->r.ret_str; break;
case oSetFilename: opt.filename = pargs->r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oFilesFrom: files_from = pargs->r.ret_str; break;
case oNull: null_names = 1; break;
case aList:
case aDecrypt:
case aEncrypt:
case aSign:
set_cmd (&cmd, pargs->r_opt);
break;
case aCreate:
set_cmd (&cmd, aEncrypt);
skip_crypto = 1;
break;
case aExtract:
set_cmd (&cmd, aDecrypt);
skip_crypto = 1;
break;
case oRecipient:
add_to_strlist (&opt.recipients, pargs->r.ret_str);
break;
case oUser:
opt.user = pargs->r.ret_str;
break;
case oSymmetric:
set_cmd (&cmd, aEncrypt);
opt.symmetric = 1;
break;
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
case oSkipCrypto:
skip_crypto = 1;
break;
case oOpenPGP: /* Dummy option for now. */ break;
case oCMS: /* Dummy option for now. */ break;
case oGpgArgs:;
{
strlist_t list;
if (shell_parse_stringlist (pargs->r.ret_str, &list))
log_error ("failed to parse gpg arguments '%s'\n",
pargs->r.ret_str);
else
{
if (opt.gpg_arguments)
strlist_last (opt.gpg_arguments)->next = list;
else
opt.gpg_arguments = list;
}
}
break;
case oTarArgs:;
{
int tar_argc;
char **tar_argv;
if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv))
log_error ("failed to parse tar arguments '%s'\n",
pargs->r.ret_str);
else
{
ARGPARSE_ARGS tar_args;
tar_args.argc = &tar_argc;
tar_args.argv = &tar_argv;
tar_args.flags = ARGPARSE_FLAG_ARG0;
parse_arguments (&tar_args, tar_opts);
if (tar_args.err)
log_error ("unsupported tar arguments '%s'\n",
pargs->r.ret_str);
pargs->err = tar_args.err;
}
}
break;
case oDryRun:
opt.dry_run = 1;
break;
default: pargs->err = 2; break;
}
}
}
/* gpgtar main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
const char *fname;
ARGPARSE_ARGS pargs;
assert (sizeof (struct ustar_raw_header) == 512);
gnupg_reopen_std (GPGTAR_NAME);
set_strusage (my_strusage);
log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
if ((files_from && !null_names) || (!files_from && null_names))
log_error ("--files-from and --null may only be used in conjunction\n");
if (files_from && strcmp (files_from, "-"))
log_error ("--files-from only supports argument \"-\"\n");
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]);
}
if (! opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (opt.verbose > 1)
opt.debug_level = 1024;
switch (cmd)
{
case aList:
if (argc > 1)
usage (1);
fname = argc ? *argv : NULL;
if (opt.filename)
log_info ("note: ignoring option --set-filename\n");
if (files_from)
log_info ("note: ignoring option --files-from\n");
err = gpgtar_list (fname, !skip_crypto);
if (err && log_get_errorcount (0) == 0)
log_error ("listing archive failed: %s\n", gpg_strerror (err));
break;
case aEncrypt:
case aSign:
case aSignEncrypt:
if ((!argc && !null_names)
|| (argc && null_names))
usage (1);
if (opt.filename)
log_info ("note: ignoring option --set-filename\n");
err = gpgtar_create (null_names? NULL :argv,
!skip_crypto
&& (cmd == aEncrypt || cmd == aSignEncrypt),
cmd == aSign || cmd == aSignEncrypt);
if (err && log_get_errorcount (0) == 0)
log_error ("creating archive failed: %s\n", gpg_strerror (err));
break;
case aDecrypt:
if (argc != 1)
usage (1);
if (opt.outfile)
log_info ("note: ignoring option --output\n");
if (files_from)
log_info ("note: ignoring option --files-from\n");
fname = argc ? *argv : NULL;
err = gpgtar_extract (fname, !skip_crypto);
if (err && log_get_errorcount (0) == 0)
log_error ("extracting archive failed: %s\n", gpg_strerror (err));
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
break;
}
return log_get_errorcount (0)? 1:0;
}
/* Read the next record from STREAM. RECORD is a buffer provided by
the caller and must be at leadt of size RECORDSIZE. The function
return 0 on success and error code on failure; a diagnostic
printed as well. Note that there is no need for an EOF indicator
because a tarball has an explicit EOF record. */
gpg_error_t
read_record (estream_t stream, void *record)
{
gpg_error_t err;
size_t nread;
nread = es_fread (record, 1, RECORDSIZE, stream);
if (nread != RECORDSIZE)
{
err = gpg_error_from_syserror ();
if (es_ferror (stream))
log_error ("error reading '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
else
log_error ("error reading '%s': premature EOF "
"(size of last record: %zu)\n",
es_fname_get (stream), nread);
}
else
err = 0;
return err;
}
/* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the
name of the file used for diagnostics. */
gpg_error_t
write_record (estream_t stream, const void *record)
{
gpg_error_t err;
size_t nwritten;
nwritten = es_fwrite (record, 1, RECORDSIZE, stream);
if (nwritten != RECORDSIZE)
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
}
else
err = 0;
return err;
}
/* Return true if FP is an unarmored OpenPGP message. Note that this
function reads a few bytes from FP but pushes them back. */
#if 0
static int
openpgp_message_p (estream_t fp)
{
int ctb;
ctb = es_getc (fp);
if (ctb != EOF)
{
if (es_ungetc (ctb, fp))
log_fatal ("error ungetting first byte: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
if ((ctb & 0x80))
{
switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf))
{
case PKT_MARKER:
case PKT_SYMKEY_ENC:
case PKT_ONEPASS_SIG:
case PKT_PUBKEY_ENC:
case PKT_SIGNATURE:
case PKT_COMMENT:
case PKT_OLD_COMMENT:
case PKT_PLAINTEXT:
case PKT_COMPRESSED:
case PKT_ENCRYPTED:
return 1; /* Yes, this seems to be an OpenPGP message. */
default:
break;
}
}
}
return 0;
}
#endif
diff --git a/tools/mime-maker.c b/tools/mime-maker.c
index 2e3206964..0332f3169 100644
--- a/tools/mime-maker.c
+++ b/tools/mime-maker.c
@@ -1,804 +1,804 @@
/* mime-maker.c - Create MIME structures
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
-#include "zb32.h"
+#include "../common/util.h"
+#include "../common/zb32.h"
#include "mime-maker.h"
/* All valid characters in a header name. */
#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"-01234567890")
/* An object to store an header. Also used for a list of headers. */
struct header_s
{
struct header_s *next;
char *value; /* Malloced value. */
char name[1]; /* Name. */
};
typedef struct header_s *header_t;
/* An object to store a MIME part. A part is the header plus the
* content (body). */
struct part_s
{
struct part_s *next; /* Next part in the current container. */
struct part_s *child; /* Child container. */
char *boundary; /* Malloced boundary string. */
header_t headers; /* List of headers. */
header_t *headers_tail;/* Address of last header in chain. */
size_t bodylen; /* Length of BODY. */
char *body; /* Malloced buffer with the body. This is the
* non-encoded value. */
unsigned int partid; /* The part ID. */
};
typedef struct part_s *part_t;
/* Definition of the mime parser object. */
struct mime_maker_context_s
{
void *cookie; /* Cookie passed to all callbacks. */
unsigned int verbose:1; /* Enable verbose mode. */
unsigned int debug:1; /* Enable debug mode. */
part_t mail; /* The MIME tree. */
part_t current_part;
unsigned int partid_counter; /* Counter assign part ids. */
int boundary_counter; /* Used to create easy to read boundaries. */
char *boundary_suffix; /* Random string used in the boundaries. */
struct b64state *b64state; /* NULL or malloced Base64 decoder state. */
/* Helper to convey the output stream to recursive functions. */
estream_t outfp;
};
/* Create a new mime make object. COOKIE is a values woich will be
* used as first argument for all callbacks registered with this
* object. */
gpg_error_t
mime_maker_new (mime_maker_t *r_maker, void *cookie)
{
mime_maker_t ctx;
*r_maker = NULL;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
ctx->cookie = cookie;
*r_maker = ctx;
return 0;
}
static void
release_parts (part_t part)
{
while (part)
{
part_t partnext = part->next;
while (part->headers)
{
header_t hdrnext = part->headers->next;
xfree (part->headers);
part->headers = hdrnext;
}
release_parts (part->child);
xfree (part->boundary);
xfree (part->body);
xfree (part);
part = partnext;
}
}
/* Release a mime maker object. */
void
mime_maker_release (mime_maker_t ctx)
{
if (!ctx)
return;
release_parts (ctx->mail);
xfree (ctx->boundary_suffix);
xfree (ctx);
}
/* Set verbose and debug mode. */
void
mime_maker_set_verbose (mime_maker_t ctx, int level)
{
if (!level)
{
ctx->verbose = 0;
ctx->debug = 0;
}
else
{
ctx->verbose = 1;
if (level > 10)
ctx->debug = 1;
}
}
static void
dump_parts (part_t part, int level)
{
header_t hdr;
for (; part; part = part->next)
{
log_debug ("%*s[part %u]\n", level*2, "", part->partid);
for (hdr = part->headers; hdr; hdr = hdr->next)
{
log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
}
if (part->body)
log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
if (part->child)
{
log_debug ("%*s[container]\n", level*2, "");
dump_parts (part->child, level+1);
}
}
}
/* Dump the mime tree for debugging. */
void
mime_maker_dump_tree (mime_maker_t ctx)
{
dump_parts (ctx->mail, 0);
}
/* Find the parent node for NEEDLE starting at ROOT. */
static part_t
find_parent (part_t root, part_t needle)
{
part_t node, n;
for (node = root->child; node; node = node->next)
{
if (node == needle)
return root;
if ((n = find_parent (node, needle)))
return n;
}
return NULL;
}
/* Find the part node from the PARTID. */
static part_t
find_part (part_t root, unsigned int partid)
{
part_t node, n;
for (node = root->child; node; node = node->next)
{
if (node->partid == partid)
return root;
if ((n = find_part (node, partid)))
return n;
}
return NULL;
}
/* Create a boundary string. Outr codes is aware of the general
* structure of that string (gebins with "=-=") so that
* it can protect against accidentally-used boundaries within the
* content. */
static char *
generate_boundary (mime_maker_t ctx)
{
if (!ctx->boundary_suffix)
{
char buffer[12];
gcry_create_nonce (buffer, sizeof buffer);
ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer);
if (!ctx->boundary_suffix)
return NULL;
}
ctx->boundary_counter++;
return es_bsprintf ("=-=%02d-%s=-=",
ctx->boundary_counter, ctx->boundary_suffix);
}
/* Ensure that the context has a MAIL and CURRENT_PART object and
* return the parent object if available */
static gpg_error_t
ensure_part (mime_maker_t ctx, part_t *r_parent)
{
if (!ctx->mail)
{
ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
if (!ctx->mail)
{
if (r_parent)
*r_parent = NULL;
return gpg_error_from_syserror ();
}
log_assert (!ctx->current_part);
ctx->current_part = ctx->mail;
ctx->current_part->headers_tail = &ctx->current_part->headers;
}
log_assert (ctx->current_part);
if (r_parent)
*r_parent = find_parent (ctx->mail, ctx->current_part);
return 0;
}
/* Transform a header name into a standard capitalized format.
* "Content-Type". Conversion stops at the colon. */
static void
capitalize_header_name (char *name)
{
unsigned char *p = name;
int first = 1;
/* Special cases first. */
if (!ascii_strcasecmp (name, "MIME-Version"))
{
strcpy (name, "MIME-Version");
return;
}
/* Regular cases. */
for (; *p && *p != ':'; p++)
{
if (*p == '-')
first = 1;
else if (first)
{
if (*p >= 'a' && *p <= 'z')
*p = *p - 'a' + 'A';
first = 0;
}
else if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
}
}
/* Check whether a header with NAME has already been set into PART.
* NAME must be in canonical capitalized format. Return true or
* false. */
static int
have_header (part_t part, const char *name)
{
header_t hdr;
for (hdr = part->headers; hdr; hdr = hdr->next)
if (!strcmp (hdr->name, name))
return 1;
return 0;
}
/* Helper to add a header to a part. */
static gpg_error_t
add_header (part_t part, const char *name, const char *value)
{
gpg_error_t err;
header_t hdr;
size_t namelen;
const char *s;
char *p;
if (!value)
{
s = strchr (name, '=');
if (!s)
return gpg_error (GPG_ERR_INV_ARG);
namelen = s - name;
value = s+1;
}
else
namelen = strlen (name);
hdr = xtrymalloc (sizeof *hdr + namelen);
if (!hdr)
return gpg_error_from_syserror ();
hdr->next = NULL;
memcpy (hdr->name, name, namelen);
hdr->name[namelen] = 0;
/* Check that the header name is valid. We allow all lower and
* uppercase letters and, except for the first character, digits and
* the dash. */
if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen
|| strchr ("-0123456789", *hdr->name))
{
xfree (hdr);
return gpg_error (GPG_ERR_INV_NAME);
}
capitalize_header_name (hdr->name);
hdr->value = xtrystrdup (value);
if (!hdr->value)
{
err = gpg_error_from_syserror ();
xfree (hdr);
return err;
}
for (p = hdr->value + strlen (hdr->value) - 1;
(p >= hdr->value
&& (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'));
p--)
*p = 0;
if (!(p >= hdr->value))
{
xfree (hdr->value);
xfree (hdr);
return gpg_error (GPG_ERR_INV_VALUE); /* Only spaces. */
}
if (part)
{
*part->headers_tail = hdr;
part->headers_tail = &hdr->next;
}
else
xfree (hdr);
return 0;
}
/* Add a header with NAME and VALUE to the current mail. A LF in the
* VALUE will be handled automagically. If NULL is used for VALUE it
* is expected that the NAME has the format "NAME=VALUE" and VALUE is
* taken from there.
*
* If no container has been added, the header will be used for the
* regular mail headers and not for a MIME part. If the current part
* is in a container and a body has been added, we append a new part
* to the current container. Thus for a non-MIME mail the caller
* needs to call this function followed by a call to add a body. When
* adding a Content-Type the boundary parameter must not be included.
*/
gpg_error_t
mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
{
gpg_error_t err;
part_t part, parent;
/* Hack to use this function for a syntax check of NAME and VALUE. */
if (!ctx)
return add_header (NULL, name, value);
err = ensure_part (ctx, &parent);
if (err)
return err;
part = ctx->current_part;
if ((part->body || part->child) && !parent)
{
/* We already have a body but no parent. Adding another part is
* thus not possible. */
return gpg_error (GPG_ERR_CONFLICT);
}
if (part->body || part->child)
{
/* We already have a body and there is a parent. We now append
* a new part to the current container. */
part = xtrycalloc (1, sizeof *part);
if (!part)
return gpg_error_from_syserror ();
part->partid = ++ctx->partid_counter;
part->headers_tail = &part->headers;
log_assert (!ctx->current_part->next);
ctx->current_part->next = part;
ctx->current_part = part;
}
/* If no NAME and no VALUE has been given we do not add a header.
* This can be used to create a new part without any header. */
if (!name && !value)
return 0;
/* If we add Content-Type, make sure that we have a MIME-version
* header first; this simply looks better. */
if (!ascii_strcasecmp (name, "Content-Type")
&& !have_header (ctx->mail, "MIME-Version"))
{
err = add_header (ctx->mail, "MIME-Version", "1.0");
if (err)
return err;
}
return add_header (part, name, value);
}
/* Helper for mime_maker_add_{body,stream}. */
static gpg_error_t
add_body (mime_maker_t ctx, const void *data, size_t datalen)
{
gpg_error_t err;
part_t part, parent;
err = ensure_part (ctx, &parent);
if (err)
return err;
part = ctx->current_part;
if (part->body)
return gpg_error (GPG_ERR_CONFLICT);
part->body = xtrymalloc (datalen? datalen : 1);
if (!part->body)
return gpg_error_from_syserror ();
part->bodylen = datalen;
if (data)
memcpy (part->body, data, datalen);
return 0;
}
/* Add STRING as body to the mail or the current MIME container. A
* second call to this function is not allowed.
*
* FIXME: We may want to have an append_body to add more data to a body.
*/
gpg_error_t
mime_maker_add_body (mime_maker_t ctx, const char *string)
{
return add_body (ctx, string, strlen (string));
}
/* This is the same as mime_maker_add_body but takes a stream as
* argument. As of now the stream is copied to the MIME object but
* eventually we may delay that and read the stream only at the time
* it is needed. Note that the address of the stream object must be
* passed and that the ownership of the stream is transferred to this
* MIME object. To indicate the latter the function will store NULL
* at the ADDR_STREAM so that a caller can't use that object anymore
* except for es_fclose which accepts a NULL pointer. */
gpg_error_t
mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
{
void *data;
size_t datalen;
es_rewind (*stream_addr);
if (es_fclose_snatch (*stream_addr, &data, &datalen))
return gpg_error_from_syserror ();
*stream_addr = NULL;
return add_body (ctx, data, datalen);
}
/* Add a new MIME container. A container can be used instead of a
* body. */
gpg_error_t
mime_maker_add_container (mime_maker_t ctx)
{
gpg_error_t err;
part_t part;
err = ensure_part (ctx, NULL);
if (err)
return err;
part = ctx->current_part;
if (part->body)
return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
if (part->child || part->boundary)
return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
/* Create a child node. */
part->child = xtrycalloc (1, sizeof *part->child);
if (!part->child)
return gpg_error_from_syserror ();
part->child->headers_tail = &part->child->headers;
part->boundary = generate_boundary (ctx);
if (!part->boundary)
{
err = gpg_error_from_syserror ();
xfree (part->child);
part->child = NULL;
return err;
}
part = part->child;
part->partid = ++ctx->partid_counter;
ctx->current_part = part;
return 0;
}
/* Finish the current container. */
gpg_error_t
mime_maker_end_container (mime_maker_t ctx)
{
gpg_error_t err;
part_t parent;
err = ensure_part (ctx, &parent);
if (err)
return err;
if (!parent)
return gpg_error (GPG_ERR_CONFLICT); /* No container. */
while (parent->next)
parent = parent->next;
ctx->current_part = parent;
return 0;
}
/* Return the part-ID of the current part. */
unsigned int
mime_maker_get_partid (mime_maker_t ctx)
{
if (ensure_part (ctx, NULL))
return 0; /* Ooops. */
return ctx->current_part->partid;
}
/* Write a header and handle emdedded LFs. If BOUNDARY is not NULL it
* is appended to the value. */
/* Fixme: Add automatic line wrapping. */
static gpg_error_t
write_header (mime_maker_t ctx, const char *name, const char *value,
const char *boundary)
{
const char *s;
es_fprintf (ctx->outfp, "%s: ", name);
/* Note that add_header made sure that VALUE does not end with a LF.
* Thus we can assume that a LF is followed by non-whitespace. */
for (s = value; *s; s++)
{
if (*s == '\n')
es_fputs ("\r\n\t", ctx->outfp);
else
es_fputc (*s, ctx->outfp);
}
if (boundary)
{
if (s > value && s[-1] != ';')
es_fputc (';', ctx->outfp);
es_fprintf (ctx->outfp, "\r\n\tboundary=\"%s\"", boundary);
}
es_fputs ("\r\n", ctx->outfp);
return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
}
static gpg_error_t
write_gap (mime_maker_t ctx)
{
es_fputs ("\r\n", ctx->outfp);
return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
}
static gpg_error_t
write_boundary (mime_maker_t ctx, const char *boundary, int last)
{
es_fprintf (ctx->outfp, "\r\n--%s%s\r\n", boundary, last?"--":"");
return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
}
/* Fixme: Apply required encoding. */
static gpg_error_t
write_body (mime_maker_t ctx, const void *body, size_t bodylen)
{
const char *s;
for (s = body; bodylen; s++, bodylen--)
{
if (*s == '\n' && !(s > (const char *)body && s[-1] == '\r'))
es_fputc ('\r', ctx->outfp);
es_fputc (*s, ctx->outfp);
}
return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
}
/* Recursive worker for mime_maker_make. */
static gpg_error_t
write_tree (mime_maker_t ctx, part_t parent, part_t part)
{
gpg_error_t err;
header_t hdr;
for (; part; part = part->next)
{
for (hdr = part->headers; hdr; hdr = hdr->next)
{
if (part->child && !strcmp (hdr->name, "Content-Type"))
err = write_header (ctx, hdr->name, hdr->value, part->boundary);
else
err = write_header (ctx, hdr->name, hdr->value, NULL);
if (err)
return err;
}
err = write_gap (ctx);
if (err)
return err;
if (part->body)
{
err = write_body (ctx, part->body, part->bodylen);
if (err)
return err;
}
if (part->child)
{
log_assert (part->boundary);
err = write_boundary (ctx, part->boundary, 0);
if (!err)
err = write_tree (ctx, part, part->child);
if (!err)
err = write_boundary (ctx, part->boundary, 1);
if (err)
return err;
}
if (part->next)
{
log_assert (parent && parent->boundary);
err = write_boundary (ctx, parent->boundary, 0);
if (err)
return err;
}
}
return 0;
}
/* Add headers we always require. */
static gpg_error_t
add_missing_headers (mime_maker_t ctx)
{
gpg_error_t err;
if (!ctx->mail)
return gpg_error (GPG_ERR_NO_DATA);
if (!have_header (ctx->mail, "MIME-Version"))
{
/* Even if a Content-Type has never been set, we want to
* announce that we do MIME. */
err = add_header (ctx->mail, "MIME-Version", "1.0");
if (err)
goto leave;
}
if (!have_header (ctx->mail, "Date"))
{
char *p = rfctimestamp (make_timestamp ());
if (!p)
err = gpg_error_from_syserror ();
else
err = add_header (ctx->mail, "Date", p);
xfree (p);
if (err)
goto leave;
}
err = 0;
leave:
return err;
}
/* Create message from the tree MIME and write it to FP. Note that
* the output uses only a LF and a later called sendmail(1) is
* expected to convert them to network line endings. */
gpg_error_t
mime_maker_make (mime_maker_t ctx, estream_t fp)
{
gpg_error_t err;
err = add_missing_headers (ctx);
if (err)
return err;
ctx->outfp = fp;
err = write_tree (ctx, NULL, ctx->mail);
ctx->outfp = NULL;
return err;
}
/* Create a stream object from the MIME part identified by PARTID and
* store it at R_STREAM. If PARTID identifies a container the entire
* tree is returned. Using that function may read stream objects
* which have been added as MIME bodies. The caller must close the
* stream object. */
gpg_error_t
mime_maker_get_part (mime_maker_t ctx, unsigned int partid, estream_t *r_stream)
{
gpg_error_t err;
part_t part;
estream_t fp;
*r_stream = NULL;
/* When the entire tree is requested, we make sure that all missing
* headers are applied. We don't do that if only a part is
* requested because the additional headers (like Date:) will only
* be added to part 0 headers anyway. */
if (!partid)
{
err = add_missing_headers (ctx);
if (err)
return err;
part = ctx->mail;
}
else
part = find_part (ctx->mail, partid);
/* For now we use a memory stream object; however it would also be
* possible to create an object created on the fly while the caller
* is reading the returned stream. */
fp = es_fopenmem (0, "w+b");
if (!fp)
return gpg_error_from_syserror ();
ctx->outfp = fp;
err = write_tree (ctx, NULL, part);
ctx->outfp = NULL;
if (!err)
{
es_rewind (fp);
*r_stream = fp;
}
else
es_fclose (fp);
return err;
}
diff --git a/tools/mime-parser.c b/tools/mime-parser.c
index 169ea2b1e..08b021925 100644
--- a/tools/mime-parser.c
+++ b/tools/mime-parser.c
@@ -1,832 +1,832 @@
/* mime-parser.c - Parse MIME structures (high level rfc822 parser).
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
+#include "../common/util.h"
#include "rfc822parse.h"
#include "mime-parser.h"
enum pgpmime_states
{
PGPMIME_NONE = 0,
PGPMIME_WAIT_ENCVERSION,
PGPMIME_IN_ENCVERSION,
PGPMIME_WAIT_ENCDATA,
PGPMIME_IN_ENCDATA,
PGPMIME_GOT_ENCDATA,
PGPMIME_WAIT_SIGNEDDATA,
PGPMIME_IN_SIGNEDDATA,
PGPMIME_WAIT_SIGNATURE,
PGPMIME_IN_SIGNATURE,
PGPMIME_GOT_SIGNATURE,
PGPMIME_INVALID
};
/* Definition of the mime parser object. */
struct mime_parser_context_s
{
void *cookie; /* Cookie passed to all callbacks. */
/* The callback to announce the transation from header to body. */
gpg_error_t (*t2body) (void *cookie, int level);
/* The callback to announce a new part. */
gpg_error_t (*new_part) (void *cookie,
const char *mediatype,
const char *mediasubtype);
/* The callback to return data of a part. */
gpg_error_t (*part_data) (void *cookie,
const void *data,
size_t datalen);
/* The callback to collect encrypted data. */
gpg_error_t (*collect_encrypted) (void *cookie, const char *data);
/* The callback to collect signed data. */
gpg_error_t (*collect_signeddata) (void *cookie, const char *data);
/* The callback to collect a signature. */
gpg_error_t (*collect_signature) (void *cookie, const char *data);
/* The RFC822 parser context is stored here during callbacks. */
rfc822parse_t msg;
/* Helper to convey error codes from user callbacks. */
gpg_error_t err;
int nesting_level; /* The current nesting level. */
int hashing_at_level; /* The nesting level at which we are hashing. */
enum pgpmime_states pgpmime; /* Current PGP/MIME state. */
unsigned int delay_hashing:1;/* Helper for PGPMIME_IN_SIGNEDDATA. */
unsigned int want_part:1; /* Return the current part. */
unsigned int decode_part:2; /* Decode the part. 1 = QP, 2 = Base64. */
unsigned int verbose:1; /* Enable verbose mode. */
unsigned int debug:1; /* Enable debug mode. */
/* Flags to help with debug output. */
struct {
unsigned int n_skip; /* Skip showing these number of lines. */
unsigned int header:1; /* Show the header lines. */
unsigned int data:1; /* Show the data lines. */
unsigned int as_note:1; /* Show the next data line as a note. */
unsigned int boundary : 1;
} show;
struct b64state *b64state; /* NULL or malloced Base64 decoder state. */
/* A buffer for reading a mail line, */
char line[5000];
};
/* Print the event received by the parser for debugging. */
static void
show_message_parser_event (rfc822parse_event_t event)
{
const char *s;
switch (event)
{
case RFC822PARSE_OPEN: s= "Open"; break;
case RFC822PARSE_CLOSE: s= "Close"; break;
case RFC822PARSE_CANCEL: s= "Cancel"; break;
case RFC822PARSE_T2BODY: s= "T2Body"; break;
case RFC822PARSE_FINISH: s= "Finish"; break;
case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
default: s= "[unknown event]"; break;
}
log_debug ("*** RFC822 event %s\n", s);
}
/* Do in-place decoding of quoted-printable data of LENGTH in BUFFER.
Returns the new length of the buffer and stores true at R_SLBRK if
the line ended with a soft line break; false is stored if not.
This function asssumes that a complete line is passed in
buffer. */
static size_t
qp_decode (char *buffer, size_t length, int *r_slbrk)
{
char *d, *s;
if (r_slbrk)
*r_slbrk = 0;
/* Fixme: We should remove trailing white space first. */
for (s=d=buffer; length; length--)
{
if (*s == '=')
{
if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2))
{
s++;
*(unsigned char*)d++ = xtoi_2 (s);
s += 2;
length -= 2;
}
else if (length > 2 && s[1] == '\r' && s[2] == '\n')
{
/* Soft line break. */
s += 3;
length -= 2;
if (r_slbrk && length == 1)
*r_slbrk = 1;
}
else if (length > 1 && s[1] == '\n')
{
/* Soft line break with only a Unix line terminator. */
s += 2;
length -= 1;
if (r_slbrk && length == 1)
*r_slbrk = 1;
}
else if (length == 1)
{
/* Soft line break at the end of the line. */
s += 1;
if (r_slbrk)
*r_slbrk = 1;
}
else
*d++ = *s++;
}
else
*d++ = *s++;
}
return d - buffer;
}
/* This function is called by parse_mail to communicate events. This
* callback communicates with the caller using a structure passed in
* OPAQUE. Should return 0 on success or set ERRNO and return -1. */
static int
parse_message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
{
mime_parser_t ctx = opaque;
const char *s;
int rc = 0;
/* Make the RFC822 parser context availabale for callbacks. */
ctx->msg = msg;
if (ctx->debug)
show_message_parser_event (event);
if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
{
/* We need to check here whether to start collecting signed data
* because attachments might come without header lines and thus
* we won't see the BEGIN_HEADER event. */
if (ctx->pgpmime == PGPMIME_WAIT_SIGNEDDATA)
{
if (ctx->debug)
log_debug ("begin_hash\n");
ctx->hashing_at_level = ctx->nesting_level;
ctx->pgpmime = PGPMIME_IN_SIGNEDDATA;
ctx->delay_hashing = 0;
}
}
if (event == RFC822PARSE_OPEN)
{
/* Initialize for a new message. */
ctx->show.header = 1;
}
else if (event == RFC822PARSE_T2BODY)
{
rfc822parse_field_t field;
ctx->want_part = 0;
ctx->decode_part = 0;
if (ctx->t2body)
{
rc = ctx->t2body (ctx->cookie, ctx->nesting_level);
if (rc)
goto t2body_leave;
}
field = rfc822parse_parse_field (msg, "Content-Type", -1);
if (field)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (field, &s2);
if (s1)
{
if (ctx->verbose)
log_debug ("h media: %*s%s %s\n",
ctx->nesting_level*2, "", s1, s2);
if (ctx->pgpmime == PGPMIME_WAIT_ENCVERSION)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "pgp-encrypted"))
{
if (ctx->debug)
log_debug ("c begin_encversion\n");
ctx->pgpmime = PGPMIME_IN_ENCVERSION;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/pgp-encrypted", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_WAIT_ENCDATA)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "octet-stream"))
{
if (ctx->debug)
log_debug ("c begin_encdata\n");
ctx->pgpmime = PGPMIME_IN_ENCDATA;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/octet-stream", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_WAIT_SIGNATURE)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "pgp-signature"))
{
if (ctx->debug)
log_debug ("c begin_signature\n");
ctx->pgpmime = PGPMIME_IN_SIGNATURE;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/pgp-signature", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (!strcmp (s1, "multipart")
&& !strcmp (s2, "encrypted"))
{
s = rfc822parse_query_parameter (field, "protocol", 0);
if (s)
{
if (ctx->debug)
log_debug ("h encrypted.protocol: %s\n", s);
if (!strcmp (s, "application/pgp-encrypted"))
{
if (ctx->pgpmime)
log_error ("note: "
"ignoring nested PGP/MIME signature\n");
else
ctx->pgpmime = PGPMIME_WAIT_ENCVERSION;
}
else if (ctx->verbose)
log_debug ("# this protocol is not supported\n");
}
}
else if (!strcmp (s1, "multipart")
&& !strcmp (s2, "signed"))
{
s = rfc822parse_query_parameter (field, "protocol", 1);
if (s)
{
if (ctx->debug)
log_debug ("h signed.protocol: %s\n", s);
if (!strcmp (s, "application/pgp-signature"))
{
if (ctx->pgpmime)
log_error ("note: "
"ignoring nested PGP/MIME signature\n");
else
ctx->pgpmime = PGPMIME_WAIT_SIGNEDDATA;
}
else if (ctx->verbose)
log_debug ("# this protocol is not supported\n");
}
}
else if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, s1, s2);
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
else
{
if (ctx->debug)
log_debug ("h media: %*s none\n", ctx->nesting_level*2, "");
if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, "", "");
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
rfc822parse_release_field (field);
}
else
{
if (ctx->verbose)
log_debug ("h media: %*stext plain [assumed]\n",
ctx->nesting_level*2, "");
if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, "text", "plain");
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
/* Figure out the encoding if needed. */
if (ctx->decode_part)
{
char *value;
size_t valueoff;
ctx->decode_part = 0; /* Fallback for unknown encoding. */
value = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1,
&valueoff);
if (value)
{
if (!stricmp (value+valueoff, "quoted-printable"))
ctx->decode_part = 1;
else if (!stricmp (value+valueoff, "base64"))
{
ctx->decode_part = 2;
if (ctx->b64state)
b64dec_finish (ctx->b64state); /* Reuse state. */
else
{
ctx->b64state = xtrymalloc (sizeof *ctx->b64state);
if (!ctx->b64state)
rc = gpg_error_from_syserror ();
}
if (!rc)
rc = b64dec_start (ctx->b64state, NULL);
}
free (value); /* Right, we need a plain free. */
}
}
t2body_leave:
ctx->show.header = 0;
ctx->show.data = 1;
ctx->show.n_skip = 1;
}
else if (event == RFC822PARSE_PREAMBLE)
ctx->show.as_note = 1;
else if (event == RFC822PARSE_LEVEL_DOWN)
{
if (ctx->debug)
log_debug ("b down\n");
ctx->nesting_level++;
}
else if (event == RFC822PARSE_LEVEL_UP)
{
if (ctx->debug)
log_debug ("b up\n");
if (ctx->nesting_level)
ctx->nesting_level--;
else
log_error ("invalid structure (bad nesting level)\n");
}
else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
{
ctx->show.data = 0;
ctx->show.boundary = 1;
if (event == RFC822PARSE_BOUNDARY)
{
ctx->show.header = 1;
ctx->show.n_skip = 1;
if (ctx->debug)
log_debug ("b part\n");
}
else if (ctx->debug)
log_debug ("b last\n");
if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
{
if (ctx->debug)
log_debug ("c end_encdata\n");
ctx->pgpmime = PGPMIME_GOT_ENCDATA;
/* FIXME: We should assert (event == LAST_BOUNDARY). */
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA
&& ctx->nesting_level == ctx->hashing_at_level)
{
if (ctx->debug)
log_debug ("c end_hash\n");
ctx->pgpmime = PGPMIME_WAIT_SIGNATURE;
if (ctx->collect_signeddata)
ctx->err = ctx->collect_signeddata (ctx->cookie, NULL);
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
{
if (ctx->debug)
log_debug ("c end_signature\n");
ctx->pgpmime = PGPMIME_GOT_SIGNATURE;
/* FIXME: We should assert (event == LAST_BOUNDARY). */
}
else if (ctx->want_part)
{
if (ctx->part_data)
{
/* FIXME: We may need to flush things. */
ctx->err = ctx->part_data (ctx->cookie, NULL, 0);
}
ctx->want_part = 0;
}
}
ctx->msg = NULL;
return rc;
}
/* Create a new mime parser object. COOKIE is a values which will be
* used as first argument for all callbacks registered with this
* parser object. */
gpg_error_t
mime_parser_new (mime_parser_t *r_parser, void *cookie)
{
mime_parser_t ctx;
*r_parser = NULL;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
ctx->cookie = cookie;
*r_parser = ctx;
return 0;
}
/* Release a mime parser object. */
void
mime_parser_release (mime_parser_t ctx)
{
if (!ctx)
return;
if (ctx->b64state)
{
b64dec_finish (ctx->b64state);
xfree (ctx->b64state);
}
xfree (ctx);
}
/* Set verbose and debug mode. */
void
mime_parser_set_verbose (mime_parser_t ctx, int level)
{
if (!level)
{
ctx->verbose = 0;
ctx->debug = 0;
}
else
{
ctx->verbose = 1;
if (level > 10)
ctx->debug = 1;
}
}
/* Set a callback for the transition from header to body. LEVEL is
* the current nesting level, starting with 0. This callback can be
* used to evaluate headers before any other action is done. Note
* that if a new NEW_PART callback needs to be called it is done after
* this T2BODY callback. */
void
mime_parser_set_t2body (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie, int level))
{
ctx->t2body = fnc;
}
/* Set the callback used to announce a new part. It will be called
* with the media type and media subtype of the part. If no
* Content-type header was given both values are the empty string.
* The callback should return 0 on success or an error code. The
* error code GPG_ERR_FALSE indicates that the caller is not
* interested in the part and data shall not be returned via a
* registered part_data callback. The error code GPG_ERR_TRUE
* indicates that the parts shall be redurned in decoded format
* (i.e. base64 or QP encoding is removed). */
void
mime_parser_set_new_part (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *mediatype,
const char *mediasubtype))
{
ctx->new_part = fnc;
}
/* Set the callback used to return the data of a part to the caller.
* The end of the part is indicated by passing NUL for DATA. */
void
mime_parser_set_part_data (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const void *data,
size_t datalen))
{
ctx->part_data = fnc;
}
/* Set the callback to collect encrypted data. A NULL passed to the
* callback indicates the end of the encrypted data; the callback may
* then decrypt the collected data. */
void
mime_parser_set_collect_encrypted (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_encrypted = fnc;
}
/* Set the callback to collect signed data. A NULL passed to the
* callback indicates the end of the signed data. */
void
mime_parser_set_collect_signeddata (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_signeddata = fnc;
}
/* Set the callback to collect the signature. A NULL passed to the
* callback indicates the end of the signature; the callback may the
* verify the signature. */
void
mime_parser_set_collect_signature (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_signature = fnc;
}
/* Return the RFC888 parser context. This is only available inside a
* callback. */
rfc822parse_t
mime_parser_rfc822parser (mime_parser_t ctx)
{
return ctx->msg;
}
/* Helper for mime_parser_parse. */
static gpg_error_t
process_part_data (mime_parser_t ctx, char *line, size_t *length)
{
gpg_error_t err;
size_t nbytes;
if (!ctx->want_part)
return 0;
if (!ctx->part_data)
return 0;
if (ctx->decode_part == 1)
{
*length = qp_decode (line, *length, NULL);
}
else if (ctx->decode_part == 2)
{
log_assert (ctx->b64state);
err = b64dec_proc (ctx->b64state, line, *length, &nbytes);
if (err)
return err;
*length = nbytes;
}
return ctx->part_data (ctx->cookie, line, *length);
}
/* Read and parse a message from FP and call the appropriate
* callbacks. */
gpg_error_t
mime_parser_parse (mime_parser_t ctx, estream_t fp)
{
gpg_error_t err;
rfc822parse_t msg = NULL;
unsigned int lineno = 0;
size_t length;
char *line;
line = ctx->line;
msg = rfc822parse_open (parse_message_cb, ctx);
if (!msg)
{
err = gpg_error_from_syserror ();
log_error ("can't open mail parser: %s", gpg_strerror (err));
goto leave;
}
/* Fixme: We should not use fgets because it can't cope with
embedded nul characters. */
while (es_fgets (ctx->line, sizeof (ctx->line), fp))
{
lineno++;
if (lineno == 1 && !strncmp (line, "From ", 5))
continue; /* We better ignore a leading From line. */
length = strlen (line);
if (length && line[length - 1] == '\n')
line[--length] = 0;
else
log_error ("mail parser detected too long or"
" non terminated last line (lnr=%u)\n", lineno);
if (length && line[length - 1] == '\r')
line[--length] = 0;
ctx->err = 0;
if (rfc822parse_insert (msg, line, length))
{
err = gpg_error_from_syserror ();
log_error ("mail parser failed: %s", gpg_strerror (err));
goto leave;
}
if (ctx->err)
{
/* Error from a callback detected. */
err = ctx->err;
goto leave;
}
/* Debug output. Note that the boundary is shown before n_skip
* is evaluated. */
if (ctx->show.boundary)
{
if (ctx->debug)
log_debug ("# Boundary: %s\n", line);
ctx->show.boundary = 0;
}
if (ctx->show.n_skip)
ctx->show.n_skip--;
else if (ctx->show.data)
{
if (ctx->show.as_note)
{
if (ctx->verbose)
log_debug ("# Note: %s\n", line);
ctx->show.as_note = 0;
}
else if (ctx->debug)
log_debug ("# Data: %s\n", line);
}
else if (ctx->show.header && ctx->verbose)
log_debug ("# Header: %s\n", line);
if (ctx->pgpmime == PGPMIME_IN_ENCVERSION)
{
trim_trailing_spaces (line);
if (!*line)
; /* Skip empty lines. */
else if (!strcmp (line, "Version: 1"))
ctx->pgpmime = PGPMIME_WAIT_ENCDATA;
else
{
log_error ("invalid PGP/MIME structure;"
" garbage in pgp-encrypted part ('%s')\n", line);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
{
if (ctx->collect_encrypted)
{
err = ctx->collect_encrypted (ctx->cookie, line);
if (!err)
err = ctx->collect_encrypted (ctx->cookie, "\r\n");
if (err)
goto leave;
}
}
else if (ctx->pgpmime == PGPMIME_GOT_ENCDATA)
{
ctx->pgpmime = PGPMIME_NONE;
if (ctx->collect_encrypted)
ctx->collect_encrypted (ctx->cookie, NULL);
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA)
{
/* If we are processing signed data, store the signed data.
* We need to delay the hashing of the CR/LF because the
* last line ending belongs to the next boundary. This is
* the reason why we can't use the PGPMIME state as a
* condition. */
if (ctx->debug)
log_debug ("# hashing %s'%s'\n",
ctx->delay_hashing? "CR,LF+":"", line);
if (ctx->collect_signeddata)
{
if (ctx->delay_hashing)
ctx->collect_signeddata (ctx->cookie, "\r\n");
ctx->collect_signeddata (ctx->cookie, line);
}
ctx->delay_hashing = 1;
err = process_part_data (ctx, line, &length);
if (err)
goto leave;
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
{
if (ctx->collect_signeddata)
{
ctx->collect_signature (ctx->cookie, line);
ctx->collect_signature (ctx->cookie, "\r\n");
}
}
else if (ctx->pgpmime == PGPMIME_GOT_SIGNATURE)
{
ctx->pgpmime = PGPMIME_NONE;
if (ctx->collect_signeddata)
ctx->collect_signature (ctx->cookie, NULL);
}
else
{
err = process_part_data (ctx, line, &length);
if (err)
goto leave;
}
}
rfc822parse_close (msg);
msg = NULL;
err = 0;
leave:
rfc822parse_cancel (msg);
return err;
}
diff --git a/tools/send-mail.c b/tools/send-mail.c
index 56f250029..34d47c16d 100644
--- a/tools/send-mail.c
+++ b/tools/send-mail.c
@@ -1,129 +1,129 @@
/* send-mail.c - Invoke sendmail or other delivery tool.
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
-#include "exectool.h"
-#include "sysutils.h"
+#include "../common/util.h"
+#include "../common/exectool.h"
+#include "../common/sysutils.h"
#include "send-mail.h"
static gpg_error_t
run_sendmail (estream_t data)
{
gpg_error_t err;
const char pgmname[] = "/usr/lib/sendmail";
const char *argv[3];
argv[0] = "-oi";
argv[1] = "-t";
argv[2] = NULL;
err = gnupg_exec_tool_stream (pgmname, argv, data, NULL, NULL, NULL, NULL);
if (err)
log_error ("running '%s' failed: %s\n", pgmname, gpg_strerror (err));
return err;
}
/* Send the data in FP as mail. */
gpg_error_t
send_mail (estream_t fp)
{
return run_sendmail (fp);
}
/* Convenience function to write a mail to a named file. */
gpg_error_t
send_mail_to_file (estream_t fp, const char *fname)
{
gpg_error_t err;
estream_t outfp = NULL;
char *buffer = NULL;
size_t buffersize = 32 * 1024;
size_t nbytes, nwritten;
if (!fname)
fname = "-";
buffer = xtrymalloc (buffersize);
if (!buffer)
return gpg_error_from_syserror ();
outfp = !strcmp (fname,"-")? es_stdout : es_fopen (fname, "wb");
if (!outfp)
{
err = gpg_error_from_syserror ();
log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
for (;;)
{
if (es_read (fp, buffer, sizeof buffer, &nbytes))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n",
es_fname_get (fp), gpg_strerror (err));
goto leave;
}
if (!nbytes)
{
err = 0;
break; /* Ready. */
}
if (es_write (outfp, buffer, nbytes, &nwritten))
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
else if (nwritten != nbytes)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error writing '%s': %s\n", fname, "short write");
goto leave;
}
}
leave:
if (err)
{
if (outfp && outfp != es_stdout)
{
es_fclose (outfp);
gnupg_remove (fname);
}
}
else if (outfp && outfp != es_stdout && es_fclose (outfp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
}
xfree (buffer);
return err;
}
diff --git a/tools/wks-receive.c b/tools/wks-receive.c
index 94f8bc6a9..49a15179b 100644
--- a/tools/wks-receive.c
+++ b/tools/wks-receive.c
@@ -1,533 +1,533 @@
/* wks-receive.c - Receive a WKS mail
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
-#include "ccparray.h"
-#include "exectool.h"
+#include "../common/util.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
#include "gpg-wks.h"
#include "rfc822parse.h"
#include "mime-parser.h"
/* Limit of acceptable signed data. */
#define MAX_SIGNEDDATA 10000
/* Limit of acceptable signature. */
#define MAX_SIGNATURE 10000
/* Limit of acceptable encrypted data. */
#define MAX_ENCRYPTED 100000
/* Data for a received object. */
struct receive_ctx_s
{
mime_parser_t parser;
estream_t encrypted;
estream_t plaintext;
estream_t signeddata;
estream_t signature;
estream_t key_data;
estream_t wkd_data;
unsigned int collect_key_data:1;
unsigned int collect_wkd_data:1;
unsigned int draft_version_2:1; /* This is a draft version 2 request. */
unsigned int multipart_mixed_seen:1;
};
typedef struct receive_ctx_s *receive_ctx_t;
static void
decrypt_data_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Decrypt the collected data. */
static void
decrypt_data (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
int c;
es_rewind (ctx->encrypted);
if (!ctx->plaintext)
ctx->plaintext = es_fopenmem (0, "w+b");
if (!ctx->plaintext)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for plaintext: %s\n",
gpg_strerror (err));
return;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
/* We limit the output to 64 KiB to avoid DoS using compression
* tricks. A regular client will anyway only send a minimal key;
* that is one w/o key signatures and attribute packets. */
ccparray_put (&ccp, "--max-output=0xf0000"); /*FIXME: Change s/F/1/ */
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--decrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->encrypted,
NULL, ctx->plaintext,
decrypt_data_status_cb, ctx);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
{
es_rewind (ctx->plaintext);
log_debug ("plaintext: '");
while ((c = es_getc (ctx->plaintext)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
es_rewind (ctx->plaintext);
leave:
xfree (argv);
}
static void
verify_signature_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Verify the signed data. */
static void
verify_signature (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
log_assert (ctx->signeddata);
log_assert (ctx->signature);
es_rewind (ctx->signeddata);
es_rewind (ctx->signature);
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--enable-special-filenames");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust"); /* To avoid trustdb checks. */
ccparray_put (&ccp, "--verify");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, "-&@INEXTRA@");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->signeddata,
ctx->signature, NULL,
verify_signature_status_cb, ctx);
if (err)
{
log_error ("verification failed: %s\n", gpg_strerror (err));
goto leave;
}
log_debug ("Fixme: Verification result is not used\n");
leave:
xfree (argv);
}
static gpg_error_t
collect_encrypted (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->encrypted)
if (!(ctx->encrypted = es_fopenmem (MAX_ENCRYPTED, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->encrypted);
if (es_ferror (ctx->encrypted))
return gpg_error_from_syserror ();
if (!data)
{
decrypt_data (ctx);
}
return 0;
}
static gpg_error_t
collect_signeddata (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signeddata)
if (!(ctx->signeddata = es_fopenmem (MAX_SIGNEDDATA, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signeddata);
if (es_ferror (ctx->signeddata))
return gpg_error_from_syserror ();
return 0;
}
static gpg_error_t
collect_signature (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signature)
if (!(ctx->signature = es_fopenmem (MAX_SIGNATURE, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signature);
if (es_ferror (ctx->signature))
return gpg_error_from_syserror ();
if (!data)
{
verify_signature (ctx);
}
return 0;
}
/* The callback for the transition from header to body. We use it to
* look at some header values. */
static gpg_error_t
t2body (void *cookie, int level)
{
receive_ctx_t ctx = cookie;
rfc822parse_t msg;
char *value;
size_t valueoff;
log_info ("t2body for level %d\n", level);
if (!level)
{
/* This is the outermost header. */
msg = mime_parser_rfc822parser (ctx->parser);
if (msg)
{
value = rfc822parse_get_field (msg, "Wks-Draft-Version",
-1, &valueoff);
if (value)
{
if (atoi(value+valueoff) >= 2 )
ctx->draft_version_2 = 1;
free (value);
}
}
}
return 0;
}
static gpg_error_t
new_part (void *cookie, const char *mediatype, const char *mediasubtype)
{
receive_ctx_t ctx = cookie;
gpg_error_t err = 0;
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "pgp-keys"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->key_data)
{
log_error ("we already got a key - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->key_data = es_fopenmem (0, "w+b");
if (!ctx->key_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_key_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "vnd.gnupg.wks"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->wkd_data)
{
log_error ("we already got a wkd part - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->wkd_data = es_fopenmem (0, "w+b");
if (!ctx->wkd_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_wkd_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else if (!strcmp (mediatype, "multipart")
&& !strcmp (mediasubtype, "mixed"))
{
ctx->multipart_mixed_seen = 1;
}
else if (!strcmp (mediatype, "text"))
{
/* Check that we receive a text part only after a
* application/mixed. This is actually a too simple test and we
* should eventually employ a strict MIME structure check. */
if (!ctx->multipart_mixed_seen)
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
}
else
{
log_error ("unexpected '%s/%s' message part\n", mediatype, mediasubtype);
err = gpg_error (GPG_ERR_FALSE); /* We do not want the part. */
}
return err;
}
static gpg_error_t
part_data (void *cookie, const void *data, size_t datalen)
{
receive_ctx_t ctx = cookie;
if (data)
{
if (DBG_MIME)
log_debug ("part_data: '%.*s'\n", (int)datalen, (const char*)data);
if (ctx->collect_key_data)
{
if (es_write (ctx->key_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->key_data))
return gpg_error_from_syserror ();
}
if (ctx->collect_wkd_data)
{
if (es_write (ctx->wkd_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->wkd_data))
return gpg_error_from_syserror ();
}
}
else
{
if (DBG_MIME)
log_debug ("part_data: finished\n");
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
}
return 0;
}
/* Receive a WKS mail from FP and process it accordingly. On success
* the RESULT_CB is called with the mediatype and a stream with the
* decrypted data. */
gpg_error_t
wks_receive (estream_t fp,
gpg_error_t (*result_cb)(void *opaque,
const char *mediatype,
estream_t data,
unsigned int flags),
void *cb_data)
{
gpg_error_t err;
receive_ctx_t ctx;
mime_parser_t parser;
estream_t plaintext = NULL;
int c;
unsigned int flags = 0;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
err = mime_parser_new (&parser, ctx);
if (err)
goto leave;
if (DBG_PARSER)
mime_parser_set_verbose (parser, 1);
mime_parser_set_t2body (parser, t2body);
mime_parser_set_new_part (parser, new_part);
mime_parser_set_part_data (parser, part_data);
mime_parser_set_collect_encrypted (parser, collect_encrypted);
mime_parser_set_collect_signeddata (parser, collect_signeddata);
mime_parser_set_collect_signature (parser, collect_signature);
ctx->parser = parser;
err = mime_parser_parse (parser, fp);
if (err)
goto leave;
if (ctx->key_data)
log_info ("key data found\n");
if (ctx->wkd_data)
log_info ("wkd data found\n");
if (ctx->draft_version_2)
{
log_info ("draft version 2 requested\n");
flags |= WKS_RECEIVE_DRAFT2;
}
if (ctx->plaintext)
{
if (opt.verbose)
log_info ("parsing decrypted message\n");
plaintext = ctx->plaintext;
ctx->plaintext = NULL;
if (ctx->encrypted)
es_rewind (ctx->encrypted);
if (ctx->signeddata)
es_rewind (ctx->signeddata);
if (ctx->signature)
es_rewind (ctx->signature);
err = mime_parser_parse (parser, plaintext);
if (err)
return err;
}
if (!ctx->key_data && !ctx->wkd_data)
{
log_error ("no suitable data found in the message\n");
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (ctx->key_data)
{
if (DBG_MIME)
{
es_rewind (ctx->key_data);
log_debug ("Key: '");
log_printf ("\n");
while ((c = es_getc (ctx->key_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->key_data);
err = result_cb (cb_data, "application/pgp-keys",
ctx->key_data, flags);
if (err)
goto leave;
}
}
if (ctx->wkd_data)
{
if (DBG_MIME)
{
es_rewind (ctx->wkd_data);
log_debug ("WKD: '");
log_printf ("\n");
while ((c = es_getc (ctx->wkd_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->wkd_data);
err = result_cb (cb_data, "application/vnd.gnupg.wks",
ctx->wkd_data, flags);
if (err)
goto leave;
}
}
leave:
es_fclose (plaintext);
mime_parser_release (parser);
ctx->parser = NULL;
es_fclose (ctx->encrypted);
es_fclose (ctx->plaintext);
es_fclose (ctx->signeddata);
es_fclose (ctx->signature);
es_fclose (ctx->key_data);
es_fclose (ctx->wkd_data);
xfree (ctx);
return err;
}
diff --git a/tools/wks-util.c b/tools/wks-util.c
index e6f6b7acd..389d4a4da 100644
--- a/tools/wks-util.c
+++ b/tools/wks-util.c
@@ -1,413 +1,413 @@
/* wks-utils.c - Common helper fucntions for wks tools
* 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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "util.h"
-#include "status.h"
-#include "ccparray.h"
-#include "exectool.h"
-#include "mbox-util.h"
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
+#include "../common/mbox-util.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
/* The stream to output the status information. Output is disabled if
this is NULL. */
static estream_t statusfp;
/* Set the status FD. */
void
wks_set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the outout of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
wks_write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc (' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
}
/* Helper for wks_list_key. */
static void
list_key_status_cb (void *opaque, const char *keyword, char *args)
{
(void)opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Run gpg on KEY and store the primary fingerprint at R_FPR and the
* list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL
* is stored at R_FPR and R_MBOXES and an error code is returned. */
gpg_error_t
wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t listing;
char *line = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char **fields = NULL;
int nfields;
int lnr;
char *mbox = NULL;
char *fpr = NULL;
strlist_t mboxes = NULL;
*r_fpr = NULL;
*r_mboxes = NULL;
/* Open a memory stream. */
listing = es_fopenmem (0, "w+b");
if (!listing)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--with-colons");
ccparray_put (&ccp, "--dry-run");
ccparray_put (&ccp, "--import-options=import-minimal,import-show");
ccparray_put (&ccp, "--import");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
NULL, listing,
list_key_status_cb, NULL);
if (err)
{
log_error ("import failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (listing);
lnr = 0;
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
{
lnr++;
if (!maxlen)
{
log_error ("received line too long\n");
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0
&& (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
/* log_debug ("line '%s'\n", line); */
xfree (fields);
fields = strtokenize (line, ":");
if (!fields)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
goto leave;
}
for (nfields = 0; fields[nfields]; nfields++)
;
if (!nfields)
{
err = gpg_error (GPG_ERR_INV_ENGINE);
goto leave;
}
if (!strcmp (fields[0], "sec"))
{
/* gpg may return "sec" as the first record - but we do not
* accept secret keys. */
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
if (lnr == 1 && strcmp (fields[0], "pub"))
{
/* First record is not a public key. */
err = gpg_error (GPG_ERR_INV_ENGINE);
goto leave;
}
if (lnr > 1 && !strcmp (fields[0], "pub"))
{
/* More than one public key. */
err = gpg_error (GPG_ERR_TOO_MANY);
goto leave;
}
if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
break; /* We can stop parsing here. */
if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
{
fpr = xtrystrdup (fields[9]);
if (!fpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else if (!strcmp (fields[0], "uid") && nfields > 9)
{
/* Fixme: Unescape fields[9] */
xfree (mbox);
mbox = mailbox_from_userid (fields[9]);
if (mbox && !append_to_strlist_try (&mboxes, mbox))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
}
if (len < 0 || es_ferror (listing))
{
err = gpg_error_from_syserror ();
log_error ("error reading memory stream\n");
goto leave;
}
*r_fpr = fpr;
fpr = NULL;
*r_mboxes = mboxes;
mboxes = NULL;
leave:
xfree (fpr);
xfree (mboxes);
xfree (mbox);
xfree (fields);
es_free (line);
xfree (argv);
es_fclose (listing);
return err;
}
/* Helper to write mail to the output(s). */
gpg_error_t
wks_send_mime (mime_maker_t mime)
{
gpg_error_t err;
estream_t mail;
/* Without any option we take a short path. */
if (!opt.use_sendmail && !opt.output)
return mime_maker_make (mime, es_stdout);
mail = es_fopenmem (0, "w+b");
if (!mail)
{
err = gpg_error_from_syserror ();
return err;
}
err = mime_maker_make (mime, mail);
if (!err && opt.output)
{
es_rewind (mail);
err = send_mail_to_file (mail, opt.output);
}
if (!err && opt.use_sendmail)
{
es_rewind (mail);
err = send_mail (mail);
}
es_fclose (mail);
return err;
}
/* Parse the policy flags by reading them from STREAM and storing them
* into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are
* ignored. */
gpg_error_t
wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
{
enum tokens {
TOK_MAILBOX_ONLY,
TOK_DANE_ONLY,
TOK_AUTH_SUBMIT,
TOK_MAX_PENDING
};
static struct {
const char *name;
enum tokens token;
} keywords[] = {
{ "mailbox-only", TOK_MAILBOX_ONLY },
{ "dane-only", TOK_DANE_ONLY },
{ "auth-submit", TOK_AUTH_SUBMIT },
{ "max-pending", TOK_MAX_PENDING }
};
gpg_error_t err = 0;
int lnr = 0;
char line[1024];
char *p, *keyword, *value;
int i, n;
memset (flags, 0, sizeof *flags);
while (es_fgets (line, DIM(line)-1, stream) )
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
break;
}
trim_trailing_spaces (line);
/* Skip empty and comment lines. */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
if (*p == ':')
{
err = gpg_error (GPG_ERR_SYNTAX);
break;
}
keyword = p;
value = NULL;
if ((p = strchr (p, ':')))
{
/* Colon found: Keyword with value. */
*p++ = 0;
for (; spacep (p); p++)
;
if (!*p)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
break;
}
value = p;
}
for (i=0; i < DIM (keywords); i++)
if (!ascii_strcasecmp (keywords[i].name, keyword))
break;
if (!(i < DIM (keywords)))
{
if (ignore_unknown)
continue;
err = gpg_error (GPG_ERR_INV_NAME);
break;
}
switch (keywords[i].token)
{
case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
case TOK_DANE_ONLY: flags->dane_only = 1; break;
case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break;
case TOK_MAX_PENDING:
if (!value)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* FIXME: Define whether these are seconds, hours, or days
* and decide whether to allow other units. */
flags->max_pending = atoi (value);
break;
}
}
if (!err && !es_feof (stream))
err = gpg_error_from_syserror ();
leave:
if (err)
log_error ("error reading '%s', line %d: %s\n",
es_fname_get (stream), lnr, gpg_strerror (err));
return err;
}

File Metadata

Mime Type
application/octet-stream
Expires
Tue, Jun 4, 3:37 AM (1 d, 23 h)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
IrEnScsOpRbP

Event Timeline