Search code examples
postgresqlsslherokussl-certificatepgadmin

SSL error when connection pgAdmin3 to Heroku postgreSQL DB


When trying to connect to Heroku PostgresSQL DB using pgAdmin3 I'm getting the following error:

Error connecting to the server: SSL error: certificate verify failed

The connection is based on pg:credentials output and defined as below:

[Properties]:

  • Host: <host>
  • Port: 5432
  • Service: [blank]
  • Maintenance DB: <database>
  • Username: <user>

[SSL]:

  • SSL: require
  • Server Root Certificate File: [blank]
  • Server Certificate Revocation List: [blank]
  • Client Certificate File: [blank]
  • Client Key File: [blank]
  • SSL compression: on

[SSH Tunnel] and [Advanced] left default

so as per Heroku guidelines SSL is enabled (set to: [**require**]).

Any ideas how to provide/fix the certificate referred by the error message?


Solution

  • It's likely that pgAdmin is picking up a bundle of CA certificates that has been configured on your system, in which case require would try to verify the server certificate against that bundle.

    Typically, this would be a root.crt file located in %APPDATA%\postgresql\ (C:\Users\YourUserName\AppData\Roaming\postgresql\) under Windows, or in ~/.postgresql/ under Linux.

    If there is such a file, try to move it out of the way temporarily to check if it works better.


    The problem with moving it out of the way is that you are then no longer verifying any remote PostgreSQL certificates against anything but it still works (with require, it would fail with verify-full).

    You can solve this by putting the root.crt file in the right place and adding the server certificate to the list of trusted certificates.


    It can be bit tricky to find a remote PostgreSQL certificate, but this simple Python application should let you do it (replace hostname and port as required):

    import socket
    import ssl
    import struct
    
    
    hostname = '...'
    port = 5432
    
    sock = socket.socket()
    sock.connect((hostname, port))
    # We first connect without encryption and tell the server
    # we want to upgrade to SSL/TLS.
    initiate_ssl_command = struct.Struct('!ii').pack(8, 80877103)
    sock.sendall(initiate_ssl_command)
    resp = sock.recv(1)
    print "Response should be S: %s" % (resp,)
    
    # We then initiate an SSL/TLS connection on this socket.
    ssl_sock = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_NONE)
    ssl_sock.do_handshake()
    peer_cert = ssl_sock.getpeercert(True)
    print ssl.DER_cert_to_PEM_cert(peer_cert)
    ssl_sock.close()
    

    (For details about what this code does, see the PostgreSQL protocol documentation, in particular the "SSL Session Encryption" section and the SSLRequest message, which is similar to what STARTTLS does in other protocols such as SMTP.)

    Security warning: Here, you're just hoping that this particular connection has not been attacked and returns the genuine certificate the first time. It's what you'll use as a trust anchor for subsequent connections. (It's very similar to accepting an SSH server key for the first connection, it will flag changes to the certificate if it changes.)

    It's also worth noting that the certificate Subject DN might not match that of the server you're connecting to, therefore you might not be able to use PostgreSQL's verify-full mode (which is the only really secure mode, since it also verifies the host name as well as the trust anchor).

    Ideally, Heroku (or whoever provides this service) should give you that certificate by another secure means, and make sure that the Subject DN in that certificate matches the host name they give you. (I'm not sure if this is the case at the moment, perhaps it is available from the administration interface.)