view common/binverify.c @ 1369:948f03bb5254

Add signature time extraction for Linux and test for it in binverifytest
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 24 Nov 2014 14:43:10 +0100
parents 28885e8c891f
children c64b6c56ce96
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;
    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_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;

/** 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/