none
SSL-Socketverbindung mit POP3S- und IMAP4S-Mailserver (InitializeSecurityContext -> ExtraData) RRS feed

  • Frage

  • SSL-Socketverbindung mit POP3S- und IMAP4S-Mailserver (InitializeSecurityContext liefert ExtraData)

    Problem:
    Timeout bei recv nach SSL-Handshake nach letztem Datenempfang vom Mailserver.
    Das Problem tritt nur sporadisch auf und auch nur beim Google-Mailserver. :-(

    Hintergrund:
    Bei Aufbau der SSL-Verbindung zu einem Mailserver (IMAP-Port: 993 oder POP3-Port: 995) wird laut
    http://www.ietf.org/rfc/rfc2246.txt ein Handshake durchgeführt.

    Im Platform-SDK ist ein Bsp. enthalten, welches ich leicht umschreiben konnte, um die SSL-Verbindung mit einem Mailserver zu testen.

    [PSDK]\SSL\WebClient

    Das Programm läuft bis zum Handshake:

     if(PerformClientHandshake(Socket,
        &hClientCreds,
        pszServerName,
        &hContext,
        &ExtraData))
     {
     printf("Error performing handshake\n");
     goto cleanup;
     }
     fContextInitialized = TRUE;
    
    

    Auch die Funktion ClientHandShakeLoop wird fehlerfrei aufgerufen.

    In ClientHandShakeLoop jedoch wird die while-Schleife 2 mal durchlaufen und dann verlassen.

     while(scRet == SEC_I_CONTINUE_NEEDED ||
      scRet == SEC_E_INCOMPLETE_MESSAGE ||
      scRet == SEC_I_INCOMPLETE_CREDENTIALS) 
     {
     //
     // Read data from server.
     //
     if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE)
     {
      if(fDoRead)
      {
      cbData = recv(Socket, 
        (PTCHAR)(IoBuffer + cbIoBuffer), 
        IO_BUFFER_SIZE - cbIoBuffer, 
        0);
    
    

    Dabei wird der Type ExtraData im 2. Buffer vorgefunden. Hier der Austritt aus dieser Funktion:

    if(scRet == SEC_E_OK)
     {
      //
      // If the "extra" buffer contains data, this is encrypted application
      // protocol layer stuff. It needs to be saved. The application layer
      // will later decrypt it with DecryptMessage.
      //
      printf("Handshake was successful\n");
    
      if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
      pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED, 
           InBuffers[1].cbBuffer);
      if(pExtraData->pvBuffer == NULL)
      {
       printf("**** Out of memory (2)\n");
       return SEC_E_INTERNAL_ERROR;
      }
    
      MoveMemory(pExtraData->pvBuffer,
        IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
        InBuffers[1].cbBuffer);
    
    

    Im Beispiel-Code würde nun HttpGetFile aufgerufen werden, um eine GET-Anforderung an einen Webserver abzusenden.

    Ich möchte jedoch nur die Begrüßung vom Mailserver ausgeben und dann wieder disconnecten.

    Genau das funktioniert nicht mehr, weil der Mailserver wahrscheinlich noch auf Daten wartet.

    Ist im letzten vom Mailserver gesendeten Paket in Funktion ClientHandshakeLoop InBuffer[1].BufferType != SECBUFFER_EXTRA, dann sendet der Server seine nächsten Daten (Begrüßung) verschlüsselt und alles
    ist gut.

    Ist im letzten gesendeten Paket, wie oben beschrieben, InBuffer[1].BufferType == SECBUFFER_EXTRA, dann scheint der Server noch zusätzliche Daten zu erwarten.
    Er sendet nämlich keine verschlüsselten Daten mehr, sondern wartet wahrscheinlich wahrscheinlich mit recv auf zusätzliche Daten vom Client.
    Jetzt gehe ich aber (nach Handshake) wieder auf recv, weil ich vom Mailserver die verschlüsselte Begrüßung erwarte und laufe auf ein timeout (Der Server liefert nichts, da er noch wartet).

    Sollte ich irgend etwas mit ExtraData anfangen?
    Wieso verhalten sich die Server so unterschiedlich (manche senden die Begrüßung sofort, manche nicht)?

     

    In folgendem Code-Abschnitt sind manchmal Extra-Daten vorhanden (dann erwartet der Server noch irgend etwas) und manchmal nicht (dann liefert der Server seine verschlüsselte Begrüßung sofort).

      // If the "extra" buffer contains data, this is encrypted application
      // protocol layer stuff. It needs to be saved. The application layer
      // will later decrypt it with DecryptMessage.
      if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
    				ASSERT(pExtraData);
      pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED, InBuffers[1].cbBuffer);
      if (!pExtraData->pvBuffer)
      {
       iSocketError = GetLastError();
       OnSecurityError(lStatus,
    						_T("Out of memory."));
       break; // ERROR_OUTOFMEMORY;
      }
    
    
    				ASSERT(dwIoBuffer > InBuffers[1].cbBuffer);
      MoveMemory(pExtraData->pvBuffer,
        IoBuffer + (dwIoBuffer - InBuffers[1].cbBuffer),
        InBuffers[1].cbBuffer);
    
      pExtraData->cbBuffer = InBuffers[1].cbBuffer;
      pExtraData->BufferType = SECBUFFER_TOKEN;
    

     

     

    Hier nochmal die gesamten Funktion PerformHandshake und HandshakeLoop:

    /*****************************************************************************/
    static SECURITY_STATUS PerformClientHandshake(SOCKET  Socket,  // in
    											 PCredHandle phCreds, // in
    											 LPCTSTR  pszServerName, // in
    											 CtxtHandle * phContext, // out
    											 SecBuffer * pExtraData) // out
    {
     SecBufferDesc OutBuffer;
     SecBuffer OutBuffers[1];
     DWORD  dwSSPIFlags;
     DWORD  dwSSPIOutFlags;
     TimeStamp tsExpiry;
     SECURITY_STATUS scRet;
     DWORD  cbData;
    
     dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
       ISC_REQ_REPLAY_DETECT |
       ISC_REQ_CONFIDENTIALITY |
       ISC_RET_EXTENDED_ERROR |
       ISC_REQ_ALLOCATE_MEMORY |
       ISC_REQ_STREAM;
    
     //
     // Initiate a ClientHello message and generate a token.
     //
    
     OutBuffers[0].pvBuffer = NULL;
     OutBuffers[0].BufferType = SECBUFFER_TOKEN;
     OutBuffers[0].cbBuffer = 0;
    
     OutBuffer.cBuffers = 1;
     OutBuffer.pBuffers = OutBuffers;
     OutBuffer.ulVersion = SECBUFFER_VERSION;
    
     scRet = g_pSSPI->InitializeSecurityContextA(
       phCreds,
       NULL,
       (SEC_CHAR*)pszServerName,
       dwSSPIFlags,
       0,
       SECURITY_NATIVE_DREP,
       NULL,
       0,
       phContext,
       &OutBuffer,
       &dwSSPIOutFlags,
       &tsExpiry);
    
     if(scRet != SEC_I_CONTINUE_NEEDED)
     {
     printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
     return scRet;
     }
    
     // Send response to server if there is one.
     if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
     {
     cbData = send(Socket,
       (const PTCHAR)OutBuffers[0].pvBuffer,
       OutBuffers[0].cbBuffer,
       0);
     if(cbData == SOCKET_ERROR || cbData == 0)
     {
      printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
      g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
      g_pSSPI->DeleteSecurityContext(phContext);
      return SEC_E_INTERNAL_ERROR;
     }
     printf("%d bytes of handshake data sent\n", cbData);
    
     if(fVerbose)
     {
      PrintHexDump(cbData, (PBYTE)OutBuffers[0].pvBuffer);
      printf("\n");
     }
    
    
     // Free output buffer.
     g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
     OutBuffers[0].pvBuffer = NULL;
     }
    
     return ClientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData);
    }
    
    /*****************************************************************************/
    static SECURITY_STATUS ClientHandshakeLoop(SOCKET  Socket,  // in
    										 PCredHandle phCreds, // in
    										 CtxtHandle * phContext, // in, out
    										 BOOL  fDoInitialRead, // in
    										 SecBuffer * pExtraData) // out
    {
     SecBufferDesc InBuffer;
     SecBuffer InBuffers[2];
     SecBufferDesc OutBuffer;
     SecBuffer OutBuffers[1];
     DWORD  dwSSPIFlags;
     DWORD  dwSSPIOutFlags;
     TimeStamp tsExpiry;
     SECURITY_STATUS scRet;
     DWORD  cbData;
     PUCHAR  IoBuffer;
     DWORD  cbIoBuffer;
     BOOL  fDoRead;
    
     dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
       ISC_REQ_REPLAY_DETECT |
       ISC_REQ_CONFIDENTIALITY |
       ISC_RET_EXTENDED_ERROR |
       ISC_REQ_ALLOCATE_MEMORY |
       ISC_REQ_STREAM;
    
     //
     // Allocate data buffer.
     //
     IoBuffer = (PUCHAR)LocalAlloc(LMEM_FIXED, IO_BUFFER_SIZE);
     if(IoBuffer == NULL)
     {
     printf("**** Out of memory (1)\n");
     return SEC_E_INTERNAL_ERROR;
     }
     cbIoBuffer = 0;
     fDoRead = fDoInitialRead;
    
     // 
     // Loop until the handshake is finished or an error occurs.
     //
     scRet = SEC_I_CONTINUE_NEEDED;
     while(scRet == SEC_I_CONTINUE_NEEDED ||
      scRet == SEC_E_INCOMPLETE_MESSAGE ||
      scRet == SEC_I_INCOMPLETE_CREDENTIALS) 
     {
     //
     // Read data from server.
     //
     if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE)
     {
      if(fDoRead)
      {
      cbData = recv(Socket, 
        (PTCHAR)(IoBuffer + cbIoBuffer), 
        IO_BUFFER_SIZE - cbIoBuffer, 
        0);
      if(cbData == SOCKET_ERROR)
      {
       printf("**** Error %d reading data from server\n", WSAGetLastError());
       scRet = SEC_E_INTERNAL_ERROR;
       break;
      }
      else if(cbData == 0)
      {
       printf("**** Server unexpectedly disconnected\n");
       scRet = SEC_E_INTERNAL_ERROR;
       break;
      }
      printf("%d bytes of handshake data received\n", cbData);
    
      if(fVerbose)
      {
       PrintHexDump(cbData, IoBuffer + cbIoBuffer);
       printf("\n");
      }
    
      cbIoBuffer += cbData;
      }
      else
      {
      fDoRead = TRUE;
      }
     }
    
     //
     // Set up the input buffers. Buffer 0 is used to pass in data
     // received from the server. Schannel will consume some or all
     // of this. Leftover data (if any) will be placed in buffer 1 and
     // given a buffer type of SECBUFFER_EXTRA.
     //
    
     InBuffers[0].pvBuffer = IoBuffer;
     InBuffers[0].cbBuffer = cbIoBuffer;
     InBuffers[0].BufferType = SECBUFFER_TOKEN;
    
     InBuffers[1].pvBuffer = NULL;
     InBuffers[1].cbBuffer = 0;
     InBuffers[1].BufferType = SECBUFFER_EMPTY;
    
     InBuffer.cBuffers = 2;
     InBuffer.pBuffers = InBuffers;
     InBuffer.ulVersion = SECBUFFER_VERSION;
    
     //
     // Set up the output buffers. These are initialized to NULL
     // so as to make it less likely we'll attempt to free random
     // garbage later.
     //
     OutBuffers[0].pvBuffer = NULL;
     OutBuffers[0].BufferType= SECBUFFER_TOKEN;
     OutBuffers[0].cbBuffer = 0;
    
     OutBuffer.cBuffers = 1;
     OutBuffer.pBuffers = OutBuffers;
     OutBuffer.ulVersion = SECBUFFER_VERSION;
     //
     // Call InitializeSecurityContext.
     //
    
     scRet = g_pSSPI->InitializeSecurityContextA(phCreds,
          phContext,
          NULL,
          dwSSPIFlags,
          0,
          SECURITY_NATIVE_DREP,
          &InBuffer,
          0,
          NULL,
          &OutBuffer,
          &dwSSPIOutFlags,
          &tsExpiry);
     //
     // If InitializeSecurityContext was successful (or if the error was 
     // one of the special extended ones), send the contends of the output
     // buffer to the server.
     //
     if(scRet == SEC_E_OK  ||
      scRet == SEC_I_CONTINUE_NEEDED ||
      FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
     {
      if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
      {
      cbData = send(Socket,
        (const PCHAR)OutBuffers[0].pvBuffer,
        OutBuffers[0].cbBuffer,
        0);
      if(cbData == SOCKET_ERROR || cbData == 0)
      {
       printf("**** Error %d sending data to server (2)\n", 
       WSAGetLastError());
       g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
       g_pSSPI->DeleteSecurityContext(phContext);
       return SEC_E_INTERNAL_ERROR;
      }
    
      printf("%d bytes of handshake data sent\n", cbData);
    
      if(fVerbose)
      {
       PrintHexDump(cbData, (PBYTE)OutBuffers[0].pvBuffer);
       printf("\n");
      }
    
      // Free output buffer.
      g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
    
      OutBuffers[0].pvBuffer = NULL;
      }
     }
    
     //
     // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
     // then we need to read more data from the server and try again.
     //
     if(scRet == SEC_E_INCOMPLETE_MESSAGE)
     {
      continue;
     }
    
     //
     // If InitializeSecurityContext returned SEC_E_OK, then the 
     // handshake completed successfully.
     //
     if(scRet == SEC_E_OK)
     {
      //
      // If the "extra" buffer contains data, this is encrypted application
      // protocol layer stuff. It needs to be saved. The application layer
      // will later decrypt it with DecryptMessage.
      //
      printf("Handshake was successful\n");
      if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
      pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED, 
           InBuffers[1].cbBuffer);
      if(pExtraData->pvBuffer == NULL)
      {
       printf("**** Out of memory (2)\n");
       return SEC_E_INTERNAL_ERROR;
      }
      MoveMemory(pExtraData->pvBuffer,
        IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
        InBuffers[1].cbBuffer);
    
      pExtraData->cbBuffer = InBuffers[1].cbBuffer;
      pExtraData->BufferType = SECBUFFER_TOKEN;
    
      printf("%d bytes of app data was bundled with handshake data\n",
       pExtraData->cbBuffer);
      }
      else
      {
      pExtraData->pvBuffer = NULL;
      pExtraData->cbBuffer = 0;
      pExtraData->BufferType = SECBUFFER_EMPTY;
      }
    
      //
      // Bail out to quit
      //
    
      break;
     }
     //
     // Check for fatal error.
     //
     if(FAILED(scRet))
     {
      printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
      break;
     }
    
     //
     // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
     // then the server just requested client authentication. 
     //
     if(scRet == SEC_I_INCOMPLETE_CREDENTIALS)
     {
      //
      // Busted. The server has requested client authentication and
      // the credential we supplied didn't contain a client certificate.
      //
    
      // 
      // This function will read the list of trusted certificate
      // authorities ("issuers") that was received from the server
      // and attempt to find a suitable client certificate that
      // was issued by one of these. If this function is successful, 
      // then we will connect using the new certificate. Otherwise,
      // we will attempt to connect anonymously (using our current
      // credentials).
      //
      
      GetNewClientCredentials(phCreds, phContext);
    
      // Go around again.
      fDoRead = FALSE;
      scRet = SEC_I_CONTINUE_NEEDED;
      continue;
     }
    
     //
     // Copy any leftover data from the "extra" buffer, and go around
     // again.
     //
     if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
     {
      MoveMemory(IoBuffer,
       IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
       InBuffers[1].cbBuffer);
      cbIoBuffer = InBuffers[1].cbBuffer;
     }
     else
     {
      cbIoBuffer = 0;
     }
     }
     // Delete the security context in the case of a fatal error.
     if(FAILED(scRet))
     {
     g_pSSPI->DeleteSecurityContext(phContext);
    
     }
     LocalFree(IoBuffer);
     return scRet;
    }
    

     

    Frage nochmal:
    Die ExtraData-Daten werden in den ExtraData-Buffer kopiert, der dann nirgendwo verwendet wird (außer nach ReNegotiate).

    Diese Daten könnten vielleicht für die Verschlüsselung der zu versendenen Cmds an den Mailserver genutzt werden(?)
    Gibt es dafür nähere Infos?

    Vielen Dank für alle Hinweise und Tipps.
    Gruß
    Jens

    • Bearbeitet JenserB Mittwoch, 21. Juli 2010 08:06 unübersichtlicher Code (Leerzeilen entfernt)
    Dienstag, 20. Juli 2010 08:45