So. SSL certificates are still black magic to me. Especially when they cause trouble.

Like when one of the sysadmins has forgotten to add the certificate bundle to the apache2 config.

Then you get stuff like this:

$ hg pull -u
abort: error: _ssl.c:503: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

Most web browsers do not notice this as they already have the intermediate CA files, but /etc/ssl/certs/ca-certificates.crt seemingly doesn't.

The problem in this case was not that I was missing any certificates locally. The problem was that the web server was not publishing the child certificates of which the server certificate was a grandchild.

openssl can tell you all this, if you know how:

$ echo quit | openssl s_client -connect code.osso.nl:443 | sed -e '1,/^Server certificate/!d'
depth=0 OU = Domain Control Validated, OU = PositiveSSL Wildcard, CN = *.osso.nl
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 OU = Domain Control Validated, OU = PositiveSSL Wildcard, CN = *.osso.nl
verify error:num=27:certificate not trusted
verify return:1
depth=0 OU = Domain Control Validated, OU = PositiveSSL Wildcard, CN = *.osso.nl
verify error:num=21:unable to verify the first certificate
verify return:1
CONNECTED(00000003)
DONE
---
Certificate chain
 0 s:/OU=Domain Control Validated/OU=PositiveSSL Wildcard/CN=*.osso.nl
   i:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=PositiveSSL CA
---
Server certificate

I had to add the bundle of CA files that reference each other: issuer is the next signee in the list.

For apache2 this bundle is supposed to be ordered bottom-up. So, next to your SSLCertificateFile you should always have a SSLCertificateChainFile pointing to the bundle of intermediates.

SSLCertificateChainFile /etc/ssl/certs/code_osso_nl_CA.pem

Now, the openssl connect output looks a lot better:

$ echo quit | openssl s_client -connect code.osso.nl:443 | sed -e '1,/^Server certificate/!d'
depth=3 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify error:num=19:self signed certificate in certificate chain
verify return:0
CONNECTED(00000003)
DONE
---
Certificate chain
 0 s:/OU=Domain Control Validated/OU=PositiveSSL Wildcard/CN=*.osso.nl
   i:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=PositiveSSL CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=PositiveSSL CA
   i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware
 2 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
 3 s:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
Server certificate

There's still one error left, but that's correct. We need to trust the root certificate.

Now the hg pull works again, like it should.

P.S. Some applications want the certificate bundle in a different order. dovecot (ssl_cert_file) wants it bottom-up, with the server certificate first in the same file. postfix (smtpd_tls_CAfile), on the other hand, likes it top-down.

conf shell openssl certificate apache2