Skip to main content

How to Use VerifyFull with MySql.Data in .NET C#

· 7 min read
Jagdish Kumawat
Founder @ Dewiride

SslMode=VerifyFull is the strongest check MySql.Data has: it verifies who signed the server's certificate, and that it's for the host you're connecting to. With PEM files, the driver also demands a client certificate and key, in one exact format. I hit each requirement in turn, so here they all are.

What You Need​

FileOptionWhere it comes from
The server's CASslCaThe server, or your provider
A client certificateSslCertThe server's, or one you make
Its key, starting BEGIN RSA PRIVATE KEYSslKeyConverted with openssl

And the server's certificate must list the name you connect by. Oracle's documentation says of this mode, "you must also provide values for the SslCa, SslCert, and SslKey connection options".

With MySQL in Docker​

Step 1: Copy the CA and the Client Pair​

MySQL's automatic files have what you need. The key has to be converted, because it's in a format the driver can't read. Add certs/ to .gitignore, then:

Terminal
mkdir certs
docker compose cp mysql:/var/lib/mysql/ca.pem certs/ca.pem
docker compose cp mysql:/var/lib/mysql/client-cert.pem certs/client-cert.pem
docker compose exec --user mysql mysql openssl rsa -in /var/lib/mysql/client-key.pem -traditional -out /var/lib/mysql/client-rsa-key.pem
docker compose cp mysql:/var/lib/mysql/client-rsa-key.pem certs/client-key.pem

Step 2: Give the Server the Right Name​

MySQL's automatic certificate has no usable name, so sign a new one for localhost and 127.0.0.1, and reload it.

Step 3: Connect​

Terminal
export MYSQL_CONNECTION_STRING="Server=127.0.0.1;Port=3306;Database=inventory;User ID=inventory_app;Password=$(grep '^MYSQL_PASSWORD=' ../dewiride-inventory-api/.env | cut -d= -f2-);SslMode=VerifyFull;SslCa=$PWD/certs/ca.pem;SslCert=$PWD/certs/client-cert.pem;SslKey=$PWD/certs/client-key.pem"
dotnet run test-connection.cs
Output
Connected as inventory_app@% to MySQL 9.7.4
TLS: TLSv1.2

The test program with SslMode=VerifyFull, SslCa, SslCert and SslKey printing Connected as inventory_app@% with TLS: TLSv1.2.

With a Managed MySQL Server​

I didn't test against a cloud provider. What follows comes from how the driver behaved here, with a server whose certificate I controlled.

The Client Pair: Make Your Own​

You can't get a client certificate from most providers, and you don't need one. In my tests, the driver never sent these files to the server. A certificate from a CA the server had never seen connected without a problem, and a key from a different pair did too. An account set to REQUIRE X509 refused even the right pair, while the mysql client got in with those same files.

So make a throwaway pair. Use exactly these steps, because openssl req -x509 marks a certificate as a CA on a normal setup, and the driver refuses that:

Terminal
openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client.csr -subj "/CN=not-checked"
openssl x509 -req -in client.csr -signkey client-key.pem -days 3650 -out client-cert.pem
openssl rsa -in client-key.pem -traditional -out client-key.pem
Output, with that pair
Connected as inventory_app@% to MySQL 9.7.4
TLS: TLSv1.2

The pair's dates are checked: an expired one gets certificate expired on. So 10 years saves you a surprise later.

The CA: Only One Certificate​

Providers often publish a file with several. This driver reads only the first:

Output, with the right CA second in the file
Unhandled exception. MySql.Data.MySqlClient.MySqlException (0x80004005): SSL Connection error
---> System.Exception: The certificate authority (CA) does not match
Output, with the right CA first in the file
Connected as inventory_app@% to MySQL 9.7.4
TLS: TLSv1.2

So find the one that signed your server's certificate, and keep it alone in a file. The fix shows how to list what's in a bundle. Check again when your provider rotates its CA.

The Host Name: Use the One They Gave You​

Server= must be the name in the provider's certificate, and not an IP address or an alias of your own. Here's how the driver matches, from six test certificates:

CertificateServer=localhostServer=127.0.0.1
SAN DNS:localhost and IP:127.0.0.1ConnectsConnects
SAN DNS:localhostConnectsRefused
SAN IP:127.0.0.1RefusedConnects
No SAN, common name localhostConnectsRefused
No SAN, common name 127.0.0.1RefusedConnects
SAN DNS:db.example.com, common name localhostRefusedRefused

What Each Check Protects​

This is what VerifyFull refused, and Required let through:

SituationRequiredVerifyFull
A server with another CAConnectsRefused
A certificate for another nameConnectsRefused
An expired certificateConnectsRefused

Common Mistakes​

MistakeWhat happensDo this instead
Using MySQL's client-key.pem as it isThe specified file cannot be converted to a keyConvert it
Making the pair with openssl req -x509The certificate is invalidUse the three steps above
Using a .pfx fileRefused, on macOS and LinuxUse PEM files
Connecting by IP with a certificate for a nameRefusedUse the name

Troubleshooting​

SymptomLikely causeFix
No file path has been provided … SslCertThe pair is missingThe fix
The host name does not match the name on the certificateServer= isn't on the certificateThe fix
The certificate authority (CA) does not matchThe wrong CA, or not first in the fileThe fix
The provided key is invalidA different CA with the same nameThe fix

Conclusion​

VerifyFull needs a CA, a client pair with its key as BEGIN RSA PRIVATE KEY, and a server certificate with the name you connect by. The pair's only there to satisfy the driver, so a throwaway one is fine.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.