/****************************************************************************/ /* asmcpp.cpp */ /* */ /* Security Manager C++ functions */ /* */ /* Copyright (C) 1997-1999 Microsoft Corporation */ /****************************************************************************/ #include #pragma hdrstop #define TRC_FILE "asmcpp" #define pTRCWd (pRealSMHandle->pWDHandle) #include #include extern "C" { #include #include } #define DC_INCLUDE_DATA #include #undef DC_INCLUDE_DATA /****************************************************************************/ /* Name: SM_Register */ /* */ /* Purpose: Register with SM */ /* */ /* Returns: TRUE - registered OK */ /* FALSE - register failed */ /* */ /* Params: pSMHandle - SM handle */ /* pMaxPDUSize - max PDU size supported (returned) */ /* pUserID - this person's user ID (returned) */ /* */ /* Operation: This function enables the Share Class to register with SM. */ /* This allows */ /* - the Share Class to call SM */ /* - SM to issue callbacks to the Share Class (SC_SMCallback). */ /****************************************************************************/ BOOL RDPCALL SM_Register( PVOID pSMHandle, PUINT32 pMaxPDUSize, PUINT32 pUserID) { PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle; BOOL rc = FALSE; DC_BEGIN_FN("SM_Register"); // Console stacks do not go through the typical key negotiation, so update // the state appropriately if (pRealSMHandle->pWDHandle->StackClass == Stack_Console) { TRC_ALT((TB, "Console security state to SM_STATE_SM_CONNECTED")); SM_SET_STATE(SM_STATE_CONNECTED); } //Skip the following CHECK_STATE. For the console reconnect, // this is a legal transition. //SM_CHECK_STATE(SM_EVT_REGISTER); /************************************************************************/ /* Calculate max PDU size allowed to caller */ /************************************************************************/ *pMaxPDUSize = pRealSMHandle->maxPDUSize - pRealSMHandle->encryptHeaderLen; TRC_NRM((TB, "Max PDU size allowed to core is %d", *pMaxPDUSize)); /************************************************************************/ /* Return the user ID */ /************************************************************************/ *pUserID = pRealSMHandle->userID; TRC_NRM((TB, "Returning user id %d", *pUserID)); SM_SET_STATE(SM_STATE_SC_REGISTERED); pRealSMHandle->bForwardDataToSC = TRUE; rc = TRUE; //DC_EXIT_POINT: DC_END_FN(); return rc; } /* SM_Register */ /****************************************************************************/ /* Name: SM_OnConnected */ /* */ /* Purpose: Handle connection state change callbacks from NM */ /* */ /* Returns: none */ /* */ /* Params: pRealSMHandle - SM Handle */ /* userID - userID of the node causing the callback */ /* result - result of connection attempt */ /* pUserData - Network (Server-Client) user data */ /* maxPDUSize - max size of PDUs that can be sent */ /****************************************************************************/ void RDPCALL SM_OnConnected( PVOID pSMHandle, UINT32 userID, UINT32 result, PRNS_UD_SC_NET pUserData, UINT32 maxPDUSize) { PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle; DC_BEGIN_FN("SM_OnConnected"); if (result == NM_CB_CONN_OK) { TRC_NRM((TB, "Connected OK as user %x", userID)); SM_CHECK_STATE(SM_EVT_CONNECTED); /********************************************************************/ /* Store useful stuff in SM Handle */ /********************************************************************/ pRealSMHandle->userID = userID; pRealSMHandle->maxPDUSize = maxPDUSize; pRealSMHandle->channelID = pUserData->MCSChannelID; /********************************************************************/ // Pass the result to WDW. For WDW this is the start of the // connection sequence. /********************************************************************/ SM_SET_STATE(SM_STATE_SM_CONNECTING); WDW_OnSMConnecting(pRealSMHandle->pWDHandle, pRealSMHandle->pUserData, pUserData); /********************************************************************/ // Free the reply user data once we've passed it to WDW. /********************************************************************/ if (pRealSMHandle->pUserData != NULL) { TRC_NRM((TB, "Free user data")); COM_Free(pRealSMHandle->pUserData); pRealSMHandle->pUserData = NULL; } } else { TRC_NRM((TB, "Failed to connect, reason %d", result)); /********************************************************************/ // Tell WDW /********************************************************************/ WDW_OnSMConnected(pRealSMHandle->pWDHandle, result); /********************************************************************/ /* Clean up */ /********************************************************************/ SM_SET_STATE(SM_STATE_SM_CONNECTING); SM_Disconnect(pRealSMHandle); } DC_EXIT_POINT: DC_END_FN(); } /* SM_OnConnected */ /****************************************************************************/ /* Name: SM_OnDisconnected */ /* */ /* Purpose: Handle disconnection state change callback from NM */ /* */ /* Params: pRealSMHandle - SM Handle */ /* userID - userID of the node causing the callback */ /* result - reason for the disconnection */ /****************************************************************************/ void RDPCALL SM_OnDisconnected( PVOID pSMHandle, UINT32 userID, UINT32 result) { ShareClass *pSC; PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSMHandle; DC_BEGIN_FN("SM_OnDisconnected"); SM_CHECK_STATE(SM_EVT_DISCONNECTED); TRC_NRM((TB, "Disconnected, reason %d", result)); /************************************************************************/ /* First, clear up connection resources */ /************************************************************************/ SMFreeConnectResources(pRealSMHandle); SM_SET_STATE(SM_STATE_INITIALIZED); /************************************************************************/ // Tell SC. Don't call if SC is not registered. /************************************************************************/ if (pRealSMHandle->state == SM_STATE_SC_REGISTERED) { // Check that the Share Class exists. pSC = (ShareClass *)(pRealSMHandle->pWDHandle->dcShare); if (pSC != NULL) { // Call SC's callback. pSC->SC_OnDisconnected((UINT16)userID); } else { TRC_ERR((TB, "No Share Class")); } } else { TRC_ERR((TB, "SC Not registered")); } /************************************************************************/ /* Then tell WDW */ /************************************************************************/ WDW_OnSMDisconnected(pRealSMHandle->pWDHandle); DC_EXIT_POINT: DC_END_FN(); } /* SM_OnDisconnected */ /****************************************************************************/ // SM_DecodeFastPathInput // // Handles decryption of fast-path input data if it's an encrypted packet. // Then passes directly to IM for bytestream decoding and injection. /****************************************************************************/ void RDPCALL SM_DecodeFastPathInput( void *pSM, BYTE *pData, unsigned DataLength, BOOL bEncrypted, unsigned NumEvents, BOOL fSafeChecksum) { BOOL rc; PSM_HANDLE_DATA pRealSMHandle = (PSM_HANDLE_DATA)pSM; ShareClass *pShareClass; // Used if encypted using FIPS BYTE *pEncData, *pSigData; DWORD EncDataLen, dwPadLen; DC_BEGIN_FN("SM_FastPathInputDecode"); // If we are being attacked or have a bad client, we may receive data // here before we are really in a conference. If so, ignore it. // We can't disconnect here because the code to do so requires pWDHandle // to be valid. If the protocol stream is messed up, the connection will // be dropped later by other decoding code. if (pRealSMHandle->pWDHandle != NULL) { pShareClass = (ShareClass *)pRealSMHandle->pWDHandle->dcShare; if (pRealSMHandle->encrypting) { if (bEncrypted) { // // Debug verification, we always go with what the protocol header // says but verify it's consistent with the capabilities // if (pRealSMHandle->useSafeChecksumMethod != (fSafeChecksum != 0)) { TRC_ERR((TB, "fastpath: fSecureChecksum: 0x%x setting" "does not match protocol: 0x%x", pRealSMHandle->useSafeChecksumMethod, fSafeChecksum)); } // Make sure we have at least the size of the security context. if (DataLength >= DATA_SIGNATURE_SIZE) { // Check to see if we need to update the session key. if (pRealSMHandle->decryptCount == UPDATE_SESSION_KEY_COUNT) { rc = TRUE; // Don't need to update the session key if using FIPS if (pRealSMHandle->encryptionMethodSelected != SM_FIPS_ENCRYPTION_FLAG) { rc = UpdateSessionKey( pRealSMHandle->startDecryptKey, pRealSMHandle->currentDecryptKey, pRealSMHandle->encryptionMethodSelected, pRealSMHandle->keyLength, &pRealSMHandle->rc4DecryptKey, pRealSMHandle->encryptionLevel); } if (rc) { // Reset counter. pRealSMHandle->decryptCount = 0; } else { TRC_ERR((TB,"SM failed to update session key")); goto FailedKey; } } if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) { if (DataLength < (sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER))) { TRC_ERR((TB,"PDU len %u too short for security context in FIPS decryption", DataLength)); goto ShortData; } pEncData = pData + sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER); pSigData = pEncData - MAX_SIGN_SIZE; EncDataLen = DataLength - (sizeof(RNS_SECURITY_HEADER2) - sizeof(RNS_SECURITY_HEADER)); dwPadLen = *((TSUINT8 *)(pSigData - sizeof(TSUINT8))); rc = TSFIPS_DecryptData( &(pRealSMHandle->FIPSData), pEncData, EncDataLen, dwPadLen, pSigData, pRealSMHandle->totalDecryptCount); } else { // Encryption signature sits in first DATA_SIGNATURE_SIZE // bytes of the provided packet data. rc = DecryptData( pRealSMHandle->encryptionLevel, pRealSMHandle->currentDecryptKey, &pRealSMHandle->rc4DecryptKey, pRealSMHandle->keyLength, pData + DATA_SIGNATURE_SIZE, DataLength - DATA_SIGNATURE_SIZE, pRealSMHandle->macSaltKey, pData, fSafeChecksum, pRealSMHandle->totalDecryptCount); } if (rc) { TRC_DBG((TB, "Data decrypted: %u", DataLength - DATA_SIGNATURE_SIZE)); // Increment decryption counter. pRealSMHandle->decryptCount++; pRealSMHandle->totalDecryptCount++; // Skip past the encryption signature for passing to IM. if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) { pData = pEncData; DataLength = EncDataLen - *((TSUINT8 *)(pSigData - sizeof(TSUINT8))); } else { pData += DATA_SIGNATURE_SIZE; DataLength -= DATA_SIGNATURE_SIZE; } } else { TRC_ERR((TB, "SM failed to decrypt data: len=%u", DataLength - DATA_SIGNATURE_SIZE)); goto FailedDecrypt; } } else { TRC_ERR((TB,"PDU len %u too short for security context", DataLength)); goto ShortData; } } else { // Need to disconnect if client only sends encrypted data if (pRealSMHandle->pWDHandle->bForceEncryptedCSPDU) { TRC_ASSERT((FALSE), (TB, "unencrypted data in encrypted protocol")); goto FailedDecrypt; } } } // Be sure to decrypt before checking the dead and other state to // maintain the correct context between the client and server. if (!pRealSMHandle->dead && SM_CHECK_STATE_Q(SM_EVT_DATA_PACKET)) { // We directly inject into the mouse and keyboard streams if we // are a primary stack. We cannot receive fast-path data on a // passthru stack since it does not get RawInput calls. Fast-path // input cannot be received by a shadow stack since the passthru- // to-shadow stack data format is always the non-fast-path // format, munged from the fast-path format by // IM_ConvertFastPathToShadow(). TRC_ASSERT((pRealSMHandle->pWDHandle->StackClass == Stack_Primary), (TB,"Somehow we received fast-path input on a %s stack!", (pRealSMHandle->pWDHandle->StackClass == Stack_Passthru ? "passthru" : pRealSMHandle->pWDHandle->StackClass == Stack_Shadow ? "shadow" : "console"))); pShareClass->IM_DecodeFastPathInput(pData, DataLength, NumEvents); if (pRealSMHandle->pWDHandle->shadowState == SHADOW_CLIENT) pShareClass->IM_ConvertFastPathToShadow(pData, DataLength, NumEvents); } else { TRC_ALT((TB,"Ignoring fast-path input PDU on dead or bad state")); } } else { TRC_ERR((TB,"Received fast-path input data before SM initialized, " "ignoring")); goto DataTooSoon; } DC_END_FN(); return; // Error handling, segregate to keep out of performance path // instruction cache. FailedKey: WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE, Log_RDP_ENC_UpdateSessionKeyFailed, NULL, 0); return; FailedDecrypt: WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE, Log_RDP_ENC_DecryptFailed, NULL, 0); return; ShortData: WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE, Log_RDP_SecurityDataTooShort, pData, DataLength); return; DataTooSoon: // TODO: Combine the SM, NM, and TSWd state into one single struct // containing everything we need, then fix this code to disconnect // by using the pContext we need. ; } /****************************************************************************/ /* Name: SM_MCSSendDataCallback */ /* */ /* Purpose: Handle SendData callback from MCS */ /* */ /* Returns: TRUE if successful, FALSE otherwise. */ /* */ /* Params: hUser - MCS user handle for our user attachment */ /* UserDefined - our NM handle */ /* bUniform - Data received is from an MCS uniform-send-data */ /* hChannel - Handle of the receive channel */ /* Priority - MCS priority for the data */ /* SenderID - MCS UserID of the sender */ /* Segmentation - MCS segmentation flags for the data */ /* DataLength - Length of the incoming data */ /* pData - Pointer to (DataLength) sized memory block */ /****************************************************************************/ BOOLEAN __fastcall SM_MCSSendDataCallback(BYTE *pData, unsigned DataLength, void *UserDefined, UserHandle hUser, BOOLEAN bUniform, ChannelHandle hChannel, MCSPriority Priority, UserID SenderID, Segmentation Segmentation) { BOOLEAN result = TRUE; PSM_HANDLE_DATA pRealSMHandle; BOOL dataPkt; BOOL licPkt; UINT16 channelID; ShareClass *dcShare; DC_BEGIN_FN("SM_MCSSendDataCallback"); /************************************************************************/ /* SMHandle is assumed to be the first member in the NM struct pointed */ /* to by UserDefined. */ /************************************************************************/ pRealSMHandle = *((PSM_HANDLE_DATA *)UserDefined); dcShare = (ShareClass*)pRealSMHandle->pWDHandle->dcShare; /************************************************************************/ /* Check MCS segmentation. */ /************************************************************************/ TRC_ASSERT((Segmentation == (SEGMENTATION_BEGIN | SEGMENTATION_END)), (TB,"Segmented packet received")); /************************************************************************/ /* Decide what type of packet it is. This is a bit hokey. */ /* - If we are encrypting, the security header always tells us the type */ /* of packet. */ /* - If we are not encrypting */ /* - assume packets received in state SM_STATE_SC_REGISTERED are data */ /* packets */ /* - assume packets received in other states are not data packets. */ /************************************************************************/ if (pRealSMHandle->encrypting) { if (DataLength >= sizeof(RNS_SECURITY_HEADER)) { dataPkt = !(((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_NONDATA_PKT); } else { TRC_ERR((TB,"Received pkt len %u too short for security header", DataLength)); WDW_LogAndDisconnect(pRealSMHandle->pWDHandle, TRUE, Log_RDP_SecurityDataTooShort, pData, DataLength); result = FALSE; DC_QUIT; } } else { dataPkt = (pRealSMHandle->state == SM_STATE_SC_REGISTERED); } TRC_DBG((TB, "Encrypting=%d: %s packet", pRealSMHandle->encrypting, dataPkt ? "data" : "security")); /************************************************************************/ /* Handle data packets (perf path). */ /************************************************************************/ if (dataPkt) { /********************************************************************/ /* Decrypt the packet if necessary */ /********************************************************************/ if (pRealSMHandle->encrypting) { if (((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_ENCRYPT) { TRC_DBG((TB, "Decrypt the packet")); if (SMDecryptPacket(pRealSMHandle, pData, DataLength, pRealSMHandle->useSafeChecksumMethod)) { TRC_NRM((TB,"Decrypted packet at %p", pData)); } else { TRC_ERR((TB, "Failed to decrypt packet: %ld", DataLength)); DC_QUIT; } if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) { DataLength -= (sizeof(RNS_SECURITY_HEADER2) + ((PRNS_SECURITY_HEADER2_UA)pData)->padlen); pData += sizeof(RNS_SECURITY_HEADER2); } else { pData += sizeof(RNS_SECURITY_HEADER1); DataLength -= sizeof(RNS_SECURITY_HEADER1); } } else { /************************************************************/ /* Adjust pointer and length */ /************************************************************/ if (pRealSMHandle->pWDHandle->bForceEncryptedCSPDU) { TRC_ASSERT((FALSE), (TB, "unencrypted data in encrypted protocol")); WDW_LogAndDisconnect( pRealSMHandle->pWDHandle, TRUE, Log_RDP_ENC_DecryptFailed, NULL, 0); result = FALSE; DC_QUIT; } else { TRC_NRM((TB, "Pass packet to SC")); pData += sizeof(RNS_SECURITY_HEADER); DataLength -= sizeof(RNS_SECURITY_HEADER); } } } /********************************************************************/ /* Don't do anything if we're dead */ /********************************************************************/ if (!pRealSMHandle->dead) { if (SM_CHECK_STATE_Q(SM_EVT_DATA_PACKET)) { // Decide where to send the packet based on the channel ID. channelID = (UINT16)MCSGetChannelIDFromHandle(hChannel); if (channelID == pRealSMHandle->channelID) { // Pass packet to SC. Don't do it if the ShareClass // doesn't exist. TRC_NRM((TB, "Share channel %x", channelID)); if (pRealSMHandle->pWDHandle->dcShare != NULL) { // Only a non-shadowing primary stack, or a shadow // stack should process the full set of PDUs if (((pRealSMHandle->pWDHandle->StackClass == Stack_Primary) && (pRealSMHandle->pWDHandle->shadowState != SHADOW_CLIENT))) { ((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))-> SC_OnDataReceived(pData, SenderID, DataLength, Priority); } else if ((pRealSMHandle->pWDHandle->StackClass == Stack_Shadow)) { UINT16 pduType = ((PTS_SHARECONTROLHEADER)pData)->pduType & TS_MASK_PDUTYPE; // Unless it's CLIENTRANDOM PDU, we can only forward // data to Share Class if Share Class is ready. // We could be in a racing condition that Share class // hasn't finished initialization, but we have received // shadow data. if (pRealSMHandle->bForwardDataToSC == TRUE || pduType == TS_PDUTYPE_CLIENTRANDOMPDU) { ((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))-> SC_OnDataReceived(pData, SenderID, DataLength, Priority); } } // Else send to SC for shadow hotkey processing or // passthru from the shadow target to the shadow // client. else { ((ShareClass*)(pRealSMHandle->pWDHandle->dcShare))-> SC_OnShadowDataReceived(pData, SenderID, DataLength, Priority); } } else { TRC_ERR((TB, "Tried to call non-existent Share Class")); } } else { /************************************************************/ /* Virtual Channel */ /************************************************************/ TRC_NRM((TB, "Virtual channel %x", channelID)); WDW_OnDataReceived(pRealSMHandle->pWDHandle, pData, DataLength, channelID); } } else { TRC_ALT((TB,"Ignoring PDU because of bad state")); #ifdef INSTRUM_TRACK_DISCARDED pRealSMHandle->nDiscardPDUBadState++; #endif DC_QUIT; } } else { TRC_ERR((TB, "Recvd PDU when we're dead")); // // To help track down the VC decompression bug // track if we dropped any VC packets // channelID = (UINT16)MCSGetChannelIDFromHandle(hChannel); if (channelID != pRealSMHandle->channelID) { // // If this is VC data then we must hand it off // to be decompressed otherwise the server's context // will get out of sync with the client's // TRC_NRM((TB, "Virtual channel %x", channelID)); WDW_OnDataReceived(pRealSMHandle->pWDHandle, pData, DataLength, channelID); #ifdef INSTRUM_TRACK_DISCARDED pRealSMHandle->nDiscardVCDataWhenDead++; #endif } else { #ifdef INSTRUM_TRACK_DISCARDED pRealSMHandle->nDiscardNonVCPDUWhenDead++; #endif } DC_QUIT; } } else { /********************************************************************/ /* If we're encrypting, the security header tells us the packet */ /* type. If we're not encrypting, we need to use our state to */ /* decide whether this is a licensing or security packet. */ /********************************************************************/ if (pRealSMHandle->encrypting) { licPkt = (((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_LICENSE_PKT); } else { licPkt = (pRealSMHandle->state == SM_STATE_LICENSING); } if (licPkt) { #ifdef USE_LICENSE /****************************************************************/ /* License packet */ /****************************************************************/ TRC_NRM((TB, "Licensing packet")); SM_CHECK_STATE(SM_EVT_LIC_PACKET); if (((PRNS_SECURITY_HEADER_UA)pData)->flags & RNS_SEC_ENCRYPT) { TRC_DBG((TB, "Decrypt the licensing packet")); if (SMDecryptPacket(pRealSMHandle, pData, DataLength, pRealSMHandle->useSafeChecksumMethod)) { TRC_NRM((TB,"Decrypted packet at %p", pData)); } else { TRC_ERR((TB, "Failed to decrypt packet: %ld", DataLength)); DC_QUIT; } if (pRealSMHandle->encryptionMethodSelected == SM_FIPS_ENCRYPTION_FLAG) { DataLength -= (sizeof(RNS_SECURITY_HEADER2) + ((PRNS_SECURITY_HEADER2_UA)pData)->padlen); pData += sizeof(RNS_SECURITY_HEADER2); } else { pData += sizeof(RNS_SECURITY_HEADER1); DataLength -= sizeof(RNS_SECURITY_HEADER1); } } else { TRC_NRM((TB, "Licensing packet not encrypted")); pData += sizeof(RNS_SECURITY_HEADER); DataLength -= sizeof(RNS_SECURITY_HEADER); } SLicenseData(pRealSMHandle->pLicenseHandle, pRealSMHandle, pData, DataLength); #else /* USE_LICENSE */ TRC_ABORT((TB,"Licensing not implemented yet")); #endif /* USE_LICENSE */ } else { /****************************************************************/ /* Security packet */ /****************************************************************/ TRC_NRM((TB, "Security packet")); SM_CHECK_STATE(SM_EVT_SEC_PACKET); result = SMContinueSecurityExchange(pRealSMHandle, pData, DataLength); } } DC_EXIT_POINT: DC_END_FN(); return (result); } /* SM_MCSSendDataCallback */