view cinst/main.c @ 1084:b8fb6bf7f980

(issue118) Add signature check for cinst.exe and mozilla.exe
author Andre Heinecke <andre.heinecke@intevation.de>
date Thu, 11 Sep 2014 15:17:39 +0200
parents 1e429faf7c84
children fd7d04bb37cb
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
/**
 * @file main.c
 * @brief Main entry point for the cinst process.
 *
 *  The cinst process may or may not be run with elevated
 *  privileges. When run with elevated privileges this
 *  process will modify system wide certificate stores.
 *  Otherwise only the users certificate stores are modified.
 *
 *  The first parameter to this process should be list=\<file_name\>
 *  of the certificate list to work on. The second parameter should
 *  be choices=\<choices_file_name\>|uninstall
 *
 *  choices_file_name should be the absolute path to an
 *  choices file formatted as:
 *
 *  I:\<certificate\><BR>
 *  R:\<certificate\>
 *
 *  Line breaks can be system dependent in the Choices file.
 *
 *  It will only execute the choices if the
 *  I and R choices are also part of the signed
 *  certificate list. The signature is validated with the
 *  built in key.
 *
 *  The special instruction "uninstall" will cause the installer
 *  to remove all certificates (Even those marked with I) that
 *  are part of the list.
 *
 *  For more verbose debug output add --debug to the call.
 *
 **/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>

#include "strhelp.h"
#include "listutil.h"
#include "logging.h"
#include "errorcodes.h"
#include "windowsstore.h"
#include "nssstore.h"

/* The certificate list + choices may only be so long as
 * twice the accepted certificatelist size */
#define MAX_INPUT_SIZE MAX_LINE_LENGTH * MAX_LINES * 2

/* @brief Read stdin into data structures.
 *
 * Reads choices from an input file into the to_install
 * and to_remove buffers.
 *
 * Lines starting with I: are treated as install choices.
 * Lines starting with R: are treated as remove choices.
 * Other lines are ignored.
 *
 * Terminates in OOM conditions.
 *
 * The caller needs to free the memory allocated by this function
 * even when an error is returned.
 *
 * @param[in] file_name absolute path to the choices file.
 * @param[out] to_install strv of installation choices or NULL
 * @param[out] to_remove strv of remove choices or NULL
 *
 * @returns: 0 on success. An error code otherwise.
 */
static int
read_choices_file (char *file_name, char ***to_install,
                   char ***to_remove)
{
  int lines_read = 0;
  char buf[MAX_LINE_LENGTH + 2];
  FILE *f = NULL;
  long file_size;

  if (*to_install || *to_remove)
    {
      ERRORPRINTF ("Error invalid parameters.\n");
      return -1;
    }

  f = fopen (file_name, "rb");
  if (f == NULL)
    {
      ERRORPRINTF ("Failed to open file: %s\n", file_name);
      return ERR_NO_INSTRUCTIONS;
    }

  fseek (f, 0, SEEK_END);
  file_size = ftell (f);
  if (file_size <= 0)
    {
      fclose (f);
      ERRORPRINTF ("File size error: %s\n", file_name);
      return ERR_NO_INSTRUCTIONS;
    }

  fseek (f, 0, SEEK_SET);

  if (file_size + 1 == 0)
    {
      fclose (f);
      ERRORPRINTF ("File seek error: %s\n", file_name);
      return ERR_INVALID_INSTRUCTIONS;
    }

  while (fgets (buf, MAX_LINE_LENGTH + 1, f) )
    {
      size_t len = strlen (buf); /* fgets ensures buf is terminated */
      if (len <= 3)
        {
          ERRORPRINTF ("Line too short.\n");
          fclose (f);
          return ERR_INVALID_INPUT;
        }
      if (lines_read++ > MAX_LINES)
        {
          ERRORPRINTF ("Too many lines\n");
          fclose (f);
          return ERR_TOO_MUCH_INPUT;
        }
      if (*buf == 'I')
        {
          char *trimmed = buf+2;
          /* Remove leading I: and trailing whitespace */
          str_trim(&trimmed);
          strv_append (to_install, trimmed, strlen(trimmed));
          continue;
        }
      if (*buf == 'R')
        {
          char *trimmed = buf+2;
          /* Remove leading R: and trailing whitespace */
          str_trim(&trimmed);
          strv_append (to_remove, trimmed, strlen(trimmed));
          continue;
        }
    }

  fclose (f);
  return 0;
}

/** @brief Check that the insturctions match to the list
 *
 * Only certificates part of the certificate_list are allowed
 * for installation.
 *
 * @param[in] all_certs strv of all valid certificates in a list
 * @param[in] to_validate strv of choices
 *
 * @returns 0 on success, an error otherwise
 */
int
validate_choices (char **all_certs, char **to_validate)
{
  int i = 0, j = 0;

  if (!all_certs || strv_length (all_certs) < 1)
    {
      /* Invalid parameters */
      return -1;
    }

  if (to_validate == NULL)
    {
      /* Nothing is valid */
      return 0;
    }

  for (i = 0; to_validate[i]; i++)
    {
      bool found = false;
      for (j = 0; all_certs[j]; j++)
        {
          if (strncmp (to_validate[i], all_certs[j], MAX_LINE_LENGTH) ==
              0)
            {
              found = true;
              break;
            }
        }
      if (!found)
        {
          DEBUGPRINTF ("Failed to find certificate; \n%s\n", to_validate[i]);
          return ERR_INVALID_INSTRUCTIONS;
        }
    }

  return 0;
}

#ifdef DO_RELEASE_BUILD
bool g_debug = false;
#else
bool g_debug = true;
#endif

int
main (int argc, char **argv)
{
  /* TODO handle wchar arguments on Windows or do conversion dance */
  char **to_install = NULL,
         **to_remove = NULL,
           **all_valid_certs = NULL;
  int ret = -1;

  char *certificate_list = NULL,
        *certificate_file_name = NULL,
         *choices_file_name = NULL;
  size_t list_len = 0;
  list_status_t list_status;
  bool do_uninstall = false;

  /* Some very static argument parsing. list= and choices= is only
     added to make it more transparent how this programm is called if
     a user looks at the detailed uac dialog. */
  if ((argc != 3 && argc != 4) || strncmp(argv[1], "list=", 5) != 0 ||
      strncmp(argv[2], "choices=", 8) != 0)
    {
      ERRORPRINTF ("Invalid arguments.\n"
                   "Expected arguments: list=<certificate_list> \n"
                   "                    choices=<choices_file>|uninstall\n"
                   "Optional: --debug\n");
      return ERR_INVALID_PARAMS;
    }

  certificate_file_name = strchr(argv[1], '=') + 1;
  choices_file_name = strchr(argv[2], '=') + 1;

  if (argc == 4 && strncmp(argv[3], "--debug", 7) == 0)
    {
      g_debug = true;
    }

  if (!certificate_file_name || !choices_file_name)
    {
      ERRORPRINTF ("Invalid arguments.\n"
                   "Expected arguments: list=<certificate_list> \n"
                   "                    choices=<choices_file>|uninstall\n");
      return ERR_INVALID_PARAMS;
    }

  if (strncmp(choices_file_name, "uninstall", 9) == 0)
    {
      do_uninstall = true;
      choices_file_name = NULL;
    }

  list_status = read_and_verify_list (certificate_file_name, &certificate_list,
                                      &list_len);

  if (list_status != Valid)
    {
      if (list_status == InvalidSignature)
        {
          ERRORPRINTF ("Failed to verify signature.\n");
          return ERR_INVALID_SIGNATURE;
        }

      ERRORPRINTF ("Failed to read certificate list.\n");
      return ERR_INVALID_INPUT_NO_LIST;
    }

  all_valid_certs = get_certs_from_list (certificate_list, list_len);
  free (certificate_list);

  if (!all_valid_certs)
    {
      /* Impossible */
      return -1;
    }


  /* For uninstall we are done now */
  if (do_uninstall)
    {
#ifdef WIN32
      ret = write_stores_win (NULL, all_valid_certs);
      if (ret != 0)
        {
          ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret);
        }
#endif
      ret = write_stores_nss (NULL, all_valid_certs);
      strv_free (all_valid_certs);
      return ret;
    }

  ret = read_choices_file (choices_file_name, &to_install,
                           &to_remove);

  if (ret)
    {
      ERRORPRINTF ("Failed to read choices file\n");
      return ret;
    }

  if (!strv_length (to_install) && !strv_length (to_remove) )
    {
      ERRORPRINTF ("Failed to read choices file\n");
      return ERR_NO_INSTRUCTIONS;
    }

  /* Check that the choices are ok to execute */
  if (to_install)
    {
      ret = validate_choices (all_valid_certs, to_install);
      if (ret)
        {
          ERRORPRINTF ("Failed to validate choices\n");
          return ret;
        }
    }

  if (to_remove)
    {
      ret = validate_choices (all_valid_certs, to_remove);
      if (ret)
        {
          ERRORPRINTF ("Failed to validate removal choices\n");
          return ret;
        }
    }

  strv_free (all_valid_certs);

#ifdef WIN32
  ret = write_stores_win (to_install, to_remove);
  if (ret != 0)
    {
      ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret);
    }
#endif
  ret = write_stores_nss (to_install, to_remove);
  if (ret != 0)
    {
      ERRORPRINTF ("Failed to write nss stores");
    }

  /* Make valgrind happy */
  strv_free (to_install);
  strv_free (to_remove);

  return 0;
}

http://wald.intevation.org/projects/trustbridge/