Реализовать с помощью CSP часть хэндшейка TLS - это задача чуть более сложная чем просто экспорт сессионного ключа. Вы на верном пути, но путь этот долог :) Готовая реализация TLS входит в комплект CSP, поэтому если вас устраивает она - рекомендуется воспользоваться интерфейсом SSPI. Вся необходимая документация для этого имеется. Если же вы хотите чего-то экзотического, то боюсь готовой подробной документации по использованию CSP для реализации TLS не существует - хотя каждый отдельный вызов CSP, использующийся для этого, документирован.
На конкретные вопросы ответы таковы: Во первых, приведённый по ссылке алгоритм обмена не полон. Необходимо еще согласовать UKM, параметры алгоритма ключа и алгоритм обмена. Где-то в документации это должно было быть описано полностью.
Вкратце в этой цепочке не хватает следующих вызовов (помеченых восклицательным знаком):
CPGetUserKey(hProv, AT_KEYEXCHANGE, &hUserKey);
CPGenKey(hProv, CALG_TLS1_MASTER, CRYPT_EXPORTABLE, &hSessionKey);
CPImportKey(hProv, pbRecipentPublicKey, cbRecipentPublicKey, hUserKey, 0, &hExchKey);
/* ! */ CPSetKeyParam (hProv, hExchKey, KP_ALGID, (LPBYTE)&ke_alg, 0 ); /* ALG_ID ke_alg = CALG_SIMPLE_EXPORT; - Алгоритм simple key wrap */
/* ! */ CPSetKeyParam (hProv,hExchKey, KP_SV, ukm, 0); /* Задаём UKM. */
CPExportKey(hProv, hSessionKey, hExchKey, SIMPLEBLOB, 0, pbSessionKeyForRecipient, &cbSessionKeyForRecipient);
В случае если параметры ключей сторон не совпадают, то используется эфемеральный ключ. Т.е. вместо CPGetUserKey(hProv, AT_KEYEXCHANGE, &hUserKey) мы должны сгенерировать эфемеральный ключ с параметрами, такими же как у получателя. Делается это так:
CPGenKey (hProv, pubKeyAlgId, CRYPT_EXPORTABLE | CRYPT_PREGEN, &hUserKey);
CPSetKeyParam (hProv, hUserKey, KP_DHOID, (LPBYTE) recipient_public_key_parameters.publicKeyParamSet, 0);
CPSetKeyParam (hProv, hUserKey, KP_HASHOID, (LPBYTE) recipient_public_key_parameters.digestParamSet, 0);
CPSetKeyParam (hProv,hUserKey, KP_X, NULL, 0);
Что касается ukm, то для TLS он вычисляется как первые 8 байт хэша от конкатенации client_random и server_random.
Что касается KP_CLIENT_RANDOM и KP_SERVER_RANDOM, то как описано в документации, они используются при преобразовании premaster_secret в master_secret, т.е. после описанного выше экспорта ключа.
CPSetKeyParam (hProv, hSessionKey, KP_CLIENT_RANDOM, client_random, 0);
CPSetKeyParam (hProv, hSessionKey, KP_SERVER_RANDOM, server_random, 0);
CPSetKeyParam (hProv, hSessionKey, KP_PREHASH, NULL, 0);
Когда дело дойдёт до выработки сессионных ключей из master_secret, обратите внимание на алгоритм хеша CALG_TLS1_MASTER_HASH. Именно с помощью него осуществляются данные операции. Вкратце, в master_secret повторно устанавливаются KP_CLIENT_RANDOM и KP_SERVER_RANDOM, потом производится CPCreateHash (hProv, CALG_TLS1_MASTER_HASH, hSessionKey, 0, &hMasterHash), потом из него получаются 2 пары ключей (шифрования/контроля целостности для чтения/записи), каждая примерно следующей последовательностью вызовов:
DWORD Flags = (мы сервер && ключ чтения) || (мы клиент && ключ записи) ? 0 : CRYPT_SERVER;
CPDeriveKey (hProv, CALG_TLS1_ENC_KEY, hMasterHash, Flags, &hKey);
CPSetKeyParam (hProv, hKey, KP_CIPHEROID, (LPBYTE)OID_CipherVerbaO, 0);
CPSetKeyParam (hProv, hKey, KP_MODE, (LPBYTE)&cipherMode, 0);
CPDeriveKey (hProv, CALG_TLS1_MAC_KEY, hMasterHash, Flags, &hHMACKey);
CPSetKeyParam (hProv, hHMACKey, KP_CIPHEROID, (LPBYTE)OID_CipherVerbaO, 0);
CPCreateHash (hProv, CALG_G28147_IMIT, hHMACKey, 0, &hHMACHash);