Skip to content
curl_schannel.c 45.3 KiB
Newer Older
  if(ret <= 0 && ( /* special check for Windows 2000 Professional */
      sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK &&
        connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) {
    infof(data, "schannel: server closed the connection\n");
    *err = CURLE_OK;
    return 0;
  }

  /* check if something went wrong and we need to return an error */
  if(ret < 0 && sspi_status != SEC_E_OK) {
    infof(data, "schannel: failed to read data from server: %s\n",
          Curl_sspi_strerror(conn, sspi_status));
    return -1;
  }

  return ret;
}

CURLcode
Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex,
  return schannel_connect_common(conn, sockindex, TRUE, done);
}

CURLcode
Curl_schannel_connect(struct connectdata *conn, int sockindex)
{
  CURLcode retcode;
  bool done = FALSE;

  retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
  if(retcode)
    return retcode;

  DEBUGASSERT(done);

  return CURLE_OK;
}

bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex)
{
  const struct ssl_connect_data *connssl = &conn->ssl[sockindex];

  if(connssl->use) /* SSL/TLS is in use */
    return (connssl->encdata_offset > 0 ||
            connssl->decdata_offset > 0 ) ? TRUE : FALSE;
  else
    return FALSE;
}

void Curl_schannel_close(struct connectdata *conn, int sockindex)
{
  if(conn->ssl[sockindex].use)
    /* if the SSL/TLS channel hasn't been shut down yet, do that now. */
    Curl_ssl_shutdown(conn, sockindex);
}

int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
{
  /* See http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
   * Shutting Down an Schannel Connection
   */
  struct SessionHandle *data = conn->data;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];

  infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
  if(connssl->cred && connssl->ctxt) {
    SecBufferDesc BuffDesc;
    SecBuffer Buffer;
    SECURITY_STATUS sspi_status;
    SecBuffer outbuf;
    SecBufferDesc outbuf_desc;
    CURLcode code;
    TCHAR *host_name;
    DWORD dwshut = SCHANNEL_SHUTDOWN;

    InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
    InitSecBufferDesc(&BuffDesc, &Buffer, 1);

    sspi_status = s_pSecFn->ApplyControlToken(&connssl->ctxt->ctxt_handle,
                                              &BuffDesc);

    if(sspi_status != SEC_E_OK)
      failf(data, "schannel: ApplyControlToken failure: %s",
            Curl_sspi_strerror(conn, sspi_status));

Yang Tse's avatar
Yang Tse committed
    host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
    if(!host_name)
      return CURLE_OUT_OF_MEMORY;

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

    sspi_status = s_pSecFn->InitializeSecurityContext(
         &connssl->cred->cred_handle,
         &connssl->ctxt->ctxt_handle,
         host_name,
         connssl->req_flags,
         0,
         0,
         NULL,
         0,
         &connssl->ctxt->ctxt_handle,
         &outbuf_desc,
         &connssl->ret_flags,
         &connssl->ctxt->time_stamp);

Yang Tse's avatar
Yang Tse committed
    Curl_unicodefree(host_name);

    if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
      /* send close message which is in output buffer */
      ssize_t written;
      code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
                              outbuf.cbBuffer, &written);

      s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
      if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
        infof(data, "schannel: failed to send close msg: %s"
              " (bytes written: %zd)\n", curl_easy_strerror(code), written);
      }
    }
    /* free SSPI Schannel API security context handle */
    if(connssl->ctxt) {
      infof(data, "schannel: clear security context handle\n");
      s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle);
      Curl_safefree(connssl->ctxt);
    /* free SSPI Schannel API credential handle */
    if(connssl->cred) {
      /* decrement the reference counter of the credential/session handle */
      if(connssl->cred->refcount > 0) {
        connssl->cred->refcount--;
        infof(data, "schannel: decremented credential handle refcount = %d\n",
              connssl->cred->refcount);
      }
      /* if the handle was not cached and the refcount is zero */
      if(!connssl->cred->cached && connssl->cred->refcount == 0) {
        infof(data, "schannel: clear credential handle\n");
        s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle);
        Curl_safefree(connssl->cred);
  }

  /* free internal buffer for received encrypted data */
  if(connssl->encdata_buffer != NULL) {
    Curl_safefree(connssl->encdata_buffer);
    connssl->encdata_length = 0;
    connssl->encdata_offset = 0;
  }

  /* free internal buffer for received decrypted data */
  if(connssl->decdata_buffer != NULL) {
    Curl_safefree(connssl->decdata_buffer);
    connssl->decdata_length = 0;
    connssl->decdata_offset = 0;
  }

  return CURLE_OK;
void Curl_schannel_session_free(void *ptr)
{
  struct curl_schannel_cred *cred = ptr;
  if(cred && cred->cached && cred->refcount == 0) {
    s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
  return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
}

  Curl_sspi_global_cleanup();
}

size_t Curl_schannel_version(char *buffer, size_t size)
{
  size = snprintf(buffer, size, "WinSSL");
#ifdef _WIN32_WCE
static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
{
  SECURITY_STATUS status;
  struct SessionHandle *data = conn->data;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  CURLcode result = CURLE_OK;
  CERT_CONTEXT *pCertContextServer = NULL;
  const CERT_CHAIN_CONTEXT *pChainContext = NULL;

  status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle,
                                            SECPKG_ATTR_REMOTE_CERT_CONTEXT,
                                            &pCertContextServer);

  if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
    failf(data, "schannel: Failed to read remote certificate context: %s",
          Curl_sspi_strerror(conn, status));
    result = CURLE_PEER_FAILED_VERIFICATION;
  }

  if(result == CURLE_OK) {
    CERT_CHAIN_PARA ChainPara;
    memset(&ChainPara, 0, sizeof(ChainPara));
    ChainPara.cbSize = sizeof(ChainPara);

    if(!CertGetCertificateChain(NULL,
                                pCertContextServer,
                                NULL,
                                pCertContextServer->hCertStore,
                                &ChainPara,
                                0,
                                NULL,
                                &pChainContext)) {
      failf(data, "schannel: CertGetCertificateChain failed: %s",
            Curl_sspi_strerror(conn, GetLastError()));
      pChainContext = NULL;
      result = CURLE_PEER_FAILED_VERIFICATION;
    }

    if(result == CURLE_OK) {
      CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
Yang Tse's avatar
Yang Tse committed
      DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED|
                                 CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
      dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
      if(dwTrustErrorMask) {
        if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
          failf(data, "schannel: CertGetCertificateChain trust error"
                      " CERT_TRUST_IS_PARTIAL_CHAIN");
        if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
          failf(data, "schannel: CertGetCertificateChain trust error"
                      " CERT_TRUST_IS_UNTRUSTED_ROOT");
        if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
          failf(data, "schannel: CertGetCertificateChain trust error"
                      " CERT_TRUST_IS_NOT_TIME_VALID");
        failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
              dwTrustErrorMask);
        result = CURLE_PEER_FAILED_VERIFICATION;
      }
    }
  }

  if(result == CURLE_OK) {
    if(data->set.ssl.verifyhost) {
Yang Tse's avatar
Yang Tse committed
      TCHAR cert_hostname_buff[128];
      xcharp_u hostname;
      xcharp_u cert_hostname;
Yang Tse's avatar
Yang Tse committed
      cert_hostname.const_tchar_ptr = cert_hostname_buff;
      hostname.tchar_ptr = Curl_convert_UTF8_to_tchar(conn->host.name);

      len = CertGetNameString(pCertContextServer,
                              CERT_NAME_DNS_TYPE,
                              0,
                              NULL,
                              cert_hostname.tchar_ptr,
                              128);
      if(len > 0 && *cert_hostname.tchar_ptr == '*') {
        /* this is a wildcard cert.  try matching the last len - 1 chars */
        int hostname_len = strlen(conn->host.name);
Yang Tse's avatar
Yang Tse committed
        cert_hostname.tchar_ptr++;
        if(_tcsicmp(cert_hostname.const_tchar_ptr,
                    hostname.const_tchar_ptr + hostname_len - len + 2) != 0)
          result = CURLE_PEER_FAILED_VERIFICATION;
      }
Yang Tse's avatar
Yang Tse committed
      else if(len == 0 || _tcsicmp(hostname.const_tchar_ptr,
                                   cert_hostname.const_tchar_ptr) != 0) {
        result = CURLE_PEER_FAILED_VERIFICATION;
      }
      if(result == CURLE_PEER_FAILED_VERIFICATION) {
Yang Tse's avatar
Yang Tse committed
        char *_cert_hostname;
        _cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname.tchar_ptr);
        failf(data, "schannel: CertGetNameString() certificate hostname "
              "(%s) did not match connection (%s)",
              _cert_hostname, conn->host.name);
        Curl_unicodefree(_cert_hostname);
Yang Tse's avatar
Yang Tse committed
      Curl_unicodefree(hostname.tchar_ptr);
    }
  }

  if(pChainContext)
    CertFreeCertificateChain(pChainContext);

  if(pCertContextServer)
    CertFreeCertificateContext(pCertContextServer);

  return result;
}
#endif /* _WIN32_WCE */