tools: Add some code for a future gpg-signcode tool.

* tools/gpg-signcode.c: New.
* tools/Makefile.am (bin_PROGRAMS): Add gpg-signcode.
(gpg_signcode_SOURCE, gpg_signcode_LDADD): New.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-04-30 10:05:39 +02:00
parent 5ed2275892
commit e114a715d6
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
2 changed files with 602 additions and 1 deletions

View File

@ -51,7 +51,7 @@ endif
libexec_PROGRAMS = gpg-wks-client gpg-pair-tool
bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun}
bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun} gpg-signcode
if !HAVE_W32_SYSTEM
bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server}
endif
@ -139,6 +139,10 @@ gpg_card_LDADD = \
$(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
$(gpg_card_tool_rc_objs)
gpg_signcode_SOURCES = gpg-signcode.c
gpg_signcode_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(NETLIBS) $(LIBICONV)
if !DISABLE_REGEX
gpg_check_pattern_SOURCES = gpg-check-pattern.c

597
tools/gpg-signcode.c Normal file
View File

@ -0,0 +1,597 @@
/* gpg-signcode.c - An interactive tool to work with cards.
* Copyright (C) 2019 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 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.
*
* 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 Lesser 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://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/status.h"
#include "../common/init.h"
#include "../common/session-env.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
/* Constants to identify the commands and options. */
enum opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oDebug = 500,
oGpgProgram,
oGpgsmProgram,
oStatusFD,
oWithColons,
oNoAutostart,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oXauthority,
oLCctype,
oLCmessages,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
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_s (oGpgsmProgram, "gpgsm", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", ("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_end ()
};
/* We keep all global options in the structure OPT. */
struct
{
int interactive;
int verbose;
unsigned int debug;
int quiet;
int with_colons;
const char *gpg_program;
const char *gpgsm_program;
const char *agent_program;
int autostart;
/* Options passed to the gpg-agent: */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
} opt;
/* Debug values and macros. */
#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
/* { DBG_IPC_VALUE , "ipc" }, */
/* { DBG_EXTPROG_VALUE, "extprog" }, */
{ 0, NULL }
};
/* Local prototypes. */
static gpg_error_t read_file (const char *fname,
char **r_buf, size_t *r_length);
static gpg_error_t signcode (const char *fname, char *file, size_t filelen);
static gpg_error_t pe_signcode (const char *fname, char *file, size_t filelen);
/* Small helpers. */
static inline unsigned int
le16_to_uint (const void *buffer)
{
const unsigned char *p = buffer;
return (((unsigned int)p[1] << 8) | p[0]);
}
static inline unsigned int
le32_to_uint (const void *buffer)
{
const unsigned char *p = buffer;
return (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
}
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-signcode"; break;
case 12: p = "@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-signcode [options] [INFILE]");
break;
case 41:
p = ("Syntax: gpg-signcode [options] [INFILE]\n\n"
"Tool to sign PE binaries.");
break;
default: p = NULL; break;
}
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));
}
/* Command line parsing. */
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
while (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 oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
case oStatusFD:
gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons: opt.with_colons = 1; break;
case oNoAutostart: opt.autostart = 0; 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;
default: pargs->err = 2; break;
}
}
}
/* gpg-card main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
char *file;
size_t filelen;
gnupg_reopen_std ("gpg-signcode");
set_strusage (my_strusage);
log_set_prefix ("gpg-signcode", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Setup default options. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
if (argc > 2)
usage (1);
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.gpgsm_program)
opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
err = read_file (argc? *argv : NULL, &file, &filelen);
if (!err)
err = signcode (argc? *argv : "-", file, filelen);
xfree (file);
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
gnupg_status_printf (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Read the file FNAME or stdin if FNAME is NULL and store a malloced
* buffer with the content at R_BUF. R_LENGTH receives the length of
* the file. On error a diagnostic is printed and an error code is
* returned. */
static gpg_error_t
read_file (const char *fname, char **r_buf, size_t *r_length)
{
gpg_error_t err;
FILE *fp;
char *buf;
size_t buflen;
*r_buf = NULL;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
buf = NULL;
buflen = 0;
#define NCHUNK (1024*1024)
do
{
bufsize += NCHUNK;
buf = xtryrealloc (buf, bufsize);
if (!buf)
{
err = gpg_error_from_syserror ();
log_fatal ("can't allocate buffer: %s\n", gpg_strerror (err));
}
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading '[stdin]': %s\n", gpg_strerror (err));
xfree (buf);
return err;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return err;
}
if (fstat (fileno(fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
fclose (fp);
return err;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
{
err = gpg_error_from_syserror ();
log_fatal ("can't allocate buffer: %s\n", gpg_strerror (err));
}
if (fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
fclose (fp);
xfree (buf);
return err;
}
fclose (fp);
}
*r_buf = buf;
*r_length = buflen;
return 0;
}
/* Dispatch on file types. */
static gpg_error_t
signcode (const char *fname, char *file, size_t filelen)
{
if (filelen > 4 && !memcmp (file, "MSCF", 4))
{
log_error ("CAB files are not yet supported\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (filelen > 2 && !memcmp (file, "MZ", 2))
return pe_signcode (fname, file, filelen);
else if (filelen > 8 &&!memcmp (file, "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1", 8))
{
log_error ("MSI files are not yet supported\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else
{
log_error ("unknown file type\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
}
/*******************************************
*
* PE specific code
*
*******************************************/
static gpg_error_t
pe_get_indirect_data_blob (byte **blob, int *len, gcry_md_hd_t md,
char *file, unsigned int peheader, int pe32plus,
unsigned int sigpos)
{
/* byte *p; */
/* int hashlen, l; */
/* void *hash; */
/* SpcIndirectDataContent *idc; */
/* SpcPeImageData *pid; */
/* ASN1_OBJECT *dtype; */
/* idc = SpcIndirectDataContent_new(); */
/* idc->data->value = ASN1_TYPE_new(); */
/* idc->data->value->type = V_ASN1_SEQUENCE; */
/* idc->data->value->value.sequence = ASN1_STRING_new(); */
/* pid = SpcPeImageData_new(); */
/* ASN1_BIT_STRING_set(pid->flags, (unsigned char*)"0", 0); */
/* pid->file = get_obsolete_link(); */
/* l = i2d_SpcPeImageData(pid, NULL); */
/* p = OPENSSL_malloc(l); */
/* i2d_SpcPeImageData(pid, &p); */
/* p -= l; */
/* dtype = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, 1); */
/* SpcPeImageData_free(pid); */
/* idc->data->type = dtype; */
/* idc->data->value->value.sequence->data = p; */
/* idc->data->value->value.sequence->length = l; */
/* idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(EVP_MD_nid(md)); */
/* idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new(); */
/* idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL; */
/* hashlen = EVP_MD_size(md); */
/* hash = OPENSSL_malloc(hashlen); */
/* memset(hash, 0, hashlen); */
/* ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashlen); */
/* OPENSSL_free(hash); */
/* *len = i2d_SpcIndirectDataContent(idc, NULL); */
/* *blob = OPENSSL_malloc(*len); */
/* p = *blob; */
/* i2d_SpcIndirectDataContent(idc, &p); */
/* SpcIndirectDataContent_free(idc); */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
/* Main for PE files. */
static gpg_error_t
pe_signcode (const char *fname, char *file, size_t filesize)
{
gpg_error_t err;
unsigned int peheaderoff;
unsigned int magic;
int pe32plus; /* Flag */
unsigned int filelen; /* Used length of the file. */
unsigned int nrvas; /* Number of certificate resources. */
unsigned int sigoff, siglen;
unsigned int n;
char *buffer = NULL;
unsigned int bufsize;
gcry_md_hd_t hash = NULL;
estream_t outfile = NULL;
if (filesize < 64)
{
log_error ("DOS part of PE file is too short\n");
return gpg_error (GPG_ERR_TOO_SHORT);
}
peheaderoff = le32_to_uint (file+60);
if (filesize < peheaderoff + 160)
{
log_error ("corrupt PE file: %s\n", "offset to PE header to high");
return gpg_error (GPG_ERR_GENERAL);
}
if (memcmp (file + peheaderoff, "PE\0", 4))
{
log_error ("corrupt PE file: %s\n", "no PE marker");
return gpg_error (GPG_ERR_GENERAL);
}
magic = le16_to_uint (file + peheaderoff + 24);
switch (magic)
{
case 0x020b: pe32plus = 1; break;
case 0x010b: pe32plus = 0; break;
default:
log_error ("corrupt PE file: unknown magix 0x%04x\n", magic);
return gpg_error (GPG_ERR_GENERAL);
}
nrvas = le32_to_uint (file + peheaderoff + 116 + pe32plus*16);
if (nrvas < 5)
{
log_error ("not enough certificate resources in PE file (got %u)\n",
nrvas);
return gpg_error (GPG_ERR_GENERAL);
}
if (filesize < peheaderoff + 152 + pe32plus*16 + 4 + 4)
{
log_error ("corrupt PE file: %s\n",
"header too short to carry offset to the signature");
return gpg_error (GPG_ERR_GENERAL);
}
sigoff = le32_to_uint (file + peheaderoff + 152 + pe32plus*16);
siglen = le32_to_uint (file + peheaderoff + 152 + pe32plus*16 + 4);
if (sigoff && sigoff + siglen != filesize)
{
/* Since the fix for MS Bulletin MS12-024 we can assume that an
* existisng signature is the last part of the file. */
log_error ("corrupt PE file: current signature not at end of file\n");
return gpg_error (GPG_ERR_GENERAL);
}
/* Strip an existing signature. */
filelen = sigoff? sigoff : filesize;
/* Create a helper buffer. */
bufsize = 64 * 1024;
buffer = xtrymalloc (bufsize);
if (!buffer)
{
err = gpg_error_from_syserror ();
log_error ("error allocating help buffer: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_md_open (&hash, GCRY_MD_SHA256, 0);
if (err)
{
log_error ("error creating digest context: %s\n", gpg_strerror (err));
goto leave;
}
n = peheaderoff + 88;
gcry_md_write (hash, file, n);
es_write (outfile, file, n, NULL);
/* Zero out the checksum. */
memset (buffer, 0, 4);
es_write (outfile, buffer, 4, NULL);
n += 4;
gcry_md_write (hash, file + n, 60 + pe32plus*16);
es_write (outfile, file + n, 60 + pe32plus*16, NULL);
n += 60 + pe32plus*16;
/* Zero out the sigtable offset + len. */
memset (buffer, 0, 8);
es_write(outfile, buffer, 8, NULL);
n += 8;
log_assert (n < filelen);
gcry_md_write (hash, file + n, filelen - n);
es_write (outfile, file + n, filelen - n, NULL);
/* Zero pad the PE file to a 8 byte boundary. */
n = 8 - filelen % 8;
if (n > 0 && n != 8)
{
memset (buffer, 0, n);
gcry_md_write (hash, buffer, n);
es_write (outfile, buffer, n, NULL);
filelen += n;
/* Note that FILELEN might be larger than FILESIZE. */
}
/* Create the indirect data blob DER object. */
leave:
gcry_md_close (hash);
xfree (buffer);
return err;
}