Mercurial > trustbridge
view common/binverify.c @ 1396:05c62ad0c74f 0.9.10
Change error to debug for an expected failure.
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 26 Jan 2015 14:28:13 +0100 |
parents | a2574a029322 |
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 */