I originally asked a related question on security.stackexchange.com. Here's the MCVE.
Short version: when I use GnuTLS to verify an HTTPS connection to googleapis.com, it fails verification. For other sites (e.g. github.com), it succeeds.
I'm loading the /etc/ssl/certs/ca-certificates.crt
file explicitly (in the real program, we cache it, rather than hit the filesystem every time).
The CA store was updated recently by Ubuntu. Prior to that update, the following code works. Since the update, it fails.
Ubuntu 14.04, compile with g++ -o gnutls-client gnutls-client.cpp -lgnutls
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <gnutls/x509.h>
#include <assert.h>
#define CURL_CA_BUNDLE "/etc/ssl/certs/ca-certificates.crt" // FAILS
//#define CURL_CA_BUNDLE "old-ca-certificates.crt" // WORKS
#define CHECK(x) assert((x) >= 0);
// Fails with sheets.googleapis.com
// Succeeds with (e.g.) github.com
int main(int argc, char *argv[])
{
if (argc < 2) {
exit(1);
}
const char *server_name = argv[1];
gnutls_global_init();
printf("gnutls-client (GnuTLS/%s)\n", gnutls_check_version(NULL));
gnutls_certificate_credentials_t creds = NULL;
CHECK(gnutls_certificate_allocate_credentials(&creds));
gnutls_certificate_set_verify_flags(creds,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
int certificateCount = gnutls_certificate_set_x509_trust_file(creds,
CURL_CA_BUNDLE, GNUTLS_X509_FMT_PEM);
if (certificateCount >= 0) {
printf("%d certificate(s) processed\n", certificateCount);
}
else {
printf("Failed to set trust file: %d\n", certificateCount);
exit(1);
}
gnutls_session_t session = NULL;
CHECK(gnutls_init(&session, GNUTLS_CLIENT));
CHECK(gnutls_server_name_set(session, GNUTLS_NAME_DNS,
server_name, strlen(server_name)));
CHECK(gnutls_set_default_priority(session));
CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, creds));
struct addrinfo hint, *addr;
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;
getaddrinfo(server_name, "https", &hint, &addr);
int sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
CHECK(connect(sockfd, addr->ai_addr, addr->ai_addrlen));
gnutls_transport_set_int(session, sockfd);
int ret;
do {
ret = gnutls_handshake(session);
} while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
if (ret < 0) {
fprintf(stderr, "ret: %d\n", ret);
exit(1);
}
printf("Connected: %s\n", gnutls_session_get_desc(session));
unsigned int peercerts_size;
const gnutls_datum_t *peercerts = gnutls_certificate_get_peers(session,
&peercerts_size);
printf("Server presented %d certs\n", peercerts_size);
unsigned int verify_status;
CHECK(gnutls_certificate_verify_peers2(session, &verify_status));
printf("%d 0x%x\n", ret, verify_status);
assert(verify_status == 0x0);
return 0;
}
With the current CA bundle...
$ ./gnutls-client github.com
gnutls-client (GnuTLS/3.2.11)
148 certificate(s) processed
Connected: (TLS1.2)-(ECDHE-RSA-SECP256R1)-(AES-128-GCM)
Server presented 2 certs
0 0x0
$ ./gnutls-client googleapis.com
gnutls-client (GnuTLS/3.2.11)
148 certificate(s) processed
Connected: (TLS1.2)-(ECDHE-ECDSA-SECP256R1)-(AES-128-GCM)
Server presented 3 certs
0 0x42
gnutls-client: gnutls-client.cpp:82: int main(int, char**): Assertion `verify_status == 0x0' failed.
Aborted (core dumped)
With the previous CA bundle...
$ ./gnutls-client github.com
gnutls-client (GnuTLS/3.2.11)
173 certificate(s) processed
Connected: (TLS1.2)-(ECDHE-RSA-SECP256R1)-(AES-128-GCM)
Server presented 2 certs
0 0x0
$ ./gnutls-client googleapis.com
gnutls-client (GnuTLS/3.2.11)
173 certificate(s) processed
Connected: (TLS1.2)-(ECDHE-ECDSA-SECP256R1)-(AES-128-GCM)
Server presented 3 certs
0 0x0
gnutls-cli
, on the same machine, works fine:
$ gnutls-cli googleapis.com --x509cafile /etc/ssl/certs/ca-certificates.crt
Processed 148 CA certificate(s).
Resolving 'googleapis.com'...
Connecting to '108.177.119.105:443'...
- Certificate type: X.509
- Got a certificate list of 3 certificates.
- Certificate[0] info:
- subject `C=US,ST=California,L=Mountain View,O=Google Inc,CN=*.googleapis.com', issuer `C=US,O=Google Inc,CN=Google Internet Authority G2', RSA key 2048 bits, signed using RSA-SHA256, activated `2017-10-17 10:22:56 UTC', expires `2017-12-29 00:00:00 UTC', SHA-1 fingerprint `34e45ef97aadd3e73978790c2f16ce275a28cd1c'
- Certificate[1] info:
- subject `C=US,O=Google Inc,CN=Google Internet Authority G2', issuer `C=US,O=GeoTrust Inc.,CN=GeoTrust Global CA', RSA key 2048 bits, signed using RSA-SHA256, activated `2017-05-22 11:32:37 UTC', expires `2018-12-31 23:59:59 UTC', SHA-1 fingerprint `a6120fc0b4664fad0b3b6ffd5f7a33e561ddb87d'
- Certificate[2] info:
- subject `C=US,O=GeoTrust Inc.,CN=GeoTrust Global CA', issuer `C=US,O=Equifax,OU=Equifax Secure Certificate Authority', RSA key 2048 bits, signed using RSA-SHA1, activated `2002-05-21 04:00:00 UTC', expires `2018-08-21 04:00:00 UTC', SHA-1 fingerprint `7359755c6df9a0abc3060bce369564c8ec4542a3'
- The hostname in the certificate matches 'googleapis.com'.
- Peer's certificate is trusted
- Version: TLS1.2
- Key Exchange: RSA
- Cipher: AES-128-CBC
- MAC: SHA1
- Compression: NULL
- Handshake was completed
- Simple Client Mode:
^C
(Note the "Peer's certificate is trusted")
The "Equifax" certificate was removed in the update, but according to the security.stackexchange.com question, GnuTLS should see the intermediate "GeoTrust" certificate and treat that as a valid root.
What am I doing wrong?
... apt-get install libgnutls28-dev, which is the only relevant difference between my machine and a vanilla 14.04 box
There is a known problem with this version in exactly the situation you describe. For more details and a patch see gnutls28 in trusty no longer validates many valid certificate chains, such as google.com.
$ gnutls-cli googleapis.com --x509cafile /etc/ssl/certs/ca-certificates.crt
...
- Peer's certificate is trusted
gnutls-cli is still using the original gnutls version 2.12.23 which comes by default with Ubuntu 14.04 (see output of gnutls-cli -v
). This version does not seem to be affected by the problem.