Description
I have been using for a while GRPC with c# to learn and test it’s capabilities. I recently installed on a secondary computer Kubuntu and docker and tried to make use of GRPC service by calling it from my laptop. This has failed because it requires a valid certificate for that (well..at leas a certificate because without a certificate authority it will still complain).
To solve my problems I used 2 amazing source of inspiration
- https://devblogs.microsoft.com/aspnet/configuring-https-in-asp-net-core-across-different-platforms/
- https://docs.servicestack.net/grpc-ssl
I will present the steps in a simple manner below
1. Install choco
(https://jcutrer.com/windows/install-chocolatey-choco-windows10)
2. Install openssl
3. Generate Certificates
From https://docs.servicestack.net/grpc-ssl – we have the 2 .sh files which we use to create certificates
From the above link I extracted 2 shell scripts but you have to chose one of them. For this example I used gen-prod.https.sh
The first script
gen-dev.https.sh
PASSWORD=grpc
if [ $# -ge 1 ]
then
PASSWORD=$1
fi
cat <>dev.config
[ req ]
default_bits = 2048
default_md = sha256
default_keyfile = dev.key
prompt = no
encrypt_key = no
distinguished_name = dn
req_extensions = v3_req
x509_extensions = x509_req
string_mask = utf8only
[ dn ]
commonName = localhost dev cert
emailAddress = test@localtest.me
countryName = US
stateOrProvinceName = DE
localityName = Wilmington
organizationName = Todo World
[ x509_req ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical, CA:false
keyUsage = critical, keyEncipherment
subjectAltName = @alt_names
# extendedKeyUsage = serverAuth, clientAuth
nsComment = "OpenSSL Generated Certificate"
[ v3_req ]
subjectKeyIdentifier = hash
basicConstraints = critical, CA:false
subjectAltName = @alt_names
# extendedKeyUsage = serverAuth, clientAuth
nsComment = "OpenSSL Generated Certificate"
[ alt_names ]
DNS.1 = localhost
EOT
openssl req -config dev.config -new -out dev.csr.pem
openssl x509 -req -days 365 -extfile dev.config -extensions v3_req -in dev.csr.pem -signkey dev.key -out dev.crt
openssl pkcs12 -export -out dev.pfx -inkey dev.key -in dev.crt -password pass:$PASSWORD
rm dev.config dev.csr.pem
# cp dev.pfx ../MyApp
To use this script open power shell, navigate to the folder and type
C:\folder> ./gen-dev.https.sh <domain>
The second shell script is
gen-prod.https.sh
DOMAIN=todoworld.servicestack.net
if [ $# -ge 1 ]
then
DOMAIN=$1
fi
PASSWORD=grpc
if [ $# -ge 2 ]
then
PASSWORD=$2
fi
cat <>prod.config
[ req ]
default_bits = 2048
default_md = sha256
default_keyfile = prod.key
prompt = no
encrypt_key = no
distinguished_name = dn
req_extensions = v3_req
x509_extensions = x509_req
string_mask = utf8only
[ dn ]
commonName = TodoWorld prod cert
emailAddress = todoworld@servicestack.net
countryName = US
stateOrProvinceName = DE
localityName = Wilmington
organizationName = Todo World
[ x509_req ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical, CA:false
keyUsage = critical, keyEncipherment
subjectAltName = @alt_names
# extendedKeyUsage = serverAuth, clientAuth
nsComment = "OpenSSL Generated Certificate"
[ v3_req ]
subjectKeyIdentifier = hash
basicConstraints = critical, CA:false
subjectAltName = @alt_names
# extendedKeyUsage = serverAuth, clientAuth
nsComment = "OpenSSL Generated Certificate"
[ alt_names ]
DNS.1 = $DOMAIN
EOT
openssl req -config prod.config -new -out prod.csr.pem
openssl x509 -req -days 365 -extfile prod.config -extensions v3_req -in prod.csr.pem -signkey prod.key -out prod.crt
openssl pkcs12 -export -out prod.pfx -inkey prod.key -in prod.crt -password pass:$PASSWORD
rm prod.config prod.csr.pem
# cp prod.pfx ../MyApp
# cp prod.crt ../MyApp/wwwroot/grpc.crt
To use this script open power shell, navigate to the folder and type
C:\folder> ./gen-prod.https.sh <domain> <password>
I will use the prod one in the example, meaning I will provide a password for the certificate.
The generated certificates will look like:
4. The Grpc Server
We now need to load the grpc server. For this I am using Daniel’s example (see the link from the beginning of the post). I made 2 changes to this method:
- I am account for both relative/absolute path
- If the asp environment is “LinuxDevelopment” I am using the localhost “0.0.0.0”
Creating the kestrel options
namespace ComX.Integrations.SendEmailService
{
public static class KestrelServerOptionsExtensions
{
public static void ConfigureEndpoints(this KestrelServerOptions options)
{
var configuration = options.ApplicationServices.GetRequiredService<IConfiguration>();
var environment = options.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
var endpoints = configuration.GetSection("HttpServer:Endpoints")
.GetChildren()
.ToDictionary(section => section.Key, section =>
{
var endpoint = new EndpointConfiguration();
section.Bind(endpoint);
return endpoint;
});
foreach (var endpoint in endpoints)
{
var config = endpoint.Value;
var port = config.Port ?? (config.Scheme == "https" ? 443 : 80);
var ipAddresses = new List<IPAddress>();
if (config.Host == "localhost")
{
if (environment.EnvironmentName == "LinuxDevelopment")
{
ipAddresses.Add(IPAddress.Parse("0.0.0.0"));
}
else
{
ipAddresses.Add(IPAddress.IPv6Loopback);
ipAddresses.Add(IPAddress.Loopback);
}
}
else if (IPAddress.TryParse(config.Host, out var address))
{
ipAddresses.Add(address);
}
else
{
ipAddresses.Add(IPAddress.IPv6Any);
}
foreach (var address in ipAddresses)
{
options.Listen(address, port,
listenOptions =>
{
if (config.Scheme == "https")
{
var certPass = Environment.GetEnvironmentVariable("mycertpass");
if (certPass is null)
{
throw new Exception("Could not find the environment variable \'mycertpass\'");
}
config.Password = certPass;
var certificate = LoadCertificate(config, environment);
listenOptions.UseHttps(certificate);
}
});
}
}
}
private static X509Certificate2 LoadCertificate(EndpointConfiguration config, IWebHostEnvironment environment)
{
//store not used. I kept it as an example
if (config.StoreName != null && config.StoreLocation != null)
{
using (var store = new X509Store(config.StoreName, Enum.Parse<StoreLocation>(config.StoreLocation)))
{
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates.Find(
X509FindType.FindBySubjectName,
config.Host,
validOnly: !environment.IsDevelopment());
if (certificate.Count == 0)
{
throw new InvalidOperationException($"Certificate not found for {config.Host}.");
}
return certificate[0];
}
}
if (config.FilePath != null && config.Password != null)
{
string[] splitPath = config.FilePath.Split('/', '\\').Where(x => !(x.ContainsOnly('/') || x.ContainsOnly('\\'))).ToArray();
string fixedPath = string.Join(Path.DirectorySeparatorChar, splitPath);
if (File.Exists(fixedPath))
{
fixedPath = Path.Combine(AppContext.BaseDirectory, fixedPath);
if (!File.Exists(fixedPath))
{
throw new Exception("Could not find the certificate. Please provide a correct absolute or relative path");
}
}
return new X509Certificate2(fixedPath, config.Password);
}
throw new InvalidOperationException("No valid certificate configuration found for the current endpoint.");
}
}
public class EndpointConfiguration
{
public string Host { get; set; }
public int? Port { get; set; }
public string Scheme { get; set; }
public string StoreName { get; set; }
public string StoreLocation { get; set; }
public string FilePath { get; set; }
public string Password { get; set; }
}
}
Pay attention: In these options I am looking for the password in the environment variables. “mycertpass”
appsettings
Replace kestrel entry with
Add the certificate (.pfx) under the certificates folder and make sure it’s “copy if newer” on build
Environment variable
Add the environment variable for the certificate password (restart visual studio after)
Configuring Program.cs
Now we have to tell Kestrel to use our options
Docker-Compose
If you are using docker under linux like I am, you can use a docker-compose file and pass the environment variable there. A secret should not be exposed like this and there are other ways to manage secrets but for development it suffice
5. The Grpc Client
Let’s create a console application that uses the client generated by the proto file
In the console application i created the certificates folder and added the certificate (also with “copy if newer” on build)
And now you can use the Grpc client.
Note that you still have to define HttpClientHandler.DangerousAcceptAnyServerCertificateValidator. This is because the certificate cannot be valiated against a certificate authority

















