From e114a715d6183079ce51164315705d1d5ce00807 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 30 Apr 2019 10:05:39 +0200 Subject: [PATCH] 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 --- tools/Makefile.am | 6 +- tools/gpg-signcode.c | 597 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 tools/gpg-signcode.c diff --git a/tools/Makefile.am b/tools/Makefile.am index fb37c05e7..f21e859b5 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -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 diff --git a/tools/gpg-signcode.c b/tools/gpg-signcode.c new file mode 100644 index 000000000..b482f1fc6 --- /dev/null +++ b/tools/gpg-signcode.c @@ -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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include + +#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; +}