aheinecke@579: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@579: * Software engineering by Intevation GmbH aheinecke@579: * aheinecke@579: * This file is Free Software under the GNU GPL (v>=2) aheinecke@579: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@579: * See LICENSE.txt for details. aheinecke@579: */ aheinecke@579: aheinecke@579: #include "binverify.h" aheinecke@579: aheinecke@579: #include "strhelp.h" aheinecke@579: #include "logging.h" andre@1081: #include "listutil.h" andre@629: #ifdef RELEASE_BUILD andre@629: #include "pubkey-release.h" andre@629: #else andre@629: #include "pubkey-test.h" andre@629: #endif andre@629: aheinecke@586: bin_verify_result andre@905: verify_binary(const char *filename, size_t name_len) andre@905: { andre@1081: if (!filename || !name_len) { andre@1081: bin_verify_result retval; andre@1081: retval.fptr = NULL; andre@1081: retval.result = VerifyUnknownError; andre@1364: retval.sig_time = 0; andre@1081: return retval; andre@1081: } andre@1081: aheinecke@586: #ifdef WIN32 aheinecke@586: return verify_binary_win(filename, name_len); aheinecke@586: #else andre@771: return verify_binary_linux(filename, name_len); aheinecke@586: #endif aheinecke@586: } aheinecke@586: aheinecke@579: #ifdef WIN32 aheinecke@579: andre@629: #include andre@629: aheinecke@579: #include aheinecke@579: #include aheinecke@579: #include aheinecke@579: #include aheinecke@579: andre@629: andre@629: /** @brief Check if the certificate @a pCCertContext is pinned andre@629: * andre@629: * Compares the certificate's binary data (public key and attributes) andre@629: * with each other to validate that the certificate pCCertContext has andre@629: * exactly the same data as the builtin public certificate. andre@629: * andre@629: * @param[in] pCCertContext pointer to the certificate to check andre@629: * andre@629: * @returns true if the certificate matches, false otherwise. andre@629: */ andre@629: static bool andre@629: check_certificate (PCCERT_CONTEXT pCCertContext) andre@629: { andre@629: x509_crt codesign_cert; andre@629: int ret = 0; andre@629: DWORD dwI = 0; andre@629: bool retval = false; andre@629: andre@629: if (pCCertContext == NULL) andre@629: { andre@629: ERRORPRINTF ("Invalid call to check_certificate"); andre@629: return false; andre@629: } andre@629: andre@629: x509_crt_init(&codesign_cert); andre@629: andre@629: /* Parse the pinned certificate */ andre@629: ret = x509_crt_parse(&codesign_cert, andre@629: public_key_codesign_pem, andre@629: public_key_codesign_pem_size); andre@629: if (ret != 0) andre@629: { andre@629: ERRORPRINTF ("x509_crt_parse failed with -0x%04x\n\n", -ret); andre@629: goto done; andre@629: } andre@629: andre@629: if (codesign_cert.raw.len != pCCertContext->cbCertEncoded || andre@629: codesign_cert.raw.len <= 0) andre@629: { andre@629: ERRORPRINTF ("Certificate size mismatch"); andre@629: goto done; andre@629: } andre@629: andre@629: /* Check that the certificate is exactly the same as the pinned one */ andre@629: for (dwI = 0; dwI < pCCertContext->cbCertEncoded; dwI++) andre@629: { andre@629: if (pCCertContext->pbCertEncoded[dwI] != codesign_cert.raw.p[dwI]) andre@629: { andre@629: ERRORPRINTF ("Certificate content mismatch"); andre@629: goto done; andre@629: } andre@629: } andre@629: andre@629: retval = true; andre@629: andre@629: done: andre@629: x509_crt_free(&codesign_cert); andre@629: return retval; andre@629: } andre@629: andre@1364: time_t andre@1364: systemtime_to_time_t (SYSTEMTIME *systemTime) andre@1364: andre@1364: { andre@1364: LARGE_INTEGER jan1970FT = {{0}}; andre@1364: jan1970FT.QuadPart = 116444736000000000LL; // january 1st 1970 well known value andre@1364: LARGE_INTEGER utcFT = {{0}}; andre@1364: andre@1364: SystemTimeToFileTime(systemTime, (FILETIME*)&utcFT); andre@1364: andre@1364: __int64 utcDosTime = (utcFT.QuadPart - jan1970FT.QuadPart)/10000000; andre@1364: andre@1364: return (time_t)utcDosTime; andre@1364: } andre@1364: andre@1364: andre@1364: time_t andre@1364: get_signature_time (HCRYPTMSG hMsg) andre@1364: { andre@1364: FILETIME lft, ft; andre@1364: SYSTEMTIME st; andre@1364: DWORD dwData = 0, andre@1364: n = 0, andre@1364: dwSignerInfo = 0; andre@1364: PCMSG_SIGNER_INFO pSignerInfo = NULL; andre@1364: andre@1364: time_t ret = -1; andre@1364: andre@1364: if (!hMsg) andre@1364: { andre@1364: return -1; andre@1364: } andre@1364: andre@1364: // Get signer information size. andre@1364: if (!CryptMsgGetParam(hMsg, andre@1364: CMSG_SIGNER_INFO_PARAM, andre@1364: 0, andre@1364: NULL, andre@1364: &dwSignerInfo)) andre@1364: { andre@1364: ERRORPRINTF ("Failed to get signer info size."); andre@1364: return -1; andre@1364: } andre@1364: pSignerInfo = xmalloc (dwSignerInfo); andre@1364: andre@1364: if (!CryptMsgGetParam(hMsg, andre@1364: CMSG_SIGNER_INFO_PARAM, andre@1364: 0, andre@1364: (PVOID)pSignerInfo, andre@1364: &dwSignerInfo)) andre@1364: { andre@1364: ERRORPRINTF ("Failed to get signer info."); andre@1364: goto done; andre@1364: } andre@1364: andre@1364: andre@1364: // Loop through authenticated attributes and find andre@1364: // szOID_RSA_signingTime OID. andre@1364: for (n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++) andre@1364: { andre@1364: if (lstrcmpA(szOID_RSA_signingTime, andre@1364: pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0) andre@1364: { andre@1364: dwData = sizeof(ft); andre@1364: if (!CryptDecodeObject((X509_ASN_ENCODING | PKCS_7_ASN_ENCODING), andre@1364: szOID_RSA_signingTime, andre@1364: pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData, andre@1364: pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData, andre@1364: 0, andre@1364: (PVOID)&ft, andre@1364: &dwData)) andre@1364: { andre@1364: PRINTLASTERROR ("Failed to decode time: "); andre@1364: break; andre@1364: } andre@1364: andre@1364: // Convert to local time. andre@1364: FileTimeToLocalFileTime(&ft, &lft); andre@1364: FileTimeToSystemTime(&lft, &st); andre@1364: andre@1364: ret = systemtime_to_time_t(&st); andre@1364: break; andre@1364: } andre@1364: } andre@1364: andre@1364: done: andre@1364: xfree(pSignerInfo); andre@1364: andre@1364: return ret; andre@1364: } andre@1364: aheinecke@579: bin_verify_result andre@905: verify_binary_win(const char *filename, size_t name_len) andre@905: { andre@1081: bin_verify_result retval; aheinecke@579: WCHAR *filenameW = NULL; aheinecke@579: BOOL result = FALSE; aheinecke@579: DWORD dwEncoding = 0, aheinecke@579: dwContentType = 0, aheinecke@579: dwFormatType = 0, aheinecke@579: dwSignerInfoSize = 0; aheinecke@579: HCERTSTORE hStore = NULL; aheinecke@579: HCRYPTMSG hMsg = NULL; aheinecke@579: PCERT_INFO pSignerCert = NULL; aheinecke@579: PCCERT_CONTEXT pSignerCertContext = NULL; andre@1081: FILE *fptr = NULL; andre@1081: size_t data_size = 0; andre@1081: char *data = NULL; andre@1081: int ret = -1; andre@1081: CRYPT_INTEGER_BLOB blob; andre@1081: andre@1081: retval.result = VerifyUnknownError; andre@1081: retval.fptr = NULL; aheinecke@579: aheinecke@579: if (!filename || name_len > MAX_PATH || strlen(filename) != name_len) aheinecke@579: { aheinecke@579: ERRORPRINTF ("Invalid parameters\n"); andre@1081: return retval; aheinecke@579: } aheinecke@579: andre@1081: ret = read_file(filename, &data, &data_size, MAX_VALID_BIN_SIZE, &fptr); aheinecke@579: andre@1081: if (ret != 0) andre@1081: { andre@1081: ERRORPRINTF ("Read file failed with error: %i\n", ret); andre@1081: retval.result = VerifyReadFailed; andre@1081: return retval; andre@1081: } andre@1081: blob.cbData = (DWORD) data_size; andre@1081: blob.pbData = (PBYTE) data; andre@1081: andre@1081: result = CryptQueryObject (CERT_QUERY_OBJECT_BLOB, andre@1081: &blob, aheinecke@579: CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, aheinecke@579: CERT_QUERY_FORMAT_FLAG_BINARY, aheinecke@579: 0, aheinecke@579: &dwEncoding, aheinecke@579: &dwContentType, aheinecke@579: &dwFormatType, aheinecke@579: &hStore, aheinecke@579: &hMsg, aheinecke@579: NULL); aheinecke@579: aheinecke@579: if (!result || !hMsg) aheinecke@579: { aheinecke@579: PRINTLASTERROR ("Failed to query crypto object"); andre@1081: retval.result = VerifyReadFailed; aheinecke@579: goto done; aheinecke@579: } aheinecke@579: aheinecke@579: /* Get the cert info so that we can look up the signer in the store later */ aheinecke@579: if (CryptMsgGetParam(hMsg, aheinecke@579: CMSG_SIGNER_CERT_INFO_PARAM, aheinecke@579: 0, aheinecke@579: NULL, aheinecke@579: &dwSignerInfoSize) && dwSignerInfoSize > 0) aheinecke@579: { aheinecke@579: pSignerCert = xmalloc (dwSignerInfoSize); aheinecke@579: } aheinecke@579: else aheinecke@579: { aheinecke@579: ERRORPRINTF ("Failed to get signer cert size."); andre@1081: retval.result = VerifyUnknownError; aheinecke@579: goto done; aheinecke@579: } aheinecke@579: aheinecke@579: if (!(CryptMsgGetParam(hMsg, aheinecke@579: CMSG_SIGNER_CERT_INFO_PARAM, aheinecke@579: 0, aheinecke@579: pSignerCert, aheinecke@579: &dwSignerInfoSize))) aheinecke@579: { aheinecke@579: ERRORPRINTF ("Failed to get signer cert."); andre@1081: retval.result = VerifyUnknownError; aheinecke@579: goto done; aheinecke@579: } aheinecke@579: aheinecke@579: pSignerCertContext = CertGetSubjectCertificateFromStore( andre@905: hStore, andre@905: PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, andre@905: pSignerCert); aheinecke@579: aheinecke@579: if (!pSignerCertContext) aheinecke@579: { aheinecke@579: ERRORPRINTF ("Failed to find signer cert in store."); andre@1081: retval.result = VerifyUnknownError; aheinecke@579: goto done; aheinecke@579: } aheinecke@579: aheinecke@579: /* Verify that the signature is actually valid */ andre@629: if(!CryptMsgControl(hMsg, andre@629: 0, andre@629: CMSG_CTRL_VERIFY_SIGNATURE, andre@629: pSignerCertContext->pCertInfo)) aheinecke@579: { andre@629: ERRORPRINTF ("The signature is invalid. \n"); andre@1081: retval.result = VerifyInvalidSignature; andre@629: syslog_error_printf ("Software update embedded signature is invalid."); andre@629: goto done; andre@629: } andre@629: andre@629: if(check_certificate(pSignerCertContext)) andre@629: { andre@629: DEBUGPRINTF ("Valid signature with pinned certificate."); andre@1081: retval.result = VerifyValid; andre@1081: retval.fptr = fptr; andre@1364: retval.sig_time = get_signature_time (hMsg); andre@629: goto done; andre@629: } andre@629: else andre@629: { andre@629: ERRORPRINTF ("Certificate mismatch. \n"); andre@1081: retval.result = VerifyInvalidCertificate; andre@629: syslog_error_printf ("Software update embedded signature " andre@629: "created with wrong certificate."); aheinecke@579: goto done; aheinecke@579: } aheinecke@579: aheinecke@579: done: andre@1081: xfree(data); aheinecke@579: xfree(filenameW); aheinecke@579: xfree(pSignerCert); aheinecke@579: andre@1081: if (retval.result != VerifyValid) andre@1081: { andre@1081: fclose(fptr); andre@1081: } andre@1081: aheinecke@579: if(pSignerCertContext) aheinecke@579: { aheinecke@579: CertFreeCertificateContext(pSignerCertContext); aheinecke@579: } aheinecke@579: if (hStore) aheinecke@579: { aheinecke@579: CertCloseStore(hStore, 0); aheinecke@579: } aheinecke@579: if (hMsg) aheinecke@579: { aheinecke@579: CryptMsgClose(hMsg); aheinecke@579: } aheinecke@579: return retval; aheinecke@579: } andre@771: #else /* WIN32 */ andre@771: andre@1264: #ifndef __clang__ andre@771: #pragma GCC diagnostic ignored "-Wconversion" andre@1264: #endif andre@771: /* Polarssl mh.h contains a conversion which gcc warns about */ andre@771: #include andre@771: #include andre@771: #include andre@774: #include andre@774: #include andre@1264: #ifndef __clang__ andre@771: #pragma GCC diagnostic pop andre@1264: #endif andre@1369: #include andre@1369: andre@1369: #define SIG_DT_MARKER "\r\nS_DT:" andre@1369: andre@1369: /** This function is only intended to be used on well formatted input andre@1369: * after verifification as it makes some hard assumptions what andre@1369: * follows the SIG_DT_MARKER*/ andre@1369: time_t andre@1369: get_signature_time (char *data, size_t data_size) andre@1369: { andre@1369: char *p = NULL, andre@1369: *end = NULL, andre@1369: *buf = NULL; andre@1369: long lSigTime = 0; andre@1369: size_t len = 0; andre@1369: andre@1369: andre@1369: /** Look for a DOS linebreak followed by an S_DT: */ andre@1369: size_t marker_len = strlen(SIG_DT_MARKER); andre@1369: for (p = data + data_size - 1; p > data; p--) andre@1369: { andre@1369: if (!memcmp(SIG_DT_MARKER, p, marker_len)) andre@1369: break; andre@1369: } andre@1369: andre@1369: if (!p || p == data) andre@1369: { andre@1369: ERRORPRINTF ("Failed to find signature timestamp.\n"); andre@1369: return 0; andre@1369: } andre@1369: p = strchr (p, ':'); andre@1369: end = strchr (p, '\r'); andre@1369: if (!end) andre@1369: { andre@1369: return 0; andre@1369: } andre@1369: if (end - p <= 0) andre@1369: { andre@1369: // Should never happen but we check to ensure that andre@1369: // the following cast is valid which makes a size_t andre@1369: ERRORPRINTF ("Signature timestamp does not compute.\n"); andre@1369: return 0; andre@1369: } andre@1369: len = (size_t) (end - p); andre@1369: andre@1369: buf = xstrndup (p + 1, len); andre@1369: andre@1369: lSigTime = strtol (buf, NULL, 10); andre@1369: xfree (buf); andre@1369: return (time_t) lSigTime; andre@1369: } andre@771: andre@771: bin_verify_result andre@771: verify_binary_linux(const char *filename, size_t name_len) andre@771: { andre@771: int ret = -1; andre@771: const size_t sig_b64_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8 * 4 / 3; andre@771: char *data = NULL, andre@905: signature_b64[sig_b64_size + 1]; andre@771: size_t data_size = 0, andre@771: sig_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8; andre@771: unsigned char signature[sig_size], andre@905: hash[32]; andre@1081: FILE *fptr = NULL; andre@771: andre@1081: bin_verify_result retval; andre@1081: retval.result = VerifyUnknownError; andre@1081: retval.fptr = NULL; andre@774: x509_crt codesign_cert; andre@771: andre@771: if (strnlen(filename, name_len + 1) != name_len || name_len == 0) andre@771: { andre@771: ERRORPRINTF ("Invalid call to verify_binary_linux\n"); andre@1081: retval.result = VerifyUnknownError; andre@1081: return retval; andre@771: } andre@771: andre@1081: ret = read_file(filename, &data, &data_size, MAX_VALID_BIN_SIZE, &fptr); andre@771: andre@771: if (ret != 0) andre@771: { andre@771: ERRORPRINTF ("Read file failed with error: %i\n", ret); andre@1081: retval.result = VerifyReadFailed; andre@1081: return retval; andre@771: } andre@771: andre@771: /* Fetch the signature from the end of data */ andre@774: if (data_size < sig_b64_size + 5) andre@771: { andre@771: ERRORPRINTF ("File to small to contain a signature.\n"); andre@1081: retval.result = VerifyInvalidSignature; andre@771: goto done; andre@771: } andre@771: andre@774: if (data[data_size - sig_b64_size - 2] != ':' || andre@774: data[data_size - sig_b64_size - 3] != 'S' || andre@774: data[data_size - sig_b64_size - 4] != '\n'|| andre@774: data[data_size - sig_b64_size - 5] != '\r') andre@771: { andre@771: ERRORPRINTF ("Failed to find valid signature line.\n"); andre@1081: retval.result = VerifyInvalidSignature; andre@771: goto done; andre@771: } andre@771: andre@774: strncpy(signature_b64, data + (data_size - sig_b64_size - 1), sig_b64_size); andre@771: signature_b64[sig_b64_size] = '\0'; andre@771: andre@771: ret = base64_decode(signature, &sig_size, andre@771: (unsigned char *)signature_b64, sig_b64_size); andre@771: andre@771: if (ret != 0 || sig_size != TRUSTBRIDGE_RSA_KEY_SIZE / 8) andre@771: { andre@774: ERRORPRINTF ("Base 64 decode failed with error: %i\n", ret); andre@771: goto done; andre@771: } andre@771: andre@771: /* Hash is calculated over the data without the signature at the end. */ andre@774: sha256((unsigned char *)data, data_size - sig_b64_size - 5, hash, 0); andre@771: andre@774: x509_crt_init(&codesign_cert); andre@771: andre@774: /* Parse the pinned certificate */ andre@774: ret = x509_crt_parse(&codesign_cert, andre@774: public_key_codesign_pem, andre@774: public_key_codesign_pem_size); andre@771: if (ret != 0) andre@771: { andre@774: char errbuf[1020]; andre@774: polarssl_strerror(ret, errbuf, 1020); andre@774: errbuf[1019] = '\0'; /* Just to be sure */ andre@774: ERRORPRINTF ("x509_crt_parse failed with -0x%04x\n%s\n", -ret, errbuf); andre@774: x509_crt_free(&codesign_cert); andre@1081: retval.result = VerifyUnknownError; andre@1081: goto done; andre@771: } andre@771: andre@774: ret = pk_verify(&codesign_cert.pk, POLARSSL_MD_SHA256, hash, 0, andre@771: signature, sig_size); andre@771: andre@771: if (ret != 0) andre@771: { andre@774: char errbuf[1020]; andre@774: polarssl_strerror(ret, errbuf, 1020); andre@774: errbuf[1019] = '\0'; /* Just to be sure */ andre@774: ERRORPRINTF ("pk_verify failed with -0x%04x\n %s\n", -ret, errbuf); andre@774: x509_crt_free(&codesign_cert); andre@1081: retval.result = VerifyInvalidSignature; andre@774: goto done; andre@771: } andre@774: x509_crt_free(&codesign_cert); andre@771: andre@1081: retval.result = VerifyValid; andre@1081: retval.fptr = fptr; andre@771: andre@1369: /** We know know that the signature is valid we can trust the data content. */ andre@1369: retval.sig_time = get_signature_time (data, data_size); andre@1369: andre@771: done: andre@1081: if (retval.result != VerifyValid) andre@1081: { andre@1081: if (fptr) andre@1081: { andre@1081: fclose(fptr); andre@1081: } andre@1081: } andre@771: xfree (data); andre@771: return retval; andre@771: } andre@771: aheinecke@579: #endif /* WIN32 */