On Windows 10 Pro 21H2 with VS2022 17.1.2 and .NET 6, I am porting a simple C# gRPC client to C , but the C client always fails to connect to the server despite my code seemingly doing the same, and I ran out of ideas why.
My gRPC server is using SSL with a LetsEncrypt generated certificate (through LettuceEncrypt), thus I use default SslCredentials
.
In C# (with Grpc.Core
), I use gRPC as follows:
// 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
});
I converted this to C as follows, mostly based on examples given on the gRPC website:
// 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);
However, with the code like this in C , status
always reports failure with UNAVAILABLE (14) "Empty update".
Why is that the case, and how can I fix this?
Investigation I did so far:
Using only
InsecureChannelCredentials()
results in UNAVAILABLE (14) "failed to connect to all addresses".Running the server in plain-text, the call does work with OK (0) for testing purposes only (suggested by Lei Yang).
I set
GRPC_TRACE=all
andGRPC_VERBOSITY=DEBUG
environment variables, but saw no extra logging from the client.In the C# server, I set the Grpc logging level to Debug, and only noticed getting no "Reading message" log line as with working / unencrypted clients.
CodePudding user response:
This is a known issue in the Windows C implementation of the gRPC client (and apparently macOS too). There is a small note on the gRPC authentication guide stating:
Non-POSIX-compliant systems (such as Windows) need to specify the root certificates in SslCredentialsOptions, since the defaults are only configured for POSIX filesystems.
So, to implement this on Windows, you populate SslCredentialsOptions
as follows:
#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;
}
And if you need the utf8Encode
method too:
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;
}
There is a (sadly constantly staling) feature request on the gRPC repository to add out-of-the-box support for this on Windows aswell to make the behavior of default SslCredentials()
the same on all systems.