schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
                       int sockindex)
{
  ssize_t written = -1;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  SecBuffer outbuf;
  SecBufferDesc outbuf_desc;
  SecBuffer inbuf;
  SecBufferDesc inbuf_desc;
#ifdef HAS_ALPN
  unsigned char alpn_buffer[128];
#endif
  SCHANNEL_CRED schannel_cred;
  PCCERT_CONTEXT client_certs[1] = { NULL };
  SECURITY_STATUS sspi_status = SEC_E_OK;
  struct Curl_schannel_cred *old_cred = NULL;
  struct in_addr addr;
#ifdef ENABLE_IPV6
  struct in6_addr addr6;
#endif
  TCHAR *host_name;
  CURLcode result;
#ifndef CURL_DISABLE_PROXY
  char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
    conn->host.name;
#else
  char * const hostname = conn->host.name;
#endif

  DEBUGF(infof(data,
               "schannel: SSL/TLS connection with %s port %hu (step 1/3)\n",
               hostname, conn->remote_port));

  if(curlx_verify_windows_version(5, 1, PLATFORM_WINNT,
                                  VERSION_LESS_THAN_EQUAL)) {
    /* Schannel in Windows XP (OS version 5.1) uses legacy handshakes and
       algorithms that may not be supported by all servers. */
    infof(data, "schannel: Windows version is old and may not be able to "
          "connect to some servers due to lack of SNI, algorithms, etc.\n");
  }

#ifdef HAS_ALPN
  /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above.
     Also it doesn't seem to be supported for Wine, see curl bug #983. */
  BACKEND->use_alpn = conn->bits.tls_enable_alpn &&
    !GetProcAddress(GetModuleHandle(TEXT("ntdll")),
                    "wine_get_version") &&
    curlx_verify_windows_version(6, 3, PLATFORM_WINNT,
                                 VERSION_GREATER_THAN_EQUAL);
#else
  BACKEND->use_alpn = false;
#endif

#ifdef _WIN32_WCE
#ifdef HAS_MANUAL_VERIFY_API
  /* certificate validation on CE doesn't seem to work right; we'll
   * do it following a more manual process. */
  BACKEND->use_manual_cred_validation = true;
#else
#error "compiler too old to support requisite manual cert verify for Win CE"
#endif
#else
#ifdef HAS_MANUAL_VERIFY_API
  if(SSL_CONN_CONFIG(CAfile)) {
    if(curlx_verify_windows_version(6, 1, PLATFORM_WINNT,
                                    VERSION_GREATER_THAN_EQUAL)) {
      BACKEND->use_manual_cred_validation = true;
    }
    else {
      failf(data, "schannel: this version of Windows is too old to support "
            "certificate verification via CA bundle file.");
      return CURLE_SSL_CACERT_BADFILE;
    }
  }
  else
    BACKEND->use_manual_cred_validation = false;
#else
  if(SSL_CONN_CONFIG(CAfile)) {
    failf(data, "schannel: CA cert support not built in");
    return CURLE_NOT_BUILT_IN;
  }
#endif
#endif

  BACKEND->cred = NULL;

  /* check for an existing re-usable credential handle */
  if(SSL_SET_OPTION(primary.sessionid)) {
    Curl_ssl_sessionid_lock(data);
    if(!Curl_ssl_getsessionid(data, conn,
                              SSL_IS_PROXY() ? TRUE : FALSE,
                              (void **)&old_cred, NULL, sockindex)) {
      BACKEND->cred = old_cred;
      DEBUGF(infof(data, "schannel: re-using existing credential handle\n"));

      /* increment the reference counter of the credential/session handle */
      BACKEND->cred->refcount++;
      DEBUGF(infof(data,
                   "schannel: incremented credential handle refcount = %d\n",
                   BACKEND->cred->refcount));
    }
    Curl_ssl_sessionid_unlock(data);
  }

  if(!BACKEND->cred) {
    /* setup Schannel API options */
    memset(&schannel_cred, 0, sizeof(schannel_cred));
    schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;

    if(conn->ssl_config.verifypeer) {
#ifdef HAS_MANUAL_VERIFY_API
      if(BACKEND->use_manual_cred_validation)
        schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION;
      else
#endif
        schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION;

      if(SSL_SET_OPTION(no_revoke)) {
        schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
          SCH_CRED_IGNORE_REVOCATION_OFFLINE;

        DEBUGF(infof(data, "schannel: disabled server certificate revocation "
                     "checks\n"));
      }
      else if(SSL_SET_OPTION(revoke_best_effort)) {
        schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
          SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN;

        DEBUGF(infof(data, "schannel: ignore revocation offline errors"));
      }
      else {
        schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN;

        DEBUGF(infof(data,
                     "schannel: checking server certificate revocation\n"));
      }
    }
    else {
      schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
        SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
        SCH_CRED_IGNORE_REVOCATION_OFFLINE;
      DEBUGF(infof(data,
                   "schannel: disabled server cert revocation checks\n"));
    }

    if(!conn->ssl_config.verifyhost) {
      schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
      DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from "
                   "comparing the supplied target name with the subject "
                   "names in server certificates.\n"));
    }

    switch(conn->ssl_config.version) {
    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:
    {
      result = set_ssl_version_min_max(&schannel_cred, data, conn);
      if(result != CURLE_OK)
        return result;
      break;
    }
    case CURL_SSLVERSION_SSLv3:
      schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
      break;
    case CURL_SSLVERSION_SSLv2:
      schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
      break;
    default:
      failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
      return CURLE_SSL_CONNECT_ERROR;
    }

    if(SSL_CONN_CONFIG(cipher_list)) {
      result = set_ssl_ciphers(&schannel_cred, SSL_CONN_CONFIG(cipher_list));
      if(CURLE_OK != result) {
        failf(data, "Unable to set ciphers to passed via SSL_CONN_CONFIG");
        return result;
      }
    }


#ifdef HAS_CLIENT_CERT_PATH
    /* client certificate */
    if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) {
      DWORD cert_store_name = 0;
      TCHAR *cert_store_path = NULL;
      TCHAR *cert_thumbprint_str = NULL;
      CRYPT_HASH_BLOB cert_thumbprint;
      BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN];
      HCERTSTORE cert_store = NULL;
      FILE *fInCert = NULL;
      void *certdata = NULL;
      size_t certsize = 0;
      bool blob = data->set.ssl.primary.cert_blob != NULL;
      TCHAR *cert_path = NULL;
      if(blob) {
        certdata = data->set.ssl.primary.cert_blob->data;
        certsize = data->set.ssl.primary.cert_blob->len;
      }
      else {
        cert_path = curlx_convert_UTF8_to_tchar(
          data->set.ssl.primary.clientcert);
        if(!cert_path)
          return CURLE_OUT_OF_MEMORY;

        result = get_cert_location(cert_path, &cert_store_name,
          &cert_store_path, &cert_thumbprint_str);

        if(result && (data->set.ssl.primary.clientcert[0]!='\0'))
          fInCert = fopen(data->set.ssl.primary.clientcert, "rb");

        if(result && !fInCert) {
          failf(data, "schannel: Failed to get certificate location"
                " or file for %s",
                data->set.ssl.primary.clientcert);
          curlx_unicodefree(cert_path);
          return result;
        }
      }

      if((fInCert || blob) && (data->set.ssl.cert_type) &&
          (!strcasecompare(data->set.ssl.cert_type, "P12"))) {
        failf(data, "schannel: certificate format compatibility error "
                " for %s",
                blob ? "(memory blob)" : data->set.ssl.primary.clientcert);
        curlx_unicodefree(cert_path);
        return CURLE_SSL_CERTPROBLEM;
      }

      if(fInCert || blob) {
        /* Reading a .P12 or .pfx file, like the example at bottom of
             https://social.msdn.microsoft.com/Forums/windowsdesktop/
                            en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5
        */
        CRYPT_DATA_BLOB datablob;
        WCHAR* pszPassword;
        size_t pwd_len = 0;
        int str_w_len = 0;
        const char *cert_showfilename_error = blob ?
          "(memory blob)" : data->set.ssl.primary.clientcert;
        curlx_unicodefree(cert_path);
        if(fInCert) {
          long cert_tell = 0;
          bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0;
          if(continue_reading)
            cert_tell = ftell(fInCert);
          if(cert_tell < 0)
            continue_reading = FALSE;
          else
            certsize = (size_t)cert_tell;
          if(continue_reading)
            continue_reading = fseek(fInCert, 0, SEEK_SET) == 0;
          if(continue_reading)
            certdata = malloc(certsize + 1);
          if((!certdata) ||
             ((int) fread(certdata, certsize, 1, fInCert) != 1))
            continue_reading = FALSE;
          fclose(fInCert);
          if(!continue_reading) {
            failf(data, "schannel: Failed to read cert file %s",
                data->set.ssl.primary.clientcert);
            free(certdata);
            return CURLE_SSL_CERTPROBLEM;
          }
        }

        /* Convert key-pair data to the in-memory certificate store */
        datablob.pbData = (BYTE*)certdata;
        datablob.cbData = (DWORD)certsize;

        if(data->set.ssl.key_passwd != NULL)
          pwd_len = strlen(data->set.ssl.key_passwd);
        pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1));
        if(pszPassword) {
          if(pwd_len > 0)
            str_w_len = MultiByteToWideChar(CP_UTF8,
               MB_ERR_INVALID_CHARS,
               data->set.ssl.key_passwd, (int)pwd_len,
               pszPassword, (int)(pwd_len + 1));

          if((str_w_len >= 0) && (str_w_len <= (int)pwd_len))
            pszPassword[str_w_len] = 0;
          else
            pszPassword[0] = 0;

          cert_store = PFXImportCertStore(&datablob, pszPassword, 0);
          free(pszPassword);
        }
        if(!blob)
          free(certdata);
        if(cert_store == NULL) {
          DWORD errorcode = GetLastError();
          if(errorcode == ERROR_INVALID_PASSWORD)
            failf(data, "schannel: Failed to import cert file %s, "
                  "password is bad",
                  cert_showfilename_error);
          else
            failf(data, "schannel: Failed to import cert file %s, "
                  "last error is 0x%x",
                  cert_showfilename_error, errorcode);
          return CURLE_SSL_CERTPROBLEM;
        }

        client_certs[0] = CertFindCertificateInStore(
          cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
          CERT_FIND_ANY, NULL, NULL);

        if(client_certs[0] == NULL) {
          failf(data, "schannel: Failed to get certificate from file %s"
                ", last error is 0x%x",
                cert_showfilename_error, GetLastError());
          CertCloseStore(cert_store, 0);
          return CURLE_SSL_CERTPROBLEM;
        }

        schannel_cred.cCreds = 1;
        schannel_cred.paCred = client_certs;
      }
      else {
        cert_store =
          CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0,
                        (HCRYPTPROV)NULL,
                        CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name,
                        cert_store_path);
        if(!cert_store) {
          failf(data, "schannel: Failed to open cert store %x %s, "
                "last error is 0x%x",
                cert_store_name, cert_store_path, GetLastError());
          free(cert_store_path);
          curlx_unicodefree(cert_path);
          return CURLE_SSL_CERTPROBLEM;
        }
        free(cert_store_path);

        cert_thumbprint.pbData = cert_thumbprint_data;
        cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN;

        if(!CryptStringToBinary(cert_thumbprint_str,
                                CERT_THUMBPRINT_STR_LEN,
                                CRYPT_STRING_HEX,
                                cert_thumbprint_data,
                                &cert_thumbprint.cbData,
                                NULL, NULL)) {
          curlx_unicodefree(cert_path);
          CertCloseStore(cert_store, 0);
          return CURLE_SSL_CERTPROBLEM;
        }

        client_certs[0] = CertFindCertificateInStore(
          cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
          CERT_FIND_HASH, &cert_thumbprint, NULL);

        curlx_unicodefree(cert_path);

        if(client_certs[0]) {
          schannel_cred.cCreds = 1;
          schannel_cred.paCred = client_certs;
        }
        else {
          /* CRYPT_E_NOT_FOUND / E_INVALIDARG */
          CertCloseStore(cert_store, 0);
          return CURLE_SSL_CERTPROBLEM;
        }
      }
      CertCloseStore(cert_store, 0);
    }
#else
    if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) {
      failf(data, "schannel: client cert support not built in");
      return CURLE_NOT_BUILT_IN;
    }
#endif

    /* allocate memory for the re-usable credential handle */
    BACKEND->cred = (struct Curl_schannel_cred *)
      calloc(1, sizeof(struct Curl_schannel_cred));
    if(!BACKEND->cred) {
      failf(data, "schannel: unable to allocate memory");

      if(client_certs[0])
        CertFreeCertificateContext(client_certs[0]);

      return CURLE_OUT_OF_MEMORY;
    }
    BACKEND->cred->refcount = 1;

    /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx
     */
    sspi_status =
      s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
                                         SECPKG_CRED_OUTBOUND, NULL,
                                         &schannel_cred, NULL, NULL,
                                         &BACKEND->cred->cred_handle,
                                         &BACKEND->cred->time_stamp);

    if(client_certs[0])
      CertFreeCertificateContext(client_certs[0]);

    if(sspi_status != SEC_E_OK) {
      char buffer[STRERROR_LEN];
      failf(data, "schannel: AcquireCredentialsHandle failed: %s",
            Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
      Curl_safefree(BACKEND->cred);
      switch(sspi_status) {
      case SEC_E_INSUFFICIENT_MEMORY:
        return CURLE_OUT_OF_MEMORY;
      case SEC_E_NO_CREDENTIALS:
      case SEC_E_SECPKG_NOT_FOUND:
      case SEC_E_NOT_OWNER:
      case SEC_E_UNKNOWN_CREDENTIALS:
      case SEC_E_INTERNAL_ERROR:
      default:
        return CURLE_SSL_CONNECT_ERROR;
      }
    }
  }

  /* Warn if SNI is disabled due to use of an IP address */
  if(Curl_inet_pton(AF_INET, hostname, &addr)
#ifdef ENABLE_IPV6
     || Curl_inet_pton(AF_INET6, hostname, &addr6)
#endif
    ) {
    infof(data, "schannel: using IP address, SNI is not supported by OS.\n");
  }

#ifdef HAS_ALPN
  if(BACKEND->use_alpn) {
    int cur = 0;
    int list_start_index = 0;
    unsigned int *extension_len = NULL;
    unsigned short* list_len = NULL;

    /* The first four bytes will be an unsigned int indicating number
       of bytes of data in the rest of the buffer. */
    extension_len = (unsigned int *)(&alpn_buffer[cur]);
    cur += sizeof(unsigned int);

    /* The next four bytes are an indicator that this buffer will contain
       ALPN data, as opposed to NPN, for example. */
    *(unsigned int *)&alpn_buffer[cur] =
      SecApplicationProtocolNegotiationExt_ALPN;
    cur += sizeof(unsigned int);

    /* The next two bytes will be an unsigned short indicating the number
       of bytes used to list the preferred protocols. */
    list_len = (unsigned short*)(&alpn_buffer[cur]);
    cur += sizeof(unsigned short);

    list_start_index = cur;

#ifdef USE_NGHTTP2
    if(data->state.httpversion >= CURL_HTTP_VERSION_2) {
      memcpy(&alpn_buffer[cur], NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN);
      cur += NGHTTP2_PROTO_ALPN_LEN;
      infof(data, "schannel: ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID);
    }
#endif

    alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH;
    memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
    cur += ALPN_HTTP_1_1_LENGTH;
    infof(data, "schannel: ALPN, offering %s\n", ALPN_HTTP_1_1);

    *list_len = curlx_uitous(cur - list_start_index);
    *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short);

    InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur);
    InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
  }
  else {
    InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
    InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
  }
#else /* HAS_ALPN */
  InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
  InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
#endif

  /* setup output buffer */
  InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
  InitSecBufferDesc(&outbuf_desc, &outbuf, 1);

  /* setup request flags */
  BACKEND->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
    ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
    ISC_REQ_STREAM;

  /* allocate memory for the security context handle */
  BACKEND->ctxt = (struct Curl_schannel_ctxt *)
    calloc(1, sizeof(struct Curl_schannel_ctxt));
  if(!BACKEND->ctxt) {
    failf(data, "schannel: unable to allocate memory");
    return CURLE_OUT_OF_MEMORY;
  }

  host_name = curlx_convert_UTF8_to_tchar(hostname);
  if(!host_name)
    return CURLE_OUT_OF_MEMORY;

  /* Schannel InitializeSecurityContext:
     https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx

     At the moment we don't pass inbuf unless we're using ALPN since we only
     use it for that, and Wine (for which we currently disable ALPN) is giving
     us problems with inbuf regardless. https://github.com/curl/curl/issues/983
  */
  sspi_status = s_pSecFn->InitializeSecurityContext(
    &BACKEND->cred->cred_handle, NULL, host_name, BACKEND->req_flags, 0, 0,
    (BACKEND->use_alpn ? &inbuf_desc : NULL),
    0, &BACKEND->ctxt->ctxt_handle,
    &outbuf_desc, &BACKEND->ret_flags, &BACKEND->ctxt->time_stamp);

  curlx_unicodefree(host_name);

  if(sspi_status != SEC_I_CONTINUE_NEEDED) {
    char buffer[STRERROR_LEN];
    Curl_safefree(BACKEND->ctxt);
    switch(sspi_status) {
    case SEC_E_INSUFFICIENT_MEMORY:
      failf(data, "schannel: initial InitializeSecurityContext failed: %s",
            Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
      return CURLE_OUT_OF_MEMORY;
    case SEC_E_WRONG_PRINCIPAL:
      failf(data, "schannel: SNI or certificate check failed: %s",
            Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
      return CURLE_PEER_FAILED_VERIFICATION;
      /*
        case SEC_E_INVALID_HANDLE:
        case SEC_E_INVALID_TOKEN:
        case SEC_E_LOGON_DENIED:
        case SEC_E_TARGET_UNKNOWN:
        case SEC_E_NO_AUTHENTICATING_AUTHORITY:
        case SEC_E_INTERNAL_ERROR:
        case SEC_E_NO_CREDENTIALS:
        case SEC_E_UNSUPPORTED_FUNCTION:
        case SEC_E_APPLICATION_PROTOCOL_MISMATCH:
      */
    default:
      failf(data, "schannel: initial InitializeSecurityContext failed: %s",
            Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
      return CURLE_SSL_CONNECT_ERROR;
    }
  }

  DEBUGF(infof(data, "schannel: sending initial handshake data: "
               "sending %lu bytes...\n", outbuf.cbBuffer));

  /* send initial handshake data which is now stored in output buffer */
  result = Curl_write_plain(data, conn->sock[sockindex], outbuf.pvBuffer,
                            outbuf.cbBuffer, &written);
  s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
  if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
    failf(data, "schannel: failed to send initial handshake data: "
          "sent %zd of %lu bytes", written, outbuf.cbBuffer);
    return CURLE_SSL_CONNECT_ERROR;
  }

  DEBUGF(infof(data, "schannel: sent initial handshake data: "
               "sent %zd bytes\n", written));

  BACKEND->recv_unrecoverable_err = CURLE_OK;
  BACKEND->recv_sspi_close_notify = false;
  BACKEND->recv_connection_closed = false;
  BACKEND->encdata_is_incomplete = false;

  /* continue to second handshake step */
  connssl->connecting_state = ssl_connect_2;

  return CURLE_OK;
}