1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "registrycertificates.h"
#include "pathhash.h"
#include "updatecommon.h"
#include "updatehelper.h"
#define MAX_KEY_LENGTH 255
/**
* Verifies if the file path matches any certificate stored in the registry.
*
* @param filePath
* The file path of the application to check if allowed.
* @param allowFallbackKeySkip
* When this is TRUE the fallback registry key can be used to skip the
* certificate check. This is the default since the fallback registry
* key is located under HKEY_LOCAL_MACHINE which can't be written to by
* a low integrity process.
* Note: The maintenance service binary can be used to perform this
* check for testing or troubleshooting.
* Note: When this is `TRUE` and we are building with
* `DISABLE_UPDATER_AUTHENTICODE_CHECK`, this function will
* unconditionally return `TRUE` since that flag is meant to
* disable specifically this. We don't fall through in the `FALSE`
* case since currently the only time when we don't allow the
* fallback key is when we are running this for debugging purposes
* and, in that case, it's more helpful if we return something
* meaningful here.
*
* @return TRUE if the binary matches any of the allowed certificates.
*/
BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate,
LPCWSTR filePath,
BOOL allowFallbackKeySkip) {
#ifdef DISABLE_UPDATER_AUTHENTICODE_CHECK
if (allowFallbackKeySkip) {
LOG_WARN(("Skipping authenticode check"));
return TRUE;
} else {
LOG(("Performing a diagnostic authenticode check"));
}
#endif
WCHAR maintenanceServiceKey[MAX_PATH + 1];
if (!CalculateRegistryPathFromFilePath(basePathForUpdate,
maintenanceServiceKey)) {
return FALSE;
}
// We use KEY_WOW64_64KEY to always force 64-bit view.
// The user may have both x86 and x64 applications installed
// which each register information. We need a consistent place
// to put those certificate attributes in and hence why we always
// force the non redirected registry under Wow6432Node.
// This flag is ignored on 32bit systems.
HKEY baseKey;
LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
KEY_READ | KEY_WOW64_64KEY, &baseKey);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not open key. (%ld)", retCode));
// Our tests run with a different apply directory for each test.
// We use this registry key on our test machines to store the
// allowed name/issuers.
retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEST_ONLY_FALLBACK_KEY_PATH, 0,
KEY_READ | KEY_WOW64_64KEY, &baseKey);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not open fallback key. (%ld)", retCode));
return FALSE;
} else if (allowFallbackKeySkip) {
LOG_WARN(
("Fallback key present, skipping VerifyCertificateTrustForFile "
"check and the certificate attribute registry matching "
"check."));
RegCloseKey(baseKey);
return TRUE;
}
}
// Get the number of subkeys.
DWORD subkeyCount = 0;
retCode = RegQueryInfoKeyW(baseKey, nullptr, nullptr, nullptr, &subkeyCount,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not query info key. (%ld)", retCode));
RegCloseKey(baseKey);
return FALSE;
}
// Enumerate the subkeys, each subkey represents an allowed certificate.
for (DWORD i = 0; i < subkeyCount; i++) {
WCHAR subkeyBuffer[MAX_KEY_LENGTH];
DWORD subkeyBufferCount = MAX_KEY_LENGTH;
retCode = RegEnumKeyExW(baseKey, i, subkeyBuffer, &subkeyBufferCount,
nullptr, nullptr, nullptr, nullptr);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not enum certs. (%ld)", retCode));
RegCloseKey(baseKey);
return FALSE;
}
// Open the subkey for the current certificate
HKEY subKey;
retCode = RegOpenKeyExW(baseKey, subkeyBuffer, 0,
KEY_READ | KEY_WOW64_64KEY, &subKey);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not open subkey. (%ld)", retCode));
continue; // Try the next subkey
}
const int MAX_CHAR_COUNT = 256;
DWORD valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
WCHAR name[MAX_CHAR_COUNT] = {L'\0'};
WCHAR issuer[MAX_CHAR_COUNT] = {L'\0'};
// Get the name from the registry
retCode = RegQueryValueExW(subKey, L"name", 0, nullptr, (LPBYTE)name,
&valueBufSize);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not obtain name from registry. (%ld)", retCode));
RegCloseKey(subKey);
continue; // Try the next subkey
}
// Get the issuer from the registry
valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
retCode = RegQueryValueExW(subKey, L"issuer", 0, nullptr, (LPBYTE)issuer,
&valueBufSize);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Could not obtain issuer from registry. (%ld)", retCode));
RegCloseKey(subKey);
continue; // Try the next subkey
}
CertificateCheckInfo allowedCertificate = {
name,
issuer,
};
retCode = CheckCertificateForPEFile(filePath, allowedCertificate);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Error on certificate check. (%ld)", retCode));
RegCloseKey(subKey);
continue; // Try the next subkey
}
retCode = VerifyCertificateTrustForFile(filePath);
if (retCode != ERROR_SUCCESS) {
LOG_WARN(("Error on certificate trust check. (%ld)", retCode));
RegCloseKey(subKey);
continue; // Try the next subkey
}
RegCloseKey(baseKey);
// Raise the roof, we found a match!
return TRUE;
}
RegCloseKey(baseKey);
// No certificates match, :'(
return FALSE;
}
|