view common/binverify.c @ 1316:ff9cd05e861e

(issue166) Fix certificiate removal The index that should be removed came from the filter proxy model and did not map to the real index. This was broken.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 13 Oct 2014 17:23:35 +0200
parents 3cd8dd706aaa
children 28885e8c891f
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.
 */

#include "binverify.h"

#include "strhelp.h"
#include "logging.h"
#include "listutil.h"
#ifdef RELEASE_BUILD
#include "pubkey-release.h"
#else
#include "pubkey-test.h"
#endif

bin_verify_result
verify_binary(const char *filename, size_t name_len)
{
  if (!filename || !name_len) {
    bin_verify_result retval;
    retval.fptr = NULL;
    retval.result = VerifyUnknownError;
    return retval;
  }

#ifdef WIN32
  return verify_binary_win(filename, name_len);
#else
  return verify_binary_linux(filename, name_len);
#endif
}

#ifdef WIN32

#include <polarssl/x509_crt.h>

#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>


/** @brief Check if the certificate @a pCCertContext is pinned
  *
  * Compares the certificate's binary data (public key and attributes)
  * with each other to validate that the certificate pCCertContext has
  * exactly the same data as the builtin public certificate.
  *
  * @param[in] pCCertContext pointer to the certificate to check
  *
  * @returns true if the certificate matches, false otherwise.
  */
static bool
check_certificate (PCCERT_CONTEXT pCCertContext)
{
  x509_crt codesign_cert;
  int ret = 0;
  DWORD dwI = 0;
  bool retval = false;

  if (pCCertContext == NULL)
    {
      ERRORPRINTF ("Invalid call to check_certificate");
      return false;
    }

  x509_crt_init(&codesign_cert);

  /* Parse the pinned certificate */
  ret = x509_crt_parse(&codesign_cert,
                       public_key_codesign_pem,
                       public_key_codesign_pem_size);
  if (ret != 0)
    {
      ERRORPRINTF ("x509_crt_parse failed with -0x%04x\n\n", -ret);
      goto done;
    }

  if (codesign_cert.raw.len != pCCertContext->cbCertEncoded ||
      codesign_cert.raw.len <= 0)
    {
      ERRORPRINTF ("Certificate size mismatch");
      goto done;
    }

  /* Check that the certificate is exactly the same as the pinned one */
  for (dwI = 0; dwI < pCCertContext->cbCertEncoded; dwI++)
    {
      if (pCCertContext->pbCertEncoded[dwI] != codesign_cert.raw.p[dwI])
        {
          ERRORPRINTF ("Certificate content mismatch");
          goto done;
        }
    }

  retval = true;

done:
  x509_crt_free(&codesign_cert);
  return retval;
}

bin_verify_result
verify_binary_win(const char *filename, size_t name_len)
{
  bin_verify_result retval;
  WCHAR *filenameW = NULL;
  BOOL result = FALSE;
  DWORD dwEncoding = 0,
        dwContentType = 0,
        dwFormatType = 0,
        dwSignerInfoSize = 0;
  HCERTSTORE hStore = NULL;
  HCRYPTMSG hMsg = NULL;
  PCERT_INFO pSignerCert = NULL;
  PCCERT_CONTEXT pSignerCertContext = NULL;
  FILE *fptr = NULL;
  size_t data_size = 0;
  char *data = NULL;
  int ret = -1;
  CRYPT_INTEGER_BLOB blob;

  retval.result = VerifyUnknownError;
  retval.fptr = NULL;

  if (!filename || name_len > MAX_PATH || strlen(filename) != name_len)
    {
      ERRORPRINTF ("Invalid parameters\n");
      return retval;
    }

  ret = read_file(filename, &data, &data_size, MAX_VALID_BIN_SIZE, &fptr);

  if (ret != 0)
    {
      ERRORPRINTF ("Read file failed with error: %i\n", ret);
      retval.result = VerifyReadFailed;
      return retval;
    }
  blob.cbData = (DWORD) data_size;
  blob.pbData = (PBYTE) data;

  result = CryptQueryObject (CERT_QUERY_OBJECT_BLOB,
                             &blob,
                             CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
                             CERT_QUERY_FORMAT_FLAG_BINARY,
                             0,
                             &dwEncoding,
                             &dwContentType,
                             &dwFormatType,
                             &hStore,
                             &hMsg,
                             NULL);

  if (!result || !hMsg)
    {
      PRINTLASTERROR ("Failed to query crypto object");
      retval.result = VerifyReadFailed;
      goto done;
    }

  /* Get the cert info so that we can look up the signer in the store later */
  if (CryptMsgGetParam(hMsg,
                       CMSG_SIGNER_CERT_INFO_PARAM,
                       0,
                       NULL,
                       &dwSignerInfoSize) && dwSignerInfoSize > 0)
    {
      pSignerCert = xmalloc (dwSignerInfoSize);
    }
  else
    {
      ERRORPRINTF ("Failed to get signer cert size.");
      retval.result = VerifyUnknownError;
      goto done;
    }

  if (!(CryptMsgGetParam(hMsg,
                         CMSG_SIGNER_CERT_INFO_PARAM,
                         0,
                         pSignerCert,
                         &dwSignerInfoSize)))
    {
      ERRORPRINTF ("Failed to get signer cert.");
      retval.result = VerifyUnknownError;
      goto done;
    }

  pSignerCertContext = CertGetSubjectCertificateFromStore(
                         hStore,
                         PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
                         pSignerCert);

  if (!pSignerCertContext)
    {
      ERRORPRINTF ("Failed to find signer cert in store.");
      retval.result = VerifyUnknownError;
      goto done;
    }

  /* Verify that the signature is actually valid */
  if(!CryptMsgControl(hMsg,
                      0,
                      CMSG_CTRL_VERIFY_SIGNATURE,
                      pSignerCertContext->pCertInfo))
    {
      ERRORPRINTF ("The signature is invalid. \n");
      retval.result = VerifyInvalidSignature;
      syslog_error_printf ("Software update embedded signature is invalid.");
      goto done;
    }

  if(check_certificate(pSignerCertContext))
    {
      DEBUGPRINTF ("Valid signature with pinned certificate.");
      retval.result = VerifyValid;
      retval.fptr = fptr;
      goto done;
    }
  else
    {
      ERRORPRINTF ("Certificate mismatch. \n");
      retval.result = VerifyInvalidCertificate;
      syslog_error_printf ("Software update embedded signature "
                           "created with wrong certificate.");
      goto done;
    }

done:
  xfree(data);
  xfree(filenameW);
  xfree(pSignerCert);

  if (retval.result != VerifyValid)
    {
      fclose(fptr);
    }

  if(pSignerCertContext)
    {
      CertFreeCertificateContext(pSignerCertContext);
    }
  if (hStore)
    {
      CertCloseStore(hStore, 0);
    }
  if (hMsg)
    {
      CryptMsgClose(hMsg);
    }
  return retval;
}
#else /* WIN32 */

#ifndef __clang__
#pragma GCC diagnostic ignored "-Wconversion"
#endif
/* Polarssl mh.h contains a conversion which gcc warns about */
#include <polarssl/pk.h>
#include <polarssl/base64.h>
#include <polarssl/sha256.h>
#include <polarssl/error.h>
#include <polarssl/x509_crt.h>
#ifndef __clang__
#pragma GCC diagnostic pop
#endif

bin_verify_result
verify_binary_linux(const char *filename, size_t name_len)
{
  int ret = -1;
  const size_t sig_b64_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8 * 4 / 3;
  char *data = NULL,
        signature_b64[sig_b64_size + 1];
  size_t data_size = 0,
         sig_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8;
  unsigned char signature[sig_size],
           hash[32];
  FILE *fptr = NULL;

  bin_verify_result retval;
  retval.result = VerifyUnknownError;
  retval.fptr = NULL;
  x509_crt codesign_cert;

  if (strnlen(filename, name_len + 1) != name_len || name_len == 0)
    {
      ERRORPRINTF ("Invalid call to verify_binary_linux\n");
      retval.result = VerifyUnknownError;
      return retval;
    }

  ret = read_file(filename, &data, &data_size, MAX_VALID_BIN_SIZE, &fptr);

  if (ret != 0)
    {
      ERRORPRINTF ("Read file failed with error: %i\n", ret);
      retval.result = VerifyReadFailed;
      return retval;
    }

  /* Fetch the signature from the end of data */
  if (data_size < sig_b64_size + 5)
    {
      ERRORPRINTF ("File to small to contain a signature.\n");
      retval.result = VerifyInvalidSignature;
      goto done;
    }

  if (data[data_size - sig_b64_size - 2] != ':' ||
      data[data_size - sig_b64_size - 3] != 'S' ||
      data[data_size - sig_b64_size - 4] != '\n'||
      data[data_size - sig_b64_size - 5] != '\r')
    {
      ERRORPRINTF ("Failed to find valid signature line.\n");
      retval.result = VerifyInvalidSignature;
      goto done;
    }

  strncpy(signature_b64, data + (data_size - sig_b64_size - 1), sig_b64_size);
  signature_b64[sig_b64_size] = '\0';

  ret = base64_decode(signature, &sig_size,
                      (unsigned char *)signature_b64, sig_b64_size);

  if (ret != 0 || sig_size != TRUSTBRIDGE_RSA_KEY_SIZE / 8)
    {
      ERRORPRINTF ("Base 64 decode failed with error: %i\n", ret);
      goto done;
    }

  /* Hash is calculated over the data without the signature at the end. */
  sha256((unsigned char *)data, data_size - sig_b64_size - 5, hash, 0);

  x509_crt_init(&codesign_cert);

  /* Parse the pinned certificate */
  ret = x509_crt_parse(&codesign_cert,
                       public_key_codesign_pem,
                       public_key_codesign_pem_size);
  if (ret != 0)
    {
      char errbuf[1020];
      polarssl_strerror(ret, errbuf, 1020);
      errbuf[1019] = '\0'; /* Just to be sure */
      ERRORPRINTF ("x509_crt_parse failed with -0x%04x\n%s\n", -ret, errbuf);
      x509_crt_free(&codesign_cert);
      retval.result = VerifyUnknownError;
      goto done;
    }

  ret = pk_verify(&codesign_cert.pk, POLARSSL_MD_SHA256, hash, 0,
                  signature, sig_size);

  if (ret != 0)
    {
      char errbuf[1020];
      polarssl_strerror(ret, errbuf, 1020);
      errbuf[1019] = '\0'; /* Just to be sure */
      ERRORPRINTF ("pk_verify failed with -0x%04x\n %s\n", -ret, errbuf);
      x509_crt_free(&codesign_cert);
      retval.result = VerifyInvalidSignature;
      goto done;
    }
  x509_crt_free(&codesign_cert);

  retval.result = VerifyValid;
  retval.fptr = fptr;

done:
  if (retval.result != VerifyValid)
    {
      if (fptr)
        {
          fclose(fptr);
        }
    }
  xfree (data);
  return retval;
}

#endif /* WIN32 */

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