Asynchronous Java Driver Blog     About     Archive     Feed

MongoDB, TLS, and x.509 Authentication Deep Dive

MongoDB, TLS, and x.509 Authentication Deep Dive

MongoDB has a growing number of security features. One of the core features is also one that is commonly misunderstood: TLS (Transport Layer Security) which is also known as SSL (Secure Sockets Layer). In addition to the encryption of the data while in transit over the network MongoDB also relies on the passing of x.509 certificates to provide the basis for MONGODB_X509 authentication. Many of the common pitfalls when using TLS and x.509 authentication are based on misunderstanding of either TLS or how x.509 authentication is achieved and its requirements for the way the TLS connection is created.

Browser https:// Connection

Taking a step even further back we have to undo some of our common knowledge about TLS. Most of our day-to-day experience with TLS is based on HTTPS. We go to our bank, GMail, or any host of a number of other sites and our browsers use TLS to establish a secure connection with the server. While it is true that the protocol that our browser is using is the exact same one that MongoDB uses, the actual message exchanges are different.

TLS Handshake

Lets start by looking at a typical handshake that your browser (the client) and the HTTP server will do to establish a TLS connection. Going into the details of every message in the exchange is beyond the scope of this post but there are a few important items to point out.

Browser TLS Handshake

The main thing to realize from this exchange is that once the handshake is complete the client (your browser) has the server’s certificate and has validated that the (HTTP) server has the matching private key to the public in the certificate. (We will call the certificate and associated public/private key pair the cryptographic identity.) The server knows nothing about the client.

MongoDB TLS Handshake

In contrast lets look at a typical TLS connection from an application (e.g., client) to a MongoDB server that wants to leverage x.509 authentication. We immediately see the addition of 3 messages. The server now requests the application’s certificate and the client responds with the certificate and certificate verify. For the client to prove its cryptographic identity the certificate verify contains a hash of the previous exchange messages (including its certificate) that is encrypted with the private key matching the public key in its certificate. (This is typically referred to as a signature.) If the server can decrypt the encrypted hash and verify it matches what it computed for the hash it has established the client’s cryptographic identity.

At the end of the MongoDB exchange both the client and server have verified the cryptographic identity for the other side. Remember that cryptographic identity is just validation that the public/private keys match for the parties. What is to stop either side just generating a new public/private key pair and wrapping the public in a certificate that claims they are Google or anyone else? That is a matter of trust.

Trust

The validation of certificates is not specified by the TLS protocol (but it is mentioned in the implementation notes). This allows flexibility in the trust model. For the purposes of this discussion we look at 3 different models.

Trust via Knowing Each Other

In the first model, Alice and Bob, know each other and have already exchanged public keys/certificates. With this model a direct comparison of the certificates from the handshake to those from the previous exchange is all that is needed. For this model to be effective the parties need to exchange certificates via a secure mechanism. This could be as simple as a face-to-face exchange of certificate.

Web of Trust

The second model to consider is where Alice does not know Bob and Bob does not know Alice. Alice and Bob both know Charlie and Charlie has also signed each of Alice’s and Bob’s certificates. Since Charlie vouches for both Alice and Bob they can both trust (to the level they trust Charlie) that they are talking to who they think they are. While we have described this link between Alice and Bob as a single person the “Charlie” does not need to be the same person when establishing the trust in each direction. Similarly, there does not need to be 1 person in establishing the chain of trust from Alice to Bob and Bob to Alice, there can be N links in the trust chain. This model is often called a web of trust.

Trust via a CA (Certificate Authority)

The trust model used by browsers, and most TLS libraries, is based on a trust structure based on a forest of trees. The root of each tree and internal branches are referred to as a CA (Certificate Authority). This small (relative to the number of HTTP servers) collection of trusted certificate authorities is provided with each browser installation. The browser will verify that the server’s certificate is signed by a certificate authority that it already knows or can create a chain of trust to the certificate authority it knows. A chain of trust is established by locating the certificate’s signer’s certificate and validating it and its signer. The browser will also verify the certificate’s valid period and certificate revocation list (if available).

Lastly, a browser will verify the certificate’s CN or alternate names match the name of the server they are attempting to contact. This last check is not provided by most TLS libraries as it assumes a specific use case.

MongoDB Trust

The MongoDB server has two distinct scenarios: trusting that a server is in the cluster and trusting a client.

For establishing trust in the identity of a server that is considered a member of the cluster MongoDB can leverage the x.509 certificate. The rules for when to consider a connection as a cluster member include:

These constraints create a stronger trust model than the normal CA model provides.

If you are using x.509 authentication with clients and need to restrict their access to the server, then you should plan your Public Key Infrastructure (PKI) such that you avoid having clients seen as members of the cluster. The easiest method for doing this is to add an additional OU component to the DN for each of the MongoDB server certificates. Another approach is to setup a distinct CA for the MongoDB cluster. We will walk through the process of creating a CA in the appendix to this post.

The second trust model is for the clients. For trusting the information in the certificate the server uses the standard CA model. It then requires that the DN for the certificate be explicitly registered with the server and the explicit permissions to grant to that user.

Configuring MongoDB

Now that we understand how the TLS protocol establishes a users cryptographic identity and how we can extend that via a trust model to establish a full identity of a user we can start to look at the details of how to configure a MongoDB server to leverage TLS and x.509 authentication.

To use TLS and x.509 authentication we need to either compile MongoDB with TLS support or use the enterprise edition. Compiling the MongoDB with SSL support is as easy as downloading and untarring the community source. We can then run the following command within the directory created by the tar ball.

scons --ssl --prefix=<install_prefix> install

Once the server is installed we can use the following configuration file to enable TLS connections and x.509 authentication:

net:
  ssl:
    mode:           requireSSL
    PEMKeyFile:     ./ca/server.pem
    PEMKeyPassword: supersecret
    clusterFile:    ./ca/server.pem
    clusterPassword:supersecret
    CAFile:         ./ca/trust.crt
    # CRLFile:
    weakCertificateValidation: false
    allowInvalidCertificates:  false
    # Enterprise Only
    # FIPSMode:                true
security:
  authorization:   enabled
  clusterAuthMode: x509

storage:
  dbPath :      ./data
systemLog:
   destination: file
   path:        ./mongodb.log
   logAppend:   true

The creation of the server certificate, keys, and the CA file is discussed in the appendix. The PEMKeyFile and clusterFile options contain the private key and certificate that the server will use for incoming and outgoing connections respectfully.

The CAFile option contains all of the certificates for each trusted CAs.

The weakCertificateValidation may be set to true if there are some clients that will be using x.509 authentication and some that will not. Allowing weak certificate validation has the MongoDB server still ask for the client’s certificate in the handshake but it will not reject the connection if the client does not provide one.

For a full description for each field see the Configuration Options section of the MongoDB documentation.

Common Failures

To end our discussion of MongoDB and TLS we look at a few common failure conditions. The good news is that most of these errors will be discovered during the initial configuration of a cluster. We also now have a much better grasp of the underlying technology to be able to quickly diagnose and correct each issue.

Wrong DN

For x.509 authentication to succeed the DN used by the client must match the DN read by the MongoDB server from the client’s certificate. If the comparison fails the authentication will fail with an error message like:

Error: 18 Username "C=US, ST=DC, L=Washington, O=Allanbank Consulting, Inc., CN=client1" 
does not match the provided client certificate user "CN=client1,O=Allanbank Consulting\, Inc.,L=Washington,ST=DC,C=US"

The easiest solution to this issue is to delete the configured user using the old name and use the name reported in the error message.

When first provisioning users use this order for the fields in the DN: CN, OU, O, L, ST, C

Client Validation Error

When a client’s certificate cannot be validated via one of the certificate authorities in the MongoDB server’s CAFile the clients connections will appear to simply fail. With the MongoDB server log you will see messages like:

ERROR: SSL peer certificate validation failed:self signed certificate

To fix the issue add either the client’s certificate or the certificate for the client’s CA to the CA file.

To add a certificate to a CA file simply concatenate the new certificate to the end of the CA file making sure there is a line break between a —–END CERTIFICATE—– and the next —–BEGIN CERTIFICATE—–.

Failure to Exchange the Client’s Certificate

As we saw at the beginning of this post the MongoDb server has to request the client’s certificate within the TLS handshake. The MongoDB server will only do that if there is a CAFile configured to validate the client’s certificate against.

If you start the MongoDB server with TLS enabled but without a CAFile it will print a warning to the console (not the log) on startup:

warning: No SSL certificate validation can be performed since no CA file has been provided; 
please specify an sslCAFile parameter

When the client tries to authenticate using x.509 authentication it will see a very generic error:

Error: 18 { ok: 0.0, errmsg: "auth failed", code: 18 }

Within the server log we will see a more descriptive error message.

Failed to authenticate <DN>@$external with mechanism MONGODB-X509: 
AuthenticationFailed There is no x.509 client certificate matching the user.

Client Key Missing

If you configured the server to allow client connection without certificate (weakCertificateValidation=true) then it is possible for a client to connect and fail to present a x.509 certificate to the server.

In these cases if the client tries to still use x.509 authentication, then the server will report the lack of a certificate in the log:

warning: no SSL certificate provided by peer

Setting up a client to properly load the client’s certificate and keys is beyond the scope of this post but below are some pointers to the driver specific documentation for setting up the clients.

Authenticate as Cluster Member

It is very difficult to detect that a client is in fact authenticating as a cluster member. There is be no error messages or log messages that can be used to detect the failure. The only reliable mechanism is to try and access a feature or database/collection the client should not be able to access.

If you are giving your applications full access to MongoDB then this will not be a concern. In a multi-tenant or security constrained environment then we suggest the administrators take affirmative steps to ensure that only the cluster’s servers are seen as cluster members. The easiest solution is to negotiate with your CA to include a unique OU field in each of the MongoDB cluster member’s certificates. The CA should then ensure that no other organization uses the same OU.

A more secure approach is to establish a distinct CA to only be used to generate the MongoDB cluster member’s certificates.

Appendix: Setting Up a Certificate Authority

While creating a completely secure CA requires the use of an air-gap system that only communicates with external systems via read-only media,we can quickly demonstrate the process of creating a local certificate authority. This can be useful for testing as well as a bootstrap for creating a local CA.

Warning: This guide is for testing and demonstration purposes only.

We will not provide a large amount of narrative on the various OpenSSL command line options.

To start we need to create a public/private key and wrap the public portion in an x.509 certificate. This will form the root of our CA tree. We then setup a directory to serve as the database for managing the certificate this root certificate will sign. Root CAs usually have a very long life since changing them requires re-issuing all certificates that they form the trust anchor for. In the command below we have used 10 years (3650 days).

# Create the self signed root certificate
openssl genrsa -aes256 -out root-ca.key 2048
openssl req -new -x509 -days 3650 -key root-ca.key -out root-ca.crt 
mkdir RootCA
mkdir RootCA/ca.db.certs
echo "01" >> RootCA/ca.db.serial
touch RootCA/ca.db.index
echo $RANDOM >> RootCA/ca.db.rand
mv root-ca* RootCA/

Before we can sign any certificate we need to create a configuration file so OpenSSL knows where all of the CA files are:

#  The basic OpenSSL config file:
cat >> root-ca.cfg <<EOF
[ RootCA ]
dir             = ./RootCA
certs           = \$dir/ca.db.certs
database        = \$dir/ca.db.index
new_certs_dir   = \$dir/ca.db.certs
certificate     = \$dir/root-ca.crt
serial          = \$dir/ca.db.serial
private_key     = \$dir/root-ca.key
RANDFILE        = \$dir/ca.db.rand
default_md      = sha256
default_days    = 365
default_crl_days= 30
email_in_dn     = no
unique_subject  = no
policy          = policy_match

[ SigningCA1 ]
dir             = ./SigningCA1
certs           = \$dir/ca.db.certs
database        = \$dir/ca.db.index
new_certs_dir   = \$dir/ca.db.certs
certificate     = \$dir/signing-ca-1.crt
serial          = \$dir/ca.db.serial
private_key     = \$dir/signing-ca-1.key
RANDFILE        = \$dir/ca.db.rand
default_md      = sha256
default_days    = 365
default_crl_days= 30
email_in_dn     = no
unique_subject  = no
policy          = policy_match

[ SigningCA2 ]
dir             = ./SigningCA2
certs           = \$dir/ca.db.certs
database        = \$dir/ca.db.index
new_certs_dir   = \$dir/ca.db.certs
certificate     = \$dir/signing-ca-2.crt
serial          = \$dir/ca.db.serial
private_key     = \$dir/signing-ca-2.key
RANDFILE        = \$dir/ca.db.rand
default_md      = sha256
default_days    = 365
default_crl_days= 30
email_in_dn     = no
unique_subject  = no
policy          = policy_match

[ policy_match ]
countryName     = match
stateOrProvinceName = match
localityName            = match
organizationName    = match
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
basicConstraints = CA:true
EOF

We can now create a two signing certificates (two branches of the CA tree) which are signed by the root certificate:

# Generate the 2 signing certificates.
index=1
openssl genrsa -aes256 -out signing-ca-${index}.key 2048
openssl req -new -days 1460 -key signing-ca-${index}.key \
            -out signing-ca-${index}.csr
openssl ca -name RootCA -config root-ca.cfg -extensions v3_ca \
           -out signing-ca-${index}.crt \
           -infiles signing-ca-${index}.csr

mkdir SigningCA${index}
mkdir SigningCA${index}
mkdir SigningCA${index}/ca.db.certs
echo "01" >> SigningCA${index}/ca.db.serial
touch SigningCA${index}/ca.db.index
# Should use a better source of random here..
echo $RANDOM >> SigningCA${index}/ca.db.rand
mv signing-ca-${index}* SigningCA${index}/

# repeat with...
index=2
openssl genrsa -aes256 -out signing-ca-${index}.key 2048
openssl req -new -days 1460 -key signing-ca-${index}.key \
            -out signing-ca-${index}.csr
openssl ca -name RootCA -config root-ca.cfg -extensions v3_ca \
           -out signing-ca-${index}.crt \
           -infiles signing-ca-${index}.csr

mkdir SigningCA${index}
mkdir SigningCA${index}
mkdir SigningCA${index}/ca.db.certs
echo "01" >> SigningCA${index}/ca.db.serial
touch SigningCA${index}/ca.db.index
# Should use a better source of random here..
echo $RANDOM >> SigningCA${index}/ca.db.rand
mv signing-ca-${index}* SigningCA${index}/

Now that we have our two signing CAs we can start generating the key pairs for the MongoDB cluster members and clients. Typically server certificates will be used by clients:

host=$(hostname -s)
fqdn=${host}.$(dnsdomainname)
openssl genrsa -aes256 -out ${host}.key 2048
openssl req -new -days 365 -key ${host}.key -out ${host}.csr \
            -subj "/C=US/ST=DC/L=Washington/O=Allanbank Consulting, Inc./CN=${fqdn}"
openssl ca -name SigningCA1 -config root-ca.cfg -out ${host}.crt \
           -infiles ${host}.csr
# Create the .pem file with the certificate and private key
cat ${host}.crt ${host}.key >> ${host}.pem

For the MongoDB cluster members we use the second CA and insert a unique OU:

host=$(hostname -s)
fqdn=${host}.$(dnsdomainname)
openssl genrsa -aes256 -out ${host}.key 2048
openssl req -new -days 365 -key ${host}.key -out ${host}.csr \
            -subj "/C=US/ST=DC/L=Washington/O=Allanbank Consulting, Inc./OU=mongodb/CN=${fqdn}" 
openssl ca -name SigningCA2 -config root-ca.cfg -out ${host}.crt \
           -infiles ${host}.csr
# Create the .pem file with the certificate and private key
cat ${host}.crt ${host}.key >> ${host}.pem