// Initialize the chain value. void CallId::initialize() { NetMd5Codec encoder; // Get the start time. OsTime currentTime; OsDateTime::getCurTime(currentTime); encoder.hash(¤tTime, sizeof(currentTime)); // Get the process ID. PID processId; processId = OsProcess::getCurrentPID(); encoder.hash(&processId, sizeof(processId)); // Get the host identity. UtlString thisHost; OsSocket::getHostIp(&thisHost); encoder.hash(thisHost); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "CallId::initialize sChainValue"); // Save the initial hash used to seed the sequence encoder.appendBase64Sig(sChainValue); // Note initialization is done. sChainValueInitialized = true; }
// Compute the next chain value. void CallId::nextValue(const char* seed) { // If we haven't initialized yet, do so. if (!sChainValueInitialized) { initialize(); } // Get the time. OsTime currentTime; OsDateTime::getCurTime(currentTime); // Force usecs. to be 6 digits with leading zeros so that we do // not have to do 64 bit integer math just to build a big unique // string. char buffer[256]; sprintf(buffer, "%s/%d%.6d/%.*s/%s", sKey.data(), currentTime.seconds(), currentTime.usecs(), MAX_SEED_CHARS, seed, sChainValue.data()); // Hash them. NetMd5Codec encoder; encoder.encode(buffer, sChainValue); // Truncate the hash to CHAIN_VALUE_LENGTH characters. sChainValue.remove(CHAIN_VALUE_LENGTH); }
void SignedUrl::computeSignature( const UtlString& userInfo, const UtlString& hostPort, UtlString& signature ) { // signature-hash=MD5(<secret><userinfo><hostport>) NetMd5Codec signatureHash; signatureHash.hash( sSignatureSecret ); if( !userInfo.isNull() ) { signatureHash.hash( userInfo ); } signatureHash.hash( hostPort ); signature.remove(0); signatureHash.appendHashValue( signature ); }
// Compute the next chain value. void CallId::nextValue() { NetMd5Codec encoder; // If we haven't initialized yet, do so. if (!sChainValueInitialized) { initialize(); } // Use the previous chain value to seed the next one encoder.hash(sChainValue); // Get the time and hash it into the next value OsTime currentTime; OsDateTime::getCurTime(currentTime); encoder.hash(¤tTime, sizeof(currentTime)); // Replace the old chain value with the new one sChainValue.remove(0); encoder.appendBase64Sig(sChainValue); }
// Initialize the chain value. void CallId::initialize() { // Get the start time. OsTime currentTime; OsDateTime::getCurTime(currentTime); // Get the process ID. PID processId; processId = OsProcess::getCurrentPID(); // Get the host identity. UtlString thisHost; OsSocket::getHostIp(&thisHost); // Ensure it does not contain @. thisHost.replace('@','*'); // Force usecs. to be 6 digits with leading zeros so that we do // not have to do 64 bit integer math just to build a big unique // string. char buffer[256]; sprintf(buffer, "%d/%d.%.6d/%s", processId, currentTime.seconds(), currentTime.usecs(), thisHost.data()); OsSysLog::add(FAC_SIP, PRI_DEBUG, "CallId::initialize sChainValue generated from '%s'", buffer); // Hash them. NetMd5Codec encoder; encoder.encode(buffer, sChainValue); // Truncate the hash to CHAIN_VALUE_LENGTH characters. sChainValue.remove(CHAIN_VALUE_LENGTH); // Note initialization is done. sChainValueInitialized = TRUE; }
/// Encodes identity info void SipXauthIdentity::encode(UtlString & identityValue, const UtlString & callId, const UtlString & fromTag, const OsDateTime & timestamp, DialogRule bindRule ) { // calculate timestamp OsTime osTime; timestamp.cvtToTimeSinceEpoch(osTime); long seconds = osTime.seconds(); char stamp[65]; sprintf(stamp, "%lX", seconds); UtlString strSignature(stamp); strSignature.append(SignatureFieldSeparator); // signature-hash=MD5(<timestamp><secret><from-tag><call-id><identity>) NetMd5Codec signatureHash; signatureHash.hash(stamp); signatureHash.hash(sSignatureSecret); if (requireDialogBinding == bindRule) { signatureHash.hash(fromTag); signatureHash.hash(callId); } else { strSignature.append(SignatureFieldSeparator); } signatureHash.hash(mIdentity); UtlString strSignatureHash; signatureHash.appendHashValue(strSignature); Url encodedUrl(mIdentity); encodedUrl.setScheme(Url::SipUrlScheme); encodedUrl.setUrlParameter(SignatureUrlParamName, strSignature.data()); encodedUrl.toString(identityValue); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "SipXauthIdentity::encode " "identity '%s'", identityValue.data() ); }
RedirectPlugin::LookUpStatus SipRedirectorJoin::lookUpDialog( const UtlString& requestString, const UtlString& incomingCallId, ContactList& contactList, RedirectPlugin::RequestSeqNo requestSeqNo, int redirectorNo, SipRedirectorPrivateStorage*& privateStorage, const char* subscribeUser, State stateFilter) { Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "%s::lookUpDialog requestString = '%s', " "requestSeqNo = %d, redirectorNo = %d, privateStorage = %p, " "subscribeUser = '******', stateFilter = %d", mLogName.data(), requestString.data(), requestSeqNo, redirectorNo, privateStorage, subscribeUser, stateFilter); // If the private storage is already allocated, then this is a // reprocessing cycle, and the dialog to pick up (if any) is // recorded in private storage. if (privateStorage) { // Cast the private storage pointer to the right type so we can // access the needed dialog information. SipRedirectorPrivateStorageJoin* dialog_info = dynamic_cast<SipRedirectorPrivateStorageJoin*> (privateStorage); if (dialog_info->mTargetDialogDuration != SipRedirectorPrivateStorageJoin::TargetDialogDurationAbsent) { // A dialog has been recorded. Construct a contact for it. // Beware that as recorded in the dialog event notice, the // target URI is in addr-spec format; any parameters are URI // parameters. (Field parameters have been broken out in // param elements.) Url contact_URI(dialog_info->mTargetDialogLocalURI, TRUE); // Construct the Join: header value the caller should use. UtlString header_value(dialog_info->mTargetDialogCallId); // Note that according to RFC 3891, the to-tag parameter is // the local tag at the destination of the INVITE/Join. // But the INVITE/Join goes to the end of the call that // we queried with SUBSCRIBE, so the to-tag in the // Join: header is the *local* tag from the NOTIFY. header_value.append(";to-tag="); header_value.append(dialog_info->mTargetDialogLocalTag); header_value.append(";from-tag="); header_value.append(dialog_info->mTargetDialogRemoteTag); // Add a header parameter to specify the Join: header. contact_URI.setHeaderParameter("Join", header_value.data()); // We add a header parameter to cause the redirection to // include a "Require: join" header. Then if the caller // phone does not support INVITE/Join:, the pick-up will // fail entirely. Without it, if the caller phone does not // support INVITE/Join, the caller will get a // simultaneous incoming call from the executing phone. // Previously, we thought the latter behavior was better, but // it is not -- Consider if the device is a gateway from the // PSTN. Then the INVITE/Join will generate an outgoing // call to the calling phone. contact_URI.setHeaderParameter(SIP_REQUIRE_FIELD, SIP_JOIN_EXTENSION); // Record the URI as a contact. contactList.add( contact_URI, *this ); } // We do not need to suspend this time. return RedirectPlugin::SUCCESS; } else { UtlString userId; Url requestUri(requestString); requestUri.getUserId(userId); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "%s::lookUpDialog userId '%s'", mLogName.data(), userId.data()); // Construct the SUBSCRIBE for the call join. SipMessage subscribe; UtlString subscribeRequestUri("sip:"); // The user of the request URI is our subscribeUser parameter. subscribeRequestUri.append(subscribeUser); subscribeRequestUri.append("@"); subscribeRequestUri.append(mDomain); // Construct a Call-Id for the SUBSCRIBE. UtlString callId; CallId::getNewCallId(callId); // Construct the From: value. UtlString fromUri; { // Get the local address and port. UtlString address; int port; mpSipUserAgent->getLocalAddress(&address, &port); // Use the first 8 chars of the MD5 of the Call-Id as the from-tag. NetMd5Codec encoder; UtlString tag; encoder.encode(callId.data(), tag); tag.remove(8); // Assemble the URI. SipMessage::buildSipUri(&fromUri, address.data(), port, NULL, // protocol NULL, // user NULL, // userLabel, tag.data()); } // Set the standard request headers. // Allow the SipUserAgent to fill in Contact:. subscribe.setRequestData( SIP_SUBSCRIBE_METHOD, subscribeRequestUri.data(), // request URI fromUri, // From: subscribeRequestUri.data(), // To: callId, mCSeq); // Increment CSeq and roll it over if necessary. mCSeq++; mCSeq &= 0x0FFFFFFF; // Set the Expires header. // If "1 second subscriptions" is set (needed for some versions // of Snom phones), use a 1-second subscription. Otherwise, use // a 0-second subscription, so we get just one NOTIFY. subscribe.setExpiresField(mOneSecondSubscription ? 1 : 0); // Set the "Event: dialog" header. subscribe.setEventField("dialog"); // Set the "Accept: application/dialog-info+xml" header. // Not strictly necessary (per the I-D), but it makes the SUBSCRIBE // more strictly compliant. subscribe.setHeaderValue(SIP_ACCEPT_FIELD, DIALOG_EVENT_CONTENT_TYPE); // Set the References header for tracing dialog associations. { UtlString referencesValue(incomingCallId); referencesValue.append(";rel=inquiry"); subscribe.setHeaderValue(SIP_REFERENCES_FIELD, referencesValue); } // Send the SUBSCRIBE. mpSipUserAgent->send(subscribe); // Allocate private storage. SipRedirectorPrivateStorageJoin *storage = new SipRedirectorPrivateStorageJoin(requestSeqNo, redirectorNo); privateStorage = storage; // Record the Call-Id of the SUBSCRIBE, so we can correlated the // NOTIFYs with it. storage->mSubscribeCallId = callId; // Record the state filtering criterion. storage->mStateFilter = stateFilter; // If we are printing debug messages, record when the SUBSCRIBE // was sent, so we can report how long it took to get the NOTIFYs. if (Os::Logger::instance().willLog(FAC_SIP, PRI_DEBUG)) { OsDateTime::getCurTime(storage->mSubscribeSendTime); } // Set the timer to resume. storage->mTimer.oneshotAfter(OsTime(mWaitSecs, mWaitUSecs)); // Suspend processing the request. return RedirectPlugin::SEARCH_PENDING; } }
/// Check the signature and parse the identity bool SipXauthIdentity::decode(const UtlString& identityValue, const UtlString& callId, const UtlString& fromTag, DialogRule bindRule ) { /** * See SipXauthIdentity Encoding comment at the top of the file */ Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "SipXauthIdentity::decode " "parse '%s'", identityValue.data() ); mIdentity.remove(0); mIsValidIdentity = FALSE; bool decodeError = false; // false iff the identity was correctly signed and successfully parsed UtlString decodedIdentity; unsigned long epochTimestamp = 0; UtlString timestamp; UtlString actualSignatureHash; bool isBound = false; Url encodedUrl(identityValue, Url::NameAddr); if (Url::SipUrlScheme == encodedUrl.getScheme()) { // Only proceed if the URL parsing succeeded // Extract the identity encodedUrl.getIdentity(decodedIdentity); // Extract signature parameter UtlString signatureParamValue; if (encodedUrl.getUrlParameter(SignatureUrlParamName, signatureParamValue)) { // only proceed if signature parameter was found RegEx signatureRegEx(SignatureRegEx); if (signatureRegEx.Search(signatureParamValue)) { UtlString secondSeparator; isBound = ( signatureRegEx.MatchString(&secondSeparator,2) && secondSeparator.isNull()); // there is only one ':' separator if ( (requireDialogBinding == bindRule) // must be bound && ! isBound ) { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' is an unbound identity", identityValue.data() ); } else { // extract timestamp if ( !signatureRegEx.MatchString(×tamp,1) || !from_string(epochTimestamp, timestamp) ) { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' invalid timestamp", identityValue.data() ); } // extract signature else if (!signatureRegEx.MatchString(&actualSignatureHash,3)) { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode '%s' missing hash", identityValue.data() ); } } } else { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' invalid signature format", identityValue.data() ); } } else { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' missing '%s' param", identityValue.data(), SignatureUrlParamName ); } } else { decodeError = true; Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' URL parsing failed", identityValue.data() ); } // validate timestamp if (!decodeError && !sSignatureValidityInterval.isNoWait()) { // timestamp validity check if (epochTimestamp + sSignatureValidityInterval.seconds() < OsDateTime::getSecsSinceEpoch()) { decodeError = true; OsDateTime generateDate(OsTime(epochTimestamp,0)); UtlString generateTimeString; generateDate.getIsoTimeStringZ(generateTimeString); Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode(%s)" " timestamp '%lX' from '%s' too old with interval of '%d' seconds", identityValue.data(), epochTimestamp, generateTimeString.data(), sSignatureValidityInterval.seconds() ); } } // validate signature hash if (!decodeError) { UtlString validSignature; // signature-hash=MD5(<timestamp><secret><from-tag><call-id><identity>) NetMd5Codec signatureHash; signatureHash.hash(timestamp); signatureHash.hash(sSignatureSecret); if (isBound) { signatureHash.hash(fromTag); signatureHash.hash(callId); } signatureHash.hash(decodedIdentity); signatureHash.appendHashValue(validSignature); if (validSignature.compareTo(actualSignatureHash) == 0) { // the signature checks out mIdentity = decodedIdentity; mIsValidIdentity = TRUE; } else { Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "SipXauthIdentity::decode " "'%s' invalid signature '%s' != '%s'", identityValue.data(), actualSignatureHash.data(), validSignature.data() ); } } return mIsValidIdentity; }