// This test validates that the construction and parsing of the message integrity attribute in a stun message works as expected // The test also validates both short term and long term credential modes with or without the presence of a fingerprint attribute HRESULT CTestIntegrity::TestMessageIntegrity(bool fWithFingerprint, bool fLongCredentials) { HRESULT hr = S_OK; const char* pszUserName = "******"; const char* pszRealm = "stunrealm"; const char* pszPassword = "******"; CStunMessageBuilder builder; CStunMessageReader reader; uint8_t *pMsg = NULL; size_t sizeMsg = 0; CStunMessageReader::ReaderParseState state; CRefCountedBuffer spBuffer; builder.AddBindingRequestHeader(); builder.AddRandomTransactionId(NULL); builder.AddUserName(pszUserName); builder.AddRealm(pszRealm); if (fLongCredentials == false) { Chk(builder.AddMessageIntegrityShortTerm(pszPassword)); } else { Chk(builder.AddMessageIntegrityLongTerm(pszUserName, pszRealm, pszPassword)); } if (fWithFingerprint) { builder.AddFingerprintAttribute(); } Chk(builder.GetResult(&spBuffer)); pMsg = spBuffer->GetData(); sizeMsg = spBuffer->GetSize(); state = reader.AddBytes(pMsg, sizeMsg); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); ChkIfA(reader.HasMessageIntegrityAttribute()==false, E_FAIL); if (fLongCredentials == false) { ChkA(reader.ValidateMessageIntegrityShort(pszPassword)); } else { ChkA(reader.ValidateMessageIntegrityLong(pszUserName, pszRealm, pszPassword)); } Cleanup: return hr; }
HRESULT CStunThreadMessageHandler::ProcessBindingRequest(CStunMessageReader& reader) { HRESULT hrTmp; bool fRequestHasPaddingAttribute = false; SocketRole socketOutput = _message.localSocket; StunChangeRequestAttribute changerequest = {}; bool fSendOtherAddress = false; bool fSendOriginAddress = false; SocketRole socketOther; CSocketAddress addrOrigin; CSocketAddress addrOther; CStunMessageBuilder builder; uint16_t paddingSize = 0; bool fLegacyFormat = false; // set to true if the client appears to be rfc3489 based instead of based on rfc 5789 _spResponseBuffer->SetSize(0); builder.GetStream().Attach(_spResponseBuffer, true); fLegacyFormat = reader.IsMessageLegacyFormat(); // check for an alternate response port // check for padding attribute (todo - figure out how to inject padding into the response) // check for a change request and validate we can do it. If so, set _socketOutput. If not, fill out _error and return. // determine if we have an "other" address to notify the caller about // did the request come with a padding request if (SUCCEEDED(reader.GetPaddingAttributeSize(&paddingSize))) { // todo - figure out how we're going to get the MTU size of the outgoing interface fRequestHasPaddingAttribute = true; } // as per 5780, section 6.1, If the Request contained a PADDING attribute... // "If the Request also contains the RESPONSE-PORT attribute the server MUST return an error response of type 400." if (_fRequestHasResponsePort && fRequestHasPaddingAttribute) { _error.errorcode = STUN_ERROR_BADREQUEST; return E_FAIL; } // handle change request logic and figure out what "other-address" attribute is going to be if (SUCCEEDED(reader.GetChangeRequest(&changerequest))) { if (changerequest.fChangeIP) { socketOutput = SocketRoleSwapIP(socketOutput); } if(changerequest.fChangePort) { socketOutput = SocketRoleSwapPort(socketOutput); } // IsValidSocketRole just validates the enum, not whether or not we can send on it ASSERT(IsValidSocketRole(socketOutput)); // now, make sure we have the ability to send from another socket if (_spStunResponder->HasAddress(socketOutput) == false) { // send back an error. We're being asked to respond using another address that we don't have a socket for _error.errorcode = STUN_ERROR_BADREQUEST; return E_FAIL; } } // If we're only working one socket, then that's ok, we just don't send back an "other address" unless we have all four sockets confgiured // now here's a problem. If we binded to "INADDR_ANY", all of the sockets will have "0.0.0.0" for an address (same for IPV6) // So we effectively can't send back "other address" if don't really know our own IP address // Fortunately, recvfromex and the ioctls on the socket allow address discovery a bit better fSendOtherAddress = (_spStunResponder->HasAddress(RolePP) && _spStunResponder->HasAddress(RolePA) && _spStunResponder->HasAddress(RoleAP) && _spStunResponder->HasAddress(RoleAA)); if (fSendOtherAddress) { socketOther = SocketRoleSwapIP(SocketRoleSwapPort(_message.localSocket)); hrTmp = _spStunResponder->GetSocketAddressForRole(socketOther, &addrOther); ASSERT(SUCCEEDED(hrTmp)); // so if our ip address is "0.0.0.0", disable this attribute fSendOtherAddress = (SUCCEEDED(hrTmp) && (addrOther.IsIPAddressZero()==false)); } // What's our address origin? VERIFY(SUCCEEDED(_spStunResponder->GetSocketAddressForRole(socketOutput, &addrOrigin))); if (addrOrigin.IsIPAddressZero()) { // Since we're sending back from the IP address we received on, we can just use the address the message came in on // Otherwise, we don't actually know it if (socketOutput == _message.localSocket) { addrOrigin = _message.localAddr; } } fSendOriginAddress = (false == addrOrigin.IsIPAddressZero()); // Success - we're all clear to build the response _socketOutput = socketOutput; _spResponseBuffer->SetSize(0); builder.GetStream().Attach(_spResponseBuffer, true); builder.AddHeader(StunMsgTypeBinding, StunMsgClassSuccessResponse); builder.AddTransactionId(_transid); builder.AddMappedAddress(_message.remoteAddr); if (fLegacyFormat == false) { builder.AddXorMappedAddress(_message.remoteAddr); } if (fSendOriginAddress) { builder.AddResponseOriginAddress(addrOrigin); } if (fSendOtherAddress) { builder.AddOtherAddress(addrOther, fLegacyFormat); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS } // finally - if we're supposed to have a message integrity attribute as a result of authorization, add it at the very end if (_integrity.fSendWithIntegrity) { if (_integrity.fUseLongTerm == false) { builder.AddMessageIntegrityShortTerm(_integrity.szPassword); } else { builder.AddMessageIntegrityLongTerm(_integrity.szUser, _integrity.szRealm, _integrity.szPassword); } } builder.FixLengthField(); return S_OK; }