CURLcode Curl_ossl_ctx_init()

in libs/curl/lib/vtls/openssl.c [3439:3951]


CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
                            struct Curl_cfilter *cf,
                            struct Curl_easy *data,
                            struct ssl_peer *peer,
                            int transport, /* TCP or QUIC */
                            const unsigned char *alpn, size_t alpn_len,
                            Curl_ossl_ctx_setup_cb *cb_setup,
                            void *cb_user_data,
                            Curl_ossl_new_session_cb *cb_new_session,
                            void *ssl_user_data)
{
  CURLcode result = CURLE_OK;
  const char *ciphers;
  SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
  ctx_option_t ctx_options = 0;
  void *ssl_sessionid = NULL;
  struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
  const long int ssl_version_min = conn_config->version;
  char * const ssl_cert = ssl_config->primary.clientcert;
  const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
  const char * const ssl_cert_type = ssl_config->cert_type;
  const bool verifypeer = conn_config->verifypeer;
  char error_buffer[256];

  /* Make funny stuff to get random input */
  result = ossl_seed(data);
  if(result)
    return result;

  ssl_config->certverifyresult = !X509_V_OK;

  switch(transport) {
  case TRNSPRT_TCP:
    /* check to see if we have been told to use an explicit SSL/TLS version */
    switch(ssl_version_min) {
    case CURL_SSLVERSION_DEFAULT:
    case CURL_SSLVERSION_TLSv1:
    case CURL_SSLVERSION_TLSv1_0:
    case CURL_SSLVERSION_TLSv1_1:
    case CURL_SSLVERSION_TLSv1_2:
    case CURL_SSLVERSION_TLSv1_3:
      /* it will be handled later with the context options */
  #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
      req_method = TLS_client_method();
  #else
      req_method = SSLv23_client_method();
  #endif
      break;
    case CURL_SSLVERSION_SSLv2:
      failf(data, "No SSLv2 support");
      return CURLE_NOT_BUILT_IN;
    case CURL_SSLVERSION_SSLv3:
      failf(data, "No SSLv3 support");
      return CURLE_NOT_BUILT_IN;
    default:
      failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
      return CURLE_SSL_CONNECT_ERROR;
    }
    break;
  case TRNSPRT_QUIC:
    if(conn_config->version_max &&
       (conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) {
      failf(data, "QUIC needs at least TLS version 1.3");
      return CURLE_SSL_CONNECT_ERROR;
    }

#ifdef USE_OPENSSL_QUIC
    req_method = OSSL_QUIC_client_method();
#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    req_method = TLS_method();
#else
    req_method = SSLv23_client_method();
#endif
    break;
  default:
    failf(data, "unsupported transport %d in SSL init", transport);
    return CURLE_SSL_CONNECT_ERROR;
  }


  DEBUGASSERT(!octx->ssl_ctx);
  octx->ssl_ctx = SSL_CTX_new(req_method);

  if(!octx->ssl_ctx) {
    failf(data, "SSL: could not create a context: %s",
          ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
    return CURLE_OUT_OF_MEMORY;
  }

  if(cb_setup) {
    result = cb_setup(cf, data, cb_user_data);
    if(result)
      return result;
  }

#ifdef SSL_CTRL_SET_MSG_CALLBACK
  if(data->set.fdebug && data->set.verbose) {
    /* the SSL trace callback is only used for verbose logging */
    SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace);
    SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf);
  }
#endif

  /* OpenSSL contains code to work around lots of bugs and flaws in various
     SSL-implementations. SSL_CTX_set_options() is used to enabled those
     work-arounds. The manpage for this option states that SSL_OP_ALL enables
     all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
     enable the bug workaround options if compatibility with somewhat broken
     implementations is desired."

     The "-no_ticket" option was introduced in OpenSSL 0.9.8j. it is a flag to
     disable "rfc4507bis session ticket support". rfc4507bis was later turned
     into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077

     The enabled extension concerns the session management. I wonder how often
     libcurl stops a connection and then resumes a TLS session. Also, sending
     the session data is some overhead. I suggest that you just use your
     proposed patch (which explicitly disables TICKET).

     If someone writes an application with libcurl and OpenSSL who wants to
     enable the feature, one can do this in the SSL callback.

     SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
     interoperability with web server Netscape Enterprise Server 2.0.1 which
     was released back in 1996.

     Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
     become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
     CVE-2010-4180 when using previous OpenSSL versions we no longer enable
     this option regardless of OpenSSL version and SSL_OP_ALL definition.

     OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability:
     https://web.archive.org/web/20240114184648/openssl.org/~bodo/tls-cbc.txt.
     In 0.9.6e they added a bit to SSL_OP_ALL that _disables_ that work-around
     despite the fact that SSL_OP_ALL is documented to do "rather harmless"
     workarounds. In order to keep the secure work-around, the
     SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set.
  */

  ctx_options = SSL_OP_ALL;

#ifdef SSL_OP_NO_TICKET
  ctx_options |= SSL_OP_NO_TICKET;
#endif

#ifdef SSL_OP_NO_COMPRESSION
  ctx_options |= SSL_OP_NO_COMPRESSION;
#endif

#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
  /* mitigate CVE-2010-4180 */
  ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
#endif

#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
  /* unless the user explicitly asks to allow the protocol vulnerability we
     use the work-around */
  if(!ssl_config->enable_beast)
    ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
#endif

  switch(ssl_version_min) {
  case CURL_SSLVERSION_SSLv2:
  case CURL_SSLVERSION_SSLv3:
    return CURLE_NOT_BUILT_IN;

    /* "--tlsv<x.y>" options mean TLS >= version <x.y> */
  case CURL_SSLVERSION_DEFAULT:
  case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
  case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
  case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
  case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
  case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
    /* asking for any TLS version as the minimum, means no SSL versions
       allowed */
    ctx_options |= SSL_OP_NO_SSLv2;
    ctx_options |= SSL_OP_NO_SSLv3;

#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
    result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx);
#else
    result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data);
#endif
    if(result != CURLE_OK)
      return result;
    break;

  default:
    failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
    return CURLE_SSL_CONNECT_ERROR;
  }

  SSL_CTX_set_options(octx->ssl_ctx, ctx_options);

#ifdef HAS_ALPN
  if(alpn && alpn_len) {
    if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) {
      failf(data, "Error setting ALPN");
      return CURLE_SSL_CONNECT_ERROR;
    }
  }
#endif

  if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
    if(!result &&
       !cert_stuff(data, octx->ssl_ctx,
                   ssl_cert, ssl_cert_blob, ssl_cert_type,
                   ssl_config->key, ssl_config->key_blob,
                   ssl_config->key_type, ssl_config->key_passwd))
      result = CURLE_SSL_CERTPROBLEM;
    if(result)
      /* failf() is already done in cert_stuff() */
      return result;
  }

  ciphers = conn_config->cipher_list;
  if(!ciphers && (peer->transport != TRNSPRT_QUIC))
    ciphers = DEFAULT_CIPHER_SELECTION;
  if(ciphers) {
    if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) {
      failf(data, "failed setting cipher list: %s", ciphers);
      return CURLE_SSL_CIPHER;
    }
    infof(data, "Cipher selection: %s", ciphers);
  }

#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
  {
    const char *ciphers13 = conn_config->cipher_list13;
    if(ciphers13) {
      if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) {
        failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
        return CURLE_SSL_CIPHER;
      }
      infof(data, "TLS 1.3 cipher selection: %s", ciphers13);
    }
  }
#endif

#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
  /* OpenSSL 1.1.1 requires clients to opt-in for PHA */
  SSL_CTX_set_post_handshake_auth(octx->ssl_ctx, 1);
#endif

#ifdef HAVE_SSL_CTX_SET_EC_CURVES
  {
    const char *curves = conn_config->curves;
    if(curves) {
      if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, curves)) {
        failf(data, "failed setting curves list: '%s'", curves);
        return CURLE_SSL_CIPHER;
      }
    }
  }
#endif

#ifdef USE_OPENSSL_SRP
  if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) {
    char * const ssl_username = ssl_config->primary.username;
    char * const ssl_password = ssl_config->primary.password;
    infof(data, "Using TLS-SRP username: %s", ssl_username);

    if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) {
      failf(data, "Unable to set SRP username");
      return CURLE_BAD_FUNCTION_ARGUMENT;
    }
    if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) {
      failf(data, "failed setting SRP password");
      return CURLE_BAD_FUNCTION_ARGUMENT;
    }
    if(!conn_config->cipher_list) {
      infof(data, "Setting cipher list SRP");

      if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) {
        failf(data, "failed setting SRP cipher list");
        return CURLE_SSL_CIPHER;
      }
    }
  }
#endif

  /* OpenSSL 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. */
  SSL_CTX_set_verify(octx->ssl_ctx,
                     verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);

  /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
#ifdef HAVE_KEYLOG_CALLBACK
  if(Curl_tls_keylog_enabled()) {
    SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback);
  }
#endif

  if(cb_new_session) {
    /* Enable the session cache because it is a prerequisite for the
     * "new session" callback. Use the "external storage" mode to prevent
     * OpenSSL from creating an internal session cache.
     */
    SSL_CTX_set_session_cache_mode(octx->ssl_ctx,
                                   SSL_SESS_CACHE_CLIENT |
                                   SSL_SESS_CACHE_NO_INTERNAL);
    SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session);
  }

  /* give application a chance to interfere with SSL set up. */
  if(data->set.ssl.fsslctx) {
    /* When a user callback is installed to modify the SSL_CTX,
     * we need to do the full initialization before calling it.
     * See: #11800 */
    if(!octx->x509_store_setup) {
      result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx);
      if(result)
        return result;
      octx->x509_store_setup = TRUE;
    }
    Curl_set_in_callback(data, true);
    result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx,
                                      data->set.ssl.fsslctxp);
    Curl_set_in_callback(data, false);
    if(result) {
      failf(data, "error signaled by ssl ctx callback");
      return result;
    }
  }

  /* Let's make an SSL structure */
  if(octx->ssl)
    SSL_free(octx->ssl);
  octx->ssl = SSL_new(octx->ssl_ctx);
  if(!octx->ssl) {
    failf(data, "SSL: could not create a context (handle)");
    return CURLE_OUT_OF_MEMORY;
  }

  SSL_set_app_data(octx->ssl, ssl_user_data);

#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
  !defined(OPENSSL_NO_OCSP)
  if(conn_config->verifystatus)
    SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp);
#endif

#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \
    defined(ALLOW_RENEG)
  SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely);
#endif

  SSL_set_connect_state(octx->ssl);

  octx->server_cert = 0x0;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
  if(peer->sni) {
    if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) {
      failf(data, "Failed set SNI");
      return CURLE_SSL_CONNECT_ERROR;
    }
  }

#ifdef USE_ECH
  if(ECH_ENABLED(data)) {
    unsigned char *ech_config = NULL;
    size_t ech_config_len = 0;
    char *outername = data->set.str[STRING_ECH_PUBLIC];
    int trying_ech_now = 0;

    if(data->set.tls_ech & CURLECH_GREASE) {
      infof(data, "ECH: will GREASE ClientHello");
# ifdef OPENSSL_IS_BORINGSSL
      SSL_set_enable_ech_grease(octx->ssl, 1);
# else
      SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE);
# endif
    }
    else if(data->set.tls_ech & CURLECH_CLA_CFG) {
# ifdef OPENSSL_IS_BORINGSSL
      /* have to do base64 decode here for boring */
      const char *b64 = data->set.str[STRING_ECH_CONFIG];

      if(!b64) {
        infof(data, "ECH: ECHConfig from command line empty");
        return CURLE_SSL_CONNECT_ERROR;
      }
      ech_config_len = 2 * strlen(b64);
      result = Curl_base64_decode(b64, &ech_config, &ech_config_len);
      if(result || !ech_config) {
        infof(data, "ECH: cannot base64 decode ECHConfig from command line");
        if(data->set.tls_ech & CURLECH_HARD)
          return result;
      }
      if(SSL_set1_ech_config_list(octx->ssl, ech_config,
                                  ech_config_len) != 1) {
        infof(data, "ECH: SSL_ECH_set1_echconfig failed");
        if(data->set.tls_ech & CURLECH_HARD) {
          free(ech_config);
          return CURLE_SSL_CONNECT_ERROR;
        }
      }
      free(ech_config);
      trying_ech_now = 1;
# else
      ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG];
      if(!ech_config) {
        infof(data, "ECH: ECHConfig from command line empty");
        return CURLE_SSL_CONNECT_ERROR;
      }
      ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]);
      if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) {
        infof(data, "ECH: SSL_ECH_set1_echconfig failed");
        if(data->set.tls_ech & CURLECH_HARD)
          return CURLE_SSL_CONNECT_ERROR;
      }
      else
        trying_ech_now = 1;
# endif
      infof(data, "ECH: ECHConfig from command line");
    }
    else {
      struct Curl_dns_entry *dns = NULL;

      if(peer->hostname)
        dns = Curl_fetch_addr(data, peer->hostname, peer->port);
      if(!dns) {
        infof(data, "ECH: requested but no DNS info available");
        if(data->set.tls_ech & CURLECH_HARD)
          return CURLE_SSL_CONNECT_ERROR;
      }
      else {
        struct Curl_https_rrinfo *rinfo = NULL;

        rinfo = dns->hinfo;
        if(rinfo && rinfo->echconfiglist) {
          unsigned char *ecl = rinfo->echconfiglist;
          size_t elen = rinfo->echconfiglist_len;

          infof(data, "ECH: ECHConfig from DoH HTTPS RR");
# ifndef OPENSSL_IS_BORINGSSL
          if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) {
            infof(data, "ECH: SSL_ECH_set1_echconfig failed");
            if(data->set.tls_ech & CURLECH_HARD)
              return CURLE_SSL_CONNECT_ERROR;
          }
# else
          if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
            infof(data, "ECH: SSL_set1_ech_config_list failed (boring)");
            if(data->set.tls_ech & CURLECH_HARD)
              return CURLE_SSL_CONNECT_ERROR;
          }
# endif
          else {
            trying_ech_now = 1;
            infof(data, "ECH: imported ECHConfigList of length %zu", elen);
          }
        }
        else {
          infof(data, "ECH: requested but no ECHConfig available");
          if(data->set.tls_ech & CURLECH_HARD)
            return CURLE_SSL_CONNECT_ERROR;
        }
        Curl_resolv_unlock(data, dns);
      }
    }
# ifdef OPENSSL_IS_BORINGSSL
    if(trying_ech_now && outername) {
      infof(data, "ECH: setting public_name not supported with BoringSSL");
      return CURLE_SSL_CONNECT_ERROR;
    }
# else
    if(trying_ech_now && outername) {
      infof(data, "ECH: inner: '%s', outer: '%s'",
            peer->hostname ? peer->hostname : "NULL", outername);
      result = SSL_ech_set_server_names(octx->ssl,
                                        peer->hostname, outername,
                                        0 /* do send outer */);
      if(result != 1) {
        infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result);
        return CURLE_SSL_CONNECT_ERROR;
      }
    }
# endif  /* not BORING */
    if(trying_ech_now
       && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) {
      infof(data, "ECH: cannot force TLSv1.3 [ERROR]");
      return CURLE_SSL_CONNECT_ERROR;
    }
  }
#endif  /* USE_ECH */

#endif

  octx->reused_session = FALSE;
  if(ssl_config->primary.cache_session && transport == TRNSPRT_TCP) {
    Curl_ssl_sessionid_lock(data);
    if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, NULL)) {
      /* we got a session id, use it! */
      if(!SSL_set_session(octx->ssl, ssl_sessionid)) {
        Curl_ssl_sessionid_unlock(data);
        failf(data, "SSL: SSL_set_session failed: %s",
              ossl_strerror(ERR_get_error(), error_buffer,
                            sizeof(error_buffer)));
        return CURLE_SSL_CONNECT_ERROR;
      }
      /* Informational message */
      infof(data, "SSL reusing session ID");
      octx->reused_session = TRUE;
    }
    Curl_ssl_sessionid_unlock(data);
  }

  return CURLE_OK;
}