Re: Trust intermediate CA for client certificates

From: Craig Ringer <craig(at)2ndquadrant(dot)com>
To: Ian Pilcher <arequipeno(at)gmail(dot)com>
Cc: pgsql-general(at)postgresql(dot)org, tgl(at)sss(dot)pgh(dot)pa(dot)us, stellr(at)vt(dot)edu, pgsql-hackers(at)postgresql(dot)org
Subject: Re: Trust intermediate CA for client certificates
Date: 2013-03-18 05:07:15
Message-ID: 5146A103.8080609@2ndquadrant.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general pgsql-hackers

On 03/09/2013 04:52 PM, Ian Pilcher wrote:
> After looking at be-secure.c and investigating the way that OpenSSL
> validates certificates, I do not believe that there is any way of
> achieving the desired behavior with the current codebase.

Test process:

SET UP SERVER VERIFIED SSL (NO CLIENT CERTS)
------------------------------------------------------------------

Edited postgresql.conf and set:

ssl=on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ca_file='root.crt'

Copied your samples:

# cp $CERTS/postgres.crt server.crt
# cp $CERTS/postgres.key server.key
# cp $CERTS/client-ca.crt root.crt
# chown postgres:postgres root.crt server.crt server.key
# chmod 0600 server.crt server.key root.crt
# systemctl restart postgresql-9.2.service

$ psql "postgresql://localhost/?sslmode=require"
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)

(connects OK; expected since we aren't requiring client certs yet and we
aren't validating the server cert).

$ psql "postgresql://localhost/?sslmode=verify-ca"
psql: root certificate file "/home/craig/.postgresql/root.crt" does not
exist
Either provide the file or change sslmode to disable server certificate
verification.

Again expected, though we really should be using the system SSL cert
database. Anyway:

$ mkdir .postgresql
$ cp $CERTS/root-ca.crt ~/.postgresql/root.crt

(This should be the trusted root, not server-ca or client-ca since we
shouldn't have to keep copies of either to verify server trust). Now,
test we can verify the server's identity:

$ psql "postgresql://localhost/?sslmode=require"
psql: SSL error: certificate verify failed

Plonk, we can't. The reason for this is that the server has sent us its
cert and we have the root cert, but we don't have the intermediate
server-ca.crt . Append that to server.crt:

# cat $CERTS/postgres.crt $CERTS/server-ca.crt > server.crt
# systemctl restart postgresql-9.2.service

$ psql "postgresql://localhost/?sslmode=require"
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)

OK, we're good now, the server is sending us the intermediate cert we
require. Regular non-client-cert verified SSL is fine. Examination of
the protocol chat shows that the server is sending a Server Hello with a
Certificate message containing the server and intermdediate certificate DNs:

id-at-commonName=postgres.example.com,id-at-organizationName=Ian
Pilcher,id-at-localityName=Carrollton,id-at-stateOrProvinceName=Texas,id-at-countryName=US
id-at-commonName=Server CA,id-at-organizationName=Ian
Pilcher,id-at-localityName=Carrollton,id-at-stateOrProvinceName=Texas,id-at-countryName=US

as expected.

ENABLE CLIENT CERTIFICATE VERIFICATION
---------------------------------------------------------

Now lets install our client certificates in the client.

$ cp $CERTS/good-client.key ~/.postgresql/postgresql.key
$ # Note that the order is important here, the client cert must appear
first, followed by the chain cert(s)
$ cat $CERTS/good-client.crt $CERTS/client-ca.crt >
~/.postgresql/postgresql.crt
$ chmod 0600 .postgresql/postgresql.key

$ psql "postgresql://localhost/?sslmode=verify-ca"
psql: SSL error: tlsv1 alert unknown ca

Examination of the handshake shows that the server is sending a request
for client certificates signed by:

Distinguished Name: (id-at-commonName=Client
CA,id-at-organizationName=Ian
Pilcher,id-at-localityName=Carrollton,id-at-stateOrProvinceName=Texas,id-at-countryName=US)

and the client is sending in response:

Certificate (id-at-commonName=Good Client,id-at-organizationName=Ian
Pilcher,id-at-localityName=Carrollton,id-at-stateOrProvinceName=Texas,id-at-countryName=US)
Certificate (id-at-commonName=Client CA,id-at-organizationName=Ian
Pilcher,id-at-localityName=Carrollton,id-at-stateOrProvinceName=Texas,id-at-countryName=US)

as expected, and good-client.crt is indeed signed by client-ca.crt .
Again the issue appears to be that Pg can't find the root of trust,
which is fair enough given that it is not present in Pg's root.crt or
server.crt and it isn't installed system-wide either. We could add it to
the server's root.crt but that'd cause the issues that started this thread:

# cat $CERTS/client-ca.crt $CERTS/root-ca.crt > root.crt
# systemctl restart postgresql-9.2.service

the good client cert works now:

$ psql "postgresql://localhost/?sslmode=verify-ca"
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)

PROBLEM VERIFIED
--------------------------

... but so does the one we don't want to trust, as per the problem report:

$ cat $CERTS/bad-client.crt $CERTS/server-ca.crt > postgresql.crt
$ cp $CERTS/bad-client.key postgresql.key
$ chmod 600 postgresql.key
$ psql "postgresql://localhost/?sslmode=verify-ca"
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)

So this problem is verified.

THE SOLUTION
--------------------

What we need to happen instead is for root.crt to contain only the
trusted certificates and have a *separate* file or directory for
intermediate certificates that OpenSSL can look up to get the
intermediates it needs to validate client certs, like
`ssl_ca_chain_file` or `ssl_ca_chain_path` if we want to support
OpenSSL's hashed certificate directories.

System wide installation of the root may allow OpenSSL to discover it
and use it for verification back to the root without having to trust it
to sign clients. I'll do some more checking to see if this is possible
with how Pg uses OpenSSL but I'm inclined to doubt it.

I thought you might be able to add the common root to the server.crt
certificate chain to let OpenSSL discover it that way, but it looks like
OpenSSL won't use certs it's seen in server.crt when verifying client
cert trust paths.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Tom Lane 2013-03-18 05:09:07 Re: Enforcing Parameterised Nested Loop Join Order for Foreign Table Joins
Previous Message Adam Zegelin 2013-03-18 04:09:22 Enforcing Parameterised Nested Loop Join Order for Foreign Table Joins

Browse pgsql-hackers by date

  From Date Subject
Next Message Boszormenyi Zoltan 2013-03-18 05:22:42 Re: Strange Windows problem, lock_timeout test request
Previous Message Alvaro Herrera 2013-03-18 04:12:51 Re: Patch to add regression tests for SCHEMA