OLD | NEW |
| (Empty) |
1 // Copyright 2002-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 | |
17 #include "omaha/base/signaturevalidator.h" | |
18 | |
19 #include <atltime.h> | |
20 #include <softpub.h> | |
21 #include <wincrypt.h> | |
22 #include <wintrust.h> | |
23 #pragma warning(push) | |
24 // C4100: unreferenced formal parameter | |
25 // C4310: cast truncates constant value | |
26 // C4548: expression before comma has no effect | |
27 #pragma warning(disable : 4100 4310 4548) | |
28 #include "base/basictypes.h" | |
29 #pragma warning(pop) | |
30 #include "omaha/base/constants.h" | |
31 #include "omaha/base/error.h" | |
32 | |
33 namespace omaha { | |
34 | |
35 namespace { | |
36 | |
37 const LPCTSTR kEmptyStr = _T(""); | |
38 const DWORD kCertificateEncoding = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; | |
39 | |
40 // Gets a handle to the certificate store and optionally the cryptographic | |
41 // message from the specified file. | |
42 // The caller is responsible for closing the store and message. | |
43 // message can be NULL if the handle is not needed. | |
44 HRESULT GetCertStoreFromFile(const wchar_t* signed_file, | |
45 HCERTSTORE* cert_store, | |
46 HCRYPTMSG* message) { | |
47 if (!signed_file || !cert_store) { | |
48 return E_INVALIDARG; | |
49 } | |
50 | |
51 // Get message handle and store handle from the signed file. | |
52 if (!::CryptQueryObject(CERT_QUERY_OBJECT_FILE, | |
53 signed_file, | |
54 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, | |
55 CERT_QUERY_FORMAT_FLAG_BINARY, | |
56 0, // reserved, must be 0 | |
57 NULL, // pdwMsgAndCertEncodingType | |
58 NULL, // pdwContentType | |
59 NULL, // pdwFormatType | |
60 cert_store, | |
61 message, | |
62 NULL)) { // ppvContext | |
63 return HRESULT_FROM_WIN32(::GetLastError()); | |
64 } | |
65 | |
66 return S_OK; | |
67 } | |
68 | |
69 // Gets the signer info from the crypt message. | |
70 // The caller is responsible for freeing the signer info using LocalFree. | |
71 HRESULT GetSignerInfo(HCRYPTMSG message, PCMSG_SIGNER_INFO* signer_info) { | |
72 if (!signer_info) { | |
73 return E_INVALIDARG; | |
74 } | |
75 *signer_info = NULL; | |
76 | |
77 DWORD info_size = 0; | |
78 if (!::CryptMsgGetParam(message, | |
79 CMSG_SIGNER_INFO_PARAM, | |
80 0, | |
81 NULL, | |
82 &info_size)) { | |
83 return HRESULT_FROM_WIN32(::GetLastError()); | |
84 } | |
85 | |
86 *signer_info = static_cast<PCMSG_SIGNER_INFO>(::LocalAlloc(LPTR, info_size)); | |
87 if (!*signer_info) { | |
88 return HRESULT_FROM_WIN32(::GetLastError()); | |
89 } | |
90 | |
91 if (!::CryptMsgGetParam(message, | |
92 CMSG_SIGNER_INFO_PARAM, | |
93 0, | |
94 *signer_info, | |
95 &info_size)) { | |
96 return HRESULT_FROM_WIN32(::GetLastError()); | |
97 } | |
98 | |
99 return S_OK; | |
100 } | |
101 | |
102 // Gets the signer info for the time stamp signature in the specified signature. | |
103 HRESULT GetTimeStampSignerInfo(PCMSG_SIGNER_INFO signer_info, | |
104 PCMSG_SIGNER_INFO* countersigner_info) { | |
105 if (!signer_info || !countersigner_info) { | |
106 return E_INVALIDARG; | |
107 } | |
108 *countersigner_info = NULL; | |
109 | |
110 PCRYPT_ATTRIBUTE attr = NULL; | |
111 | |
112 // The countersigner info is contained in the unauthenticated attributes and | |
113 // indicated by the szOID_RSA_counterSign OID. | |
114 for (size_t i = 0; i < signer_info->UnauthAttrs.cAttr; ++i) { | |
115 if (lstrcmpA(szOID_RSA_counterSign, | |
116 signer_info->UnauthAttrs.rgAttr[i].pszObjId) == 0) { | |
117 attr = &signer_info->UnauthAttrs.rgAttr[i]; | |
118 break; | |
119 } | |
120 } | |
121 | |
122 if (!attr) { | |
123 return E_FAIL; | |
124 } | |
125 | |
126 // Decode and get CMSG_SIGNER_INFO structure for the timestamp certificate. | |
127 DWORD data_size = 0; | |
128 if (!::CryptDecodeObject(kCertificateEncoding, | |
129 PKCS7_SIGNER_INFO, | |
130 attr->rgValue[0].pbData, | |
131 attr->rgValue[0].cbData, | |
132 0, | |
133 NULL, | |
134 &data_size)) { | |
135 return HRESULT_FROM_WIN32(::GetLastError()); | |
136 } | |
137 | |
138 *countersigner_info = | |
139 static_cast<PCMSG_SIGNER_INFO>(::LocalAlloc(LPTR, data_size)); | |
140 if (!*countersigner_info) { | |
141 return HRESULT_FROM_WIN32(::GetLastError()); | |
142 } | |
143 | |
144 if (!::CryptDecodeObject(kCertificateEncoding, | |
145 PKCS7_SIGNER_INFO, | |
146 attr->rgValue[0].pbData, | |
147 attr->rgValue[0].cbData, | |
148 0, | |
149 *countersigner_info, | |
150 &data_size)) { | |
151 return HRESULT_FROM_WIN32(::GetLastError()); | |
152 } | |
153 | |
154 return S_OK; | |
155 } | |
156 | |
157 // Gets the time of the date stamp for the specified signature. | |
158 // The time is in UTC. | |
159 HRESULT GetDateOfTimeStamp(PCMSG_SIGNER_INFO signer_info, | |
160 SYSTEMTIME* system_time) { | |
161 if (!signer_info || !system_time) { | |
162 return E_INVALIDARG; | |
163 } | |
164 | |
165 PCRYPT_ATTRIBUTE attr = NULL; | |
166 | |
167 // The signing time is contained in the authenticated attributes and | |
168 // indicated by the szOID_RSA_signingTime OID. | |
169 for (size_t i = 0; i < signer_info->AuthAttrs.cAttr; ++i) { | |
170 if (lstrcmpA(szOID_RSA_signingTime, | |
171 signer_info->AuthAttrs.rgAttr[i].pszObjId) == 0) { | |
172 attr = &signer_info->AuthAttrs.rgAttr[i]; | |
173 break; | |
174 } | |
175 } | |
176 | |
177 if (!attr) { | |
178 return E_FAIL; | |
179 } | |
180 | |
181 FILETIME file_time = {0}; | |
182 | |
183 // Decode and get FILETIME structure. | |
184 DWORD data_size = sizeof(file_time); | |
185 if (!::CryptDecodeObject(kCertificateEncoding, | |
186 szOID_RSA_signingTime, | |
187 attr->rgValue[0].pbData, | |
188 attr->rgValue[0].cbData, | |
189 0, | |
190 &file_time, | |
191 &data_size)) { | |
192 return HRESULT_FROM_WIN32(::GetLastError()); | |
193 } | |
194 | |
195 if (!::FileTimeToSystemTime(&file_time, system_time)) { | |
196 return HRESULT_FROM_WIN32(::GetLastError()); | |
197 } | |
198 | |
199 return S_OK; | |
200 } | |
201 | |
202 } // namespace | |
203 | |
204 CertInfo::CertInfo(const CERT_CONTEXT* given_cert_context) | |
205 : cert_context_(NULL) { | |
206 if (given_cert_context) { | |
207 // CertDuplicateCertificateContext just increases reference count of a given | |
208 // CERT_CONTEXT. | |
209 cert_context_ = CertDuplicateCertificateContext(given_cert_context); | |
210 not_valid_before_ = cert_context_->pCertInfo->NotBefore; | |
211 not_valid_after_ = cert_context_->pCertInfo->NotAfter; | |
212 // Extract signed party details. | |
213 ExtractIssuerInfo(cert_context_, | |
214 &issuing_company_name_, | |
215 &issuing_dept_name_, | |
216 &trust_authority_name_); | |
217 } | |
218 } | |
219 | |
220 CertInfo::~CertInfo() { | |
221 // Decrement reference count, if needed. | |
222 if (cert_context_) | |
223 CertFreeCertificateContext(cert_context_); | |
224 } | |
225 | |
226 | |
227 bool CertInfo::IsValidNow() const { | |
228 // we cannot directly get current time in FILETIME format. | |
229 // so first get it in SYSTEMTIME format and convert it into FILETIME. | |
230 SYSTEMTIME now; | |
231 GetSystemTime(&now); | |
232 FILETIME filetime_now; | |
233 SystemTimeToFileTime(&now, &filetime_now); | |
234 // CompareFileTime() is a windows function | |
235 return ((CompareFileTime(&filetime_now, ¬_valid_before_) > 0) | |
236 && (CompareFileTime(&filetime_now, ¬_valid_after_) < 0)); | |
237 } | |
238 | |
239 | |
240 CString CertInfo::FileTimeToString(const FILETIME* ft) { | |
241 if (ft == NULL) | |
242 return _T(""); | |
243 SYSTEMTIME st; | |
244 if (!FileTimeToSystemTime(ft, &st)) | |
245 return _T(""); | |
246 | |
247 // Build a string showing the date and time. | |
248 CString time_str; | |
249 time_str.Format(_T("%02d/%02d/%d %02d:%02d"), st.wDay, st.wMonth, st.wYear, | |
250 st.wHour, st.wMinute); | |
251 return time_str; | |
252 } | |
253 | |
254 | |
255 bool CertInfo::ExtractField(const CERT_CONTEXT* cert_context, | |
256 const char* field_name, | |
257 CString* field_value) { | |
258 if ((!cert_context) || (!field_name) || (!field_value)) { | |
259 return false; | |
260 } | |
261 | |
262 field_value->Empty(); | |
263 | |
264 DWORD num_chars = ::CertGetNameString(cert_context, | |
265 CERT_NAME_ATTR_TYPE, | |
266 0, | |
267 const_cast<char*>(field_name), | |
268 NULL, | |
269 0); | |
270 if (num_chars > 1) { | |
271 num_chars = ::CertGetNameString(cert_context, | |
272 CERT_NAME_ATTR_TYPE, | |
273 0, | |
274 const_cast<char*>(field_name), | |
275 CStrBuf(*field_value, num_chars), | |
276 num_chars); | |
277 } | |
278 | |
279 return num_chars > 1 ? true : false; | |
280 } | |
281 | |
282 | |
283 bool CertInfo::ExtractIssuerInfo(const CERT_CONTEXT* cert_context, | |
284 CString* orgn_name, | |
285 CString* orgn_dept_name, | |
286 CString* trust_authority) { | |
287 // trust-authority is optional, so no check. | |
288 if ((!orgn_name) || (!orgn_dept_name)) { | |
289 return false; | |
290 } | |
291 | |
292 ExtractField(cert_context, szOID_COMMON_NAME, orgn_name); | |
293 ExtractField(cert_context, szOID_ORGANIZATIONAL_UNIT_NAME, orgn_dept_name); | |
294 if (trust_authority != NULL) { | |
295 ExtractField(cert_context, szOID_ORGANIZATION_NAME, trust_authority); | |
296 } | |
297 | |
298 return true; | |
299 } | |
300 | |
301 | |
302 void CertList::FindFirstCert(CertInfo** result_cert_info, | |
303 const CString &company_name_to_match, | |
304 const CString &orgn_unit_to_match, | |
305 const CString &trust_authority_to_match, | |
306 bool allow_test_variant, | |
307 bool check_cert_is_valid_now) { | |
308 if (!result_cert_info) | |
309 return; | |
310 (*result_cert_info) = NULL; | |
311 | |
312 for (CertInfoList::const_iterator cert_iter = cert_list_.begin(); | |
313 cert_iter != cert_list_.end(); | |
314 ++cert_iter) { | |
315 // If any of the criteria does not match, continue on to next certificate | |
316 if (!company_name_to_match.IsEmpty()) { | |
317 const TCHAR* certificate_company_name = | |
318 (*cert_iter)->issuing_company_name_; | |
319 bool names_match = company_name_to_match == certificate_company_name; | |
320 if (!names_match && allow_test_variant) { | |
321 CString test_variant = company_name_to_match; | |
322 test_variant += _T(" (TEST)"); | |
323 names_match = test_variant == certificate_company_name; | |
324 } | |
325 if (!names_match) | |
326 continue; | |
327 } | |
328 if (!orgn_unit_to_match.IsEmpty() && | |
329 orgn_unit_to_match != (*cert_iter)->issuing_dept_name_) | |
330 continue; | |
331 if (!trust_authority_to_match.IsEmpty() && | |
332 trust_authority_to_match != (*cert_iter)->trust_authority_name_) | |
333 continue; | |
334 // All the criteria matched. But, add only if it is a valid certificate. | |
335 if (!check_cert_is_valid_now || (*cert_iter)->IsValidNow()) { | |
336 (*result_cert_info) = (*cert_iter); | |
337 return; | |
338 } | |
339 } | |
340 } | |
341 | |
342 | |
343 void ExtractAllCertificatesFromSignature(const wchar_t* signed_file, | |
344 CertList* cert_list) { | |
345 if ((!signed_file) || (!cert_list)) | |
346 return; | |
347 | |
348 DWORD encoding_type = 0, content_type = 0, format_type = 0; | |
349 // If successful, cert_store will be populated by | |
350 // a store containing all the certificates related to the file signature. | |
351 HCERTSTORE cert_store = NULL; | |
352 BOOL succeeded = CryptQueryObject(CERT_QUERY_OBJECT_FILE, | |
353 signed_file, | |
354 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, | |
355 CERT_QUERY_FORMAT_FLAG_ALL, | |
356 0, // has to be zero as documentation says | |
357 &encoding_type, // DWORD *pdwMsgAndCertEncodingType, | |
358 &content_type, // DWORD *pdwContentType, | |
359 &format_type, // DWORD *pdwFormatType, | |
360 &cert_store, // HCERTSTORE *phCertStore, | |
361 NULL, // HCRYPTMSG *phMsg, | |
362 NULL); // const void** pvContext | |
363 | |
364 if (succeeded && (cert_store != NULL)) { | |
365 PCCERT_CONTEXT cert_context_ptr = NULL; | |
366 while ((cert_context_ptr = | |
367 CertEnumCertificatesInStore(cert_store, cert_context_ptr)) | |
368 != NULL) { | |
369 CertInfo* cert_info = new CertInfo(cert_context_ptr); | |
370 cert_list->AddCertificate(cert_info); | |
371 } | |
372 } | |
373 if (cert_store) { | |
374 CertCloseStore(cert_store, 0); | |
375 } | |
376 return; | |
377 } | |
378 | |
379 // Only check the CN. The OU can change. | |
380 // TODO(omaha): A better way to implement the valid now check would be to add | |
381 // a parameter to VerifySignature that adds WTD_LIFETIME_SIGNING_FLAG. | |
382 bool VerifySigneeIsGoogleInternal(const wchar_t* signed_file, | |
383 bool check_cert_is_valid_now) { | |
384 CertList cert_list; | |
385 ExtractAllCertificatesFromSignature(signed_file, &cert_list); | |
386 if (cert_list.size() > 0) { | |
387 CertInfo* required_cert = NULL; | |
388 // now, see if one of the certificates in the signature belongs to Google. | |
389 cert_list.FindFirstCert(&required_cert, | |
390 kCertificateSubjectName, | |
391 CString(), | |
392 CString(), | |
393 true, | |
394 check_cert_is_valid_now); | |
395 if (required_cert != NULL) { | |
396 return true; | |
397 } | |
398 } | |
399 return false; | |
400 } | |
401 | |
402 // Does not verify that the certificate is currently valid. | |
403 // VerifySignature verifies that the certificate was valid at signing time as | |
404 // part of the normal signature verification. | |
405 bool VerifySigneeIsGoogle(const wchar_t* signed_file) { | |
406 return VerifySigneeIsGoogleInternal(signed_file, false); | |
407 } | |
408 | |
409 HRESULT VerifySignature(const wchar_t* signed_file, bool allow_network_check) { | |
410 // Don't pop up any windows | |
411 HWND const kWindowMode = reinterpret_cast<HWND>(INVALID_HANDLE_VALUE); | |
412 | |
413 // Verify file & certificates | |
414 GUID verification_type = WINTRUST_ACTION_GENERIC_VERIFY_V2; | |
415 | |
416 // Info for the file we're going to verify | |
417 WINTRUST_FILE_INFO file_info = {0}; | |
418 file_info.cbStruct = sizeof(file_info); | |
419 file_info.pcwszFilePath = signed_file; | |
420 | |
421 // Info for request to WinVerifyTrust | |
422 WINTRUST_DATA trust_data; | |
423 ZeroMemory(&trust_data, sizeof(trust_data)); | |
424 trust_data.cbStruct = sizeof(trust_data); | |
425 trust_data.dwUIChoice = WTD_UI_NONE; // no graphics | |
426 // No additional revocation checking -- note that this flag does not | |
427 // cancel the flag we set in dwProvFlags; it specifies that no -additional- | |
428 // checks are to be performed beyond the provider-specified ones. | |
429 trust_data.fdwRevocationChecks = WTD_REVOKE_NONE; | |
430 trust_data.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; | |
431 | |
432 if (!allow_network_check) | |
433 trust_data.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL; | |
434 | |
435 trust_data.dwUnionChoice = WTD_CHOICE_FILE; // check a file | |
436 trust_data.pFile = &file_info; // check this file | |
437 | |
438 // If the trust provider verifies that the subject is trusted for the | |
439 // specified action, the return value is zero. No other value besides zero | |
440 // should be considered a successful return. | |
441 LONG result = ::WinVerifyTrust(kWindowMode, &verification_type, &trust_data); | |
442 if (result != 0) { | |
443 return FAILED(result) ? result : HRESULT_FROM_WIN32(result); | |
444 } | |
445 return S_OK; | |
446 } | |
447 | |
448 // This method must not return until the end to avoid leaking memory. | |
449 // More info on Authenticode Signatures Time Stamping can be found at | |
450 // http://msdn2.microsoft.com/en-us/library/bb931395.aspx. | |
451 HRESULT GetSigningTime(const wchar_t* signed_file, SYSTEMTIME* signing_time) { | |
452 if (!signed_file || !signing_time) { | |
453 return E_INVALIDARG; | |
454 } | |
455 | |
456 HCERTSTORE cert_store = NULL; | |
457 HCRYPTMSG message = NULL; | |
458 PCMSG_SIGNER_INFO signer_info = NULL; | |
459 PCMSG_SIGNER_INFO countersigner_info = NULL; | |
460 | |
461 HRESULT hr = GetCertStoreFromFile(signed_file, &cert_store, &message); | |
462 | |
463 if (SUCCEEDED(hr)) { | |
464 hr = GetSignerInfo(message, &signer_info); | |
465 } | |
466 | |
467 if (SUCCEEDED(hr)) { | |
468 hr = GetTimeStampSignerInfo(signer_info, &countersigner_info); | |
469 } | |
470 | |
471 if (SUCCEEDED(hr)) { | |
472 hr = GetDateOfTimeStamp(countersigner_info, signing_time); | |
473 } | |
474 | |
475 if (cert_store) { | |
476 ::CertCloseStore(cert_store, 0); | |
477 } | |
478 if (message) { | |
479 ::CryptMsgClose(message); | |
480 } | |
481 ::LocalFree(signer_info); | |
482 ::LocalFree(countersigner_info); | |
483 | |
484 return hr; | |
485 } | |
486 | |
487 HRESULT VerifyFileSignedWithinDays(const wchar_t* signed_file, int days) { | |
488 if (!signed_file || days <= 0) { | |
489 return E_INVALIDARG; | |
490 } | |
491 | |
492 SYSTEMTIME signing_time = {0}; | |
493 HRESULT hr = GetSigningTime(signed_file, &signing_time); | |
494 if (FAILED(hr)) { | |
495 return hr; | |
496 } | |
497 | |
498 // Use the Win32 API instead of CTime::GetCurrentTime() because the latter | |
499 // is broken in VS 2003 and 2005 and doesn't account for the timezone. | |
500 SYSTEMTIME current_system_time = {0}; | |
501 ::GetSystemTime(¤t_system_time); | |
502 | |
503 CTime signed_time(signing_time); | |
504 CTime current_time(current_system_time); | |
505 | |
506 if (current_time <= signed_time) { | |
507 return TRUST_E_TIME_STAMP; | |
508 } | |
509 | |
510 CTimeSpan time_since_signed = current_time - signed_time; | |
511 CTimeSpan max_duration(days, 0, 0, 0); | |
512 | |
513 if (max_duration < time_since_signed) { | |
514 return TRUST_E_TIME_STAMP; | |
515 } | |
516 | |
517 return S_OK; | |
518 } | |
519 | |
520 } // namespace omaha | |
521 | |
OLD | NEW |