GnuTLS 未正确验证 googleapis.com 的证书

GnuTLS doesn't correctly verify certs for googleapis.com

我本来问了一个related question on security.stackexchange.com。这是 MCVE。

简短版:当我使用 GnuTLS 验证到 googleapis.com 的 HTTPS 连接时,验证失败。对于其他站点(例如 github.com),它会成功。

我正在显式加载 /etc/ssl/certs/ca-certificates.crt 文件(在实际程序中,我们缓存它,而不是每次都访问文件系统)。

CA 商店由 Ubuntu updated recently。在此更新之前,以下代码有效。更新后失败了

Ubuntu 14.04,用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;
}

使用当前的 CA 包...

$ ./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)

使用之前的 CA 捆绑包...

$ ./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,在同一台机器上,工作正常:

$ 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

(注意"Peer's certificate is trusted")

更新中删除了 "Equifax" 证书,但根据 security.stackexchange.com 问题,GnuTLS 应该看到中间 "GeoTrust" 证书并将其视为有效根。

我做错了什么?

... apt-get install libgnutls28-dev, which is the only relevant difference between my machine and a vanilla 14.04 box

在您描述的情况下,此版本存在一个已知问题。有关详细信息和补丁,请参阅 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 仍在使用原始的 gnutls 版本 2.12.23,默认情况下带有 Ubuntu 14.04(请参阅 gnutls-cli -v 的输出)。此版本似乎不受此问题影响。