GateKeeper.cpp
author Volker Birk <vb@pep-project.org>
Thu, 23 Jun 2016 21:08:02 +0200
changeset 123 6d311985ee41
parent 121 6ee2155ec27a
child 124 ce9401d49537
permissions -rw-r--r--
Micro$oft Crypto Nonsense
vb@88
     1
#include "stdafx.h"
vb@88
     2
vb@88
     3
#include "GateKeeper.h"
vb@110
     4
#include "pEpCOMServerAdapter.h"
vb@123
     5
#include "utf8_helper.h"
vb@88
     6
vb@88
     7
using namespace std;
vb@88
     8
vb@123
     9
// https://gist.github.com/mcdurdin/5626617
vb@123
    10
vb@123
    11
struct PUBLIC_KEY_VALUES {
vb@123
    12
    BLOBHEADER blobheader;
vb@123
    13
    RSAPUBKEY rsapubkey;
vb@123
    14
    BYTE modulus[4096];
vb@123
    15
};
vb@123
    16
vb@123
    17
static void ReverseMemCopy(
vb@123
    18
    _Out_ BYTE       *pbDest,
vb@123
    19
    _In_  BYTE const *pbSource,
vb@123
    20
    _In_  DWORD       cb
vb@123
    21
    )
vb@123
    22
{
vb@123
    23
    for (DWORD i = 0; i < cb; i++) {
vb@123
    24
        pbDest[cb - 1 - i] = pbSource[i];
vb@123
    25
    }
vb@123
    26
}
vb@123
    27
vb@123
    28
static NTSTATUS ImportRsaPublicKey(
vb@123
    29
    _In_ BCRYPT_ALG_HANDLE  hAlg,    // CNG provider
vb@123
    30
    _In_ PUBLIC_KEY_VALUES *pKey,    // Pointer to the RSAPUBKEY blob.
vb@123
    31
    _In_ BCRYPT_KEY_HANDLE *phKey    // Receives a handle the imported public key.
vb@123
    32
    )
vb@123
    33
{
vb@123
    34
    NTSTATUS hr = 0;
vb@123
    35
vb@123
    36
    BYTE *pbPublicKey = NULL;
vb@123
    37
    DWORD cbKey = 0;
vb@123
    38
vb@123
    39
    // Layout of the RSA public key blob:
vb@123
    40
vb@123
    41
    //  +----------------------------------------------------------------+
vb@123
    42
    //  |     BCRYPT_RSAKEY_BLOB    | BE( dwExp ) |   BE( Modulus )      |
vb@123
    43
    //  +----------------------------------------------------------------+
vb@123
    44
    //
vb@123
    45
    //  sizeof(BCRYPT_RSAKEY_BLOB)       cbExp           cbModulus 
vb@123
    46
    //  <--------------------------><------------><---------------------->
vb@123
    47
    //
vb@123
    48
    //   BE = Big Endian Format                                                     
vb@123
    49
vb@123
    50
    DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
vb@123
    51
    DWORD dwExp = pKey->rsapubkey.pubexp;
vb@123
    52
    DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
vb@123
    53
        (dwExp & 0x00FF0000) ? 3 :
vb@123
    54
        (dwExp & 0x0000FF00) ? 2 : 1;
vb@123
    55
vb@123
    56
    BCRYPT_RSAKEY_BLOB *pRsaBlob;
vb@123
    57
    PBYTE pbCurrent;
vb@123
    58
vb@123
    59
    if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
vb@123
    60
        goto cleanup;
vb@123
    61
    }
vb@123
    62
vb@123
    63
    cbKey += cbExp;
vb@123
    64
vb@123
    65
    pbPublicKey = (PBYTE) CoTaskMemAlloc(cbKey);
vb@123
    66
    if (pbPublicKey == NULL) {
vb@123
    67
        hr = E_OUTOFMEMORY;
vb@123
    68
        goto cleanup;
vb@123
    69
    }
vb@123
    70
vb@123
    71
    ZeroMemory(pbPublicKey, cbKey);
vb@123
    72
    pRsaBlob = (BCRYPT_RSAKEY_BLOB *) (pbPublicKey);
vb@123
    73
vb@123
    74
    //
vb@123
    75
    // Make the Public Key Blob Header
vb@123
    76
    //
vb@123
    77
vb@123
    78
    pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
vb@123
    79
    pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
vb@123
    80
    pRsaBlob->cbPublicExp = cbExp;
vb@123
    81
    pRsaBlob->cbModulus = cbModulus;
vb@123
    82
    pRsaBlob->cbPrime1 = 0;
vb@123
    83
    pRsaBlob->cbPrime2 = 0;
vb@123
    84
vb@123
    85
    pbCurrent = (PBYTE) (pRsaBlob + 1);
vb@123
    86
vb@123
    87
    //
vb@123
    88
    // Copy pubExp Big Endian 
vb@123
    89
    //
vb@123
    90
vb@123
    91
    ReverseMemCopy(pbCurrent, (PBYTE) &dwExp, cbExp);
vb@123
    92
    pbCurrent += cbExp;
vb@123
    93
vb@123
    94
    //
vb@123
    95
    // Copy Modulus Big Endian 
vb@123
    96
    //
vb@123
    97
vb@123
    98
    ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
vb@123
    99
vb@123
   100
    //
vb@123
   101
    // Import the public key
vb@123
   102
    //
vb@123
   103
vb@123
   104
    hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR) pbPublicKey, cbKey, 0);
vb@123
   105
vb@123
   106
cleanup:
vb@123
   107
    CoTaskMemFree(pbPublicKey);
vb@123
   108
    return hr;
vb@123
   109
}
vb@123
   110
vb@96
   111
namespace pEp {
vb@96
   112
vb@96
   113
    const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
vb@96
   114
    const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
vb@96
   115
    const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater";)
vb@96
   116
vb@96
   117
    const time_t GateKeeper::cycle = 7200;   // 7200 sec is 2 h
vb@96
   118
    const DWORD GateKeeper::waiting = 10000; // 10000 ms is 10 sec
vb@88
   119
vb@117
   120
    GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
vb@116
   121
        : _self(self), now(time(NULL)), next(now + time_diff()), hkUpdater(NULL), internet(NULL), hAES(NULL), hRSA(NULL)
vb@96
   122
    {
vb@96
   123
        LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
vb@96
   124
        assert(lResult == ERROR_SUCCESS);
vb@96
   125
        if (lResult == ERROR_SUCCESS)
vb@96
   126
            cu_open = true;
vb@96
   127
        else
vb@96
   128
            cu_open = false;
vb@88
   129
vb@96
   130
        if (cu_open) {
vb@96
   131
            LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
vb@96
   132
            assert(lResult == ERROR_SUCCESS);
vb@96
   133
            if (lResult != ERROR_SUCCESS)
vb@96
   134
                return;
vb@96
   135
        }
vb@96
   136
    }
vb@96
   137
    
vb@96
   138
    GateKeeper::~GateKeeper()
vb@96
   139
    {
vb@96
   140
        if (cu_open) {
vb@96
   141
            if (hkUpdater)
vb@96
   142
                RegCloseKey(hkUpdater);
vb@96
   143
            RegCloseKey(cu);
vb@96
   144
        }
vb@96
   145
    }
vb@88
   146
vb@96
   147
    time_t GateKeeper::time_diff()
vb@96
   148
    {
vb@96
   149
        try {
vb@96
   150
            static random_device rd;
vb@96
   151
            static mt19937 gen(rd());
vb@88
   152
vb@96
   153
            uniform_int_distribution<time_t> dist(0, cycle);
vb@96
   154
vb@96
   155
            return dist(gen);
vb@96
   156
        }
vb@96
   157
        catch (exception&) {
vb@96
   158
            assert(0);
vb@96
   159
            return 0;
vb@96
   160
        }
vb@88
   161
    }
vb@96
   162
vb@96
   163
    void GateKeeper::keep()
vb@96
   164
    {
vb@96
   165
        if (!cu_open)
vb@96
   166
            return;
vb@96
   167
vb@96
   168
        while (1) {
vb@96
   169
            keep_plugin();
vb@96
   170
vb@96
   171
            now = time(NULL);
vb@96
   172
            assert(now != -1);
vb@96
   173
vb@96
   174
            if (now > next) {
vb@96
   175
                next = now + GateKeeper::cycle;
vb@96
   176
                keep_updated();
vb@96
   177
            }
vb@96
   178
vb@96
   179
            Sleep(waiting);
vb@96
   180
        }
vb@88
   181
    }
vb@96
   182
vb@96
   183
    void GateKeeper::keep_plugin()
vb@96
   184
    {
vb@110
   185
        while (!_self->m_bComInitialized)
vb@110
   186
            Sleep(1);
vb@110
   187
vb@123
   188
        //MessageBox(NULL, _T("test"), _T("keep_plugin"), MB_ICONINFORMATION | MB_TOPMOST);
vb@110
   189
vb@96
   190
        DWORD value;
vb@96
   191
        DWORD size;
vb@88
   192
vb@96
   193
        LONG lResult = RegGetValue(cu, plugin_reg_path, plugin_reg_value_name, RRF_RT_REG_DWORD, NULL, &value, &size);
vb@96
   194
        if (lResult != ERROR_SUCCESS)
vb@96
   195
            return;
vb@96
   196
vb@96
   197
        if (value != 3) {
vb@96
   198
            lResult = RegSetValue(cu, plugin_reg_path, RRF_RT_REG_DWORD, plugin_reg_value_name, 3);
vb@96
   199
            assert(lResult == ERROR_SUCCESS);
vb@96
   200
        }
vb@96
   201
    }
vb@96
   202
vb@111
   203
    string GateKeeper::update_key()
vb@110
   204
    {
vb@110
   205
        static string key;
vb@110
   206
vb@110
   207
        if (key.length() == 0) {
vb@110
   208
            HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
vb@110
   209
            assert(res);
vb@110
   210
            if (!res)
vb@110
   211
                throw runtime_error("FindResource: IRD_UPDATEKEY");
vb@110
   212
vb@110
   213
            HGLOBAL hRes = LoadResource(_self->hModule(), res);
vb@110
   214
            assert(hRes);
vb@110
   215
            if (!hRes)
vb@110
   216
                throw runtime_error("LoadResource: IRD_UPDATEKEY");
vb@110
   217
vb@110
   218
            key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
vb@110
   219
            UnlockResource(hRes);
vb@110
   220
        }
vb@110
   221
vb@110
   222
        return key;
vb@110
   223
    }
vb@110
   224
vb@116
   225
    BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
vb@112
   226
    {
vb@112
   227
        aeskey_t key;
vb@112
   228
vb@112
   229
        static random_device rd;
vb@112
   230
        static mt19937 gen(rd());
vb@112
   231
vb@123
   232
        uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
vb@112
   233
vb@123
   234
        for (int i = 0; i < 8; i++)
vb@123
   235
            key.dw_key[i] = (uint32_t) dist(gen);
vb@113
   236
vb@116
   237
        BCRYPT_KEY_HANDLE hKey;
vb@116
   238
        NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR) &key, (ULONG) sizeof(aeskey_t), 0);
vb@116
   239
        assert(status == 0);
vb@116
   240
        if (status)
vb@116
   241
            throw runtime_error("BCryptGenerateSymmetricKey");
vb@116
   242
vb@116
   243
        return hKey;
vb@113
   244
    }
vb@113
   245
vb@121
   246
    string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
vb@113
   247
    {
vb@113
   248
        string result;
vb@113
   249
vb@116
   250
        BCRYPT_KEY_HANDLE hUpdateKey;
vb@117
   251
        string _update_key = update_key();
vb@113
   252
vb@123
   253
        PCERT_PUBLIC_KEY_INFO uk;
vb@123
   254
        DWORD uk_size;
vb@123
   255
        BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
vb@123
   256
                (const BYTE *) _update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
vb@123
   257
        if (!bResult)
vb@123
   258
            throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
vb@123
   259
vb@123
   260
        PUBLIC_KEY_VALUES *_uk;
vb@123
   261
        DWORD _uk_size;
vb@123
   262
        bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
vb@123
   263
            uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
vb@123
   264
        LocalFree(uk);
vb@123
   265
        if (!bResult)
vb@123
   266
            throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
vb@123
   267
vb@123
   268
        HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
vb@123
   269
        LocalFree(_uk);
vb@123
   270
        if (hResult)
vb@123
   271
            throw runtime_error("ImportRsaPublicKey");
vb@117
   272
vb@120
   273
        aeskey_t _delivery_key;
vb@120
   274
        ULONG copied;
vb@123
   275
        NTSTATUS status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, (PUCHAR) &_delivery_key, sizeof(aeskey_t),
vb@123
   276
                &copied, 0);
vb@120
   277
        if (status)
vb@120
   278
            throw runtime_error("BCryptExportKey: delivery_key");
vb@120
   279
vb@117
   280
        static random_device rd;
vb@117
   281
        static mt19937 gen(rd());
vb@123
   282
        uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
vb@123
   283
        uint32_t r[64];
vb@123
   284
        for (int i = 0; i < 64; i++)
vb@123
   285
            r[i] = (uint32_t) dist(gen);
vb@117
   286
vb@117
   287
        BCRYPT_OAEP_PADDING_INFO pi;
vb@119
   288
        memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
vb@117
   289
        pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
vb@117
   290
        pi.pbLabel = (PUCHAR) r;
vb@117
   291
        pi.cbLabel = sizeof(r);
vb@117
   292
vb@117
   293
        ULONG result_size;
vb@118
   294
        PUCHAR _result = NULL;
vb@120
   295
        status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
vb@119
   296
        if (status) {
vb@119
   297
            BCryptDestroyKey(hUpdateKey);
vb@117
   298
            throw runtime_error("BCryptEncrypt: calculating result size");
vb@119
   299
        }
vb@117
   300
vb@117
   301
        _result = new UCHAR[result_size];
vb@120
   302
        status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
vb@119
   303
        if (status) {
vb@119
   304
            BCryptDestroyKey(hUpdateKey);
vb@119
   305
            delete[] _result;
vb@118
   306
            throw runtime_error("BCryptEncrypt: encrypting using update_key");
vb@119
   307
        }
vb@117
   308
vb@120
   309
        BCryptDestroyKey(hUpdateKey);
vb@120
   310
vb@117
   311
        stringstream s;
vb@118
   312
        s << hex << setw(2) << setfill('0');
vb@118
   313
        for (ULONG i = 0; i < copied; i++)
vb@118
   314
            s << (int) _result[i];
vb@117
   315
        delete[] _result;
vb@117
   316
        s >> result;
vb@117
   317
vb@113
   318
        return result;
vb@112
   319
    }
vb@112
   320
vb@96
   321
    GateKeeper::product_list& GateKeeper::registered_products()
vb@96
   322
    {
vb@96
   323
        static product_list products;
vb@88
   324
vb@96
   325
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
vb@96
   326
        static TCHAR value_name[16384];
vb@96
   327
        DWORD value_name_size;
vb@96
   328
        static TCHAR value[L_MAX_URL_LENGTH + 1];
vb@96
   329
        DWORD value_size;
vb@88
   330
vb@96
   331
        products.empty();
vb@96
   332
vb@96
   333
        LONG lResult = ERROR_SUCCESS;
vb@96
   334
        for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
vb@96
   335
            value_size = L_MAX_URL_LENGTH + 1;
vb@96
   336
            lResult = RegEnumValue(hkUpdater, 0, value_name, &value_name_size, NULL, NULL, (LPBYTE) value, &value_size);
vb@96
   337
            if (lResult == ERROR_SUCCESS)
vb@96
   338
                products.push_back({ value_name, value });
vb@88
   339
        }
vb@88
   340
vb@96
   341
        return products;
vb@88
   342
    }
vb@88
   343
vb@97
   344
    void GateKeeper::update_product(product p, DWORD context)
vb@96
   345
    {
vb@123
   346
        string delivery = wrapped_delivery_key(delivery_key());
vb@123
   347
vb@97
   348
        HINTERNET hUrl = InternetOpenUrl(internet, p.second.c_str(), NULL, 0,
vb@97
   349
                INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
vb@97
   350
        if (hUrl == NULL)
vb@97
   351
            return;
vb@94
   352
vb@97
   353
        // update
vb@107
   354
        PCTSTR rgpszAcceptTypes[] = { _T("text/plain"), NULL };
vb@107
   355
        HINTERNET hRequest = HttpOpenRequest(hUrl, NULL, _T("challenge"), NULL, NULL, rgpszAcceptTypes, INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
vb@107
   356
vb@97
   357
vb@97
   358
        InternetCloseHandle(hUrl);
vb@96
   359
    }
vb@88
   360
vb@96
   361
    void GateKeeper::keep_updated()
vb@96
   362
    {
vb@107
   363
        return; // disabled for now
vb@107
   364
vb@116
   365
        NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
vb@116
   366
        assert(status == 0);
vb@116
   367
        if (status)
vb@116
   368
            goto closing;
vb@116
   369
vb@116
   370
        status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
vb@116
   371
        assert(status == 0);
vb@116
   372
        if (status)
vb@116
   373
            goto closing;
vb@116
   374
vb@97
   375
        internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PROXY, NULL, NULL, 0);
vb@123
   376
        //if (!internet)
vb@123
   377
        //    goto closing;
vb@97
   378
vb@96
   379
        product_list& products = registered_products();
vb@97
   380
        DWORD context = 0;
vb@96
   381
        for (auto i = products.begin(); i != products.end(); i++) {
vb@97
   382
            update_product(*i, context++);
vb@96
   383
        }
vb@97
   384
vb@116
   385
    closing:
vb@116
   386
        if (internet)
vb@116
   387
            InternetCloseHandle(internet);
vb@116
   388
        if (hAES)
vb@116
   389
            BCryptCloseAlgorithmProvider(hAES, 0);
vb@116
   390
        if (hRSA)
vb@116
   391
            BCryptCloseAlgorithmProvider(hRSA, 0);
vb@97
   392
        internet = NULL;
vb@116
   393
        hAES = NULL;
vb@116
   394
        hRSA = NULL;
vb@96
   395
    }
vb@88
   396
vb@96
   397
} // namespace pEp