Adds security timeout event, triggered whenever we tried and couldn't establish security connection (zrtp sends initial hello packets and don't receive response). Adds initial implementation of paranoia mode, if enabled and the other party didn't indicate encryption we fail the call.

cusax-fix
Damian Minkov 15 years ago
parent b58a019dc4
commit da7a976995

@ -91,8 +91,18 @@ public void run()
receivedCallDialog.setVisible(true);
final String peerName
= sourceCall.getCallPeers().next().getDisplayName();
Iterator<? extends CallPeer> peerIterator =
sourceCall.getCallPeers();
if(!peerIterator.hasNext())
{
if (receivedCallDialog.isVisible())
receivedCallDialog.setVisible(false);
return;
}
final String peerName = peerIterator.next().getDisplayName();
final Date callDate = new Date();
sourceCall.addCallChangeListener(new CallChangeAdapter()

@ -8,8 +8,10 @@
import java.beans.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
/**
* The <tt>CallPeerAdapter</tt> is an adapter that implements all common
@ -188,6 +190,25 @@ public void securityOff(CallPeerSecurityOffEvent securityOffEvent)
renderer.securityOff(securityOffEvent);
}
/**
* The handler for the security event received. The security event
* represents a timeout trying to establish a secure connection.
* Most probably the other peer doesn't support it.
*
* @param securityTimeoutEvent
* the security timeout event received
*/
public void securityTimeout(
CallPeerSecurityTimeoutEvent securityTimeoutEvent)
{
CallPeer peer = (CallPeer) securityTimeoutEvent.getSource();
if (!peer.equals(callPeer))
return;
renderer.securityTimeout(securityTimeoutEvent);
}
/**
* Adds a <tt>ConferenceMemberPanel</tt> to this container when a
* <tt>ConferenceMember</tt> has been added to the corresponding conference.

@ -77,6 +77,13 @@ public interface CallPeerRenderer
*/
public void securityOff(CallPeerSecurityOffEvent evt);
/**
* Indicates that the security is timeouted, is not supported by the
* other end.
* @param evt Details about the event that caused this message.
*/
public void securityTimeout(CallPeerSecurityTimeoutEvent evt);
/**
* Sets the call peer adapter that manages all related listeners.
*

@ -719,6 +719,16 @@ public void securityOff(CallPeerSecurityOffEvent evt)
}
}
/**
* Indicates that the security is timeouted, is not supported by the
* other end.
* @param evt Details about the event that caused this message.
*/
public void securityTimeout(CallPeerSecurityTimeoutEvent evt)
{
}
/**
* Sets the call peer adapter managing all related listeners.
* @param adapter the adapter to set

@ -341,6 +341,16 @@ public void securityOff(CallPeerSecurityOffEvent evt)
}
}
/**
* Indicates that the security is timeouted, is not supported by the
* other end.
* @param evt Details about the event that caused this message.
*/
public void securityTimeout(CallPeerSecurityTimeoutEvent evt)
{
}
/**
* Sets the call peer adapter that manages all related listeners.
*

@ -236,6 +236,16 @@ public void securityOff(CallPeerSecurityOffEvent evt)
}
}
/**
* Indicates that the security is timeouted, is not supported by the
* other end.
* @param evt Details about the event that caused this message.
*/
public void securityTimeout(CallPeerSecurityTimeoutEvent evt)
{
}
/**
* Sets the mute status icon to the status panel.
*

@ -350,6 +350,8 @@ public void zrtpNotSuppOther()
logger.debug(sessionTypeToString(sessionType)
+ ": Other party does not support ZRTP key negotiation protocol,"
+ " no secure calls possible.");
securityListener.securityTimeout(sessionType);
}
/**

@ -11,6 +11,7 @@
import org.jivesoftware.smack.packet.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
@ -127,6 +128,28 @@ public CallPeerGTalkImpl processGTalkInitiate(SessionIQ sessionIQ)
//before notifying about this call, make sure that it looks alright
callPeer.processSessionInitiate(sessionIQ);
// if paranoia is set, to accept the call we need to know that
// the other party has support for media encryption
if(getProtocolProvider().getAccountID().getAccountPropertyBoolean(
ProtocolProviderFactory.MODE_PARANOIA, false)
&& callPeer.getMediaHandler().getAdvertisedEncryptionMethods().length
== 0)
{
//send an error response;
String reasonText = "Encryption required!";
SessionIQ errResp = GTalkPacketFactory.createSessionTerminate(
sessionIQ.getTo(),
sessionIQ.getFrom(),
sessionIQ.getID(),
Reason.SECURITY_ERROR,
reasonText);
callPeer.setState(CallPeerState.FAILED, reasonText);
getProtocolProvider().getConnection().sendPacket(errResp);
return null;
}
if( callPeer.getState() == CallPeerState.FAILED)
return null;

@ -184,6 +184,28 @@ public CallPeerJabberImpl processSessionInitiate(JingleIQ jingleIQ)
//before notifying about this call, make sure that it looks alright
callPeer.processSessionInitiate(jingleIQ);
// if paranoia is set, to accept the call we need to know that
// the other party has support for media encryption
if(getProtocolProvider().getAccountID().getAccountPropertyBoolean(
ProtocolProviderFactory.MODE_PARANOIA, false)
&& callPeer.getMediaHandler().getAdvertisedEncryptionMethods().length
== 0)
{
//send an error response;
String reasonText = "Encryption required!";
JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
jingleIQ.getTo(),
jingleIQ.getFrom(),
jingleIQ.getSID(),
Reason.SECURITY_ERROR,
reasonText);
callPeer.setState(CallPeerState.FAILED, reasonText);
getProtocolProvider().getConnection().sendPacket(errResp);
return null;
}
if( callPeer.getState() == CallPeerState.FAILED)
return null;

@ -148,6 +148,21 @@ else if(ext.getNamespace().equals(
}
}
EncryptionPacketExtension encryptionPacketExtension
= offer.getFirstChildOfType(EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
ZrtpHashPacketExtension zrtpHashPacketExtension =
encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class);
if(zrtpHashPacketExtension != null
&& zrtpHashPacketExtension.getValue() != null)
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
}
for(MediaType mediaType : MediaType.values())
{
if(!(isAudio && mediaType == MediaType.AUDIO) &&
@ -339,6 +354,21 @@ public void processAnswer(RtpDescriptionPacketExtension answer)
{
List<PayloadTypePacketExtension> lst = answer.getPayloadTypes();
EncryptionPacketExtension encryptionPacketExtension
= answer.getFirstChildOfType(EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
ZrtpHashPacketExtension zrtpHashPacketExtension =
encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class);
if(zrtpHashPacketExtension != null
&& zrtpHashPacketExtension.getValue() != null)
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
}
boolean masterStreamSet = true;
for(MediaType mediaType : MediaType.values())
{

@ -398,6 +398,21 @@ public void processOffer(List<ContentPacketExtension> offer)
localContentMap.put(content.getName(), ourContent);
atLeastOneValidDescription = true;
EncryptionPacketExtension encryptionPacketExtension
= description.getFirstChildOfType(EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
ZrtpHashPacketExtension zrtpHashPacketExtension =
encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class);
if(zrtpHashPacketExtension != null
&& zrtpHashPacketExtension.getValue() != null)
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
}
}
if (!atLeastOneValidDescription)
@ -1178,6 +1193,21 @@ private void processContent(ContentPacketExtension content, boolean modify,
}
}
EncryptionPacketExtension encryptionPacketExtension
= description.getFirstChildOfType(EncryptionPacketExtension.class);
if(encryptionPacketExtension != null)
{
ZrtpHashPacketExtension zrtpHashPacketExtension =
encryptionPacketExtension.getFirstChildOfType(
ZrtpHashPacketExtension.class);
if(zrtpHashPacketExtension != null
&& zrtpHashPacketExtension.getValue() != null)
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
}
// create the corresponding stream...
initStream(content.getName(), connector, dev,
supportedFormats.get(0), target, direction, rtpExtensions,

@ -665,7 +665,7 @@ && getPeer().getCall().isSipZrtpAttribute())
String helloHash = zcontrol.getHelloHash();
if(helloHash != null && helloHash.length() > 0)
md.setAttribute("zrtp-hash", helloHash);
md.setAttribute(SdpUtils.ZRTP_HASH_ATTR, helloHash);
}
catch (SdpException ex)
@ -870,6 +870,8 @@ private synchronized void processAnswer(SessionDescription answer)
this.setCallInfoURL(SdpUtils.getCallInfoURL(answer));
boolean masterStreamSet = false;
boolean hasZrtp = false;
boolean hasSdes = false;
List<MediaType> seenMediaTypes = new ArrayList<MediaType>();
for (MediaDescription mediaDescription : remoteDescriptions)
{
@ -1012,6 +1014,8 @@ private synchronized void processAnswer(SessionDescription answer)
it.remove();
}
}
hasSdes = true;
}
}
@ -1034,10 +1038,25 @@ private synchronized void processAnswer(SessionDescription answer)
}
}
try
{
hasZrtp = mediaDescription.getAttribute(
SdpUtils.ZRTP_HASH_ATTR) != null;
}
catch (SdpParseException e)
{
logger.error("received an unparsable sdp attribute", e);
}
// create the corresponding stream...
initStream(connector, dev, supportedFormats.get(0), target,
direction, rtpExtensions, masterStream);
}
if(hasSdes)
addAdvertisedEncryptionMethod(SrtpControlType.SDES);
if(hasZrtp)
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
}
/**

@ -258,6 +258,8 @@ private CallPeerSipImpl createCallPeerFor(
mediaDirections.put(MediaType.AUDIO, MediaDirection.INACTIVE);
mediaDirections.put(MediaType.VIDEO, MediaDirection.INACTIVE);
boolean hasZrtp = false;
boolean hasSdes = false;
//this check is not mandatory catch all to skip if a problem exists
try
{
@ -279,6 +281,9 @@ private CallPeerSipImpl createCallPeerFor(
MediaType mediaType =
SdpUtils.getMediaType(mediaDescription);
hasZrtp = hasZrtp || mediaDescription.getAttribute(
SdpUtils.ZRTP_HASH_ATTR) != null;
if(mediaType.equals(MediaType.VIDEO))
{
MediaDirection videoDirection =
@ -293,6 +298,25 @@ else if(mediaType.equals(MediaType.AUDIO))
mediaDirections.put(MediaType.AUDIO,
audioDirection);
}
@SuppressWarnings("unchecked")
Vector<Attribute> attrs =
mediaDescription.getAttributes(true);
for (Attribute a : attrs)
{
try
{
if (a.getName().equals("crypto"))
{
hasSdes = true;
}
}
catch (SdpParseException e)
{
logger.error(
"received an unparsable sdp attribute", e);
}
}
}
}
}
@ -309,6 +333,18 @@ else if(mediaType.equals(MediaType.AUDIO))
this,
mediaDirections);
}
if(hasZrtp)
{
callPeer.getMediaHandler().addAdvertisedEncryptionMethod(
SrtpControlType.ZRTP);
}
if(hasSdes)
{
callPeer.getMediaHandler().addAdvertisedEncryptionMethod(
SrtpControlType.SDES);
}
}
return callPeer;

@ -190,7 +190,7 @@ public Call createCall(Contact callee, CallGroup group)
* for use by other <tt>OperationSet</tt>s willing to initialize
* <tt>Call</tt>s and willing to control their establishment in ways
* different than {@link #createOutgoingCall(Address,
* javax.sip.message.Message)}.
* javax.sip.message.Message,CallGroup)}.
*
* @return a new outgoing <tt>Call</tt> with no peers in it
* @throws OperationFailedException if initializing the new outgoing
@ -623,12 +623,29 @@ && isRemoteControlNotification(request))
|| (responseStatusCodeRange == 5)
|| (responseStatusCodeRange == 6))
{
logger.error("Received error: " + response.getStatusCode()
+ " " + response.getReasonPhrase());
String reason = response.getReasonPhrase();
WarningHeader warningHeader
= (WarningHeader)response.getHeader(WarningHeader.NAME);
if(warningHeader != null)
{
reason = warningHeader.getText();
logger.error("Received error: " + response.getStatusCode()
+ " " + response.getReasonPhrase()
+ " " + warningHeader.getText()
+ "-" + warningHeader.getAgent()
+ "-" + warningHeader.getName());
}
else
{
logger.error("Received error: " + response.getStatusCode()
+ " " + response.getReasonPhrase());
}
if (callPeer != null)
callPeer.setState(CallPeerState.FAILED,
response.getReasonPhrase());
reason);
processed = true;
}
@ -1053,7 +1070,56 @@ private void processInvite(SipProvider sourceProvider,
//this is a brand new call (not a transferred one)
CallSipImpl call = new CallSipImpl(this);
call.processInvite(sourceProvider, serverTransaction);
MediaAwareCallPeer peer =
call.processInvite(sourceProvider, serverTransaction);
if(getProtocolProvider().getAccountID()
.getAccountPropertyBoolean(
ProtocolProviderFactory.MODE_PARANOIA, false)
&& peer.getMediaHandler()
.getAdvertisedEncryptionMethods().length == 0)
{
// if in paranoia mode and we don't find any encryption
// fail peer/call send error with warning explaining why
peer.setState(
CallPeerState.FAILED,
"Encryption required!",
Response.SESSION_NOT_ACCEPTABLE);
// 606 Not acceptable
// warning header : encryption required
WarningHeader warning = null;
try
{
//399 Miscellaneous warning
warning = protocolProvider.getHeaderFactory()
.createWarningHeader(
protocolProvider.getAccountID().getService()
, 399, "Encryption required!");
}
catch(InvalidArgumentException e)
{
logger.error("Cannot create warning header", e);
}
catch(ParseException e)
{
logger.error("Cannot create warning header", e);
}
try
{
protocolProvider.sayError(serverTransaction,
Response.SESSION_NOT_ACCEPTABLE,
warning);
}
catch(OperationFailedException e)
{
logger.error("Cannot send 606 error!", e);
}
return;
}
// checks for auto answering of call, if no further processing
// is needed return

@ -1938,6 +1938,25 @@ public void sendAck(ClientTransaction clientTransaction)
*/
public void sayError(ServerTransaction serverTransaction, int errorCode)
throws OperationFailedException
{
sayError(serverTransaction, errorCode, null);
}
/**
* Send an error response with the <tt>errorCode</tt> code using
* <tt>serverTransaction</tt>.
*
* @param serverTransaction the transaction that we'd like to send an error
* response in.
* @param errorCode the code that the response should have.
*
* @throws OperationFailedException if we failed constructing or sending a
* SIP Message.
*/
public void sayError(ServerTransaction serverTransaction,
int errorCode,
Header header)
throws OperationFailedException
{
Request request = serverTransaction.getRequest();
Response errorResponse = null;
@ -1946,6 +1965,9 @@ public void sayError(ServerTransaction serverTransaction, int errorCode)
errorResponse = getMessageFactory().createResponse(
errorCode, request);
if(header != null)
errorResponse.setHeader(header);
//we used to be adding a To tag here and we shouldn't. 3261 says:
//"Dialogs are created through [...] non-failure responses". and
//we are using this method for failure responses only.

@ -50,6 +50,11 @@ public class SdpUtils
*/
private static final String EXTMAP_ATTR = "extmap";
/**
* The name of the SDP attribute that defines zrtp hello hash.
*/
public static final String ZRTP_HASH_ATTR = "zrtp-hash";
/**
* Parses the specified <tt>sdp String</tt> into a
* <tt>SessionDescription</tt> and returns it;

@ -670,7 +670,7 @@ public static NotificationData fireNotification(String eventType)
/**
* Stops all sounds for the given event type.
*
* @param notificationData the event type for which we should stop sounds. One of
* @param data the event type for which we should stop sounds. One of
* the static event types defined in this class.
*/
public static void stopSound(NotificationData data)
@ -1200,6 +1200,18 @@ public void securityOn(CallPeerSecurityOnEvent evt)
public void securityOff(CallPeerSecurityOffEvent securityOffEvent)
{}
/**
* The handler for the security event received. The security event
* represents a timeout trying to establish a secure connection.
* Most probably the other peer doesn't support it.
*
* @param securityTimeoutEvent
* the security timeout event received
*/
public void securityTimeout(
CallPeerSecurityTimeoutEvent securityTimeoutEvent)
{}
/**
* Processes the received security message.
* @param event the event we received

@ -50,4 +50,12 @@ public void securityTurnedOn( int sessionType,
public void securityMessageReceived(String message,
String i18nMessage,
int severity);
/**
* Indicates that the other party has timeouted replying to our
* offer to secure the connection.
*
* @param sessionType the type of the call session - audio or video.
*/
public void securityTimeout(int sessionType);
}

@ -334,6 +334,36 @@ protected void fireCallPeerSecurityOffEvent(CallPeerSecurityOffEvent evt)
}
}
/**
* Constructs a <tt>CallPeerSecurityStatusEvent</tt> using this call peer as
* source, setting it to be of type <tt>eventType</tt> and the corresponding
* <tt>oldValue</tt> and <tt>newValue</tt>.
*
* @param evt the event object with details to pass on to the consumers
*/
protected void fireCallPeerSecurityTimeoutEvent(
CallPeerSecurityTimeoutEvent evt)
{
lastSecurityEvent = evt;
if (logger.isDebugEnabled())
logger.debug("Dispatching a CallPeerSecurityStatusEvent event to "
+ callPeerSecurityListeners.size()
+" listeners. event is: " + evt.toString());
List<CallPeerSecurityListener> listeners = null;
synchronized (callPeerSecurityListeners)
{
listeners = new ArrayList<CallPeerSecurityListener>(
callPeerSecurityListeners);
}
for(CallPeerSecurityListener listener : listeners)
{
listener.securityTimeout(evt);
}
}
/**
* Constructs a <tt>CallPeerSecurityStatusEvent</tt> using this call peer as
* source, setting it to be of type <tt>eventType</tt> and the corresponding

@ -403,6 +403,12 @@ public abstract class ProtocolProviderFactory
*/
public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
/**
* Paranoia mode when turned on requires all calls to be secure and
* indicated as such.
*/
public static final String MODE_PARANOIA = "MODE_PARANOIA";
/**
* The <code>BundleContext</code> containing (or to contain) the service
* registration of this factory.

@ -43,6 +43,17 @@ public void securityOn(
public void securityOff(
CallPeerSecurityOffEvent securityEvent);
/**
* The handler for the security event received. The security event
* represents a timeout trying to establish a secure connection.
* Most probably the other peer doesn't support it.
*
* @param securityTimeoutEvent
* the security timeout event received
*/
public void securityTimeout(
CallPeerSecurityTimeoutEvent securityTimeoutEvent);
/**
* The handler of the security message event.
*

@ -0,0 +1,39 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.service.protocol.event;
import net.java.sip.communicator.service.protocol.*;
/**
* The <tt>CallPeerSecurityTimeoutEvent</tt> is triggered whenever a
* communication with a given peer cannot be established, the peer
* did not answer our tries to secure the connection.
*
* @author Damian Minkov
*/
public class CallPeerSecurityTimeoutEvent
extends CallPeerSecurityStatusEvent
{
/**
* Serial version UID.
*/
private static final long serialVersionUID = 0L;
/**
* The event constructor
*
* @param callPeer the call peer associated with this event
* @param sessionType the type of the session, either
* {@link CallPeerSecurityStatusEvent#AUDIO_SESSION} or
* {@link CallPeerSecurityStatusEvent#VIDEO_SESSION}
*/
public CallPeerSecurityTimeoutEvent( CallPeer callPeer,
int sessionType)
{
super(callPeer, sessionType);
}
}

@ -88,6 +88,13 @@ public abstract class CallPeerMediaHandler<
private MediaDirection audioDirectionUserPreference
= MediaDirection.SENDRECV;
/**
* List of advertised encryption methods. Indicated before establishing the
* call.
*/
private List<SrtpControlType> advertisedEncryptionMethods =
new ArrayList<SrtpControlType>();
/**
* A reference to the CallPeer instance that this handler is managing media
* streams for.
@ -722,6 +729,29 @@ public boolean isSecure()
return true;
}
/**
* Returns the advertised methods for securing the call,
* this are the methods like SDES, ZRTP that are
* indicated in the initial session initialization. Missing here doesn't
* mean the other party don't support it.
* @return the advertised encryption methods.
*/
public SrtpControlType[] getAdvertisedEncryptionMethods()
{
return advertisedEncryptionMethods.toArray(
new SrtpControlType[advertisedEncryptionMethods.size()]);
}
/**
* Adds encryption method to the list of advertised secure methods.
* @param encryptionMethod the method to add.
*/
public void addAdvertisedEncryptionMethod(SrtpControlType encryptionMethod)
{
if(!advertisedEncryptionMethods.contains(encryptionMethod))
advertisedEncryptionMethods.add(encryptionMethod);
}
/**
* Passes <tt>multiStreamData</tt> to the video stream that we are using
* in this media handler (if any) so that the underlying SRTP lib could

@ -686,6 +686,19 @@ public void securityTurnedOn(int sessionType, String cipher,
fireCallPeerSecurityOnEvent(evt);
}
/**
* Indicates that the other party has timeouted replying to our
* offer to secure the connection.
*
* @param sessionType the type of the call session - audio or video.
*/
public void securityTimeout(int sessionType)
{
CallPeerSecurityTimeoutEvent evt =
new CallPeerSecurityTimeoutEvent(this, sessionType);
fireCallPeerSecurityTimeoutEvent(evt);
}
/**
* Sets the security status to OFF for this call peer.
*

Loading…
Cancel
Save