| // Copyright 2002-2009 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ======================================================================== |
| |
| |
| #include "omaha/common/signaturevalidator.h" |
| |
| #include <atltime.h> |
| #include <softpub.h> |
| #include <wincrypt.h> |
| #include <wintrust.h> |
| #pragma warning(push) |
| // C4100: unreferenced formal parameter |
| // C4310: cast truncates constant value |
| // C4548: expression before comma has no effect |
| #pragma warning(disable : 4100 4310 4548) |
| #include "base/basictypes.h" |
| #pragma warning(pop) |
| #include "omaha/common/error.h" |
| |
| namespace omaha { |
| |
| namespace { |
| |
| const LPCTSTR kEmptyStr = _T(""); |
| const DWORD kCertificateEncoding = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; |
| |
| // Gets a handle to the certificate store and optionally the cryptographic |
| // message from the specified file. |
| // The caller is responsible for closing the store and message. |
| // message can be NULL if the handle is not needed. |
| HRESULT GetCertStoreFromFile(const wchar_t* signed_file, |
| HCERTSTORE* cert_store, |
| HCRYPTMSG* message) { |
| if (!signed_file || !cert_store) { |
| return E_INVALIDARG; |
| } |
| |
| // Get message handle and store handle from the signed file. |
| if (!::CryptQueryObject(CERT_QUERY_OBJECT_FILE, |
| signed_file, |
| CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, |
| CERT_QUERY_FORMAT_FLAG_BINARY, |
| 0, // reserved, must be 0 |
| NULL, // pdwMsgAndCertEncodingType |
| NULL, // pdwContentType |
| NULL, // pdwFormatType |
| cert_store, |
| message, |
| NULL)) { // ppvContext |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| return S_OK; |
| } |
| |
| // Gets the signer info from the crypt message. |
| // The caller is responsible for freeing the signer info using LocalFree. |
| HRESULT GetSignerInfo(HCRYPTMSG message, PCMSG_SIGNER_INFO* signer_info) { |
| if (!signer_info) { |
| return E_INVALIDARG; |
| } |
| *signer_info = NULL; |
| |
| DWORD info_size = 0; |
| if (!::CryptMsgGetParam(message, |
| CMSG_SIGNER_INFO_PARAM, |
| 0, |
| NULL, |
| &info_size)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| *signer_info = static_cast<PCMSG_SIGNER_INFO>(::LocalAlloc(LPTR, info_size)); |
| if (!*signer_info) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| if (!::CryptMsgGetParam(message, |
| CMSG_SIGNER_INFO_PARAM, |
| 0, |
| *signer_info, |
| &info_size)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| return S_OK; |
| } |
| |
| // Gets the signer info for the time stamp signature in the specified signature. |
| HRESULT GetTimeStampSignerInfo(PCMSG_SIGNER_INFO signer_info, |
| PCMSG_SIGNER_INFO* countersigner_info) { |
| if (!signer_info || !countersigner_info) { |
| return E_INVALIDARG; |
| } |
| *countersigner_info = NULL; |
| |
| PCRYPT_ATTRIBUTE attr = NULL; |
| |
| // The countersigner info is contained in the unauthenticated attributes and |
| // indicated by the szOID_RSA_counterSign OID. |
| for (size_t i = 0; i < signer_info->UnauthAttrs.cAttr; ++i) { |
| if (lstrcmpA(szOID_RSA_counterSign, |
| signer_info->UnauthAttrs.rgAttr[i].pszObjId) == 0) { |
| attr = &signer_info->UnauthAttrs.rgAttr[i]; |
| break; |
| } |
| } |
| |
| if (!attr) { |
| return E_FAIL; |
| } |
| |
| // Decode and get CMSG_SIGNER_INFO structure for the timestamp certificate. |
| DWORD data_size = 0; |
| if (!::CryptDecodeObject(kCertificateEncoding, |
| PKCS7_SIGNER_INFO, |
| attr->rgValue[0].pbData, |
| attr->rgValue[0].cbData, |
| 0, |
| NULL, |
| &data_size)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| *countersigner_info = |
| static_cast<PCMSG_SIGNER_INFO>(::LocalAlloc(LPTR, data_size)); |
| if (!*countersigner_info) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| if (!::CryptDecodeObject(kCertificateEncoding, |
| PKCS7_SIGNER_INFO, |
| attr->rgValue[0].pbData, |
| attr->rgValue[0].cbData, |
| 0, |
| *countersigner_info, |
| &data_size)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| return S_OK; |
| } |
| |
| // Gets the time of the date stamp for the specified signature. |
| // The time is in UTC. |
| HRESULT GetDateOfTimeStamp(PCMSG_SIGNER_INFO signer_info, |
| SYSTEMTIME* system_time) { |
| if (!signer_info || !system_time) { |
| return E_INVALIDARG; |
| } |
| |
| PCRYPT_ATTRIBUTE attr = NULL; |
| |
| // The signing time is contained in the authenticated attributes and |
| // indicated by the szOID_RSA_signingTime OID. |
| for (size_t i = 0; i < signer_info->AuthAttrs.cAttr; ++i) { |
| if (lstrcmpA(szOID_RSA_signingTime, |
| signer_info->AuthAttrs.rgAttr[i].pszObjId) == 0) { |
| attr = &signer_info->AuthAttrs.rgAttr[i]; |
| break; |
| } |
| } |
| |
| if (!attr) { |
| return E_FAIL; |
| } |
| |
| FILETIME file_time = {0}; |
| |
| // Decode and get FILETIME structure. |
| DWORD data_size = sizeof(file_time); |
| if (!::CryptDecodeObject(kCertificateEncoding, |
| szOID_RSA_signingTime, |
| attr->rgValue[0].pbData, |
| attr->rgValue[0].cbData, |
| 0, |
| &file_time, |
| &data_size)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| if (!::FileTimeToSystemTime(&file_time, system_time)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace |
| |
| CertInfo::CertInfo(const CERT_CONTEXT* given_cert_context) |
| : cert_context_(NULL) { |
| if (given_cert_context) { |
| // CertDuplicateCertificateContext just increases reference count of a given |
| // CERT_CONTEXT. |
| cert_context_ = CertDuplicateCertificateContext(given_cert_context); |
| not_valid_before_ = cert_context_->pCertInfo->NotBefore; |
| not_valid_after_ = cert_context_->pCertInfo->NotAfter; |
| // Extract signed party details. |
| ExtractIssuerInfo(cert_context_, |
| &issuing_company_name_, |
| &issuing_dept_name_, |
| &trust_authority_name_); |
| } |
| } |
| |
| CertInfo::~CertInfo() { |
| // Decrement reference count, if needed. |
| if (cert_context_) |
| CertFreeCertificateContext(cert_context_); |
| } |
| |
| |
| bool CertInfo::IsValidNow() const { |
| // we cannot directly get current time in FILETIME format. |
| // so first get it in SYSTEMTIME format and convert it into FILETIME. |
| SYSTEMTIME now; |
| GetSystemTime(&now); |
| FILETIME filetime_now; |
| SystemTimeToFileTime(&now, &filetime_now); |
| // CompareFileTime() is a windows function |
| return ((CompareFileTime(&filetime_now, ¬_valid_before_) > 0) |
| && (CompareFileTime(&filetime_now, ¬_valid_after_) < 0)); |
| } |
| |
| |
| CString CertInfo::FileTimeToString(const FILETIME* ft) { |
| if (ft == NULL) |
| return _T(""); |
| SYSTEMTIME st; |
| if (!FileTimeToSystemTime(ft, &st)) |
| return _T(""); |
| |
| // Build a string showing the date and time. |
| CString time_str; |
| time_str.Format(_T("%02d/%02d/%d %02d:%02d"), st.wDay, st.wMonth, st.wYear, |
| st.wHour, st.wMinute); |
| return time_str; |
| } |
| |
| |
| bool CertInfo::ExtractField(const TCHAR* str, |
| const TCHAR* field_name, |
| CString* field_value) { |
| if ((!str) || (!field_name) || (!field_value)) |
| return false; |
| |
| // first, we have to locate pattern - "<field-name>=" |
| CString field_name_with_equal_sign = field_name + CString(_T("=")); |
| const TCHAR* field_name_start = _tcsstr(str, field_name_with_equal_sign); |
| |
| // there is no such field? ok, sorry.. |
| if (field_name_start == NULL) { |
| field_value->Empty(); |
| return false; |
| } |
| |
| // now, locate the end of this field-value. we know each field value |
| // is followed by a semi-colon (except last one). |
| const TCHAR* next_field = _tcschr(field_name_start, ';'); |
| // identify where exactly field-value starts. |
| const TCHAR* field_value_start = field_name_start + |
| field_name_with_equal_sign.GetLength(); |
| if (next_field) { |
| // if next-field exists, copy all the chars before semi-colon |
| field_value->SetString(field_value_start, |
| static_cast<int>(next_field - field_value_start)); |
| } else { |
| // this must be the last field. copy till the end of the string. |
| field_value->SetString(field_value_start); |
| } |
| |
| return true; |
| } |
| |
| |
| bool CertInfo::ExtractIssuerInfo(const CERT_CONTEXT* cert_context, |
| CString* orgn_name, |
| CString* orgn_dept_name, |
| CString* trust_authority) { |
| // trust-authority is optional, so no check. |
| if ((!orgn_name) || (!orgn_dept_name)) |
| return false; |
| |
| if (!cert_context) { |
| orgn_name->Empty(); |
| orgn_dept_name->Empty(); |
| return false; |
| } |
| |
| // Retrieve organization info in the form of a BLOB |
| CERT_NAME_BLOB orgn_blob = cert_context->pCertInfo->Subject; |
| |
| TCHAR name_str[1024]; |
| DWORD name_size = sizeof(name_str); |
| |
| DWORD num_converted_bytes = |
| ::CertNameToStr( |
| kCertificateEncoding, |
| &orgn_blob, |
| CERT_X500_NAME_STR|CERT_NAME_STR_NO_QUOTING_FLAG| |
| CERT_NAME_STR_SEMICOLON_FLAG| // all the fields to be separated by ';' |
| CERT_NAME_STR_REVERSE_FLAG, // we are reversing the order of fields |
| // so that subject/signee related fields |
| // turn up first. |
| name_str, |
| name_size); |
| |
| if ((num_converted_bytes <= 0) || (num_converted_bytes > name_size)) { |
| // num_converted_bytes > name_size - means that name_str needs to be larger. |
| // That's very unlikely so I don't call again it with a bigger string. |
| orgn_name->Empty(); |
| orgn_dept_name->Empty(); |
| return false; |
| } |
| |
| ExtractField(name_str, _T("CN"), orgn_name); // CN - Common Name |
| ExtractField(name_str, _T("OU"), orgn_dept_name); // OU - Organizational Unit |
| if (trust_authority != NULL) |
| ExtractField(name_str, _T("O"), trust_authority); |
| return true; |
| } |
| |
| |
| void CertList::FindFirstCert(CertInfo** result_cert_info, |
| const CString &company_name_to_match, |
| const CString &orgn_unit_to_match, |
| const CString &trust_authority_to_match, |
| bool allow_test_variant, |
| bool check_cert_is_valid_now) { |
| if (!result_cert_info) |
| return; |
| (*result_cert_info) = NULL; |
| |
| for (CertInfoList::const_iterator cert_iter = cert_list_.begin(); |
| cert_iter != cert_list_.end(); |
| ++cert_iter) { |
| // If any of the criteria does not match, continue on to next certificate |
| if (!company_name_to_match.IsEmpty()) { |
| const TCHAR* certificate_company_name = |
| (*cert_iter)->issuing_company_name_; |
| bool names_match = company_name_to_match == certificate_company_name; |
| if (!names_match && allow_test_variant) { |
| CString test_variant = company_name_to_match; |
| test_variant += _T(" (TEST)"); |
| names_match = test_variant == certificate_company_name; |
| } |
| if (!names_match) |
| continue; |
| } |
| if (!orgn_unit_to_match.IsEmpty() && |
| orgn_unit_to_match != (*cert_iter)->issuing_dept_name_) |
| continue; |
| if (!trust_authority_to_match.IsEmpty() && |
| trust_authority_to_match != (*cert_iter)->trust_authority_name_) |
| continue; |
| // All the criteria matched. But, add only if it is a valid certificate. |
| if (!check_cert_is_valid_now || (*cert_iter)->IsValidNow()) { |
| (*result_cert_info) = (*cert_iter); |
| return; |
| } |
| } |
| } |
| |
| |
| void ExtractAllCertificatesFromSignature(const wchar_t* signed_file, |
| CertList* cert_list) { |
| if ((!signed_file) || (!cert_list)) |
| return; |
| |
| DWORD encoding_type = 0, content_type = 0, format_type = 0; |
| // If successful, cert_store will be populated by |
| // a store containing all the certificates related to the file signature. |
| HCERTSTORE cert_store = NULL; |
| BOOL succeeded = CryptQueryObject(CERT_QUERY_OBJECT_FILE, |
| signed_file, |
| CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, |
| CERT_QUERY_FORMAT_FLAG_ALL, |
| 0, // has to be zero as documentation says |
| &encoding_type, // DWORD *pdwMsgAndCertEncodingType, |
| &content_type, // DWORD *pdwContentType, |
| &format_type, // DWORD *pdwFormatType, |
| &cert_store, // HCERTSTORE *phCertStore, |
| NULL, // HCRYPTMSG *phMsg, |
| NULL); // const void** pvContext |
| |
| if (succeeded && (cert_store != NULL)) { |
| PCCERT_CONTEXT cert_context_ptr = NULL; |
| while ((cert_context_ptr = |
| CertEnumCertificatesInStore(cert_store, cert_context_ptr)) |
| != NULL) { |
| CertInfo* cert_info = new CertInfo(cert_context_ptr); |
| cert_list->AddCertificate(cert_info); |
| } |
| } |
| if (cert_store) { |
| CertCloseStore(cert_store, 0); |
| } |
| return; |
| } |
| |
| // Only check the CN. The OU can change. |
| // TODO(omaha): A better way to implement the valid now check would be to add |
| // a parameter to VerifySignature that adds WTD_LIFETIME_SIGNING_FLAG. |
| bool VerifySigneeIsGoogleInternal(const wchar_t* signed_file, |
| bool check_cert_is_valid_now) { |
| const TCHAR* google_name = _T("Google Inc"); |
| |
| CertList cert_list; |
| ExtractAllCertificatesFromSignature(signed_file, &cert_list); |
| if (cert_list.size() > 0) { |
| CertInfo* required_cert = NULL; |
| // now, see if one of the certificates in the signature belongs to Google. |
| cert_list.FindFirstCert(&required_cert, google_name, CString(), |
| CString(), true, check_cert_is_valid_now); |
| if (required_cert != NULL) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Does not verify that the certificate is currently valid. |
| // VerifySignature verifies that the certificate was valid at signing time as |
| // part of the normal signature verification. |
| bool VerifySigneeIsGoogle(const wchar_t* signed_file) { |
| return VerifySigneeIsGoogleInternal(signed_file, false); |
| } |
| |
| HRESULT VerifySignature(const wchar_t* signed_file, bool allow_network_check) { |
| // Don't pop up any windows |
| HWND const kWindowMode = reinterpret_cast<HWND>(INVALID_HANDLE_VALUE); |
| |
| // Verify file & certificates |
| GUID verification_type = WINTRUST_ACTION_GENERIC_VERIFY_V2; |
| |
| // Info for the file we're going to verify |
| WINTRUST_FILE_INFO file_info = {0}; |
| file_info.cbStruct = sizeof(file_info); |
| file_info.pcwszFilePath = signed_file; |
| |
| // Info for request to WinVerifyTrust |
| WINTRUST_DATA trust_data; |
| ZeroMemory(&trust_data, sizeof(trust_data)); |
| trust_data.cbStruct = sizeof(trust_data); |
| trust_data.dwUIChoice = WTD_UI_NONE; // no graphics |
| // No additional revocation checking -- note that this flag does not |
| // cancel the flag we set in dwProvFlags; it specifies that no -additional- |
| // checks are to be performed beyond the provider-specified ones. |
| trust_data.fdwRevocationChecks = WTD_REVOKE_NONE; |
| trust_data.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; |
| |
| if (!allow_network_check) |
| trust_data.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL; |
| |
| trust_data.dwUnionChoice = WTD_CHOICE_FILE; // check a file |
| trust_data.pFile = &file_info; // check this file |
| |
| // If the trust provider verifies that the subject is trusted for the |
| // specified action, the return value is zero. No other value besides zero |
| // should be considered a successful return. |
| LONG result = ::WinVerifyTrust(kWindowMode, &verification_type, &trust_data); |
| if (result != 0) { |
| return FAILED(result) ? result : HRESULT_FROM_WIN32(result); |
| } |
| return S_OK; |
| } |
| |
| // This method must not return until the end to avoid leaking memory. |
| // More info on Authenticode Signatures Time Stamping can be found at |
| // http://msdn2.microsoft.com/en-us/library/bb931395.aspx. |
| HRESULT GetSigningTime(const wchar_t* signed_file, SYSTEMTIME* signing_time) { |
| if (!signed_file || !signing_time) { |
| return E_INVALIDARG; |
| } |
| |
| HCERTSTORE cert_store = NULL; |
| HCRYPTMSG message = NULL; |
| PCMSG_SIGNER_INFO signer_info = NULL; |
| PCMSG_SIGNER_INFO countersigner_info = NULL; |
| |
| HRESULT hr = GetCertStoreFromFile(signed_file, &cert_store, &message); |
| |
| if (SUCCEEDED(hr)) { |
| hr = GetSignerInfo(message, &signer_info); |
| } |
| |
| if (SUCCEEDED(hr)) { |
| hr = GetTimeStampSignerInfo(signer_info, &countersigner_info); |
| } |
| |
| if (SUCCEEDED(hr)) { |
| hr = GetDateOfTimeStamp(countersigner_info, signing_time); |
| } |
| |
| if (cert_store) { |
| ::CertCloseStore(cert_store, 0); |
| } |
| if (message) { |
| ::CryptMsgClose(message); |
| } |
| ::LocalFree(signer_info); |
| ::LocalFree(countersigner_info); |
| |
| return hr; |
| } |
| |
| HRESULT VerifyFileSignedWithinDays(const wchar_t* signed_file, int days) { |
| if (!signed_file || days <= 0) { |
| return E_INVALIDARG; |
| } |
| |
| SYSTEMTIME signing_time = {0}; |
| HRESULT hr = GetSigningTime(signed_file, &signing_time); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| // Use the Win32 API instead of CTime::GetCurrentTime() because the latter |
| // is broken in VS 2003 and 2005 and doesn't account for the timezone. |
| SYSTEMTIME current_system_time = {0}; |
| ::GetSystemTime(¤t_system_time); |
| |
| CTime signed_time(signing_time); |
| CTime current_time(current_system_time); |
| |
| if (current_time <= signed_time) { |
| return TRUST_E_TIME_STAMP; |
| } |
| |
| CTimeSpan time_since_signed = current_time - signed_time; |
| CTimeSpan max_duration(days, 0, 0, 0); |
| |
| if (max_duration < time_since_signed) { |
| return TRUST_E_TIME_STAMP; |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace omaha |
| |