Клиент SSL gRPC отлично работает на С#, но не работает с НЕДОСТУПНЫМ «Пустым обновлением» на С++

В Windows 10 Pro 21H2 с VS2022 17.1.2 и .NET 6 я переношу простой клиент C# gRPC на C++, но клиент C++ всегда не может подключиться к серверу, несмотря на то, что мой код, по-видимому, делает то же самое, и у меня закончились идеи, почему.

Мой сервер gRPC использует SSL с сертификатом, сгенерированным LetsEncrypt (через LettuceEncrypt), поэтому я использую по умолчанию SslCredentials.

В C# (с Grpc.Core) я использую gRPC следующим образом:

// Channel and client creation
var channel = new Channel("my.domain.org:12345", new SslCredentials());
var client = new MyGrpc.MyGrpcClient(channel);
// Sample call
LoginUserReply reply = client.LoginUser(new LoginUserRequest()
{
    Username = username
});

Я преобразовал это в C++ следующим образом, в основном на основе примеров, приведенных на веб-сайте gRPC:

// Channel and client creation
auto channelCreds = SslCredentials(SslCredentialsOptions());
auto channel = CreateChannel("my.domain.org:12345", channelCreds);
auto stub = MyGrpc::NewStub(channel);
// Sample call
ClientContext context;
LoginUserRequest request;
request.set_username(username);
LoginUserReply reply;
Status status = m_stub->LoginUser(&context, request, &reply);

Однако с таким кодом на C++ status всегда сообщает об ошибке с НЕДОСТУПЕН (14) "Пустое обновление".

Почему это так, и как я могу это исправить?


Исследование, которое я сделал до сих пор:

  • Использование только InsecureChannelCredentials() приводит к НЕДОСТУПНО (14) «не удалось подключиться ко всем адресам».

  • Запустив сервер в текстовом формате, вызов делает работает с ОК (0) только в целях тестирования (предложено Лэй Янгом).

  • Я установил переменные окружения GRPC_TRACE=all и GRPC_VERBOSITY=DEBUG, но не увидел дополнительных логов от клиента.

  • На сервере С# я установил уровень ведения журнала Grpc на «Отладка» и заметил только отсутствие строки журнала «Чтение сообщения», как с работающими/незашифрованными клиентами.


2
88
1

Ответ:

Решено

Это известная проблема в реализации клиента gRPC для Windows C++ (и, очевидно, macOS тоже). В Руководство по аутентификации gRPC есть небольшое примечание:

Non-POSIX-compliant systems (such as Windows) need to specify the root certificates in SslCredentialsOptions, since the defaults are only configured for POSIX filesystems.

Итак, чтобы реализовать это в Windows, вы заполняете SslCredentialsOptions следующим образом:

#include <wincrypt.h>

SslCredentialsOptions getSslOptions()
{
    // Fetch root certificate as required on Windows (s. issue 25533).
    SslCredentialsOptions result;

    // Open root certificate store.
    HANDLE hRootCertStore = CertOpenSystemStoreW(NULL, L"ROOT");
    if (!hRootCertStore)
        return result;

    // Get all root certificates.
    PCCERT_CONTEXT pCert = NULL;
    while ((pCert = CertEnumCertificatesInStore(hRootCertStore, pCert)) != NULL)
    {
        // Append this certificate in PEM formatted data.
        DWORD size = 0;
        CryptBinaryToStringW(pCert->pbCertEncoded, pCert->cbCertEncoded,
            CRYPT_STRING_BASE64HEADER, NULL, &size);
        std::vector<WCHAR> pem(size);
        CryptBinaryToStringW(pCert->pbCertEncoded, pCert->cbCertEncoded,
            CRYPT_STRING_BASE64HEADER, pem.data(), &size);

        result.pem_root_certs += utf8Encode(pem.data());
    }

    CertCloseStore(hRootCertStore, 0);
    return result;
}

И если вам тоже нужен метод utf8Encode:

std::string utf8Encode(const std::wstring& wstr)
{
    if (wstr.empty())
        return string();

    int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(),
        NULL, 0, NULL, NULL);
    std::string strTo(sizeNeeded, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(),
        &strTo[0], sizeNeeded, NULL, NULL);
    return strTo;
}

В репозитории gRPC есть (к сожалению, постоянно устаревающий) запрос функции, чтобы добавить готовую поддержку этого в Windows, а также сделать поведение по умолчанию SslCredentials() одинаковым во всех системах.