tlsvalidation.c 20.6 KB
Newer Older
1
/*
Adrien Béraud's avatar
Adrien Béraud committed
2
 *  Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 *
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *          Vittorio Giovara <vittorio.giovara@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
50
#include <dirent.h>
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/abstract.h>

#include "logger.h"
#include "tlsvalidation.h"

/**
 * Load the content of a file and return the data pointer to it.
 */
static unsigned char *crypto_file_read(const char *path, size_t *out_len)
{
    struct stat st;
    int fd;
    ssize_t bytes_read;
    size_t file_size;
    unsigned char *data = NULL;

    *out_len = 0;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
74
        RING_ERR("Failed to open file '%s'.", path);
75 76 77 78
        return NULL;
    }

    if (fstat(fd, &st) < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
79
        RING_ERR("Failed to stat file '%s'.", path);
80 81 82 83
        goto out;
    }

    if (st.st_size <= 0 || st.st_size > INT_MAX) {
Adrien Béraud's avatar
Adrien Béraud committed
84
        RING_ERR("Invalid file '%s' length %ld.", path, st.st_size);
85 86 87 88 89 90
        goto out;
    }

    file_size = st.st_size;
    data = (unsigned char *)malloc(file_size);
    if (!data) {
Adrien Béraud's avatar
Adrien Béraud committed
91
        RING_ERR("Not enough memory to read file '%s'.", path);
92 93 94 95 96 97 98 99 100
        goto out;
    }

    do {
        bytes_read = read(fd, &(data[*out_len]), (st.st_size - *out_len));
        if (bytes_read < 0) {
            free(data);
            data = NULL;
            *out_len = 0;
Adrien Béraud's avatar
Adrien Béraud committed
101
            RING_ERR("Failed to read file '%s'.", path);
102 103 104
            goto out;
        }
        *out_len += bytes_read;
105
    } while ((bytes_read > 0) && (*out_len < file_size));
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

out:
    close(fd);
    return data;
}

/**
 * Check the validity date of a given certificate.
 */
static int crypto_cert_check_date(gnutls_x509_crt_t cert)
{
    time_t now = time(0);
    time_t activationTime, expirationTime;

    activationTime = gnutls_x509_crt_get_activation_time(cert);
    if (activationTime == -1) {
Adrien Béraud's avatar
Adrien Béraud committed
122
        RING_ERR("Could not retrieve activation time.");
123 124 125
        return -1;
    }
    if (now < activationTime) {
Adrien Béraud's avatar
Adrien Béraud committed
126
        RING_ERR("Certificate not yet activated.");
127 128 129 130 131
        return -1;
    }

    expirationTime = gnutls_x509_crt_get_expiration_time(cert);
    if (expirationTime == -1) {
Adrien Béraud's avatar
Adrien Béraud committed
132
        RING_ERR("Could not errrieve expiration time.");
133 134 135
        return -2;
    }
    if (now > expirationTime) {
Adrien Béraud's avatar
Adrien Béraud committed
136
        RING_ERR("Certificate expired.");
137 138 139 140 141 142 143 144 145 146 147 148 149
        return -2;
    }

    return 0;
}

/**
 * Load the content of a certificate file and return the data pointer to it.
 */
static unsigned char *crypto_cert_read(const char *path, size_t *out_len)
{
    gnutls_x509_crt_t cert;
    unsigned char *data = NULL;
150
    gnutls_datum_t dt;
151 152 153 154 155 156 157 158 159
    size_t fsize = 0;
    int err;

    dt.data = crypto_file_read(path, &fsize);
    if (!dt.data)
        return NULL;

    dt.size = (unsigned int) fsize;
    if (gnutls_x509_crt_init(&cert) != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
160
        RING_ERR("Not enough memory for certificate.");
161 162 163 164 165 166 167
        goto out;
    }

    err = gnutls_x509_crt_import(cert, &dt, GNUTLS_X509_FMT_PEM);
    if (err != GNUTLS_E_SUCCESS)
        err = gnutls_x509_crt_import(cert, &dt, GNUTLS_X509_FMT_DER);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
168
        RING_ERR("Could not import certificate %s - %s", path, gnutls_strerror(err));
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
        goto out;
    }

    /* check if cert date is valid */
    err = crypto_cert_check_date(cert);
    if (err < 0)
        goto out;

    *out_len = 10000;
    data = (unsigned char *)malloc(*out_len);
    if (!data)
        goto out;
    err = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, data, out_len);
    if (err != GNUTLS_E_SUCCESS) {
        free(data);
Tristan Matthews's avatar
Tristan Matthews committed
184
        data = NULL;
185
        *out_len = 0;
Adrien Béraud's avatar
Adrien Béraud committed
186
        RING_ERR("Certificate %s could not be exported - %s.\n",
187 188 189 190 191 192 193 194 195 196
              path, gnutls_strerror(err));
    }

out:
    if (dt.data)
        gnutls_free(dt.data);
    gnutls_x509_crt_deinit(cert);
    return data;
}

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
/**
 * Load all root CAs present in the system.
 * Normally we should use gnutls_certificate_set_x509_system_trust(), but it requires
 * GnuTLS 3.0 or later. As a workaround we iterate on the system trusted store folder
 * and load every certificate available there.
 */
static int crypto_cert_load_trusted(gnutls_certificate_credentials_t cred)
{
    DIR *trust_store;
    struct dirent *trust_ca;
    struct stat statbuf;
    int err, res = -1;
    char ca_file[512];

    trust_store = opendir("/etc/ssl/certs/");
    if (!trust_store) {
Adrien Béraud's avatar
Adrien Béraud committed
213
        RING_ERR("Failed to open system trusted store.");
214 215 216 217 218 219
        goto out;
    }
    while ((trust_ca = readdir(trust_store)) != NULL) {
        /* Prepare the string and check it is a regular file. */
        err = snprintf(ca_file, sizeof(ca_file), "/etc/ssl/certs/%s", trust_ca->d_name);
        if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
220
            RING_ERR("snprintf() error");
221 222
            goto out;
        } else if (err >= sizeof(ca_file)) {
Adrien Béraud's avatar
Adrien Béraud committed
223
            RING_ERR("File name too long '%s'.", trust_ca->d_name);
224 225 226 227
            goto out;
        }
        err = stat(ca_file, &statbuf);
        if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
228
            RING_ERR("Failed to stat file '%s'.", ca_file);
229 230 231 232 233 234 235 236
            goto out;
        }
        if (!S_ISREG(statbuf.st_mode))
            continue;

        /* Load the root CA. */
        err = gnutls_certificate_set_x509_trust_file(cred, ca_file, GNUTLS_X509_FMT_PEM);
        if (err == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
237
            RING_WARN("No trusted certificates found - %s", gnutls_strerror(err));
238
        } else if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
239
            RING_ERR("Could not load trusted certificates - %s", gnutls_strerror(err));
240 241 242 243 244 245 246 247 248 249
            goto out;
        }
    }

    res = 0;
out:
    closedir(trust_store);
    return res;
}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
/**
 * Print the Subject, the Issuer and the Verification status of a given certificate.
 */
static int crypto_cert_print_issuer(gnutls_x509_crt_t cert,
                                    gnutls_x509_crt_t issuer)
{
    char name[512];
    char issuer_name[512];
    size_t name_size;
    size_t issuer_name_size;

    issuer_name_size = sizeof(issuer_name);
    gnutls_x509_crt_get_issuer_dn(cert, issuer_name,
                                  &issuer_name_size);

    name_size = sizeof(name);
    gnutls_x509_crt_get_dn(cert, name, &name_size);

Adrien Béraud's avatar
Adrien Béraud committed
268 269
    RING_DBG("Subject: %s", name);
    RING_DBG("Issuer: %s", issuer_name);
270 271 272 273 274

    if (issuer != NULL) {
        issuer_name_size = sizeof(issuer_name);
        gnutls_x509_crt_get_dn(issuer, issuer_name, &issuer_name_size);

Adrien Béraud's avatar
Adrien Béraud committed
275
        RING_DBG("Verified against: %s", issuer_name);
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
    }

    return 0;
}

int containsPrivateKey(const char *pemPath)
{
    gnutls_datum_t dt;
    gnutls_x509_privkey_t key;
    size_t bufsize;
    int err, res = -1;

    dt.data = crypto_file_read(pemPath, &bufsize);
    if (!dt.data)
        return res;
    dt.size = bufsize;

    err = gnutls_global_init();
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
295
        RING_ERR("Could not init GnuTLS - %s", gnutls_strerror(err));
296 297
        free(dt.data);
        return res;
298 299 300 301
    }

    err = gnutls_x509_privkey_init(&key);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
302
        RING_ERR("Could not init key - %s", gnutls_strerror(err));
303 304 305
        free(dt.data);
        gnutls_global_deinit();
        return res;
306 307 308 309 310 311
    }

    err = gnutls_x509_privkey_import(key, &dt, GNUTLS_X509_FMT_PEM);
    if (err != GNUTLS_E_SUCCESS)
        err = gnutls_x509_privkey_import(key, &dt, GNUTLS_X509_FMT_DER);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
312
        RING_ERR("Could not read key - %s", gnutls_strerror(err));
313 314 315 316
        goto out;
    }

    res = 0;
Adrien Béraud's avatar
Adrien Béraud committed
317
    RING_DBG("Key from %s seems valid.", pemPath);
318 319 320 321 322 323 324 325 326 327 328
out:
    free(dt.data);
    gnutls_x509_privkey_deinit(key);
    gnutls_global_deinit();
    return res;
}

int certificateIsValid(const char *caPath, const char *certPath)
{
    gnutls_x509_crt_t ca = NULL;
    gnutls_x509_crt_t cert = NULL;
329
    gnutls_datum_t ca_dt = {}, cert_dt = {};
330 331 332 333 334 335 336
    size_t bufsize;
    unsigned int output;
    int err, self_signed;
    int res = -1;

    err = gnutls_global_init();
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
337
        RING_ERR("Could not init GnuTLS - %s", gnutls_strerror(err));
338 339 340 341 342 343 344 345 346 347
        goto out;
    }

    cert_dt.data = crypto_cert_read(certPath, &bufsize);
    cert_dt.size = bufsize;
    if (!cert_dt.data)
        goto out;

    err = gnutls_x509_crt_init(&cert);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
348
        RING_ERR("Could not init certificate - %s", gnutls_strerror(err));
349 350 351 352 353 354 355
        goto out;
    }

    err = gnutls_x509_crt_import(cert, &cert_dt, GNUTLS_X509_FMT_PEM);
    if (err != GNUTLS_E_SUCCESS)
        err = gnutls_x509_crt_import(cert, &cert_dt, GNUTLS_X509_FMT_DER);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
356
        RING_ERR("Could not read certificate - %s", gnutls_strerror(err));
357 358 359 360 361 362 363 364
        goto out;
    }
    free(cert_dt.data);
    cert_dt.data = NULL;

    /* check if cert is self signed */
    self_signed = gnutls_x509_crt_check_issuer(cert, cert);
    if (!self_signed && !caPath) {
Adrien Béraud's avatar
Adrien Béraud committed
365
        RING_ERR("Certificate is not self-signed, and CA is not provided.");
366 367 368 369 370 371 372 373 374 375
        goto out;
    }
    if (caPath) {
        ca_dt.data = crypto_cert_read(caPath, &bufsize);
        ca_dt.size = bufsize;
        if (!ca_dt.data)
            goto out;

        err = gnutls_x509_crt_init(&ca);
        if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
376
            RING_ERR("Could not init CA - %s", gnutls_strerror(err));
377 378 379 380 381 382 383
            goto out;
        }

        err = gnutls_x509_crt_import(ca, &ca_dt, GNUTLS_X509_FMT_PEM);
        if (err != GNUTLS_E_SUCCESS)
            err = gnutls_x509_crt_import(ca, &ca_dt, GNUTLS_X509_FMT_DER);
        if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
384
            RING_ERR("Could not read CA - %s", gnutls_strerror(err));
385 386 387 388 389 390 391 392
            goto out;
        }
        free(ca_dt.data);
        ca_dt.data = NULL;

        /* Check if the CA is the issuer of certificate. */
        self_signed = gnutls_x509_crt_check_issuer(cert, ca);
        if (!self_signed) {
Adrien Béraud's avatar
Adrien Béraud committed
393
            RING_ERR("Certificate is not issued by the provided CA.");
394 395 396 397 398 399
            goto out;
        }

        /* Verify the certificate with its issuer. */
        err = gnutls_x509_crt_verify(cert, &ca, 1, 0, &output);
        if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
400
            RING_ERR("Could not verify cert: %s", gnutls_strerror(err));
401 402 403
            goto out;
        }
        if (output & GNUTLS_CERT_INVALID) {
Adrien Béraud's avatar
Adrien Béraud committed
404
            RING_ERR("Verification failed.");
405
            if (output & GNUTLS_CERT_SIGNER_NOT_FOUND)
Adrien Béraud's avatar
Adrien Béraud committed
406
                RING_ERR("The certificate hasn't got a known issuer.");
407
            if (output & GNUTLS_CERT_SIGNER_NOT_CA)
Adrien Béraud's avatar
Adrien Béraud committed
408
                RING_ERR("The certificate issuer is not a CA.");
409
            if (output & GNUTLS_CERT_REVOKED)
Adrien Béraud's avatar
Adrien Béraud committed
410
                RING_ERR("The certificate has been revoked.");
411
            if (output & GNUTLS_CERT_EXPIRED)
Adrien Béraud's avatar
Adrien Béraud committed
412
                RING_ERR("The certificate has expired.");
413
            if (output & GNUTLS_CERT_NOT_ACTIVATED)
Adrien Béraud's avatar
Adrien Béraud committed
414
                RING_ERR("The certificate is not yet activated.");
415 416 417 418
            goto out;
        }
    }

Adrien Béraud's avatar
Adrien Béraud committed
419
    RING_DBG("Certificate from %s seems valid.", certPath);
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
    crypto_cert_print_issuer(cert, ca);
    res = 0;
out:
    if (ca_dt.data)
        free(ca_dt.data);
    if (cert_dt.data)
        free(cert_dt.data);
    if (ca)
        gnutls_x509_crt_deinit(ca);
    gnutls_x509_crt_deinit(cert);
    gnutls_global_deinit();
    return res;
}

/* mainly based on Fedora Defensive Coding tutorial
 * https://docs.fedoraproject.org/en-US/Fedora_Security_Team/html/Defensive_Coding/sect-Defensive_Coding-TLS-Client-GNUTLS.html
 */
int verifyHostnameCertificate(const char *host, const uint16_t port)
{
    int err, arg, res = -1;
    unsigned int status = (unsigned) -1;
    const char *errptr = NULL;
    gnutls_session_t session = NULL;
    gnutls_certificate_credentials_t cred = NULL;
    unsigned int certslen = 0;
    const gnutls_datum_t *certs = NULL;
    gnutls_x509_crt_t cert = NULL;
447

448 449 450 451 452 453 454 455 456
    char buf[4096];
    int sockfd;
    struct sockaddr_in name;
    struct hostent *hostinfo;
    const int one = 1;
    fd_set fdset;
    struct timeval tv;

    if (!host || !port) {
Adrien Béraud's avatar
Adrien Béraud committed
457
        RING_ERR("Wrong parameters used - host %s, port %d.", host, port);
458 459 460 461 462 463
        return res;
    }

    /* Create the socket. */
    sockfd = socket (PF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
464
        RING_ERR("Could not create socket.");
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
        return res;
    }
    /* Set non-blocking so we can dected timeouts. */
    arg = fcntl(sockfd, F_GETFL, NULL);
    if (arg < 0)
        goto out;
    arg |= O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, arg) < 0)
        goto out;

    /* Give the socket a name. */
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_port = htons(port);
    hostinfo = gethostbyname(host);
    if (hostinfo == NULL) {
Adrien Béraud's avatar
Adrien Béraud committed
481
        RING_ERR("Unknown host %s.", host);
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
        goto out;
    }
    name.sin_addr = *(struct in_addr *)hostinfo->h_addr;
    /* Connect to the address specified in name struct. */
    err = connect(sockfd, (struct sockaddr *)&name, sizeof(name));
    if (err < 0) {
        /* Connection in progress, use select to see if timeout is reached. */
        if (errno == EINPROGRESS) {
            do {
                FD_ZERO(&fdset);
                FD_SET(sockfd, &fdset);
                tv.tv_sec = 10;     // 10 second timeout
                tv.tv_usec = 0;
                err = select(sockfd + 1, NULL, &fdset, NULL, &tv);
                if (err < 0 && errno != EINTR) {
Adrien Béraud's avatar
Adrien Béraud committed
497
                    RING_ERR("Could not connect to hostname %s at port %d",
498 499 500 501 502 503 504 505 506
                          host, port);
                    goto out;
                } else if (err > 0) {
                    /* Select returned, if so_error is clean we are ready. */
                    int so_error;
                    socklen_t len = sizeof(so_error);
                    getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);

                    if (so_error) {
Adrien Béraud's avatar
Adrien Béraud committed
507
                        RING_ERR("Connection delayed.");
508 509 510 511
                        goto out;
                    }
                    break;  // exit do-while loop
                } else {
Adrien Béraud's avatar
Adrien Béraud committed
512
                    RING_ERR("Connection timeout.");
513 514 515 516
                    goto out;
                }
            } while(1);
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
517
            RING_ERR("Could not connect to hostname %s at port %d", host, port);
518 519 520 521 522 523 524 525 526 527 528 529 530 531
            goto out;
        }
    }
    /* Set the socked blocking again. */
    arg = fcntl(sockfd, F_GETFL, NULL);
    if (arg < 0)
        goto out;
    arg &= ~O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, arg) < 0)
        goto out;

    /* Disable Nagle algorithm that slows down the SSL handshake. */
    err = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
    if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
532
        RING_ERR("Could not set TCP_NODELAY.");
533 534 535 536 537
        goto out;
    }

    err = gnutls_global_init();
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
538
        RING_ERR("Could not init GnuTLS - %s", gnutls_strerror(err));
539 540 541 542 543
        goto out;
    }
    /* Load the trusted CA certificates. */
    err = gnutls_certificate_allocate_credentials(&cred);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
544
        RING_ERR("Could not allocate credentials - %s", gnutls_strerror(err));
545 546
        goto out;
    }
547 548
    err = crypto_cert_load_trusted(cred);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
549
        RING_ERR("Could not load credentials.");
550 551 552 553 554 555
        goto out;
    }

    /* Create the session object. */
    err = gnutls_init(&session, GNUTLS_CLIENT);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
556
        RING_ERR("Could not init session -%s\n", gnutls_strerror(err));
557 558 559 560 561 562
        goto out;
    }

    /* Configure the cipher preferences. The default set should be good enough. */
    err = gnutls_priority_set_direct(session, "NORMAL", &errptr);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
563
        RING_ERR("Could not set up ciphers - %s (%s)", gnutls_strerror(err), errptr);
564 565 566 567 568 569
        goto out;
    }

    /* Install the trusted certificates. */
    err = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
570
        RING_ERR("Could not set up credentials - %s", gnutls_strerror(err));
571 572 573 574 575 576 577
        goto out;
    }

    /* Associate the socket with the session object and set the server name. */
    gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) (uintptr_t) sockfd);
    err = gnutls_server_name_set(session, GNUTLS_NAME_DNS, host, strlen(host));
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
578
        RING_ERR("Could not set server name - %s", gnutls_strerror(err));
579 580 581 582 583 584
        goto out;
    }

    /* Establish the connection. */
    err = gnutls_handshake(session);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
585
        RING_ERR("Handshake failed - %s", gnutls_strerror(err));
586 587 588 589 590 591
        goto out;
    }
    /* Obtain the server certificate chain. The server certificate
     * itself is stored in the first element of the array. */
    certs = gnutls_certificate_get_peers(session, &certslen);
    if (certs == NULL || certslen == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
592
        RING_ERR("Could not obtain peer certificate - %s", gnutls_strerror(err));
593 594 595 596 597 598
        goto out;
    }

    /* Validate the certificate chain. */
    err = gnutls_certificate_verify_peers2(session, &status);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
599
        RING_ERR("Could not verify the certificate chain - %s", gnutls_strerror(err));
600 601 602 603 604 605 606 607 608 609 610
        goto out;
    }
    if (status != 0) {
        gnutls_datum_t msg;
#if GNUTLS_VERSION_AT_LEAST_3_1_4
        int type = gnutls_certificate_type_get(session);
        err = gnutls_certificate_verification_status_print(status, type, &out, 0);
#else
        err = -1;
#endif
        if (err == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
611
            RING_ERR("Certificate validation failed - %s\n", msg.data);
612 613 614
            gnutls_free(msg.data);
            goto out;
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
615
            RING_ERR("Certificate validation failed with code 0x%x.", status);
616 617 618 619 620 621 622 623 624 625 626
            goto out;
        }
    }

    /* Match the peer certificate against the hostname.
     * We can only obtain a set of DER-encoded certificates from the
     * session object, so we have to re-parse the peer certificate into
     * a certificate object. */

    err = gnutls_x509_crt_init(&cert);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
627
        RING_ERR("Could not init certificate - %s", gnutls_strerror(err));
628 629 630 631 632 633 634 635
        goto out;
    }

    /* The peer certificate is the first certificate in the list. */
    err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM);
    if (err != GNUTLS_E_SUCCESS)
        err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER);
    if (err != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
636
        RING_ERR("Could not read peer certificate - %s", gnutls_strerror(err));
637 638 639 640 641
        goto out;
    }
    /* Finally check if the hostnames match. */
    err = gnutls_x509_crt_check_hostname(cert, host);
    if (err == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
642
        RING_ERR("Hostname %s does not match certificate.", host);
643 644 645 646 647 648 649
        goto out;
    }

    /* Try sending and receiving some data through. */
    snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host);
    err = gnutls_record_send(session, buf, strlen(buf));
    if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
650
        RING_ERR("Send failed - %s", gnutls_strerror(err));
651 652 653 654
        goto out;
    }
    err = gnutls_record_recv(session, buf, sizeof(buf));
    if (err < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
655
        RING_ERR("Recv failed - %s", gnutls_strerror(err));
656 657 658
        goto out;
    }

Adrien Béraud's avatar
Adrien Béraud committed
659
    RING_DBG("Hostname %s seems to point to a valid server.", host);
660 661 662 663 664 665 666 667 668 669 670 671 672 673
    res = 0;
out:
    if (session) {
        gnutls_bye(session, GNUTLS_SHUT_RDWR);
        gnutls_deinit(session);
    }
    if (cert)
        gnutls_x509_crt_deinit(cert);
    if (cred)
        gnutls_certificate_free_credentials(cred);
    gnutls_global_deinit();
    close(sockfd);
    return res;
}