view common/binverify.c @ 1395:a2574a029322

Fix Base 64 signature size calculation. If the signature byte size is not equally dividable by three the base 64 encoding needs three additional bytes. The value is now fixed to avoid such errors in the future.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 26 Jan 2015 13:17:32 +0100
parents f3e2df6b49ba
children
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"
#include "pubkey.h"

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;
    retval.sig_time = 0;
    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;
}

time_t
systemtime_to_time_t (SYSTEMTIME *systemTime)

{
  LARGE_INTEGER jan1970FT = {{0}};
  jan1970FT.QuadPart = 116444736000000000LL; // january 1st 1970 well known value
  LARGE_INTEGER utcFT = {{0}};

  SystemTimeToFileTime(systemTime, (FILETIME*)&utcFT);

  __int64 utcDosTime = (utcFT.QuadPart - jan1970FT.QuadPart)/10000000;

  return (time_t)utcDosTime;
}


time_t
get_signature_time (HCRYPTMSG hMsg)
{
    FILETIME lft, ft;
    SYSTEMTIME st;
    DWORD dwData = 0,
          n = 0,
          dwSignerInfo = 0;
    PCMSG_SIGNER_INFO pSignerInfo = NULL;

    time_t ret = -1;

    if (!hMsg)
      {
        return -1;
      }

    // Get signer information size.
    if (!CryptMsgGetParam(hMsg,
                          CMSG_SIGNER_INFO_PARAM,
                          0,
                          NULL,
                          &dwSignerInfo))
      {
        ERRORPRINTF ("Failed to get signer info size.");
        return -1;
      }
    pSignerInfo = xmalloc (dwSignerInfo);

    if (!CryptMsgGetParam(hMsg,
                          CMSG_SIGNER_INFO_PARAM,
                          0,
                          (PVOID)pSignerInfo,
                          &dwSignerInfo))
      {
        ERRORPRINTF ("Failed to get signer info.");
        goto done;
      }


    // Loop through authenticated attributes and find
    // szOID_RSA_signingTime OID.
    for (n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
      {
        if (lstrcmpA(szOID_RSA_signingTime,
                     pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
          {
            dwData = sizeof(ft);
            if (!CryptDecodeObject((X509_ASN_ENCODING | PKCS_7_ASN_ENCODING),
                                   szOID_RSA_signingTime,
                                   pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
                                   pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
                                   0,
                                   (PVOID)&ft,
                                   &dwData))
              {
                PRINTLASTERROR ("Failed to decode time: ");
                break;
              }

            // Convert to local time.
            FileTimeToLocalFileTime(&ft, &lft);
            FileTimeToSystemTime(&lft, &st);

            ret = systemtime_to_time_t(&st);
            break;
          }
      }

done:
    xfree(pSignerInfo);

    return ret;
}

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;
      retval.sig_time = get_signature_time (hMsg);
      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
#include <stdlib.h>

#define SIG_DT_MARKER "\r\nS_DT:"

/** This function is only intended to be used on well formatted input
  * after verifification as it makes some hard assumptions what
  * follows the SIG_DT_MARKER*/
time_t
get_signature_time (char *data, size_t data_size)
{
  char *p = NULL,
       *end = NULL,
       *buf = NULL;
  long lSigTime = 0;
  size_t len = 0;


  /** Look for a DOS linebreak followed by an S_DT: */
  size_t marker_len = strlen(SIG_DT_MARKER);
  for (p = data + data_size - 1; p > data; p--)
    {
      if (!memcmp(SIG_DT_MARKER, p, marker_len))
        break;
    }

  if (!p || p == data)
    {
      ERRORPRINTF ("Failed to find signature timestamp.\n");
      return 0;
    }
  p = strchr (p, ':');
  end = strchr (p, '\r');
  if (!end)
    {
      return 0;
    }
  if (end - p  <= 0)
    {
      // Should never happen but we check to ensure that
      // the following cast is valid which makes a size_t
      ERRORPRINTF ("Signature timestamp does not compute.\n");
      return 0;
    }
  len = (size_t) (end - p);

  buf = xstrndup (p + 1, len);

  lSigTime = strtol (buf, NULL, 10);
  xfree (buf);
  return (time_t) lSigTime;
}

bin_verify_result
verify_binary_linux(const char *filename, size_t name_len)
{
  int ret = -1;
  const size_t sig_b64_size = TRUSTBRIDGE_RSA_CODESIGN_B64_SIZE;
  char *data = NULL,
        signature_b64[sig_b64_size + 1];
  size_t data_size = 0,
         sig_size = TRUSTBRIDGE_RSA_CODESIGN_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_CODESIGN_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;

/** We know know that the signature is valid we can trust the data content. */
  retval.sig_time = get_signature_time (data, data_size);

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

#endif /* WIN32 */

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