Skip to content
openssl.c 94.1 KiB
Newer Older
        return CURLE_SSL_CIPHER;
      }
    }
  }
#endif
  if(data->set.str[STRING_SSL_CAFILE] || data->set.str[STRING_SSL_CAPATH]) {
    /* tell SSL where to find CA certificates that are used to verify
       the servers certificate. */
    if(!SSL_CTX_load_verify_locations(connssl->ctx,
                                       data->set.str[STRING_SSL_CAFILE],
                                       data->set.str[STRING_SSL_CAPATH])) {
        /* Fail if we insist on successfully verifying the server. */
        failf(data, "error setting certificate verify locations:\n"
              "  CAfile: %s\n  CApath: %s",
              data->set.str[STRING_SSL_CAFILE]?
              data->set.str[STRING_SSL_CAFILE]: "none",
              data->set.str[STRING_SSL_CAPATH]?
              data->set.str[STRING_SSL_CAPATH] : "none");
      }
      else {
        /* Just continue with a warning if no strict  certificate verification
           is required. */
        infof(data, "error setting certificate verify locations,"
              " continuing anyway:\n");
      }
    }
    else {
      /* Everything is fine. */
      infof(data, "successfully set certificate verify locations:\n");
          data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]:
          "none",
          data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]:
          "none");
  if(data->set.str[STRING_SSL_CRLFILE]) {
    /* tell SSL where to find CRL file that is used to check certificate
     * revocation */
    lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx),
                                 X509_LOOKUP_file());
       (!X509_load_crl_file(lookup, data->set.str[STRING_SSL_CRLFILE],
                            X509_FILETYPE_PEM)) ) {
      failf(data, "error loading CRL file: %s",
            data->set.str[STRING_SSL_CRLFILE]);
      return CURLE_SSL_CRL_BADFILE;
    }
    else {
      /* Everything is fine. */
      infof(data, "successfully load CRL file:\n");
      X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx),
                           X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
    }
    infof(data,
          "  CRLfile: %s\n", data->set.str[STRING_SSL_CRLFILE] ?
          data->set.str[STRING_SSL_CRLFILE]: "none");
  /* SSL always tries to verify the peer, this only says whether it should
   * fail to connect if the verification fails, or if it should continue
   * anyway. In the latter case the result of the verification is checked with
   * SSL_get_verify_result() below. */
                     data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE,
  /* give application a chance to interfere with SSL set up. */
    result = (*data->set.ssl.fsslctx)(data, connssl->ctx,
                                      data->set.ssl.fsslctxp);
    if(result) {
      failf(data, "error signaled by ssl ctx callback");
      return result;
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* Lets make an SSL structure */
  connssl->handle = SSL_new(connssl->ctx);
    failf(data, "SSL: couldn't create a context (handle)!");
    return CURLE_OUT_OF_MEMORY;
  }
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
    !defined(OPENSSL_IS_BORINGSSL)
  if(data->set.ssl.verifystatus)
    SSL_set_tlsext_status_type(connssl->handle, TLSEXT_STATUSTYPE_ocsp);
  SSL_set_connect_state(connssl->handle);
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
  if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) &&
     (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) &&
     sni &&
     !SSL_set_tlsext_host_name(connssl->handle, conn->host.name))
    infof(data, "WARNING: failed to configure server name indication (SNI) "
          "TLS extension\n");
#endif

  /* Check if there's a cached ID we can/should use here! */
  if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
    /* we got a session id, use it! */
    if(!SSL_set_session(connssl->handle, ssl_sessionid)) {
      failf(data, "SSL: SSL_set_session failed: %s",
            ERR_error_string(ERR_get_error(), NULL));
    /* Informational message */
    infof (data, "SSL re-using session ID\n");
  /* pass the raw socket into the SSL layers */
  if(!SSL_set_fd(connssl->handle, (int)sockfd)) {
    failf(data, "SSL: SSL_set_fd failed: %s",
          ERR_error_string(ERR_get_error(), NULL));
    return CURLE_SSL_CONNECT_ERROR;
  connssl->connecting_state = ssl_connect_2;
static CURLcode ossl_connect_step2(struct connectdata *conn, int sockindex)
{
  struct SessionHandle *data = conn->data;
  int err;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
             || ssl_connect_2_reading == connssl->connecting_state
             || ssl_connect_2_writing == connssl->connecting_state);
  /* 1  is fine
     0  is "not successful but was shut down controlled"
     <0 is "handshake was not successful, because a fatal error occurred" */
  if(1 != err) {
    int detail = SSL_get_error(connssl->handle, err);
    if(SSL_ERROR_WANT_READ == detail) {
      connssl->connecting_state = ssl_connect_2_reading;
      return CURLE_OK;
    else if(SSL_ERROR_WANT_WRITE == detail) {
      connssl->connecting_state = ssl_connect_2_writing;
      return CURLE_OK;
    }
    else {
      /* untreated error */
      unsigned long errdetail;
      char error_buffer[256]; /* OpenSSL documents that this must be at least
                                 256 bytes long. */
      CURLcode result;

      connssl->connecting_state = ssl_connect_2; /* the connection failed,
                                                    we're not waiting for
                                                    anything else. */

      errdetail = ERR_get_error(); /* Gets the earliest error code from the
                                      thread's error queue and removes the
                                      entry. */

      switch(errdetail) {
      case 0x1407E086:
        /* 1407E086:
           SSL routines:
           SSL2_SET_CERTIFICATE:
           certificate verify failed */
        /* fall-through */
      case 0x14090086:
        /* 14090086:
           SSL routines:
           SSL3_GET_SERVER_CERTIFICATE:
           certificate verify failed */
        result = CURLE_SSL_CACERT;

        lerr = SSL_get_verify_result(connssl->handle);
        if(lerr != X509_V_OK) {
          snprintf(error_buffer, sizeof(error_buffer),
                   "SSL certificate problem: %s",
                   X509_verify_cert_error_string(lerr));
        }
        else
          cert_problem = "SSL certificate problem, verify that the CA cert is"
            " OK.";

        result = CURLE_SSL_CONNECT_ERROR;
        SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));

      /* detail is already set to the SSL error above */

      /* If we e.g. use SSLv2 request-method and the server doesn't like us
       * (RST connection etc.), OpenSSL gives no explanation whatsoever and
       * the SO_ERROR is also lost.
       */
      if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) {
Yang Tse's avatar
 
Yang Tse committed
        failf(data, "Unknown SSL protocol error in connection to %s:%ld ",
              conn->host.name, conn->remote_port);
        return result;
      /* Could be a CERT problem */
      failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);

      return result;
    }
  }
  else {
    /* we have been connected fine, we're not waiting for anything else. */
    connssl->connecting_state = ssl_connect_3;

    /* Informational message */
    infof(data, "SSL connection using %s / %s\n",
          get_ssl_version_txt(connssl->handle),
          SSL_get_cipher(connssl->handle));
Fabian Frank's avatar
Fabian Frank committed
#ifdef HAS_ALPN
    /* Sets data and len to negotiated protocol, len is 0 if no protocol was
     * negotiated
     */
    if(data->set.ssl_enable_alpn) {
      const unsigned char* neg_protocol;
      unsigned int len;
      SSL_get0_alpn_selected(connssl->handle, &neg_protocol, &len);
      if(len != 0) {
        infof(data, "ALPN, server accepted to use %.*s\n", len, neg_protocol);

        if(len == NGHTTP2_PROTO_VERSION_ID_LEN &&
           !memcmp(NGHTTP2_PROTO_VERSION_ID, neg_protocol, len)) {
          conn->negnpn = CURL_HTTP_VERSION_2_0;
        else
#endif
        if(len == ALPN_HTTP_1_1_LENGTH &&
           !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) {
          conn->negnpn = CURL_HTTP_VERSION_1_1;
        infof(data, "ALPN, server did not agree to a protocol\n");
static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len)
{
Yang Tse's avatar
Yang Tse committed
  int i, ilen;

  if((ilen = (int)len) < 0)
    return 1; /* buffer too big */

  i = i2t_ASN1_OBJECT(buf, ilen, a);

  if(i >= ilen)
    return 1; /* buffer too small */

  return 0;
}

static void pubkey_show(struct SessionHandle *data,
                        int num,
                        const char *type,
                        const char *name,
                        unsigned char *raw,
                        int len)
{
  left = len*3 + 1;
  buffer = malloc(left);
  if(buffer) {
    char *ptr=buffer;
    snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
    for(i=0; i< len; i++) {
      snprintf(ptr, left, "%02x:", raw[i]);
      ptr += 3;
      left -= 3;
    }
    infof(data, "   %s: %s\n", namebuf, buffer);
    Curl_ssl_push_certinfo(data, num, namebuf, buffer);
  if(pubkey->pkey._type->_name) { \
    int len = BN_num_bytes(pubkey->pkey._type->_name);  \
    if(len < CERTBUFFERSIZE) {                                    \
      BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)bufp); \
      pubkey_show(data, _num, #_type, #_name, (unsigned char*)bufp, len); \
} WHILE_FALSE

static int X509V3_ext(struct SessionHandle *data,
                      int certnum,
                      STACK_OF(X509_EXTENSION) *exts)
{

  if(sk_X509_EXTENSION_num(exts) <= 0)
    /* no extensions, bail out */
    return 1;

  for(i=0; i<sk_X509_EXTENSION_num(exts); i++) {
    ASN1_OBJECT *obj;
    X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
    BUF_MEM *biomem;
    char buf[512];
    char *ptr=buf;
    char namebuf[128];
    BIO *bio_out = BIO_new(BIO_s_mem());

    if(!bio_out)
      return 1;

    obj = X509_EXTENSION_get_object(ext);

    asn1_object_dump(obj, namebuf, sizeof(namebuf));

    infof(data, "%s: %s\n", namebuf,
          X509_EXTENSION_get_critical(ext)?"(critical)":"");

    if(!X509V3_EXT_print(bio_out, ext, 0, 0))
      ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));

    BIO_get_mem_ptr(bio_out, &biomem);

    /* biomem->length bytes at biomem->data, this little loop here is only
       done for the infof() call, we send the "raw" data to the certinfo
       function */
    for(j=0; j<(size_t)biomem->length; j++) {
      const char *sep="";
      if(biomem->data[j] == '\n') {
        sep=", ";
        j++; /* skip the newline */
      };
      while((j<(size_t)biomem->length) && (biomem->data[j] == ' '))
        ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep,
                      biomem->data[j]);
    Curl_ssl_push_certinfo(data, certnum, namebuf, buf);

    BIO_free(bio_out);

  }
  return 0; /* all is fine */
}


static void X509_signature(struct SessionHandle *data,
                           int numcert,
                           ASN1_STRING *sig)
{
  char buf[1024];
  char *ptr = buf;
  int i;
  for(i=0; i<sig->length; i++)
    ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%02x:", sig->data[i]);

  infof(data, " Signature: %s\n", buf);
  Curl_ssl_push_certinfo(data, numcert, "Signature", buf);
}

static void dumpcert(struct SessionHandle *data, X509 *x, int numcert)
{
  BIO *bio_out = BIO_new(BIO_s_mem());
  BUF_MEM *biomem;

  /* this outputs the cert in this 64 column wide style with newlines and
     -----BEGIN CERTIFICATE----- texts and more */
  PEM_write_bio_X509(bio_out, x);

  BIO_get_mem_ptr(bio_out, &biomem);

  Curl_ssl_push_certinfo_len(data, numcert,
                             "Cert", biomem->data, biomem->length);
/*
 * This size was previously 512 which has been reported "too small" without
 * any specifics, so it was enlarged to allow more data to get shown uncut.
 * The "perfect" size is yet to figure out.
 */
#define CERTBUFFERSIZE 8192

static CURLcode get_cert_chain(struct connectdata *conn,
                               struct ssl_connect_data *connssl)

{
  struct SessionHandle *data = conn->data;
  int numcerts;

  bufp = malloc(CERTBUFFERSIZE);
  if(!bufp)
    return CURLE_OUT_OF_MEMORY;
  sk = SSL_get_peer_cert_chain(connssl->handle);
  if(!sk) {
    free(bufp);

  result = Curl_ssl_init_certinfo(data, numcerts);
  if(result) {
  for(i=0; i<numcerts; i++) {
    long value;
    ASN1_INTEGER *num;
    ASN1_TIME *certdate;

    /* get the certs in "importance order" */
#if 0
    X509 *x = sk_X509_value(sk, numcerts - i - 1);
#else
    X509 *x = sk_X509_value(sk, i);
#endif

    X509_CINF *cinf;
    EVP_PKEY *pubkey=NULL;
    int j;
    char *ptr;

    (void)x509_name_oneline(X509_get_subject_name(x), bufp, CERTBUFFERSIZE);
    infof(data, "%2d Subject: %s\n", i, bufp);
    Curl_ssl_push_certinfo(data, i, "Subject", bufp);
    (void)x509_name_oneline(X509_get_issuer_name(x), bufp, CERTBUFFERSIZE);
    infof(data, "   Issuer: %s\n", bufp);
    Curl_ssl_push_certinfo(data, i, "Issuer", bufp);

    value = X509_get_version(x);
    infof(data, "   Version: %lu (0x%lx)\n", value+1, value);
    snprintf(bufp, CERTBUFFERSIZE, "%lx", value);
    Curl_ssl_push_certinfo(data, i, "Version", bufp); /* hex */
      int left = CERTBUFFERSIZE;
      if(num->type == V_ASN1_NEG_INTEGER) {
      for(j=0; (j<num->length) && (left>=3); j++) {
        snprintf(ptr, left, "%02x", num->data[j]);
        ptr += 2;
        left -= 2;
        infof(data, "   Serial Number: %s\n", bufp);
      Curl_ssl_push_certinfo(data, i, "Serial Number", bufp); /* hex */
    j = asn1_object_dump(cinf->signature->algorithm, bufp, CERTBUFFERSIZE);
      infof(data, "   Signature Algorithm: %s\n", bufp);
      Curl_ssl_push_certinfo(data, i, "Signature Algorithm", bufp);
    asn1_output(certdate, bufp, CERTBUFFERSIZE);
    infof(data, "   Start date: %s\n", bufp);
    Curl_ssl_push_certinfo(data, i, "Start date", bufp);
    asn1_output(certdate, bufp, CERTBUFFERSIZE);
    infof(data, "   Expire date: %s\n", bufp);
    Curl_ssl_push_certinfo(data, i, "Expire date", bufp);
    j = asn1_object_dump(cinf->key->algor->algorithm, bufp, CERTBUFFERSIZE);
      infof(data, "   Public Key Algorithm: %s\n", bufp);
      Curl_ssl_push_certinfo(data, i, "Public Key Algorithm", bufp);
    }

    pubkey = X509_get_pubkey(x);
    if(!pubkey)
      infof(data, "   Unable to load public key\n");
    else {
      switch(pubkey->type) {
      case EVP_PKEY_RSA:
        infof(data,  "   RSA Public Key (%d bits)\n",
              BN_num_bits(pubkey->pkey.rsa->n));
        snprintf(bufp, CERTBUFFERSIZE, "%d", BN_num_bits(pubkey->pkey.rsa->n));
        Curl_ssl_push_certinfo(data, i, "RSA Public Key", bufp);

        print_pubkey_BN(rsa, n, i);
        print_pubkey_BN(rsa, e, i);
        print_pubkey_BN(rsa, d, i);
        print_pubkey_BN(rsa, p, i);
        print_pubkey_BN(rsa, q, i);
        print_pubkey_BN(rsa, dmp1, i);
        print_pubkey_BN(rsa, dmq1, i);
        print_pubkey_BN(rsa, iqmp, i);
        break;
      case EVP_PKEY_DSA:
        print_pubkey_BN(dsa, p, i);
        print_pubkey_BN(dsa, q, i);
        print_pubkey_BN(dsa, g, i);
        print_pubkey_BN(dsa, priv_key, i);
        print_pubkey_BN(dsa, pub_key, i);
        break;
      case EVP_PKEY_DH:
        print_pubkey_BN(dh, p, i);
        print_pubkey_BN(dh, g, i);
        print_pubkey_BN(dh, priv_key, i);
        print_pubkey_BN(dh, pub_key, i);
        break;
#if 0
      case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */
        /* left TODO */
        break;
#endif
      }
    }

    X509V3_ext(data, i, cinf->extensions);

    X509_signature(data, i, x->signature);

    dumpcert(data, x, i);
  }

/*
 * Heavily modified from:
 * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL
 */
static CURLcode pkp_pin_peer_pubkey(X509* cert, const char *pinnedpubkey)
{
  /* Scratch */
  int len1 = 0, len2 = 0;

  /* Result is returned to caller */
  CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;

  /* if a path wasn't specified, don't pin */
    return CURLE_OK;

    return result;

  do {
    /* Begin Gyrations to get the subjectPublicKeyInfo     */
    /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */

    /* https://groups.google.com/group/mailing.openssl.users/browse_thread
     /thread/d61858dae102c6c7 */
    len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
    if(len1 < 1)
      break; /* failed */

    /* https://www.openssl.org/docs/crypto/buffer.html */
    buff1 = temp = OPENSSL_malloc(len1);
      break; /* failed */

    /* https://www.openssl.org/docs/crypto/d2i_X509.html */
    len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp);

    /*
     * These checks are verifying we got back the same values as when we
     * sized the buffer. It's pretty weak since they should always be the
     * same. But it gives us something to test.
     */
    if((len1 != len2) || !temp || ((temp - buff1) != len1))
      break; /* failed */

    /* End Gyrations */

    /* The one good exit point */
    result = Curl_pin_peer_pubkey(pinnedpubkey, buff1, len1);
  /* https://www.openssl.org/docs/crypto/buffer.html */
    OPENSSL_free(buff1);

  return result;
}

/*
 * Get the server cert, verify it and show it etc, only call failf() if the
 * 'strict' argument is TRUE as otherwise all this is for informational
 * purposes only!
 *
 * We check certificates to authenticate the server; otherwise we risk
 * man-in-the-middle attack.
 */
static CURLcode servercert(struct connectdata *conn,
                           struct ssl_connect_data *connssl,
                           bool strict)
  CURLcode result = CURLE_OK;
  long lerr;
  ASN1_TIME *certdate;
  struct SessionHandle *data = conn->data;
  char *buffer = data->state.buffer;

  if(data->set.ssl.certinfo)
    /* we've been asked to gather certificate info! */
    (void)get_cert_chain(conn, connssl);
  connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
  if(!connssl->server_cert) {
    if(strict)
      failf(data, "SSL: couldn't get peer certificate!");
    return CURLE_PEER_FAILED_VERIFICATION;

  infof(data, "Server certificate:\n");
  rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert),
  infof(data, "\t subject: %s\n", rc?"[NONE]":buffer);
  certdate = X509_get_notBefore(connssl->server_cert);
  asn1_output(certdate, buffer, BUFSIZE);
  infof(data, "\t start date: %s\n", buffer);
  certdate = X509_get_notAfter(connssl->server_cert);
  asn1_output(certdate, buffer, BUFSIZE);
  infof(data, "\t expire date: %s\n", buffer);
    result = verifyhost(conn, connssl->server_cert);
    if(result) {
      connssl->server_cert = NULL;
      return result;
  rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert),
    if(strict)
      failf(data, "SSL: couldn't get X509-issuer name!");
    result = CURLE_SSL_CONNECT_ERROR;
    infof(data, "\t issuer: %s\n", buffer);
Daniel Stenberg's avatar
Daniel Stenberg committed

    /* We could do all sorts of certificate verification stuff here before
       deallocating the certificate. */
    /* e.g. match issuer name with provided issuer certificate */
    if(data->set.str[STRING_SSL_ISSUERCERT]) {
      fp = fopen(data->set.str[STRING_SSL_ISSUERCERT], FOPEN_READTEXT);
          failf(data, "SSL: Unable to open issuer cert (%s)",
                data->set.str[STRING_SSL_ISSUERCERT]);
        X509_free(connssl->server_cert);
        connssl->server_cert = NULL;
        return CURLE_SSL_ISSUER_ERROR;

      issuer = PEM_read_X509(fp, NULL, ZERO_NULL, NULL);
          failf(data, "SSL: Unable to read issuer cert (%s)",
                data->set.str[STRING_SSL_ISSUERCERT]);
        X509_free(connssl->server_cert);
        X509_free(issuer);
        fclose(fp);
        return CURLE_SSL_ISSUER_ERROR;
      if(X509_check_issued(issuer, connssl->server_cert) != X509_V_OK) {
          failf(data, "SSL: Certificate issuer check failed (%s)",
                data->set.str[STRING_SSL_ISSUERCERT]);
        X509_free(connssl->server_cert);
        X509_free(issuer);
        connssl->server_cert = NULL;
      infof(data, "\t SSL certificate issuer check ok (%s)\n",
    lerr = data->set.ssl.certverifyresult =
      SSL_get_verify_result(connssl->handle);
    if(data->set.ssl.certverifyresult != X509_V_OK) {
      if(data->set.ssl.verifypeer) {
        /* We probably never reach this, because SSL_connect() will fail
           and we return earlier if verifypeer is set? */
        if(strict)
          failf(data, "SSL certificate verify result: %s (%ld)",
                X509_verify_cert_error_string(lerr), lerr);
        result = CURLE_PEER_FAILED_VERIFICATION;
        infof(data, "\t SSL certificate verify result: %s (%ld),"
              X509_verify_cert_error_string(lerr), lerr);
      infof(data, "\t SSL certificate verify ok.\n");
Daniel Stenberg's avatar
Daniel Stenberg committed

#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
    !defined(OPENSSL_IS_BORINGSSL)
  if(data->set.ssl.verifystatus) {
    result = verifystatus(conn, connssl);
    if(result) {
      X509_free(connssl->server_cert);
      connssl->server_cert = NULL;
      return result;
    }
  }
  if(!strict)
    /* when not strict, we don't bother about the verify cert problems */
    result = CURLE_OK;

  ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
  if(!result && ptr) {
    result = pkp_pin_peer_pubkey(connssl->server_cert, ptr);
    if(result)
      failf(data, "SSL: public key does not match pinned public key!");
  connssl->server_cert = NULL;
  connssl->connecting_state = ssl_connect_done;
  return result;
static CURLcode ossl_connect_step3(struct connectdata *conn, int sockindex)
  CURLcode result = CURLE_OK;
  void *old_ssl_sessionid = NULL;
  struct SessionHandle *data = conn->data;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  SSL_SESSION *our_ssl_sessionid;

  DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);

#ifdef HAVE_SSL_GET1_SESSION
  our_ssl_sessionid = SSL_get1_session(connssl->handle);
  /* SSL_get1_session() will increment the reference
     count and the session will stay in memory until explicitly freed with
     SSL_SESSION_free(3), regardless of its state.
     This function was introduced in openssl 0.9.5a. */
  our_ssl_sessionid = SSL_get_session(connssl->handle);
  /* if SSL_get1_session() is unavailable, use SSL_get_session().
     This is an inferior option because the session can be flushed
     at any time by openssl. It is included only so curl compiles
     under versions of openssl < 0.9.5a.
     WARNING: How curl behaves if it's session is flushed is
     untested.
  */

  incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL));
  if(incache) {
    if(old_ssl_sessionid != our_ssl_sessionid) {
      infof(data, "old SSL session ID is stale, removing\n");
      Curl_ssl_delsessionid(conn, old_ssl_sessionid);
      incache = FALSE;
    }
  }
    result = Curl_ssl_addsessionid(conn, our_ssl_sessionid,
                                   0 /* unknown size */);
    if(result) {
      failf(data, "failed to store ssl session");
      return result;
#ifdef HAVE_SSL_GET1_SESSION
  else {
    /* Session was incache, so refcount already incremented earlier.
     * Avoid further increments with each SSL_get1_session() call.
     * This does not free the session as refcount remains > 0
     */
    SSL_SESSION_free(our_ssl_sessionid);
  }
#endif

  /*
   * We check certificates to authenticate the server; otherwise we risk
   * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to
   * verify the peer ignore faults and failures from the server cert
   * operations.
   */

  result = servercert(conn, connssl,
                      (data->set.ssl.verifypeer || data->set.ssl.verifyhost));
  if(!result)
    connssl->connecting_state = ssl_connect_done;

  return result;
Daniel Stenberg's avatar
Daniel Stenberg committed
}
static Curl_recv ossl_recv;
static Curl_send ossl_send;

static CURLcode ossl_connect_common(struct connectdata *conn,
                                    int sockindex,
                                    bool nonblocking,
                                    bool *done)
  CURLcode result;
  struct SessionHandle *data = conn->data;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  curl_socket_t sockfd = conn->sock[sockindex];
  long timeout_ms;
  /* check if the connection has already been established */
  if(ssl_connection_complete == connssl->state) {
    *done = TRUE;
    return CURLE_OK;
  }

  if(ssl_connect_1 == connssl->connecting_state) {
    /* Find out how much more time we're allowed */
    timeout_ms = Curl_timeleft(data, NULL, TRUE);

    if(timeout_ms < 0) {
      /* no need to continue if time already is up */
      failf(data, "SSL connection timeout");
      return CURLE_OPERATION_TIMEDOUT;
    }

    result = ossl_connect_step1(conn, sockindex);
    if(result)
      return result;
  while(ssl_connect_2 == connssl->connecting_state ||
        ssl_connect_2_reading == connssl->connecting_state ||
        ssl_connect_2_writing == connssl->connecting_state) {
    timeout_ms = Curl_timeleft(data, NULL, TRUE);

    if(timeout_ms < 0) {
      /* no need to continue if time already is up */
      failf(data, "SSL connection timeout");
      return CURLE_OPERATION_TIMEDOUT;
    }

    /* if ssl is expecting something, check if it's available. */
    if(connssl->connecting_state == ssl_connect_2_reading ||
       connssl->connecting_state == ssl_connect_2_writing) {
Yang Tse's avatar
Yang Tse committed
      curl_socket_t writefd = ssl_connect_2_writing==
        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
Yang Tse's avatar
Yang Tse committed
      curl_socket_t readfd = ssl_connect_2_reading==
        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;

      what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms);
      if(what < 0) {
        /* fatal error */
        failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
        return CURLE_SSL_CONNECT_ERROR;
      }
      else if(0 == what) {
        if(nonblocking) {
          *done = FALSE;
          return CURLE_OK;
          /* timeout */
          failf(data, "SSL connection timeout");
          return CURLE_OPERATION_TIMEDOUT;
      }
      /* socket is readable or writable */
    /* Run transaction, and return to the caller if it failed or if this
     * connection is done nonblocking and this loop would execute again. This
     * permits the owner of a multi handle to abort a connection attempt
     * before step2 has completed while ensuring that a client using select()
     * or epoll() will always have a valid fdset to wait on.
    result = ossl_connect_step2(conn, sockindex);
    if(result || (nonblocking &&
                  (ssl_connect_2 == connssl->connecting_state ||
                   ssl_connect_2_reading == connssl->connecting_state ||
                   ssl_connect_2_writing == connssl->connecting_state)))
      return result;

  } /* repeat step2 until all transactions are done. */

  if(ssl_connect_3 == connssl->connecting_state) {
    result = ossl_connect_step3(conn, sockindex);
    if(result)
      return result;
  if(ssl_connect_done == connssl->connecting_state) {
    connssl->state = ssl_connection_complete;
    conn->recv[sockindex] = ossl_recv;
    conn->send[sockindex] = ossl_send;
  /* Reset our connect state machine */
  connssl->connecting_state = ssl_connect_1;

CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn,
                                       int sockindex,
                                       bool *done)
  return ossl_connect_common(conn, sockindex, TRUE, done);