When is it necessary to provide a CA file? - database

I'm writing a server in Go that uses MongoDB and I was doing some research on how to enable SSL for the connection to the database. I found several examples that explain how to add the CA file. Like so:
mongo.NewClientWithOptions(connectionString, mongo.ClientOpt.SSLCaFile(caFilePath))
I'm using a hosted database on Atlas and they state that all connections use SSL by default. This answer on a different question shows how to connect to Atlas with Go but the code example doesn't use a CA file. I also couldn't find an option to download the CA file from Atlas that I could use.
This confuses me a bit and leads to the following questions. When is it necessary to provide a CA file like shown above to use SSL? If it's always required for SSL to provide a CA file, where do I get the CA file from to connect to a managed cluster on Atlas?

You always need a CA certificate to validate the server when initiating a TLS connection. Sometimes this is already installed on your platform and used automatically. You have to provide a CA file during connection when such a root certificate is not available. The CA file is used to validate the certificate presented by the server. A trusted third party provides this CA, and also (possibly through a chain of trusted parties) provides a certificate to the server, so you can validate the server is who claims it is by validating its certificate using the CA.
All platforms come with an initial set of root certificates that can validate well-known third-party generated certificates. The mongodb server you're connecting to is probably using such a certificate, and thus, your OS certificates can be used to validate it. If you had your own PKI with your own CA not validated by a third party, then you'd need a separate CA file signed by your own CA. Then you'd need to pass that CA file to validate the server, because your root certificate will not contain your custom CA.

The CA file specifies which self-signed root certificates you trust, and can include intermediate certificate authorities as well.
When the application connects to the server, the server sends its certificate as part of the handshake. The server's certificate was digitally signed.
In order to check that the server certificate was not tampered with, the issuer's certificate is consulted, which contains a public key that can be used to validate the digital signature.
If the issuer was an intermediate CA, then its certificate was also signed by another CA, so that CA's certificate will be consulted to validate the signature on the intermediate certificate.
This continues until the chain reaches a certificate that was signed by itself. This is the root certificate. Since it signs itself, you have to explicitly indicate that you trust it in order to trust the entire chain, including the server being connected to.
The bottom line here is you need to provide a CA file when:
You care about verifying the identity of the server you are connecting to (i.e. preventing man in the middle attacks), and
The root certificate will not already be trusted implicitly by inclusion in a local trust store

Related

Linked Server Certificate issue

I have 2 SQL Servers(2017): A and B.
Server_A does not have a certificate and encryption is not enabled.
Server_B has a certificate and encryption is enabled.
While logged onto Server_A:
SELECT * FROM [Server_B].MyDB.dbo.MyTable -- This works
While logged onto Server_B:
SELECT * FROM [Server_A].SomeDb.dbo.AnyTable -- This fails with the error:
"OLE DB provider "SQLNCLI11" for linked server "Server_A" returned message "Client unable to establish connection".
Msg -2146893019, Level 16, State 1, Line 11
SSL Provider: The certificate chain was issued by an authority that is not trusted."
This seems backwards to me. I would think that the first query would fail because it originates from a SQL Server with no certificate. Instead the SQL Server with a certificate fails when communicating to a "no certificate" server.
I would like to understand why this is failing in this way.
Also, what do I have to do to make the 2nd query succeed? I suspect installing a certificate on Server_A will solve this. Does this sound correct?
Install a certificate on server A does not solve the problem.
If you don't have the certificate you can get the databases list with the relative certificate with the below query see this post
use master;
go
select
database_name = d.name,
dek.encryptor_type,
cert_name = c.name
from sys.dm_database_encryption_keys dek
left join sys.certificates c
on dek.encryptor_thumbprint = c.thumbprint
inner join sys.databases d
on dek.database_id = d.database_id;
Once you have obtained the certificate name you can BACKUP it to a folder.
From here you must know a little bit about certificates.
Each certificate has an "Issuer" (someone who signs the certificate) and/or a chain (intermediate path signed by an ancestor intermediate, and so on) when you query the db, your PC read the certificate and verify the chain.
The error says:
The certificate chain was issued by an authority that is not trusted."
This means: "I don't trust the certificate because it is emitted by someone whom I don't trust"
An exception is for a self-signed certificate where the issuer of the certificate is itself and it has no chain (single node).
You must open the certificate and see the chain.
On windows:
If is self-signed, install the certificate on "Trusted root certificate authority"
If is NOT self-signed you must lookup for the chain certificate and install it on "Trusted root certificate authority"
On Linux, I don't remember exactly and you must look on google about it.
Max,
Thank you for the reply. Your answer regarded certificates at the database level. Indeed when I query the sys.certificates system catalog it returns the certificates used for TDE encryption. However, the certificates I am using are for Transport Layer Security (TLS) encryption which are at the server level.
But your answer highlighted the error "The certificate chain was issued by an authority that is not trusted" and this got me thinking along different lines. AND you mentioned I needed to 'open' the certificate. When I opened the cert that I had exported it stated it could not be verified. Turns out I exported it incorrectly. My 2nd export attempt worked.
As I re-read the MS Docs I also realized I missed the step of granting the SQL Server service account read permission. Once I did that the Linked Server successfully connected.
So in summary if your client is another SQL Server you need to export the cert from the target server. The exported cert needs to be installed on the client with read permissions for the service account. That's it!
Thank you Max for providing me with some direction.

Create Certificates for PKINIT-based Kerberos login on Active Directory

I'm trying to set-up a PKINIT-based Kerberos login on a Active Directory. The login shall be performed using sssd on Linux. However, the kerberos server does not accept the client certificate. We receive an error with event ID 21: Certificate for user REALM/Domainuser is not valid on the server and sssd says: Client name mismatch.
The certificates were created with the following procedure on a different Linux machine:
openssl req -new -key keyfile.pem -out reqfile.pem -subj
"/CN=Domainuser/O=AAA/C=DE/OU=BBB"
env REALM=REALM.local CLIENT=Domainuser openssl x509 \
-CAkey ../ca_privkey.pem -CA ../ca_cert.pem -req -in reqfile.pem \
-extensions client_cert -extfile extensions.client \
-days 365 -out certfile.pem
We installed the AD CA on the Windows Server that hosts the AD itself. We exported the certificte of this CA to the Linux machine and stored its private key and certificate in the ca_privkey.pem and ca_cert.pemfiles to use it with openssl.
The client_cert we used during signature creation was created using the template suggested by sssd. The only thing we added is the crlDistributionPoints option to include the CRL of the CA:
[client_cert]
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment,keyAgreement
extendedKeyUsage=1.3.6.1.5.2.3.4
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
issuerAltName=issuer:copy
subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:princ_name
crlDistributionPoints=URI:http://link-to-CRL-of-CA
[princ_name]
realm=EXP:0,GeneralString:${ENV::REALM}
principal_name=EXP:1,SEQUENCE:principal_seq
[principal_seq]
name_type=EXP:0,INTEGER:1
name_string=EXP:1,SEQUENCE:principals
[principals]
princ1=GeneralString:${ENV::CLIENT}
The realm used for the authentication is REALM.local (equal to the AD Domain). The environment variables REALM and CLIENT are set to REALM.LOCAL and pkuser during certificate creation and the user pkuser also exists in the AD (Password-based login is possible).
I have no clue why the authentication is not successful. Do you have any ideas what might be wrong in the configuration or give me a hint s.t. Windows prints a more detailed error message? Note, that certutil -verify -urlfetch certfile.pem is able to validate the whole certificate chain and does not print any errors when executed with the Administrator command on the AD server.
I assume that there are some configuration errors on the Windows Server. This is the first time I configured a Windows Server ;)
Finally, we figured out what the problem was. Windows AD requires additional extended key usage fields to allow the authentication.
You have to add 1.3.6.1.5.5.7.3.2 and 1.3.6.1.4.1.311.20.2.2.
Further, the SAN must be set to: otherName:1.3.6.1.4.1.311.20.2.3;UTF8:<client-name>#<realm>

SSL / certificate validation error in spite of TrustServerCertificate=true in connection string

At first, please note that I am aware that this question has already been asked several times. However, the accepted (and non-accepted) solutions given so far did not work in my case, so something substantial must have changed since then, which hopefully justifies asking again.
Having said this:
I am currently trying to upgrade an Access 2010 .adp application to Access 2019 .accdb. The application includes a lot of VBA code which uses ADO objects to connect with and operate on Microsoft SQL server (currently: 2008 R2, but will be upgraded soon).
I'd like to keep the most part of the code, which means to stick with ADO, so the way to go is the new OleDB SQL server driver (which has been undeprecated / newly released in 2018). The SQL server runs on another machine than my client application.
I am not able to establish a connection to SQL server from VBA. When executing the following code snippet
Dim cnTemp As Connection
Set cnTemp = New Connection
cnTemp.CursorLocation = adUseServer
cntemp.Open "Provider=MSOLEDBSQL;Server=dbserver.example.com;Initial Catalog=MyDB;Authentication=SqlPassword;User ID=sa;Password=secret;DataTypeCompatibility=80;"
I get the following error when the last line is executed:
SSL Provider: The certificate chain was issued by an authority which is not trusted.
OK, no problem, after all we have found all the other questions dealing with the same issue, all suggesting the same solution: Add Trust Server Certificate=True; to the connection string.
Well, tried that, but -to my surprise- still the same situation. Then I tried some other variants like TrustServerCertificate=True; or using true instead of True, but to no avail. I also tried adding Use Encryption for Data=True; which didn't help either (that could be expected). Furthermore, I tried some of the snippets I had found when researching the problem, but which are not documented by Microsoft as being valid in ADO connection strings (like Encrypt=true or Trusted_Connection=true;); of course, that made the situation worse, raising other error messages.
I have understood that I could solve that problem by putting the SQL server certificate into the client's trusted root certificate store, or by having SQL server use a certificate which has been issued by a known, trusted CA (e.g. Let's Encrypt).
However, I'd strongly like to know why adding Trust Server Certificate=true; to my connection string does not make the error go away and what I have to put in there to disable certificate validation (and by the way, I would be grateful if we wouldn't start a discussion about why this would be bad; this is just development and testing in a trusted, closed network, and I am aware of possible risks).
The reason TrustServerCertificate=True in the connection string is not honored is twofold. One is that it isn't a valid ADO classic (ADODB) connection string keyword. According to the ActiveX Data Objects (ADO) Connection String Keywords documentation, the keyword/value pair should be Trust Server Certificate=True (note spaces). The keyword is ignored entirely without the spaces and not trusted as a result.
However, this change alone will not trust the certificate because of the Authentication-SqlPassword specification. When the Authentication keyword is specified, the documentation footnote calls out:
To improve security, encryption and certificate validation behavior is
modified when using Authentication/Access Token initialization
properties or their corresponding connection string keywords. For details, see Encryption and certificate validation link.
The referenced link includes this important note:
Certificate validation can also be controlled through the Value field
of the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\SNI18.0\GeneralFlags\Flag2
registry entry. Valid values are 0 or 1. The OLE DB driver chooses the
most secure option between the registry and the connection
property/keyword settings. That is, the driver will validate the
server certificate as long as at least one of the registry/connection
settings enables server certificate validation.
So even with Trust Server Certificate=True, the cert will be validated when this registry value is set to 0.
One solution is to simply remove the Authentication=SqlPassword specification as long as you don't need the improved security provided by not trusting the server certificate:
cntemp.Open "Provider=MSOLEDBSQL;Server=dbserver.example.com;Initial Catalog=MyDB;User ID=sa;Password=secret;Trust Server Certificate=True;DataTypeCompatibility=80;"
At first, I'd like to state that all credit goes to #Dan Guzman. It's his answer / comment which provided the solution.
However, I'd like to add some background, based on research I've done since posting my question.
The problem is that Microsoft's documentation obviously is wrong. Please have a look at the following document:
https://learn.microsoft.com/en-us/sql/connect/oledb/applications/using-connection-string-keywords-with-oledb-driver-for-sql-server?view=sql-server-2017#table3_1
It is located in the section SQL Server 2017 -> OLE DB -> Applications -> Using connection string keywords with OLE DB Driver for SQL server, so it should be the right one. It is divided into three sections; in the context of this question, the last table is what we're interested in, because only this one relates to connection strings with ADO.
That last table explicitly shows that Authentication=SqlPawword is valid in ADO / OLE DB connection strings (reformatting mine, no content altered):
Authentication SSPROP_AUTH_MODE Specifies the SQL or Active
Directory authentication used. Valid values are:
(not set): Authentication mode determined by other keywords.
ActiveDirectoryPassword: Active Directory authentication using login ID and password.
ActiveDirectoryIntegrated: Integrated authentication to Active Directory using the currently logged-in user's Windows account
credentials.
NOTE: It's recommended that applications using Integrated Security (or Trusted_Connection) authentication keywords or their corresponding
properties set the value of the Authentication keyword (or its
corresponding property) to ActiveDirectoryIntegrated to enable new
encryption and certificate validation behavior.
SqlPassword: Authentication using login ID and password.
NOTE: It's recommended that applications using SQL Server authentication set the value of the Authentication keyword (or its
corresponding property) to SqlPassword to enable new encryption and
certificate validation behavior.
It also says (again, formatting mine, no content altered):
Trust Server Certificate SSPROP_INIT_TRUST_SERVER_CERTIFICATE
Accepts the strings "true" and "false" as values. The default value
is "false", which means that the server certificate will be validated.
Every reasonable human being will understand this in the sense that Trust Server Certificate=true will disable certificate validation.
But when you look here
https://learn.microsoft.com/en-us/sql/relational-databases/native-client/applications/using-connection-string-keywords-with-sql-server-native-client?view=sql-server-2017
you'll notice that this document is structured like the first one, and that the last table does not mention the Authentication parameter.
However, this document is located in SQL Server 2017 -> Development -> SQL Server Native Client -> Applications -> Using Connection String Keywords. That means that it is not relevant for our case because it relates to SQL server native client (and not OLE DB), but it provides the correct information.
So we have the right document which provides the wrong information and an irrelevant document which provides the right information. Congratulations, Microsoft, you have made me waste a whole day again ...
Furthermore, I have found the following document:
https://learn.microsoft.com/en-us/sql/connect/oledb/features/using-azure-active-directory?view=sql-server-2017#encryption-and-certificate-validation
Reading the title ("Using Azure Active Directory"), it should relate to Azure only. However, I suspect that the following section relates to local SQL server installations as well (formatting mine, no content altered):
Certificate validation
To improve security, the new connection properties/keywords respect
the TrustServerCertificate setting (and its corresponding connection
string keywords/properties) independently of the client encryption
setting. As a result, server certificate is validated by default.
Note
Certificate validation can also be controlled through the Value field
of the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\SNI18.0\GeneralFlags\Flag2
registry entry. Valid values are 0 or 1. The OLE DB driver chooses the
most secure option between the registry and the connection
property/keyword settings. That is, the driver will validate the
server certificate as long as at least one of the registry/connection
settings enables server certificate validation.
So it could well be that we also have to change values in the registry to finally disable certificate validation when connecting to SQL server via ADO / OLE DB.

How SQL Server Always Encrypted works on client side

I have some confusion about Always Encrypted concept. Please follow below scenario.
I have created CMK (name - CMK_01) on Machine A under CurrentUser/My folder. By this way I have Always Encrypted certificate generated on Machine A which I have exported.
After this I have created CEK (name - CEK_01) from Machine A using this CMK (CMK_01).
After this I have created table (name - TBL_01) on Machine B and used this CEK_01 to encrypt its column (name – COL_01).
Now to test Always Encrypted concept I have installed Always Encrypted certificate on Machine B and applied Column Encryption Setting = Enabled in SSMS.
After doing this I am able to insert data with parameterized query in this table (TBL_01).
I queried on this table and I found that data is in decrypted form (i.e. plain text).
After this I queried on this table from Machine C without installing certificate and I found data is in encrypted form. So basically it works perfectly from db side.
This works based on certificate installed on individual machine and depends on folder in which it installed (Current or Local computer).
If Current then it works only for user which certificate is installed and if Local then works for all the users on that machine.
Now issue comes over here,
I tried to leverage this functionality to .NET side.
So I have included Column Encryption Setting = Enabled in connection string of .NET code.
And then I deployed .NET code on server machine (Machine D).
After this I have installed always encrypted certificate on one of the user machine (Machine E) under CurrentUser/My folder.
Now when user on Machine E is trying to see the data in UI, it is giving an error. Basically encryption does not work here.
To resolve this I have created new CMK (name – CMK_01) with same certificate key but path of CMK is under LocalMachine\My.
Then I have added this new CMK value in existing CEK (CEK_01).
So now basically we have 2 CMK - same certificate key with different path and 1 CEK with 2 different CMK values which means both CMK can access this CEK.
After this we installed Always Encrypted Certificate on IIS server (under LocalMachine\My path) where our code has been deployed (Machine D).
After this error has been removed but all the users who can login to that web page they can encrypt/decrypt data because we have installed certificate on IIS server (Machine D).
Now my ask is -
Is this our implementation correct?
Is this how Always Encrypted works? I mean can't we have individual user wise encryption and decryption by installing certificate on user's individual machine so who has that certificate access that user is only able to encrypt/decrypt data otherwise rest of the users who does not have that certificate those users can only see data in encrypted form on that web page?

Windows LDAP API: No connection over SSL

I’m trying to connect to an LDAP directory over SSL using the Windows LDAP C-API. This fails with error code 0x51 = LDAP_SERVER_DOWN while the event log on the client computer has this:
„The certificate received from the remote server does not contain the expected name. It is therefore not possible to determine whether we are connecting to the correct server. The server name we were expecting is eim-tsi2.sam.develop.beta.ads. The SSL connection request has failed. The attached data contains the server certificate.”
This is can’t be true since “Ldap Admin” is able to connect over SSL and port 636.
The LDAP directory is an Oracle DSEE which has the CA and the server certificate in the appropriate cert store.
The client has the CA installed in the “Trusted Root Certification Authorities” and there in the „Local Computer“ physical store. I assumed this to be the right place for the CA since my little client program uses the Windows LDAP C-API; LDAP Admin indeed expects the CA there.
Here is an excerpt of my program omitting the error handling and other obvious source code:
ld = ldap_sslinit(host, LDAP_SSL_PORT, 1);
// Set options: LDAP version, timeout ...
rc = ldap_set_option(ld, LDAP_OPT_SSL, LDAP_OPT_ON);
// Now connect:
rc = ldap_connect(ld, NULL);
Result:
0x51 = LDAP_SERVER_DOWN
Connecting without SSL succeeds so the LDAP server is generally accessible.
Since Ldap Admin is able to connect over SSL, I assume the certificates are valid and in the right place. But obviously the LDAP API expects them somewhere else and cannot get the server certificate from the server. I configured the certs as described here: https://msdn.microsoft.com/en-us/library/aa366105%28v=vs.85%29.aspx
What am I doing wrong?
Sometimes it helps reading error messages more carefully. The entry in the event viewer caused by an unsuccessful bind over SSL was "The server name we were expecting is eim-tsi2.sam.develop.beta.ads."
I should have noticed that the name should have been eim-tsi2.cgn.de.(etc.), instead. So the domain name part was wrong.
This is a bug in Schannel which can be solved by an entry in the registry as described here: https://support.microsoft.com/en-us/kb/2275950.
I still do not know why LDAPAdmin was able to connect without that additional registry key although it also uses the WINLDAP API and therefore should have run into the same error. But that doesn’t matter any more.
Thanks, Andrew, for your help.

Resources