Skip to content

Allow for client certs signed by an intermediate CA.#1267

Merged
arp242 merged 11 commits intolib:masterfrom
benchub:fix-intermediate-ca-auth
Mar 10, 2026
Merged

Allow for client certs signed by an intermediate CA.#1267
arp242 merged 11 commits intolib:masterfrom
benchub:fix-intermediate-ca-auth

Conversation

@benchub
Copy link
Copy Markdown
Contributor

@benchub benchub commented Mar 5, 2026

Before this change, client certs signed by an intermediate CA, when the server only trusts the root CA, were not handled correctly.

Add a unit test for this situation (which now passes) as well as tests for other intermediate CA situations (which passed before this change and continue to pass afterwards.)

Fixes #1266.

@arp242
Copy link
Copy Markdown
Collaborator

arp242 commented Mar 5, 2026

Does it need to use the mockPostgresSSLServer()? All the other SSL tests just run against PostgreSQL. Similarly, does it need to generate the certificates? For the others it's just stored in ./testdata/init (and can be re-generated with the Makefile).

Both are a lot of code to maintain.

@benchub
Copy link
Copy Markdown
Contributor Author

benchub commented Mar 5, 2026

Ah, I was trying to keep things as self-contained as possible. I'll make a new commit to just keep certs in ./testdata/init/ instead and modify the Makefile to remake them as needed. That's a better idea. Thanks.

I wasn't able to show a failing unit test until I used mockPostgresSSLServer(), because the problem being fixed is how this package does not send up the full chain and that was making Postgres sad.

@arp242
Copy link
Copy Markdown
Collaborator

arp242 commented Mar 5, 2026

Do the tests not fail if you run that new TestSSLIntermediateCA() against PostgreSQL?

Aside from the extra code to maintain, right now it also doesn't test whether this actually works with PostgreSQL: it only tests that it works with your mock, which may not necessarily be the same as what PostgreSQL does.

For simpler protocol tests that's okay (we already have pqtest.Fake), but SSL verification is probably complex adn subtle enough that it pays to test against PostgreSQL, unless this is unfeasible for some reason (although I don't see why that would be the case off-hand?)

@benchub
Copy link
Copy Markdown
Contributor Author

benchub commented Mar 5, 2026

Oh! I see your point now. Again, I was trying to make things self-contained, so that we don't have to configure an external postgres install. But of course other tests assume there is an external postgres. Let me lean into that pattern and see where it takes me.

@arp242
Copy link
Copy Markdown
Collaborator

arp242 commented Mar 5, 2026

You already need PostgreSQL to run many tests. docker compose up -d should start a PostgreSQL that should work with go test. Also see the README. Let me know if you have trouble with this.

Or you can rely on the GitHub CI.

@benchub
Copy link
Copy Markdown
Contributor Author

benchub commented Mar 5, 2026

Thanks for the push @arp242; you were absolutely correct that the test was only testing the mock server, which was insufficient.

While fixing this I got annoyed about the consistent two tests test that were failing before I started any of this, and just made the change to let them run cleanly.

@arp242
Copy link
Copy Markdown
Collaborator

arp242 commented Mar 9, 2026

Looks like there are some test failures?

Note I pushed some small fixes: notably, capturing the tt loop variable in the tests. Because go.mod specifies 1.21, it didn't use the new (well, "new") for loops and it appeared to pass as it always used the last test, which is the only one that works.

After fixing that, it consistently fails on my machine and in CI:

% go test -run TestSSLClientCertificateIntermediate

--- FAIL: TestSSLClientCertificateIntermediate (0.00s)
    --- FAIL: TestSSLClientCertificateIntermediate/file_certs (0.00s)
        ssl_test.go:200: wrong error
            want:
            have: remote error: tls: unknown certificate authority
    --- FAIL: TestSSLClientCertificateIntermediate/inline_certs (0.00s)
        ssl_test.go:200: wrong error
            want:
            have: pq: connection requires a valid client certificate (28000)
FAIL
exit status 1
FAIL    github.com/lib/pq       0.011s

@benchub
Copy link
Copy Markdown
Contributor Author

benchub commented Mar 9, 2026

Well that exposed some things. New fix incoming.

benchub and others added 7 commits March 10, 2026 14:48
Before this change, client certs signed by an intermediate CA, when
the server only trusts the root CA, were not handled correctly.

Add a unit test for this situation (which now passes) as well as
tests for other intermediate CA situations (which passed before
this change and continue to pass afterwards.)

Fixes lib#1266.
Instead of making them inside tests as needed, just have static
certs that are made with a Makefile.
There's no point in trying to be self-contained when so many
other tests already require an external postgres server. And hey!
Doing this reveals that our previous fix didn't actually fix
things; the mock server was not sufficient in showing the real
problem.

So in addition to removing a lot of code, we also now have a bug
fix that works against a real server.
Go's x509.Certificate.Verify() returns a plain fmt.Errorf (not a typed error)
for the "not standards compliant" check, so the type switch in
InvalidCertificate couldn't match it. Add a string-based fallback for that case.
This wasn't changed until Go 1.22, and pq works with Go 1.21
1. Inline intermediate certs were completely ignored. In giving them
   some love, discover another pre-existing bug, where os.Stat was
   trying to read inlined-PEM data when it expected a file path. Of
   course there is no file path for an inline cert.
2. File intermediate certs where sending a cert but without the
   intermediate chain.

This patch fixes all those problems. All tests pass.
@arp242 arp242 force-pushed the fix-intermediate-ca-auth branch from e9e1b3a to fa30117 Compare March 10, 2026 14:48
arp242 added 4 commits March 10, 2026 14:51
Tests seem to work fine with the old version?
sslCertificateAuthority() would already read this, so return it from
there.
@arp242 arp242 merged commit f3ef532 into lib:master Mar 10, 2026
13 checks passed
@arp242
Copy link
Copy Markdown
Collaborator

arp242 commented Mar 10, 2026

Cheers, thanks

@benchub
Copy link
Copy Markdown
Contributor Author

benchub commented Mar 10, 2026

\o/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client certs signed by intermediate CAs are not handled correctly.

2 participants