Handling Self-Signed Certificates in Auth0 Custom Database Connections

Last Updated: Sep 13, 2024

Overview

When using a custom database connection in Auth0, the following error or similar might be observed if the database or API server uses a self-signed certificate:

{ "error": "invalid_grant", "error_description": "self signed certificate" }

Applies To

  • Custom database scripts
  • Actions, hooks, rules, and other extensibility code
  • Self-signed certificates

Cause

Self-signed certificates are not trusted by default because they are not signed by a recognized Certificate Authority (CA).

Solution

Instead of disabling certificate verification (which is insecure and highly discouraged), use certificate pinning.

This involves manually comparing the fingerprint of the remote server certificate with the expected fingerprint of the self-signed certificate in the custom database script.

This solution also applies to other extensibility codes, such as actions.

NOTE: If the certificate changes, it will be necessary to change the fingerprint in the script.

Example implementations

HTTP Requests (using Axios)

const axios = require('axios');
const https = require('https');
const crypto = require('crypto');

function createAxiosInstance(pinnedFingerprint) {
  return axios.create({
    httpsAgent: new https.Agent({
      rejectUnauthorized: true,
      checkServerIdentity: (host, cert) => {
        const fingerprint = crypto
          .createHash('sha256')
          .update(cert.raw)
          .digest('hex')
          .toUpperCase();
        
        if (fingerprint !== pinnedFingerprint) {
          throw new Error('Certificate verification failed: fingerprint mismatch');
        }
      }
    })
  });
}

function login(email, password, callback) {
   const pinnedFingerprint = 'AABBCCDDEEFF00112233445566778899001122334455667788990011223344';
   const axiosInstance = createAxiosInstance(pinnedFingerprint);

   // rest of code
}

PostgreSQL

const { Client } = require('pg');
const crypto = require('crypto');

function createPgClient(config, pinnedFingerprint) {
  return new Client({
    ...config,
    ssl: {
      rejectUnauthorized: true,
      checkServerIdentity: (host, cert) => {
        const fingerprint = crypto
          .createHash('sha256')
          .update(cert.raw)
          .digest('hex')
          .toUpperCase();
        
        if (fingerprint !== pinnedFingerprint) {
          throw new Error('Certificate verification failed: fingerprint mismatch');
        }
      }
    }
  });
}

function login(email, password, callback) {
   const dbConfig = {
      host: 'localhost',
      port: 5432,
      user: 'me',
      password: 'secret',
      database: 'mydb'
   };
   const pinnedFingerprint = 'AABBCCDDEEFF00112233445566778899001122334455667788990011223344';
   const client = createPgClient(dbConfig, pinnedFingerprint);

   // rest of code
}

MySQL​​​​

const crypto = require('crypto');

function createMySQLConnection(config, pinnedFingerprint) {
  const connection = mysql.createConnection({
    ...config,
    ssl: {
      rejectUnauthorized: true
    }
  });

  connection.on('secureConnect', () => {
    const cert = connection.connection.getPeerCertificate();
    const fingerprint = crypto
      .createHash('sha256')
      .update(cert.raw)
      .digest('hex')
      .toUpperCase();

    if (fingerprint !== pinnedFingerprint) {
      connection.destroy();
      throw new Error('Certificate verification failed: fingerprint mismatch');
    }
  });

  return connection;
}

function login(email, password, callback) {
   const dbConfig = {
      host: 'localhost',
      user: 'me',
      password: 'secret',
      database: 'mydb'
   };
   const pinnedFingerprint = 'AABBCCDDEEFF00112233445566778899001122334455667788990011223344556677889900112233';
   const connection = createMySQLConnection(dbConfig, pinnedFingerprint);

   // rest of code
}

Retrieving the fingerprint

There are a couple of ways to retrieve the fingerprint. Since the output in both cases will contain sha256 Fingerprint= and the fingerprint will be formatted with colons (‘:’), it will be necessary to remove these as well.

  1. If there is direct access to the certificate file:
    $ openssl x509 -in <.crt file> -noout -sha256 -fingerprint | sed -r 's/sha256 Fingerprint=(.+)/\1/' | tr -d ':'
    
  2. If there is network access only to the server:
    $ openssl s_client -connect <server>:<port> < /dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256 | sed -r 's/sha256 Fingerprint=(.+)/\1/' | tr -d ':'
    
1 Like