view cinst/nssstore_win.c @ 449:023b565a02fc

Update certificate details on item selection change.
author Raimund Renkert <rrenkert@intevation.de>
date Wed, 23 Apr 2014 12:15:43 +0200
parents 17e1c8f37d72
children a9da8e4eeff7
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.
 */
#ifdef WIN32

/* @file
   @brief Windows implementation of nssstore process control.

   The windows process will write an instructions file for
   the mozilla process into the current users temp directory
   (%APPDATA%/Local/Temp/) and start the NSS installation process to
   exectute those instructions. If the current process is elevated
   the NSS process is run with a restricted token.
   The execution of the mozilla process is not monitored.
   You have to refer to the system log to check which certificates were
   installed / removed by it.

   If the installation process is running elevated it
   will create the file in the ProgramData directory in
   a subdirectory with the defined application name.
   %PROGRAMDATA%/$APPLICATION_NAME
   with the file name:
    current_selection.txt
   The folder will have restricted permissions so
   that only Administrators are allowed to access it.

   Additionally if this process is Elevated it also starts the
   NSS installation process in default profile mode once to change
   the default NSS certificate databases for new profiles.

   The process then adds a new RunOnce registry key
   for each user on the system that executes the NSS installation
   process on login to make sure it is launched once in the
   security context of that user.
*/

#include <windows.h>
#include <stdio.h>
#include <stdbool.h>
#include <userenv.h>
#include <io.h>
#include <accctrl.h>
#include <aclapi.h>
#include <shlobj.h>

#include "logging.h"
#include "util.h"
#include "strhelp.h"

#ifndef APPNAME
#define APPNAME L"cinst"
#endif

#ifndef SELECTION_FILE_NAME
#define SELECTION_FILE_NAME L"currently_selected.txt"
#endif

#define PROCESS_TIMEOUT 30000

#define PRINTLASTERROR(msg) \
  char *my_error = getLastErrorMsg(); \
      if (my_error) { \
        DEBUGPRINTF(msg " : %s\n", my_error); \
        ERRORPRINTF(msg" : %s\n", my_error); \
        free (my_error); \
      } \
  DEBUGPRINTF ("Failed to get error information\n");

/**@brief Write strv of instructions to a handle
*
* Writes the null terminated list of instructions to
* the handle.
*
* @param [in] base64 encoded der certificates to write
* @param [in] write_handle to write to
* @param [in] remove weather the certificate should be installed or removed
*
* @returns true on success, false on failure
*/
static bool
write_instructions(char **certificates, HANDLE write_handle,
                   bool remove)
{
  int i = 0;
  int cHandle = -1;
  FILE *write_stream = NULL;

  if (!certificates)
    {
      return true;
    }

  cHandle = _open_osfhandle ((intptr_t)write_handle, 0);

  if (cHandle == -1)
    {
      ERRORPRINTF ("Failed to open write handle.\n");
    }

  write_stream = _fdopen(cHandle, "w");
  for (i = 0; certificates[i]; i++)
    {
      int ret = 0;
      if (remove)
        ret = fprintf (write_stream, "R:%s\n", certificates[i]);
      else
        ret = fprintf (write_stream, "I:%s\n", certificates[i]);

      if (ret <= 0)
        {
          DEBUGPRINTF ("Failed to write everything.\n");
          break;
        }
    }

  return true;
}

/**@brief Start the process to install / remove
*
* Starts the NSS installation process for the current user
*
* @param [in] selection_file filename of the file containing
*             the users installall / remove selection.
*
* @returns true on success, false on error.
*/
static bool
start_procces_for_user (wchar_t *selection_file)
{
  HANDLE hToken = NULL;/*,
         hChildToken = NULL;*/
  /* TODO get this as absolute path based on current module location */
  LPWSTR lpApplicationName = L"mozilla.exe",
         lpCommandLine;
  PROCESS_INFORMATION piProcInfo = {0};
  STARTUPINFOW siStartInfo = {0};
  BOOL success = FALSE;
  size_t cmd_line_len = 0;

  if (!selection_file)
    {
      ERRORPRINTF ("Invalid call\n");
      return false;
    }

  /* set up handles. stdin and stdout go to the same stdout*/
  siStartInfo.cb = sizeof (STARTUPINFO);

  if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
    {
      PRINTLASTERROR("Failed to get current handle.");
      return false;
    }
  /* TODO! if (is_elevated())
     restrict token -> hChildToken
  */

  cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 2;
  lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t));

  wcscpy_s (lpCommandLine, cmd_line_len, lpApplicationName);
  wcscpy_s (lpCommandLine, cmd_line_len, L" ");
  wcscat_s (lpCommandLine, cmd_line_len, selection_file);

  DEBUGPRINTF ("Starting %S with command line %S\n", lpApplicationName, lpCommandLine);

  success = CreateProcessAsUserW (hToken,
                                  lpApplicationName,
                                  lpCommandLine, /* Commandline */
                                  NULL, /* Process attributes. Take hToken */
                                  NULL, /* Thread attribues. Take hToken */
                                  FALSE, /* Inherit Handles */
                                  0, /* Creation flags. */
                                  NULL, /* Inherit environment */
                                  NULL, /* Current working directory */
                                  &siStartInfo,
                                  &piProcInfo);
  xfree (lpCommandLine);
  if (!success)
    {
      PRINTLASTERROR ("Failed to create process.\n");
      return false;
    }

  if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0)
    {
      /* Should not happen... */
      ERRORPRINTF ("Failed to wait for process.\n");
      if (piProcInfo.hProcess)
        CloseHandle (piProcInfo.hProcess);
      if (piProcInfo.hThread)
        CloseHandle (piProcInfo.hThread);
      return false;
    }
  if (piProcInfo.hProcess)
    CloseHandle (piProcInfo.hProcess);
  if (piProcInfo.hThread)
    CloseHandle (piProcInfo.hThread);
  return true;
}

/**@brief Create a directory with restricted access rights
  *
  * This creates a security attributes structure that restricts
  * write access to the Administrators group but allows everyone to read files
  * in that directory.
  * Basically a very complicated version of mkdir path -m 644
  *
  * If the directory exists the permissions of that directory are checked if
  * they are acceptable and true or false is returned accordingly.
  *
  * Code based on msdn example:
  * http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx
  *
  * @param[in] path Path of the directory to create
  *
  * @returns true on success of if the directory exists, false on error
  */
bool
create_restricted_directory (LPWSTR path)
{
  bool retval = false;
  PSID everyone_SID = NULL,
       admin_SID = NULL;
  PACL access_control_list = NULL;
  PSECURITY_DESCRIPTOR descriptor = NULL;
  EXPLICIT_ACCESS explicit_access[2];
  SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY},
                           admin_identifier = {SECURITY_NT_AUTHORITY};
  SECURITY_ATTRIBUTES security_attributes;

  ZeroMemory(&security_attributes, sizeof(security_attributes));
  ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS));

  /* Create a well-known SID for the Everyone group. */
  if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */
                               1, /* subauthorties count */
                               SECURITY_WORLD_RID, /* Only one authority */
                               0, 0, 0, 0, 0, 0, 0, /* No other authorities*/
                               &everyone_SID))
    {
      PRINTLASTERROR ("Failed to allocate world sid.\n");
      return false;
    }

  /* Initialize the first EXPLICIT_ACCESS structure for an ACE.
     to allow everyone read access */
  explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */
  explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */
  explicit_access[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* make it stick */
  explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
  explicit_access[0].Trustee.ptstrName  = (LPTSTR) everyone_SID;

  /* Create the SID for the BUILTIN\Administrators group. */
  if(!AllocateAndInitializeSid(&admin_identifier,
                               2,
                               SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */
                               DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */
                               0, 0, 0, 0, 0, 0, /* No other */
                               &admin_SID))
    {
      PRINTLASTERROR ("Failed to allocate admin sid.");
      goto done;
    }

  /* explicit_access[1] grants admins full rights for this object and inherits
     it to the children */
  explicit_access[1].grfAccessPermissions = GENERIC_ALL;
  explicit_access[1].grfAccessMode = SET_ACCESS;
  explicit_access[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
  explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
  explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID;

  /* Set up the ACL structure. */
  if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list))
    {
      PRINTLASTERROR ("Failed to set up Acl.");
      goto done;
    }

  /* Initialize a security descriptor */
  descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
                                                 SECURITY_DESCRIPTOR_MIN_LENGTH);
  if (descriptor == NULL)
    {
      PRINTLASTERROR("Failed to allocate descriptor.");
      goto done;
    }

  if (!InitializeSecurityDescriptor(descriptor,
                                    SECURITY_DESCRIPTOR_REVISION))
    {
      PRINTLASTERROR("Failed to initialize descriptor.");
      goto done;
    }

  /* Now we add the ACL to the the descriptor */
  if (!SetSecurityDescriptorDacl(descriptor,
                                 TRUE,     /* bDaclPresent flag */
                                 access_control_list,
                                 FALSE))   /* not a default DACL */
    {
      PRINTLASTERROR("Failed to set security descriptor.");
      goto done;
    }

  /* Finally set up the security attributes structure */
  security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
  security_attributes.lpSecurityDescriptor = descriptor;
  security_attributes.bInheritHandle = FALSE;

  /* Use the security attributes to create the directory */
  if (!CreateDirectoryW(path, &security_attributes))
    {
      DWORD err = GetLastError();
      if (err == ERROR_ALREADY_EXISTS)
        {
          /* Verify that the directory has the correct rights */
          // TODO
          retval = true;
          goto done;
        }
      ERRORPRINTF ("Failed to create directory. Err: %lu", err);
    }
  retval = true;

done:

  if (everyone_SID)
    FreeSid(everyone_SID);
  if (admin_SID)
    FreeSid(admin_SID);
  if (access_control_list)
    LocalFree(access_control_list);
  if (descriptor)
    LocalFree(descriptor);

  return retval;
}

/**@brief Writes the selection file containing the instructions
 *
 * If the process is running elevated the instructions are
 * written to the global ProgramData directory otherwise
 * they are written in the temporary directory of the current user.
 *
 * If the return value is not NULL it needs to be freed by the caller.
 * The returned path will contain backslashes as directory seperators.
 *
 * @param[in] to_install Certificates that should be installed
 * @param[in] to_remove Certificates that should be removed
 * @returns pointer to the absolute filename of the selection file or NULL
 */
wchar_t *
write_selection_file (char **to_install, char **to_remove)
{
  wchar_t *folder_name = NULL,
          *path = NULL;
  bool elevated = is_elevated();
  HRESULT result = E_FAIL;
  HANDLE hFile = NULL;
  size_t path_len;

  if (!elevated)
    {
      /* TODO */
    }

  result = SHGetKnownFolderPath (&FOLDERID_ProgramData, /* Get program data dir */
                                 KF_FLAG_CREATE | /* Create if it does not exist */
                                 KF_FLAG_INIT, /* Initialize it if created */
                                 INVALID_HANDLE_VALUE, /* Get it for the default user */
                                 &folder_name);

  if (result != S_OK)
    {
      PRINTLASTERROR ("Failed to get folder path");
      return NULL;
    }

  path_len = wcslen (folder_name) + wcslen (APPNAME) + 2; /* path + dirsep + \0 */
  path_len += wcslen (SELECTION_FILE_NAME) + 1; /* filename + dirsep */

  if (path_len >= MAX_PATH)
    {
      /* We could go and use the full 32,767 characters but this
         should be a very weird setup if this is neccessary. */
      ERRORPRINTF ("Path too long.\n");
      return NULL;
    }

  path = xmalloc (path_len * sizeof (wchar_t));
  if (wcscpy_s (path, path_len, folder_name) != 0)
    {
      ERRORPRINTF ("Failed to copy folder name.\n");

      CoTaskMemFree (folder_name);

      return NULL;
    }

#if 0
  CoTaskMemFree (folder_name);
#endif

  if (wcscat_s (path, path_len, L"\\") != 0)
    {
      ERRORPRINTF ("Failed to cat dirsep.\n");
      xfree(path);
      return NULL;
    }

  if (wcscat_s (path, path_len, APPNAME) != 0)
    {
      ERRORPRINTF ("Failed to cat appname.\n");
      xfree(path);
      return NULL;
    }

  /* Security: if someone has created this directory before
     it might be a symlink to another place that a users
     wants us to grant read access to or makes us overwrite
     something */
  if(!create_restricted_directory (path))
    {
      ERRORPRINTF ("Failed to create directory\n");
      xfree(path);
      return NULL;
    }

  if (wcscat_s (path, path_len, L"\\") != 0)
    {
      ERRORPRINTF ("Failed to cat dirsep.\n");
      xfree(path);
      return NULL;
    }

  if (wcscat_s (path, path_len, SELECTION_FILE_NAME) != 0)
    {
      ERRORPRINTF ("Failed to cat filename.\n");
      xfree(path);
      return NULL;
    }

  hFile = CreateFileW(path,
                      GENERIC_WRITE,
                      0, /* don't share */
                      NULL, /* use the security attributes from the folder */
                      OPEN_ALWAYS,
                      0,
                      NULL);

  if (hFile == INVALID_HANDLE_VALUE)
    {
      ERRORPRINTF ("Failed to create file\n");
      xfree(path);
      return NULL;
    }
  if (!write_instructions (to_install, hFile, false))
    {
      ERRORPRINTF ("Failed to write install instructions.\n");
      CloseHandle(hFile);
      xfree(path);
      return NULL;
    }
  if (!write_instructions (to_remove, hFile, true))
    {
      ERRORPRINTF ("Failed to write remove instructions.\n");
      CloseHandle(hFile);
      xfree(path);
      return NULL;
    }
  CloseHandle(hFile);

  return path;
}

int
write_stores_nss (char **to_install, char **to_remove)
{
  wchar_t *selection_file_name = NULL;

  selection_file_name = write_selection_file (to_install, to_remove);
  if (!selection_file_name)
    {
      ERRORPRINTF ("Failed to write instructions.\n");
      return -1;
    }

  DEBUGPRINTF ("Wrote selection file. Loc: %S\n", selection_file_name);

  /* TODO loop over all users create startup entries for them*/

  if (!start_procces_for_user (selection_file_name))
    {
      ERRORPRINTF ("Failed to run NSS installation process.\n");
      xfree(selection_file_name);
      return -1;
    }
  xfree(selection_file_name);
  return 0;
}

#endif

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