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
169
170
171
172
173
174
175
176
177
178
179
180
181
/* 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 <memory>

#include "registrycertificates.hxx"
#include "pathhash.h"
#include "servicebase.hxx"
#include "updatehelper.h"
#define MAX_KEY_LENGTH 255

namespace {

struct AutoRegKey
{
    AutoRegKey(HKEY key):
        mKey(key)
    {
    }

    ~AutoRegKey()
    {
        releaseKey(mKey);
    }

    void releaseKey(HKEY key)
    {
        if (key != nullptr)
        {
            RegCloseKey(key);
        }
    }

    HKEY mKey;

    HKEY get()
    {
        return mKey;
    }
};

}

/**
 * Verifies if the file path matches any certificate stored in the registry.
 *
 * @param  filePath The file path of the application to check if allowed.
 * @return TRUE if the binary matches any of the allowed certificates.
 */
BOOL
DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, LPCWSTR filePath)
{
    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 baseKeyRaw;
    LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                                 maintenanceServiceKey, 0,
                                 KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw);
    if (retCode != ERROR_SUCCESS)
    {
        LOG_WARN(("Could not open key.  (%d)", retCode));
        // Our tests run with a different apply directory for each test.
        // We use this registry key on our test slaves to store the
        // allowed name/issuers.
        retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                                TEST_ONLY_FALLBACK_KEY_PATH, 0,
                                KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Could not open fallback key.  (%d)", retCode));
            return FALSE;
        }
    }
    AutoRegKey baseKey(baseKeyRaw);

    // Get the number of subkeys.
    DWORD subkeyCount = 0;
    retCode = RegQueryInfoKeyW(baseKey.get(), nullptr, nullptr, nullptr, &subkeyCount,
                               nullptr, nullptr, nullptr, nullptr, nullptr,
                               nullptr, nullptr);
    if (retCode != ERROR_SUCCESS)
    {
        LOG_WARN(("Could not query info key.  (%d)", retCode));
        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.get(), i, subkeyBuffer,
                                &subkeyBufferCount, nullptr,
                                nullptr, nullptr, nullptr);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Could not enum certs.  (%d)", retCode));
            return FALSE;
        }

        // Open the subkey for the current certificate
        HKEY subKeyRaw;
        retCode = RegOpenKeyExW(baseKey.get(),
                                subkeyBuffer,
                                0,
                                KEY_READ | KEY_WOW64_64KEY,
                                &subKeyRaw);
        AutoRegKey subKey(subKeyRaw);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Could not open subkey.  (%d)", 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.get(), L"name", 0, nullptr,
                                   (LPBYTE)name, &valueBufSize);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Could not obtain name from registry.  (%d)", retCode));
            continue; // Try the next subkey
        }

        // Get the issuer from the registry
        valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
        retCode = RegQueryValueExW(subKey.get(), L"issuer", 0, nullptr,
                                   (LPBYTE)issuer, &valueBufSize);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Could not obtain issuer from registry.  (%d)", retCode));
            continue; // Try the next subkey
        }

        CertificateCheckInfo allowedCertificate =
        {
            name,
            issuer,
        };

        retCode = CheckCertificateForPEFile(filePath, allowedCertificate);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Error on certificate check.  (%d)", retCode));
            continue; // Try the next subkey
        }

        retCode = VerifyCertificateTrustForFile(filePath);
        if (retCode != ERROR_SUCCESS)
        {
            LOG_WARN(("Error on certificate trust check.  (%d)", retCode));
            continue; // Try the next subkey
        }

        // Raise the roof, we found a match!
        return TRUE;
    }

    // No certificates match, :'(
    return FALSE;
}