diff --git a/resources/images/images.properties b/resources/images/images.properties index 86884200e..0cd7ed6f2 100644 --- a/resources/images/images.properties +++ b/resources/images/images.properties @@ -59,6 +59,8 @@ DIAL_BUTTON=resources/images/impl/gui/buttons/dialButton.png HOLD_BUTTON=resources/images/impl/gui/buttons/holdButton.png MUTE_BUTTON=resources/images/impl/gui/buttons/muteButton.png TRANSFER_CALL_BUTTON=resources/images/impl/gui/buttons/transferCallButton.png +SECURE_BUTTON_ON=resources/images/impl/gui/buttons/secureOn.png +SECURE_BUTTON_OFF=resources/images/impl/gui/buttons/secureOff.png INVITE_DIALOG_ICON=resources/images/impl/gui/common/inviteDialogIcon.png SEND_SMS_ICON=resources/images/impl/gui/common/gsm.png DIAL_BUTTON_BG=resources/images/impl/gui/buttons/dialButtonBg.png @@ -410,4 +412,4 @@ popupIcon=resources/images/plugin/notificationconfiguration/popupIcon.png soundIcon=resources/images/plugin/notificationconfiguration/soundIcon.png activatedIcon=resources/images/plugin/notificationconfiguration/activeIcon.png desactivatedIcon=resources/images/plugin/notificationconfiguration/desactivatedIcon.png -foldericon=resources/images/plugin/notificationconfiguration/folder.png \ No newline at end of file +foldericon=resources/images/plugin/notificationconfiguration/folder.png diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index c8dc551f0..4c70edb66 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -700,3 +700,24 @@ restore=Restore Defaults playsound=Play a sound : execprog=Execute a program : displaypopup=Show a message in a pop-up window + +# ZRTP Securing +toggleOffSecurity=Toggle OFF secure call mode +toggleOnSecurity=Try toggle ON secure call mode +engineInitFailure=Securing engine initialization failure +allowClearRequestFailure=Peer doesn't support unsecuring the call +defaultSASTooltip=Secure status field +defaultSASMessage= Not in call +sasSecuredTooltip=Check this string by voice with your peer to verify secure comm link +sasSecuredMessage=Secured: +sasNotSecuredTooltip=Secure mode not enabled +sasNotSecuredMessage=Not secured +sasEngineFailTooltip=Engine failed at initialization +sasEngineFailMessage=Engine failure +sasSecuringFailTooltip=Securing call failed +sasUnsecuredAtRequestTooltip=Call unsecured at request +peerUnsuportedSecurity=You have enabled secure init mode
You can't switch off this mode at this moment
The call is not secured yet anyway +sasPeerUnsuportedTooltip=Securing call not supported/enabled by the other peer +peerToggledOffSecurityMessage=Call peer toggled secure mode off +peerToggledOffSecurityCaption=Secure Off +sasUnsecuredAtPeerRequestTooltip=Call unsecured at peer request diff --git a/resources/languages/resources_ro.properties b/resources/languages/resources_ro.properties index d6245c38b..020a8879b 100644 --- a/resources/languages/resources_ro.properties +++ b/resources/languages/resources_ro.properties @@ -295,3 +295,25 @@ september=Sep october=Oct november=Nov december=Dec + +# ZRTP Securing +toggleOffSecurity=DeactiveazÄ? modul securizat +toggleOnSecurity=ActiveazÄ? modul securizat +engineInitFailure=Eroare la iniÈ?ializare +allowClearRequestFailure=Nu este suportatÄ? anularea securizÄ?rii +defaultSASTooltip=Cîmp de stare al securizÄ?rii +defaultSASMessage= Inactiv +sasSecuredTooltip=ConsultaÈ?i acest string prin voce cu interlocutorul pentru a verifica securizarea +sasSecuredMessage=Securizat: +sasNotSecuredTooltip=Mod securizat neactivat +sasNotSecuredMessage=Nesecurizat +sasEngineFailTooltip=Securizare eÈ?uatÄ? la iniÈ?ializare +sasEngineFailMessage=Securizare eÈ?uatÄ? +sasSecuringFailTooltip=Securizarea apelului eÈ?uatÄ? +sasUnsecuredAtRequestTooltip=Securizare anulatÄ? la cerere +peerUnsuportedSecurity=Modul securizat a fost activat
Nu puteÈ?i schimba acest mod la acest moment
Oricum apelul nu este încÄ? securizat +sasPeerUnsuportedTooltip=Securizarea apelului nu este suportatÄ?/activatÄ? de interlocutor +peerToggledOffSecurityMessage=Interlocutorul a anulat modul securizat +peerToggledOffSecurityCaption=Mod securizat anulat +sasUnsecuredAtPeerRequestTooltip=Securizarea apelului anulatÄ? la cererea interlocutorului + diff --git a/src/net/java/sip/communicator/impl/gui/main/call/SecureButton.java b/src/net/java/sip/communicator/impl/gui/main/call/SecureButton.java new file mode 100644 index 000000000..402b39b6d --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/SecureButton.java @@ -0,0 +1,91 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.event.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.impl.gui.i18n.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.media.*; + +/** + * The UI button used to toggle on or off call securing + * + * @author Emanuel Onica + * + */ +public class SecureButton + extends JButton + implements ActionListener +{ + private final CallParticipant callParticipant; + + public SecureButton(CallParticipant callParticipant) + { + super(new ImageIcon(ImageLoader.getImage(ImageLoader.SECURE_BUTTON_OFF))); + this.callParticipant = callParticipant; + this.addActionListener(this); + } + + public void actionPerformed(ActionEvent evt) + { + Call call = callParticipant.getCall(); + + if (call != null) + { + String command = evt.getActionCommand(); + if (command.equals("startSecureMode")) + { + OperationSetBasicTelephony telephony = + (OperationSetBasicTelephony) call.getProtocolProvider() + .getOperationSet(OperationSetBasicTelephony.class); + + if (telephony.getSecured(callParticipant)) + { + updateSecureButton(false); + telephony.setSecured(callParticipant, + false, + OperationSetBasicTelephony. + SecureStatusChangeSource.SECURE_STATUS_CHANGE_BY_LOCAL); + } + else + { + updateSecureButton(true); + telephony.setSecured(callParticipant, + true, + OperationSetBasicTelephony. + SecureStatusChangeSource.SECURE_STATUS_CHANGE_BY_LOCAL); + } + } + } + } + + + /** + * The method used to update the secure button state (pressed or not pressed) + * + * @param isSecured parameter reflecting the current button state + */ + public void updateSecureButton(boolean isSecured) + { + if(isSecured) + { + this.setIcon( + new ImageIcon(ImageLoader.getImage(ImageLoader.SECURE_BUTTON_ON))); + this.setToolTipText(Messages.getI18NString("toggleOffSecurity").getText()); + } + else + { + this.setIcon( + new ImageIcon(ImageLoader.getImage(ImageLoader.SECURE_BUTTON_OFF))); + this.setToolTipText(Messages.getI18NString("toggleOnSecurity").getText()); + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java index a802cd029..81274207a 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java +++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java @@ -455,6 +455,18 @@ public class ImageLoader { */ public static final ImageID TRANSFER_CALL_BUTTON = new ImageID("TRANSFER_CALL_BUTTON"); + + /** + * The secure button on icon. The icon shown in the CallParticipant panel. + */ + public static final ImageID SECURE_BUTTON_ON = + new ImageID("SECURE_BUTTON_ON"); + + /** + * The secure button off icon. The icon shown in the CallParticipant panel. + */ + public static final ImageID SECURE_BUTTON_OFF = + new ImageID("SECURE_BUTTON_OFF"); /** * The image used, when a contact has no photo specified. @@ -462,7 +474,6 @@ public class ImageLoader { public static final ImageID DEFAULT_USER_PHOTO = new ImageID("DEFAULT_USER_PHOTO"); - /** * The minimize button icon in the CallPanel. */ diff --git a/src/net/java/sip/communicator/impl/media/CallSessionImpl.java b/src/net/java/sip/communicator/impl/media/CallSessionImpl.java index 5be615273..0e4b02c71 100644 --- a/src/net/java/sip/communicator/impl/media/CallSessionImpl.java +++ b/src/net/java/sip/communicator/impl/media/CallSessionImpl.java @@ -17,6 +17,10 @@ import javax.media.rtp.event.*; import javax.sdp.*; +import net.java.sip.communicator.impl.media.transform.*; +import net.java.sip.communicator.impl.media.transform.srtp.*; +import net.java.sip.communicator.impl.media.transform.zrtp.*; +import net.java.sip.communicator.impl.media.keyshare.*; import net.java.sip.communicator.service.media.*; import net.java.sip.communicator.service.media.MediaException; import net.java.sip.communicator.service.netaddr.*; @@ -26,6 +30,7 @@ import net.java.sip.communicator.impl.media.codec.*; import javax.media.control.*; +import gnu.java.zrtp.*; /** * Contains parameters associated with a particular Call such as media (audio @@ -60,6 +65,7 @@ * @author Ken Larson * @author Dudek Przemyslaw * @author Lubomir Marinov + * @author Emanuel Onica */ public class CallSessionImpl implements CallSession @@ -69,6 +75,7 @@ public class CallSessionImpl , SendStreamListener , SessionListener , ControllerListener + , SecureEventListener { private static final Logger logger @@ -143,6 +150,29 @@ public class CallSessionImpl * session. */ private List videoFrames = new ArrayList(); + + /** + * SRTP TransformConnectors corresponding to each RTPManager when SRTP + * feature is enabled. + */ + private Hashtable transConnectors = new Hashtable(); + + /** + * Toggles default (from the call start) activation + * of secure communication + */ + private boolean usingSRTP = false; + + /** + * Vector used to hold references of various key management solutions implemented. + * For now only ZRTP and Dummy (hardcoded keys) are present. + */ + private Vector keySharingAlgorithms = null; + + /** + * The key management solution type used for the current session + */ + private KeyProviderAlgorithm selectedKeyProviderAlgorithm = null; /** * The Custom Data Destination used for this call session. @@ -236,6 +266,8 @@ public CallSessionImpl(Call call, call.addCallChangeListener(this); initializePortNumbers(); + + initializeSupportedKeyProviders(); } /** @@ -440,7 +472,31 @@ private void stopStreaming(RTPManager rtpManager, } //remove targets - rtpManager.removeTargets("Session ended."); + if (selectedKeyProviderAlgorithm != null + /* TODO: Video securing related code + * remove the next condition as part of enabling video securing + * (see comments insecureStatusChanged method for more info) + */ + && rtpManager.equals(audioRtpManager) + ) + { + TransformConnector transConnector + = (TransformConnector) this.transConnectors.get(rtpManager); + + if (transConnector != null) + { + ZRTPTransformEngine engine = (ZRTPTransformEngine)transConnector.getEngine(); + + engine.sendInfo(ZrtpCodes.MessageSeverity.Info, + EnumSet.of(ZRTPCustomInfoCodes.ZRTPDisabledByCallEnd)); + + transConnector.removeTargets(); + } + } + else + { + rtpManager.removeTargets("Session ended."); + } printFlowStatistics(rtpManager); @@ -1165,7 +1221,29 @@ private void initStreamTargets(Connection globalConnParam, try { - rtpManager.addTarget(target); + if (selectedKeyProviderAlgorithm != null + /* TODO: Video securing related code + * remove the next condition as part of enabling video securing + * (see comments insecureStatusChanged method for more info) + */ + && rtpManager.equals(audioRtpManager) + ) + { + TransformConnector transConnector = + (TransformConnector) this.transConnectors.get(rtpManager); + + if (transConnector == null) + { + throw new Exception(); + } + + transConnector.addTarget(target); + } + else + { + rtpManager.addTarget(target); + } + logger.trace("added target " + target + " for type " + type); } @@ -1869,9 +1947,120 @@ private void initializeRtpManager(RTPManager rtpManager, SessionAddress bindAddress) throws MediaException { + + /* Select a key management type from the present ones to use + * for now using the zero - top priority solution (ZRTP); + * TODO: should be extended to a selection algorithm to choose the + * key management type + */ + selectedKeyProviderAlgorithm = selectKeyProviderAlgorithm(0); + try - { - rtpManager.initialize(bindAddress); + { + // Selected key management type == ZRTP branch + if (selectedKeyProviderAlgorithm != null && + selectedKeyProviderAlgorithm.getProviderType() == + KeyProviderAlgorithm.ProviderType.ZRTP_PROVIDER + /* TODO: Video securing related code + * remove the next condition as part of enabling video securing + * (see comments insecureStatusChanged method for more info) + */ + && rtpManager.equals(audioRtpManager)) + { + // Set a ZRTP connector to use for communication + TransformConnector transConnector = null; + + TransformManager.initializeProviders(); + + // The connector is created based also on the crypto services provider type; + // The crypto provider solution should be queried somehow + // or taken from a resources file + transConnector = TransformManager.createZRTPConnector(bindAddress, + "BouncyCastle", + this); + rtpManager.initialize(transConnector); + this.transConnectors.put(rtpManager, transConnector); + + // ZRTP engine initialization + // TODO: 1. must query/randomize/find a method for the zid file name + // 2. must define an exception for initialization failure + + ZRTPTransformEngine engine = (ZRTPTransformEngine)transConnector.getEngine(); + // Case 1: user toggled secure communication prior to the call + if (usingSRTP) + { + if (!engine.initialize("my_zid.zid")) + engine.sendInfo(ZrtpCodes.MessageSeverity.Info, + EnumSet.of(ZRTPCustomInfoCodes.ZRTPEngineInitFailure)); + + } + // Case 2: user will toggle secure communication during the call + // (it's not set on at this point) + else + { + engine.sendInfo(ZrtpCodes.MessageSeverity.Info, + EnumSet.of(ZRTPCustomInfoCodes.ZRTPNotEnabledByUser)); + } + + logger.trace("RTP"+ + (rtpManager.equals(audioRtpManager)?" audio ":"video")+ + "manager initialized through connector"); + } + else + // Selected key management type == Dummy branch - hardcoded keys + if (selectedKeyProviderAlgorithm != null && + selectedKeyProviderAlgorithm.getProviderType() == + KeyProviderAlgorithm.ProviderType.DUMMY_PROVIDER + /* TODO: Video securing related code + * remove the next condition as part of enabling video securing + * (see comments insecureStatusChanged method for more info) + */ + && rtpManager.equals(audioRtpManager)) + { + SRTPPolicy srtpPolicy = + new SRTPPolicy(SRTPPolicy.AESF8_ENCRYPTION, 16, + SRTPPolicy.HMACSHA1_AUTHENTICATION, 20, 10, 14); + + // Master key and master salt are hardcoded + byte[] masterKey = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + + byte[] masterSalt = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d}; + + TransformConnector transConnector = null; + + TransformManager.initializeProviders(); + + // The connector is created based also on the crypto services provider type; + // The crypto provider solution should be queried somehow + // or taken from a resources file + transConnector = + TransformManager.createSRTPConnector(bindAddress, + masterKey, + masterSalt, + srtpPolicy, + srtpPolicy, + "BouncyCastle"); + + rtpManager.initialize(transConnector); + this.transConnectors.put(rtpManager, transConnector); + + logger.trace("RTP"+ + (rtpManager.equals(audioRtpManager)?" audio ":"video")+ + "manager initialized through connector"); + + } + // No key management solution - unsecured communication branch + else + { + rtpManager.initialize(bindAddress); + + logger.trace("RTP"+ + (rtpManager.equals(audioRtpManager)?" audio ":"video")+ + "manager initialized normally"); + } + } catch (Exception exc) { @@ -1977,9 +2166,9 @@ public void callStateChanged(CallChangeEvent evt) logger.error("Failed to start streaming.", ex); } } - else if( evt.getNewValue() == CallState.CALL_ENDED + else if( evt.getNewValue() == CallState.CALL_ENDED && evt.getNewValue() != evt.getOldValue()) - { + { logger.warn("Stopping streaming."); stopStreaming(); mediaServCallback.getMediaControl(getCall()) @@ -2408,10 +2597,241 @@ public void run() } } } + + /** + * Method for getting the default secure status value for communication + * + * @return the default enabled/disabled status value for secure communication + */ + public boolean getSecureCommunicationStatus() + { + return usingSRTP; + } + + /** + * Method for setting the default secure status value for communication + * Also has the role to trigger going secure from not secured or viceversa + * Notifies any present CallSession of change in the status value for this purpose + * + * @param activator setting for default communication securing + * @param source the source of changing the secure status (local or remote) + */ + public void setSecureCommunicationStatus(boolean activator, + OperationSetBasicTelephony. + SecureStatusChangeSource source) + { + + logger.trace("Call session secure status change event request received"); + + // Make the change for default security enabled/disabled start option + usingSRTP = activator; + + // Fire the change event to notify any present CallSession of security change status + // if not the case of a reverted secure state + // (usually case of previous change rejected due to an error) + if (source != OperationSetBasicTelephony. + SecureStatusChangeSource.SECURE_STATUS_REVERTED) + fireSecureStatusChanged(activator, source); + + } + + /** + * Fire the event of change in security status + * + * @param activator type of change - enable/disable communication security + * @param source the source of changing the secure status (local or remote) + */ + private synchronized void fireSecureStatusChanged(boolean activator, + OperationSetBasicTelephony. + SecureStatusChangeSource source) + { + if (activator) + { + this.secureStatusChanged( + new SecureEvent(this, + SecureEvent.SECURE_COMMUNICATION, + source)); + } + else + { + this.secureStatusChanged( + new SecureEvent(this, + SecureEvent.UNSECURE_COMMUNICATION, + source)); + } + } + + /** + * The method for changing security status for a specific RTPManager when + * the ZRTP key sharing solution is used. + * Called when a new SecureEvent is received. + * + * @param manager The RTP manager for which the media streams + * will be secured or unsecured + * @param event The secure status changed event + */ + public void ZRTPChangeStatus(RTPManager manager, SecureEvent event) + { + int newStatus = event.getEventID(); + OperationSetBasicTelephony.SecureStatusChangeSource source = event.getSource(); + + TransformConnector transConnector = + (TransformConnector) this.transConnectors.get(manager); + + ZRTPTransformEngine engine = (ZRTPTransformEngine)transConnector.getEngine(); + + // Perform ZRTP engine actions only if triggered by local peer - user commands; + // If the remote peer caused the event only general call session security status + // is changed (done before event processing) + if (source == OperationSetBasicTelephony. + SecureStatusChangeSource.SECURE_STATUS_CHANGE_BY_LOCAL) + { + if (newStatus == SecureEvent.SECURE_COMMUNICATION) + { + // Secure the comm after the call begins + if (!engine.isStarted()) + { + logger.trace("Normal call securing event processing"); + + if (!engine.initialize("my_zid.zid")) + engine.sendInfo(ZrtpCodes.MessageSeverity.Info, + EnumSet.of(ZRTPCustomInfoCodes.ZRTPEngineInitFailure)); + } + else + { + logger.trace("GoSecure call event processing"); + + // This point isn't reached if GoClear is not enabled + SCCallback cb = (SCCallback)engine.getUserCallback(); + cb.setGCGSByPeerFlag(false); + engine.requestGoSecure(); + } + } + else + { + // At this moment this attempts a GoClear request but + // fails due to GoClear code disabled (failing doesn't result + // in error, only in a warning tooltip set on the button, and + // a change of it's internal state to prevent further attempts) + // to re-enable GoClear uncomment the specific code parts in ZRTP4J + logger.trace("GoClear call event processing"); + + SCCallback cb = (SCCallback)engine.getUserCallback(); + cb.setGCGSByPeerFlag(false); + engine.requestGoClear(); + } + } + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.impl.media.SecureEventListener#secureStatusChanged(net.java.sip.communicator.impl.media.SecureEvent) + */ + public void secureStatusChanged(SecureEvent secureEvent) + { + // If the current selected key management solution type is ZRTP + // act accordingly + if (selectedKeyProviderAlgorithm.getProviderType() == + KeyProviderAlgorithm.ProviderType.ZRTP_PROVIDER) + { + ZRTPChangeStatus(this.audioRtpManager, secureEvent); + + /* TODO: Video securing related code + * + * We disable for the moment the video securing due to (yet) unsuported + * multistream mode in ZRTP4J; This can be re-enabled and attempted as is + * implemented, using a separate instance of ZRTP engine which will + * attempt securing through DH mode; This might result in some unexpected + * behavior at the GUI level, but in theory should work; However, due to + * incomplete standard compliance and potential problems mentioned we leave + * it disabled; To enable just check the other "Video securing related code" + * sections in this source + * + * Uncomment the next line as part of enabling video securing + */ + //ZRTPChangeStatus(this.videoRtpManager, secureEvent); + } + } /** - * Determines whether the audio of this session is (set to) mute. + * Initializes the supported key management types and establishes + * default usage priorities for them. + * This part should be further developed (by adding a more detailed + * priority setting mechanism in case of addition of other security + * providers). + */ + public void initializeSupportedKeyProviders() + { + if (keySharingAlgorithms == null) + keySharingAlgorithms = new Vector(); + + DummyKeyProvider dummyProvider = new DummyKeyProvider(1); + ZRTPKeyProvider zrtpKeyProvider = new ZRTPKeyProvider(0); + + keySharingAlgorithms.add(zrtpKeyProvider.getPriority(), zrtpKeyProvider); + keySharingAlgorithms.add(dummyProvider.getPriority(), dummyProvider); + } + + /** + * Selects a default key management type to use in securing based + * on which the actual implementation for that solution will be started + * This part should be further developed (by adding a more detailed + * priority choosing mechanism in case of addition of other security + * providers). + * + * @return the default keymanagement type used in securing + */ + public KeyProviderAlgorithm selectDefaultKeyProviderAlgorithm() + { + KeyProviderAlgorithm defaultProvider = + (KeyProviderAlgorithm)keySharingAlgorithms.get(0); + + if (defaultProvider == null) + { + return new DummyKeyProvider(0); + } + else + { + return defaultProvider; + } + } + + /** + * Selects a key management type to use in securing based on priority + * For now the priorities are equal with the position in the Vector + * holding the keymanagement types. + * This part should be further developed (by adding a more detailed + * priority choosing mechanism in case of addition of other security + * providers). + * + * @param priority the priority of the selected key management type - 0 is top + * @return the selected key management type + */ + public KeyProviderAlgorithm selectKeyProviderAlgorithm(int priority) + { + KeyProviderAlgorithm selectedProvider = + (KeyProviderAlgorithm)keySharingAlgorithms.get(priority); + + return selectedProvider; + } + + /** + * Additional info codes for ZRTP4J. + * These could be added to the library. However they are specific for this + * implementation, needing them for various GUI changes. * + */ + public static enum ZRTPCustomInfoCodes + { + ZRTPNotEnabledByUser, + ZRTPDisabledByCallEnd, + ZRTPEngineInitFailure; + + } + + /** + * Determines whether the audio of this session is (set to) mute. + * * @return true if the audio of this session is (set to) mute; * otherwise, false */ @@ -2422,7 +2842,7 @@ public boolean isMute() /** * Sets the mute state of the audio of this session. - * + * * @param mute true to mute the audio of this session; otherwise, * false */ diff --git a/src/net/java/sip/communicator/impl/media/keyshare/DummyKeyProvider.java b/src/net/java/sip/communicator/impl/media/keyshare/DummyKeyProvider.java new file mode 100644 index 000000000..5451e3af3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/keyshare/DummyKeyProvider.java @@ -0,0 +1,53 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.media.keyshare; + +import net.java.sip.communicator.impl.media.transform.srtp.*; + +/** + * DummyProvider class implements KeyProvider interface. + * Used only for testing - activates the hardcoded keys behaviour for SRTP traffic. + * + * @author Emanuel Onica (eonica@info.uaic.ro) + * + */ +public class DummyKeyProvider + implements KeyProviderAlgorithm +{ + + /** + * The constant provider type of this class + */ + private static final KeyProviderAlgorithm.ProviderType providerType = + KeyProviderAlgorithm.ProviderType.DUMMY_PROVIDER; + + private int priority; + + public DummyKeyProvider(int priority) + { + this.priority = priority; + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.impl.media.keyshare.KeyProvider#getProviderType() + */ + public KeyProviderAlgorithm.ProviderType getProviderType() + { + return providerType; + } + + public int getPriority() + { + return priority; + } + + public void setPriority(int priority) + { + this.priority = priority; + } +} diff --git a/src/net/java/sip/communicator/impl/media/keyshare/KeyProviderAlgorithm.java b/src/net/java/sip/communicator/impl/media/keyshare/KeyProviderAlgorithm.java new file mode 100644 index 000000000..c32a00ebc --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/keyshare/KeyProviderAlgorithm.java @@ -0,0 +1,51 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.media.keyshare; + +import net.java.sip.communicator.impl.media.transform.srtp.*; + +/** + * KeyProvider interface defines the current available provider types, + * and a method to obtain the set type for a provider. + * (Originally the interface contained setter and getter methods for keys + * and other cryptographic parameters but this were removed due to + * redundancy regarding the fact these are partial provided already inside + * directly in the SRTPTransformEngine class. + * It might still be a viable option.) + * + * @author Emanuel Onica (eonica@info.uaic.ro) + */ +public interface KeyProviderAlgorithm +{ + public enum ProviderType + { + DUMMY_PROVIDER, + ZRTP_PROVIDER + }; + + /** + * Obtains the current provider type for the class implementing the interface + * + * @return the provider type + */ + public ProviderType getProviderType(); + + /** + * Gets this algorithm's priority of usage in handling the key management + * + * @return the priority of usage in handling the key management + */ + public int getPriority(); + + /** + * Sets this algorithm's priority of usage in handling the key management + * + * @param priority the priority of usage in handling the key management + */ + public void setPriority(int priority); + +} diff --git a/src/net/java/sip/communicator/impl/media/keyshare/ZRTPKeyProvider.java b/src/net/java/sip/communicator/impl/media/keyshare/ZRTPKeyProvider.java new file mode 100644 index 000000000..3a3319961 --- /dev/null +++ b/src/net/java/sip/communicator/impl/media/keyshare/ZRTPKeyProvider.java @@ -0,0 +1,58 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.media.keyshare; + +/** + * ZRTPProvider class implements KeyProvider interface. + * Used to activate the ZRTPConnector creation. + * Also could be used to provide additional info. + * (Originally the interface contained setter and getter methods for keys + * and other cryptographic parameters but this were removed due to + * redundancy regarding the fact these are partial provided already inside + * directly in the SRTPTransformEngine class. + * The ZRTPConnector originally implemented the interface to have direct + * access to these as a ZRTPProvider. + * It might still be a viable option.) + * + * @author Emanuel Onica (eonica@info.uaic.ro) + * + */ +public class ZRTPKeyProvider implements KeyProviderAlgorithm { + + /** + * The constant provider type of this class + */ + private static final KeyProviderAlgorithm.ProviderType providerType + = KeyProviderAlgorithm.ProviderType.ZRTP_PROVIDER; + + private int priority; + + public ZRTPKeyProvider(int priority) + { + this.priority = priority; + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.impl.media.keyshare.KeyProvider#getProviderType() + */ + public ProviderType getProviderType() + { + return providerType; + } + + public int getPriority() + { + return priority; + } + + public void setPriority(int priority) + { + this.priority = priority; + } + +} diff --git a/src/net/java/sip/communicator/impl/media/media.manifest.mf b/src/net/java/sip/communicator/impl/media/media.manifest.mf index d0acb37ca..5a85efe24 100644 --- a/src/net/java/sip/communicator/impl/media/media.manifest.mf +++ b/src/net/java/sip/communicator/impl/media/media.manifest.mf @@ -13,11 +13,21 @@ Import-Package: org.osgi.framework, javax.swing, javax.swing.event, javax.swing.border, + javax.imageio, net.java.sip.communicator.service.fileaccess, + net.java.sip.communicator.service.gui, + net.java.sip.communicator.service.resources, javax.sound, javax.sound.sampled, javax.crypto, - org.xml.sax + javax.crypto.spec, + javax.crypto.interfaces, + org.xml.sax, + org.bouncycastle.jce.provider, + gnu.java.zrtp, + gnu.java.zrtp.zidfile, + gnu.java.zrtp.utils, + gnu.java.zrtp.packets Export-Package: net.java.sip.communicator.service.media, net.java.sip.communicator.service.media.event, net.java.sip.communicator.impl.media, diff --git a/src/net/java/sip/communicator/impl/media/transform/RawPacket.java b/src/net/java/sip/communicator/impl/media/transform/RawPacket.java index 574176dd3..cfd6f044a 100755 --- a/src/net/java/sip/communicator/impl/media/transform/RawPacket.java +++ b/src/net/java/sip/communicator/impl/media/transform/RawPacket.java @@ -85,39 +85,39 @@ public int getOffset() /** * Read a integer from this packet at specified offset * - * @param offset start offset of the integer to be read + * @param off start offset of the integer to be read * @return the integer to be read */ - public int readInt(int offset) + public int readInt(int off) { - return (this.buffer[this.offset + offset + 0] << 24) | - (this.buffer[this.offset + offset + 1] << 16) | - (this.buffer[this.offset + offset + 2] << 8) | - (this.buffer[this.offset + offset + 3]); + return (this.buffer[this.offset + off + 0] << 24) | + ((this.buffer[this.offset + off + 1] & 0xff) << 16) | + ((this.buffer[this.offset + off + 2] & 0xff) << 8) | + (this.buffer[this.offset + off + 3] & 0xff); } /** * Read a short from this packet at specified offset * - * @param offset start offset of this short + * @param off start offset of this short * @return short value at offset */ - public short readShort(int offset) + public short readShort(int off) { - return (short) ((this.buffer[this.offset + offset + 0] << 8) | - (this.buffer[this.offset + offset + 1])); + return (short) ((this.buffer[this.offset + off + 0] << 8) | + (this.buffer[this.offset + off + 1] & 0xff)); } /** * Read an unsigned short at specified offset as a int * - * @param offset start offset of the unsigned short + * @param off start offset of the unsigned short * @return the int value of the unsigned short at offset */ - public int readUnsignedShortAsInt(int offset) + public int readUnsignedShortAsInt(int off) { - int b1 = (0x000000FF & ((int)this.buffer[this.offset + offset + 0])); - int b2 = (0x000000FF & ((int)this.buffer[this.offset + offset + 1])); + int b1 = (0x000000FF & (this.buffer[this.offset + off + 0])); + int b2 = (0x000000FF & (this.buffer[this.offset + off + 1])); int val = b1 << 8 | b2; return val; } @@ -125,49 +125,49 @@ public int readUnsignedShortAsInt(int offset) /** * Read a byte from this packet at specified offset * - * @param offset start offset of the byte + * @param off start offset of the byte * @return byte at offset */ - public byte readByte(int offset) + public byte readByte(int off) { - return this.buffer[this.offset + offset]; + return buffer[offset + off]; } /** * Read an unsigned integer as long at specified offset * - * @param offset start offset of this unsigned integer + * @param off start offset of this unsigned integer * @return unsigned integer as long at offset */ - public long readUnsignedIntAsLong(int offset) + public long readUnsignedIntAsLong(int off) { - int b0 = (0x000000FF & ((int)this.buffer[this.offset + offset + 0])); - int b1 = (0x000000FF & ((int)this.buffer[this.offset + offset + 1])); - int b2 = (0x000000FF & ((int)this.buffer[this.offset + offset + 2])); - int b3 = (0x000000FF & ((int)this.buffer[this.offset + offset + 3])); + int b0 = (0x000000FF & (this.buffer[this.offset + off + 0])); + int b1 = (0x000000FF & (this.buffer[this.offset + off + 1])); + int b2 = (0x000000FF & (this.buffer[this.offset + off + 2])); + int b3 = (0x000000FF & (this.buffer[this.offset + off + 3])); - return ((long) (b0 << 24 | b1 << 16 | b2 << 8 | b3)) & 0xFFFFFFFFL; + return ((b0 << 24 | b1 << 16 | b2 << 8 | b3)) & 0xFFFFFFFFL; } /** * Read a byte region from specified offset with specified length * - * @param offset start offset of the region to be read - * @param length length of the region to be read + * @param off start offset of the region to be read + * @param len length of the region to be read * @return byte array of [offset, offset + length) */ - public byte[] readRegion(int offset, int length) + public byte[] readRegion(int off, int len) { - int startOffset = this.offset + offset; - if (offset < 0 || length <= 0 - || startOffset + length > this.buffer.length) + int startOffset = this.offset + off; + if (off < 0 || len <= 0 + || startOffset + len > this.buffer.length) { return null; } - byte[] region = new byte[length]; + byte[] region = new byte[len]; - System.arraycopy(this.buffer, startOffset, region, 0, length); + System.arraycopy(this.buffer, startOffset, region, 0, len); return region; } @@ -177,35 +177,36 @@ public byte[] readRegion(int offset, int length) * buffer of this packet. * * @param data byte array to append + * @param len the number of bytes to append */ - public void append(byte[] data) + public void append(byte[] data, int len) { - if (data == null || data.length == 0) + if (data == null || len == 0) { return; } - byte[] newBuffer = new byte[this.length + data.length]; + byte[] newBuffer = new byte[this.length + len]; System.arraycopy(this.buffer, this.offset, newBuffer, 0, this.length); - System.arraycopy(data, 0, newBuffer, this.length, data.length); + System.arraycopy(data, 0, newBuffer, this.length, len); this.offset = 0; - this.length = this.length + data.length; + this.length = this.length + len; this.buffer = newBuffer; + } - /** * Shrink the buffer of this packet by specified length * - * @param length length to shrink + * @param len length to shrink */ - public void shrink(int length) + public void shrink(int len) { - if (length <= 0) + if (len <= 0) { return; } - this.length -= length; + this.length -= len; if (this.length < 0) { this.length = 0; diff --git a/src/net/java/sip/communicator/impl/media/transform/TransformConnector.java b/src/net/java/sip/communicator/impl/media/transform/TransformConnector.java index 242c299c0..09d88d26b 100755 --- a/src/net/java/sip/communicator/impl/media/transform/TransformConnector.java +++ b/src/net/java/sip/communicator/impl/media/transform/TransformConnector.java @@ -41,7 +41,7 @@ public class TransformConnector * The customized TransformEngine object, which contains the concrete * transform logic. */ - private TransformEngine engine; + protected TransformEngine engine; /** * Local RTP session listen address. @@ -329,4 +329,103 @@ public void setSendBufferSize(int size) { // Nothing should be done here :-) } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the control input stream + */ + public TransformInputStream getCtrlInputStream() + { + return ctrlInputStream; + } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the control output stream + */ + public TransformOutputStream getCtrlOutputStream() + { + return ctrlOutputStream; + } + + /** + * Setter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @param ctrlOutputStream the control output stream to be set + */ + public void setCtrlOutputStream(TransformOutputStream ctrlOutputStream) + { + this.ctrlOutputStream = ctrlOutputStream; + } + + /** + * Setter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @param dataInputStream the data input stream to be set + */ + public void setDataInputStream(TransformInputStream dataInputStream) + { + this.dataInputStream = dataInputStream; + } + + /** + * Setter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @param ctrlInputStream the control input stream to be set + */ + public void setCtrlInputStream(TransformInputStream ctrlInputStream) + { + this.ctrlInputStream = ctrlInputStream; + } + + /** + * Setter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @param dataOutputStream the data output stream to be set + */ + public void setDataOutputStream(TransformOutputStream dataOutputStream) + { + this.dataOutputStream = dataOutputStream; + } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the data socket + */ + public DatagramSocket getDataSocket() + { + return dataSocket; + } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the control socket + */ + public DatagramSocket getCtrlSocket() + { + return ctrlSocket; + } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the engine + */ + public TransformEngine getEngine() + { + return engine; + } } diff --git a/src/net/java/sip/communicator/impl/media/transform/TransformInputStream.java b/src/net/java/sip/communicator/impl/media/transform/TransformInputStream.java index 4eb82cb92..d465d7d92 100755 --- a/src/net/java/sip/communicator/impl/media/transform/TransformInputStream.java +++ b/src/net/java/sip/communicator/impl/media/transform/TransformInputStream.java @@ -12,18 +12,19 @@ import javax.media.protocol.*; /** - * TransformInputStream implements PushSourceStream. It is used by RTPManager - * to receive RTP/RTCP packet datas. + * TransformInputStream implements PushSourceStream. It is used by RTPManager to + * receive RTP/RTCP packet datas. * - * In this implementation, we use UDP sockets to receive RTP/RTCP. We listen - * on the address / port specified by local session address. When one packet is + * In this implementation, we use UDP sockets to receive RTP/RTCP. We listen on + * the address / port specified by local session address. When one packet is * received, it is first reverse transformed through PacketTransformer defined * by user. And then returned as normal RTP/RTCP packets to RTPManager. * * @author Bing SU (nova.su@gmail.com) */ -public class TransformInputStream - implements PushSourceStream, Runnable +public class TransformInputStream + implements PushSourceStream, + Runnable { /** * UDP socket used to receive data. @@ -41,17 +42,12 @@ public class TransformInputStream */ private SourceTransferHandler transferHandler; - /** - * Whether we received some data. - */ - private boolean gotData; - /** * Whether this stream is closed. Used to control the termination of worker * thread. */ private boolean closed; - + /** * Worker thread we use to call transfer handle to received the data */ @@ -63,21 +59,29 @@ public class TransformInputStream private byte[] buffer = new byte[65535]; /** - * Construct a TransformInputStream based on the receiving socket and + * Caught an IO exception during read from socket + */ + private boolean ioError = false; + + private RawPacket pkt = null; + + /** + * Construct a TransformInputStream based on the receiving socket and * PacketTransformer * - * @param socket data receiving socket - * @param transformer packet transformer used + * @param socket + * data receiving socket + * @param transformer + * packet transformer used */ public TransformInputStream(DatagramSocket socket, - PacketTransformer transformer) + PacketTransformer transformer) { this.socket = socket; this.transformer = transformer; this.closed = false; - this.gotData = false; this.recvThread = new Thread(this); this.recvThread.start(); @@ -86,158 +90,140 @@ public TransformInputStream(DatagramSocket socket, /** * Close this stream, stops the worker thread. */ - public synchronized void close() + public synchronized void close() { this.closed = true; - - notifyAll(); + this.socket.close(); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.PushSourceStream#read(byte[], int, int) */ - public int read(byte[] buffer, int offset, int length) - throws IOException + public int read(byte[] inBuffer, int offset, int length) + throws IOException { - - // loop until we get a valid packet - while (true) + if (ioError) { - DatagramPacket p = new DatagramPacket(this.buffer, 0, 65535); - - try - { - this.socket.receive(p); - } - catch (IOException e) - { - return -1; - } - - RawPacket pkt = - this.transformer.reverseTransform(new RawPacket(this.buffer, - 0, - p.getLength())); - - // If the reverse transformed result is not valid, - // then we will not deliver this packet. - if (pkt == null) - { - continue; - } - else - { - if (length < pkt.getLength()) - { - throw new IOException("Input buffer not big enough for " - + String.valueOf(pkt.getLength())); - } - - System.arraycopy(pkt.getBuffer(), pkt.getOffset(), buffer, offset, - pkt.getLength()); - - synchronized (this) - { - this.gotData = true; - notifyAll(); - } - - return pkt.getLength(); - } + return -1; + } + if (length < pkt.getLength()) { + throw new IOException("Input buffer not big enough for " + + String.valueOf(pkt.getLength())); } + + System.arraycopy(pkt.getBuffer(), pkt.getOffset(), inBuffer, offset, + pkt.getLength()); + return pkt.getLength(); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.PushSourceStream#setTransferHandler - * (javax.media.protocol.SourceTransferHandler) + * (javax.media.protocol.SourceTransferHandler) */ - public synchronized void setTransferHandler(SourceTransferHandler handler) + public void setTransferHandler(SourceTransferHandler handler) { - if (this.closed) return; + if (this.closed) + return; this.transferHandler = handler; - - if (this.transferHandler != null) - { - this.gotData = true; - notifyAll(); - } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.PushSourceStream#getMinimumTransferSize() */ - public int getMinimumTransferSize() + public int getMinimumTransferSize() { return 2 * 1024; // twice the MTU size, just to be safe. } // ----- Not applicable methods ----- - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.SourceStream#endOfStream() */ - public boolean endOfStream() + public boolean endOfStream() { return false; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.SourceStream#getContentDescriptor() */ - public ContentDescriptor getContentDescriptor() + public ContentDescriptor getContentDescriptor() { return null; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.protocol.SourceStream#getContentLength() */ - public long getContentLength() + public long getContentLength() { return LENGTH_UNKNOWN; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.Controls#getControl(java.lang.String) */ - public Object getControl(String controlType) + public Object getControl(String controlType) { return null; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see javax.media.Controls#getControls() */ - public Object[] getControls() + public Object[] getControls() { return new Object[0]; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see java.lang.Runnable#run() */ - public void run() + public void run() { - while (!this.closed) + while (!this.closed) { - synchronized (TransformInputStream.this) + DatagramPacket p = new DatagramPacket(this.buffer, 0, 65535); + + try + { + this.socket.receive(p); + } + catch (IOException e) { - while (!this.gotData && !this.closed) - { - try - { - TransformInputStream.this.wait(); - } - catch (InterruptedException e) - { - // nothing should be done here ? - } - } - this.gotData = false; + ioError = true; + break; } + pkt = new RawPacket(this.buffer, 0, p.getLength()); + pkt = this.transformer.reverseTransform(pkt); - if (this.transferHandler != null && !this.closed) + // If the reverse transformed result is not valid, + // then we will not deliver this packet. + if (pkt == null) + { + continue; + } + if (this.transferHandler != null && !this.closed) { this.transferHandler.transferData(TransformInputStream.this); } diff --git a/src/net/java/sip/communicator/impl/media/transform/TransformManager.java b/src/net/java/sip/communicator/impl/media/transform/TransformManager.java index a85e5703f..86a3543de 100755 --- a/src/net/java/sip/communicator/impl/media/transform/TransformManager.java +++ b/src/net/java/sip/communicator/impl/media/transform/TransformManager.java @@ -7,9 +7,15 @@ package net.java.sip.communicator.impl.media.transform; import javax.media.rtp.*; +import java.security.*; import net.java.sip.communicator.impl.media.transform.dummy.*; import net.java.sip.communicator.impl.media.transform.srtp.*; +import net.java.sip.communicator.impl.media.transform.zrtp.*; +import net.java.sip.communicator.impl.media.keyshare.*; +import java.util.*; + +import org.bouncycastle.jce.provider.*; /** * TransformManager class encapsulate the logic of creating different kinds of @@ -17,9 +23,64 @@ * TransformManager class. * * @author Bing SU (nova.su@gmail.com) + * @author Emanuel Onica (eonica@info.uaic.ro) */ public class TransformManager { + + /** + * Map of supported cryptography services providers + */ + public static HashMap cryptoProviders = null; + + /** + * Initialize the supported cryptography services providers + */ + public static void initializeProviders() + { + if (cryptoProviders == null) + { + cryptoProviders = new HashMap(); + + Provider cryptoProvider = null ; + + try + { + Class c = Class + .forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + cryptoProvider = (Provider) c.newInstance(); + } + catch (ClassNotFoundException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (InstantiationException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + cryptoProviders.put("BouncyCastle", cryptoProvider); + } + } + + /** + * Select a cryptography services provider to use from the supported ones + * + * @param cryptoProvider cryptography provider selection string + * @return the actual cryptography provider to use + */ + public static Provider selectProvider(String cryptoProvider) + { + return (Provider)cryptoProviders.get(cryptoProvider); + } + /** * Create a SRTP TransformConnector, which will provide SRTP encryption / * decryption functionality, using algorithms defined in RFC3711. @@ -29,28 +90,84 @@ public class TransformManager * @param masterSalt master salt of this SRTP session * @param srtpPolicy SRTP policy for this SRTP session * @param srtcpPolicy SRTCP policy for this SRTP session + * @param cryptoProvider the cryptography services provider selection string + * should be obtained from a resource file or by querying * @return the TransformConnector used for SRTP encyption/decryption * @throws InvalidSessionAddressException if the local RTP session address * is invalid - */ + */ public static TransformConnector createSRTPConnector(SessionAddress addr, - byte[] masterKey, - byte[] masterSalt, + byte[] masterKey, + byte[] masterSalt, SRTPPolicy srtpPolicy, - SRTPPolicy srtcpPolicy) - throws InvalidSessionAddressException + SRTPPolicy srtcpPolicy, + String cryptoProvider) + throws InvalidSessionAddressException { - SRTPTransformEngine engine = new SRTPTransformEngine(masterKey, - masterSalt, - srtpPolicy, - srtcpPolicy); + SRTPTransformEngine engine = null; + + Provider cp = selectProvider(cryptoProvider); + + try + { + engine = new SRTPTransformEngine(masterKey, + masterSalt, + srtpPolicy, + srtcpPolicy, + cp); + } + catch (GeneralSecurityException e) + { + e.printStackTrace(); + return null; + } TransformConnector connector = null; + connector = new TransformConnector(addr, engine); return connector; } + /** + * Creates a connector specific for use in case of ZRTP key management + * + * @param addr local RTP session listen address + * @param cryptoProvider the cryptography services provider selection string + * should be obtained from a resource file or by querying + * @return the TransformConnector used for SRTP encyption/decryption + * @throws InvalidSessionAddressException + */ + public static TransformConnector createZRTPConnector(SessionAddress addr, + String cryptoProvider) + throws InvalidSessionAddressException + { + //for adding multistream support the engine should be instantiated + //once as a static variable of this class and passed to every ZRTP + //connector as a parameter + ZRTPTransformEngine engine = new ZRTPTransformEngine(); + + Provider cp = selectProvider(cryptoProvider); + + TransformConnector connector = null; + + connector = new ZrtpTransformConnector(addr, engine); + + //for adding multistream support this method should be replaced with + //an addConnector, which should add the connector to an internal engine + //connector array; supporting multistream mode by the engine implies + //the proper management of this connector array - practically every + //stream has it's own connector + engine.setConnector(connector); + + //for adding multistream support also the SCCallback should be instantiates + //only once as a static variable of this class and passed to the engine + engine.setUserCallback(new SCCallback()); + engine.setCryptoProvider(cp); + + return connector; + } + /** * Create a dummy TransformConnector. A dummy TransformConnector does no * modification (transformation) to RTP/RTCP packets. Its main purpose is to diff --git a/src/net/java/sip/communicator/impl/media/transform/TransformOutputStream.java b/src/net/java/sip/communicator/impl/media/transform/TransformOutputStream.java index 7e40e2e5b..384b04204 100755 --- a/src/net/java/sip/communicator/impl/media/transform/TransformOutputStream.java +++ b/src/net/java/sip/communicator/impl/media/transform/TransformOutputStream.java @@ -38,13 +38,13 @@ public class TransformOutputStream /** * Stream targets' ip addresses */ - private Vector remoteAddrs; + private Vector remoteAddrs; /** * Stream targets' ports, corresponding to their ip addresses. */ - private Vector remotePorts; - + private Vector remotePorts; + /** * Construct a TransformOutputStream based on the given UDP socket and * PacketTransformer @@ -57,8 +57,8 @@ public TransformOutputStream(DatagramSocket socket, { this.socket = socket; this.transformer = transformer; - this.remoteAddrs = new Vector(); - this.remotePorts = new Vector(); + this.remoteAddrs = new Vector(); + this.remotePorts = new Vector(); } /** @@ -103,13 +103,24 @@ public void removeTargets() * @see javax.media.rtp.OutputDataStream#write(byte[], int, int) */ public int write(byte[] buffer, int offset, int length) - { + { // Transformation could be non-inplace, we shall not modify the the old // buffer + RawPacket pkt = this.transformer.transform(new RawPacket(buffer, offset, length)); - + + // This is for the case when the ZRTP engine stops the media stream + // allowing only ZRTP packets + /* TODO GoClear + * To uncomment in order to use the GoClear feature + */ + /* + if (pkt == null) + return length; + */ + for (int i = 0; i < this.remoteAddrs.size(); ++i) { InetAddress remoteAddr = @@ -118,7 +129,7 @@ public int write(byte[] buffer, int offset, int length) ((Integer) this.remotePorts.elementAt(i)).intValue(); try - { + { this.socket.send(new DatagramPacket(pkt.getBuffer(), pkt.getOffset(), pkt.getLength(), diff --git a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherCTR.java b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherCTR.java index 9b740d283..8d89cd903 100755 --- a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherCTR.java +++ b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherCTR.java @@ -25,6 +25,9 @@ */ package net.java.sip.communicator.impl.media.transform.srtp; +import javax.crypto.*; +import java.security.*; + /** * SRTPCipherCTR implements SRTP Counter Mode AES Encryption (AES-CM). * Counter Mode AES Encryption algorithm is defined in RFC3711, section 4.1.1. @@ -51,28 +54,19 @@ * * @author Bing SU (nova.su@gmail.com) */ -public class SRTPCipherCTR implements SRTPCipher +public class SRTPCipherCTR { /** - * The AESCihper object we used to do basic AES encryption / decryption - */ - private AESCipher aesCipher; - - /** - * Construct a SRTPCipherCTR object using given encryption key - * - * @param key the encryption key for this session - */ - public SRTPCipherCTR(byte[] key) - { - this.aesCipher = new AESCipher(key); - } - - /* (non-Javadoc) - * @see net.java.sip.communicator.impl.media.transform.srtp. - * SRTPCipher#process(byte[], int, int, byte[]) + * Process (encrypt / decrypt) a byte stream, using the supplied + * initial vector. + * + * @param aesCipher the AESCihper object we use to do basic AES encryption / decryption + * @param data byte array containing the byte stream to be processed + * @param offset byte stream star offset with data byte array + * @param length byte stream length in bytes + * @param iv initial vector for this operation */ - public void process(byte[] data, int off, int len, byte[] iv) + public static void process(Cipher aesCipher, byte[] data, int off, int len, byte[] iv) { if (off + len > data.length) { @@ -82,7 +76,7 @@ public void process(byte[] data, int off, int len, byte[] iv) byte[] cipherStream = new byte[len]; - getCipherStream(cipherStream, len, iv); + getCipherStream(aesCipher, cipherStream, len, iv); for (int i = 0; i < len; i++) { @@ -94,34 +88,42 @@ public void process(byte[] data, int off, int len, byte[] iv) * Computes the cipher stream for AES CM mode. * See section 4.1.1 in RFC3711 for detailed description. * + * @param aesCipher the AESCihper object we use to do basic AES encryption / decryption * @param out byte array holding the output cipher stream * @param length length of the cipher stream to produce, in bytes * @param iv initialization vector used to generate this cipher stream */ - public void getCipherStream(byte[] out, int length, byte[] iv) + public static void getCipherStream(Cipher aesCipher, byte[] out, int length, byte[] iv) { - final int BLKLEN = AESCipher.BLOCK_SIZE; + final int BLKLEN = 16; byte[] in = new byte[BLKLEN]; byte[] tmp = new byte[BLKLEN]; System.arraycopy(iv, 0, in, 0, 14); - int ctr; - for (ctr = 0; ctr < length / BLKLEN; ctr++) + try { - // compute the cipher stream + int ctr; + for (ctr = 0; ctr < length / BLKLEN; ctr++) + { + // compute the cipher stream + in[14] = (byte) ((ctr & 0xFF00) >> 8); + in[15] = (byte) ((ctr & 0x00FF)); + + aesCipher.update(in, 0, BLKLEN, out, ctr * BLKLEN); + } + + // Treat the last bytes: in[14] = (byte) ((ctr & 0xFF00) >> 8); in[15] = (byte) ((ctr & 0x00FF)); - this.aesCipher.encryptBlock(in, 0, out, ctr * BLKLEN); + aesCipher.doFinal(in, 0, BLKLEN, tmp, 0); + System.arraycopy(tmp, 0, out, ctr * BLKLEN, length % BLKLEN); + } + catch (GeneralSecurityException e) + { + e.printStackTrace(); } - - // Treat the last bytes: - in[14] = (byte) ((ctr & 0xFF00) >> 8); - in[15] = (byte) ((ctr & 0x00FF)); - - this.aesCipher.encryptBlock(in, 0, tmp, 0); - System.arraycopy(tmp, 0, out, ctr * BLKLEN, length % BLKLEN); } } diff --git a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherF8.java b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherF8.java index f9a68f41d..4bdca7638 100755 --- a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherF8.java +++ b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCipherF8.java @@ -26,6 +26,9 @@ package net.java.sip.communicator.impl.media.transform.srtp; import java.util.*; +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.*; /** * SRTPCipherF8 implements SRTP F8 Mode AES Encryption (AES-f8). @@ -53,28 +56,13 @@ * * @author Bing SU (nova.su@gmail.com) */ -public class SRTPCipherF8 implements SRTPCipher +public class SRTPCipherF8 { - /** - * The AESCipher used to perform basic AES encryption / decryption - */ - private AESCipher aesCipher; - - /** - * Encryption key used in this session. F8 mode encryption will use this - * data. - */ - private byte[] key; - - /** - * Salting key used in this session. F8 mode encryption will use this data. - */ - private byte[] salt; /** * AES block size, just a short name. */ - private final static int BLKLEN = AESCipher.BLOCK_SIZE; + private final static int BLKLEN = 16; /** * F8 mode encryption context, see RFC3711 section 4.1.2 for detailed @@ -88,30 +76,22 @@ class F8Context } /** - * Construct a SRTPCipherF8 object using given encryption key and salting - * key. + * Process (encrypt / decrypt) a byte stream, using the supplied + * initial vector. * - * @param key the encryption key used in this session - * @param salt the salting key used in this session - */ - public SRTPCipherF8(byte[] key, byte[] salt) - { - this.aesCipher = new AESCipher(key); - - this.key = new byte[key.length]; - System.arraycopy(key, 0, this.key, 0, key.length); - - this.salt = new byte[salt.length]; - System.arraycopy(salt, 0, this.salt, 0, salt.length); - } - - /* (non-Javadoc) - * @see net.java.sip.communicator.impl.media.transform.srtp. - * SRTPCipher#process(byte[], int, int, byte[]) + * @param aesCipher the AES cipher object used for block processing + * @param data byte array containing the byte stream to be processed + * @param offset byte stream star offset with data byte array + * @param length byte stream length in bytes + * @param iv initial vector for this operation + * @param key the encryption key + * @param salt the salt key + * @param f8Cipher the F8 cipher object used for iv processing */ - public void process(byte[] data, int off, int len, byte[] iv) + public static void process(Cipher aesCipher, byte[] data, int off, int len, byte[] iv, + byte[] key, byte[] salt, Cipher f8Cipher) { - F8Context f8ctx = new F8Context(); + F8Context f8ctx = new SRTPCipherF8().new F8Context(); /* * Get memory for the derived IV (IV') @@ -122,15 +102,15 @@ public void process(byte[] data, int off, int len, byte[] iv) * Get memory for the special key. This is the key to compute the * derived IV (IV'). */ - byte[] saltMask = new byte[this.key.length]; - byte[] maskedKey = new byte[this.key.length]; + byte[] saltMask = new byte[key.length]; + byte[] maskedKey = new byte[key.length]; /* * First copy the salt into the mask field, then fill with 0x55 to * get a full key. */ - System.arraycopy(this.salt, 0, saltMask, 0, this.salt.length); - for (int i = this.salt.length; i < saltMask.length; ++i) + System.arraycopy(salt, 0, saltMask, 0, salt.length); + for (int i = salt.length; i < saltMask.length; ++i) { saltMask[i] = 0x55; } @@ -139,20 +119,31 @@ public void process(byte[] data, int off, int len, byte[] iv) * XOR the original key with the above created mask to * get the special key. */ - for (int i = 0; i < this.key.length; i++) + for (int i = 0; i < key.length; i++) { - maskedKey[i] = (byte) (this.key[i] ^ saltMask[i]); + maskedKey[i] = (byte) (key[i] ^ saltMask[i]); } /* - * Prepare the a new AES cipher with the special key to compute IV' + * Prepare the f8Cipher with the special key to compute IV' */ - AESCipher cipher = new AESCipher(maskedKey); + SecretKey encryptionKey = new SecretKeySpec(maskedKey, 0, + maskedKey.length, "AES"); + + try + { + f8Cipher.init(Cipher.ENCRYPT_MODE, encryptionKey); + /* + * Use the masked key to encrypt the original IV to produce IV'. + */ + f8Cipher.doFinal(iv, 0, BLKLEN, f8ctx.ivAccent, 0); + } + catch (GeneralSecurityException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } - /* - * Use the masked key to encrypt the original IV to produce IV'. - */ - cipher.encryptBlock(iv, 0, f8ctx.ivAccent, 0); saltMask = null; maskedKey = null; @@ -166,14 +157,16 @@ public void process(byte[] data, int off, int len, byte[] iv) while (inLen >= BLKLEN) { - processBlock(f8ctx, data, off, data, off, BLKLEN); + processBlock(aesCipher, f8ctx, data, off, data, off, BLKLEN); + inLen -= BLKLEN; off += BLKLEN; } if (inLen > 0) { - processBlock(f8ctx, data, off, data, off, inLen); + processBlock(aesCipher, f8ctx, data, off, data, off, inLen); + } } @@ -181,6 +174,7 @@ public void process(byte[] data, int off, int len, byte[] iv) * Encrypt / Decrypt a block using F8 Mode AES algorithm, read len bytes * data from in at inOff and write the output into out at outOff * + * @param aesCipher the AES cipher object used for block processing * @param f8ctx F8 encryption context * @param in byte array holding the data to be processed * @param inOff start offset of the data to be processed inside in array @@ -188,8 +182,8 @@ public void process(byte[] data, int off, int len, byte[] iv) * @param outOff start offset of output data in out * @param len length of the input data */ - private void processBlock(F8Context f8ctx, byte[] in, int inOff, - byte[] out, int outOff, int len) + private static void processBlock(Cipher aesCipher, F8Context f8ctx, byte[] in, int inOff, + byte[] out, int outOff, int len) { /* @@ -214,7 +208,16 @@ private void processBlock(F8Context f8ctx, byte[] in, int inOff, /* * Now compute the new key stream using AES encrypt */ - this.aesCipher.encryptBlock(f8ctx.S, 0, f8ctx.S, 0); + try + { + aesCipher.doFinal(f8ctx.S, 0, BLKLEN, f8ctx.S, 0); + } + catch (GeneralSecurityException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + /* * As the last step XOR the plain text with the key stream to produce diff --git a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCryptoContext.java b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCryptoContext.java index 5d5470312..e23b857f3 100755 --- a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCryptoContext.java +++ b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPCryptoContext.java @@ -29,6 +29,9 @@ import java.util.*; import net.java.sip.communicator.impl.media.transform.*; +import java.security.*; +import javax.crypto.spec.*; +import javax.crypto.*; /** * SRTPCryptoContext class is the core class of SRTP implementation. @@ -135,6 +138,26 @@ public class SRTPCryptoContext * The SRTPDigest object we used to do packet authentication */ private SRTPDigest digest; + + /** + * The cryptographic services provider + */ + private Provider cryptoProvider; + + /** + * Used for various HMAC computations + */ + private Mac hmacSha1; + + /** + * The AES cipher for counter mode + */ + private Cipher AEScipher = null; + + /** + * The AES cipher for F8 mode + */ + private Cipher AEScipherF8 = null; /** * Construct an empty SRTPCryptoContext using ssrc. @@ -157,6 +180,7 @@ public SRTPCryptoContext(long ssrc) this.saltKey = null; this.seqNumSet = false; this.policy = null; + this.cryptoProvider = null; } /** @@ -177,13 +201,16 @@ public SRTPCryptoContext(long ssrc) * authentication key and the session salt. * @param policy SRTP policy for this SRTP cryptographic context, defined * the encryption algorithm, the authentication algorithm, etc + * @param cryptoProvider cryptographic services provider */ public SRTPCryptoContext(long ssrc, int roc, long keyDerivationRate, byte[] masterKey, byte[] masterSalt, - SRTPPolicy policy) + SRTPPolicy policy, + Provider cryptoProvider) + throws GeneralSecurityException { this.ssrc = ssrc; this.mki = null; @@ -192,14 +219,16 @@ public SRTPCryptoContext(long ssrc, this.seqNum = 0; this.keyDerivationRate = keyDerivationRate; this.seqNumSet = false; + this.cryptoProvider = cryptoProvider; this.policy = policy; - this.masterKey = new byte[masterKey.length]; - System.arraycopy(masterKey, 0, this.masterKey, 0, this.masterKey.length); + this.masterKey = new byte[this.policy.getEncKeyLength()]; + System.arraycopy(masterKey, 0, this.masterKey, 0, this.policy.getEncKeyLength()); - this.masterSalt = new byte[masterSalt.length]; - System.arraycopy(masterSalt, 0, this.masterSalt, 0, this.masterSalt.length); + this.masterSalt = new byte[this.policy.getSaltKeyLength()]; + System.arraycopy(masterSalt, 0, this.masterSalt, 0, this.policy.getSaltKeyLength()); + switch (policy.getEncType()) { @@ -209,7 +238,16 @@ public SRTPCryptoContext(long ssrc, break; case SRTPPolicy.AESCM_ENCRYPTION: + hmacSha1 = Mac.getInstance("HMACSHA1", cryptoProvider); + AEScipher = Cipher.getInstance("AES/ECB/NOPADDING", cryptoProvider); + this.encKey = new byte[this.policy.getEncKeyLength()]; + this.saltKey = new byte[this.policy.getSaltKeyLength()]; + break; + case SRTPPolicy.AESF8_ENCRYPTION: + hmacSha1 = Mac.getInstance("HMACSHA1", cryptoProvider); + AEScipher = Cipher.getInstance("AES/ECB/NOPADDING", cryptoProvider); + AEScipherF8 = Cipher.getInstance("AES/ECB/NOPADDING", cryptoProvider); this.encKey = new byte[this.policy.getEncKeyLength()]; this.saltKey = new byte[this.policy.getSaltKeyLength()]; break; @@ -319,7 +357,7 @@ else if (this.policy.getEncType() == SRTPPolicy.AESF8_ENCRYPTION) if (this.policy.getAuthType() == SRTPPolicy.HMACSHA1_AUTHENTICATION) { byte[] tag = authenticatePacketHMCSHA1(pkt); - pkt.append(tag); + pkt.append(tag, policy.getAuthTagLength()); } /* Update the ROC if necessary */ @@ -362,9 +400,11 @@ public boolean reverseTransformPacket(RawPacket pkt) byte[] calculatedTag = authenticatePacketHMCSHA1(pkt); - if (!Arrays.equals(originalTag, calculatedTag)) - { - return false; + for (int i = 0; i < tagLength; i++) { + if ((originalTag[i]&0xff) == (calculatedTag[i]&0xff)) + continue; + else + return false; } } @@ -420,13 +460,11 @@ public void processPacketAESCM(RawPacket pkt) iv[14] = iv[15] = 0; - SRTPCipher cipher = new SRTPCipherCTR(this.encKey); - final int payloadOffset = PacketManipulator.GetRTPHeaderLength(pkt); final int payloadLength = PacketManipulator.GetRTPPayloadLength(pkt); - cipher.process(pkt.getBuffer(), pkt.getOffset() + payloadOffset, - payloadLength, iv); + SRTPCipherCTR.process(AEScipher, pkt.getBuffer(), pkt.getOffset() + payloadOffset, + payloadLength, iv); } /** @@ -436,42 +474,45 @@ public void processPacketAESCM(RawPacket pkt) */ public void processPacketAESF8(RawPacket pkt) { - long ssrc = PacketManipulator.GetRTPSSRC(pkt); - boolean isMarked = PacketManipulator.IsPacketMarked(pkt); - int seqNum = PacketManipulator.GetRTPSequenceNumber(pkt); - byte payload = PacketManipulator.GetRTPPayloadType(pkt); + //long ssrc = PacketManipulator.GetRTPSSRC(pkt); + //boolean isMarked = PacketManipulator.IsPacketMarked(pkt); + //int seqNum = PacketManipulator.GetRTPSequenceNumber(pkt); + //byte payload = PacketManipulator.GetRTPPayloadType(pkt); byte[] iv = new byte[16]; - iv[0] = 0; - iv[1] = (byte) (isMarked ? 0x80 : 0x00); - iv[1] |= payload & 0x7f; - iv[2] = (byte) (seqNum >> 8); - iv[3] = (byte) seqNum; + //iv[0] = 0; + //iv[1] = (byte) (isMarked ? 0x80 : 0x00); + //iv[1] |= payload & 0x7f; + //iv[2] = (byte) (seqNum >> 8); + //iv[3] = (byte) seqNum; // set the TimeStamp in network order into IV - byte[] timeStamp = PacketManipulator.ReadTimeStampIntoByteArray(pkt); - System.arraycopy(timeStamp, 0, iv, 4, 4); + //byte[] timeStamp = PacketManipulator.ReadTimeStampIntoByteArray(pkt); + //System.arraycopy(timeStamp, 0, iv, 4, 4); // set the SSRC in network order into IV - iv[8] = (byte) (ssrc >> 24); - iv[9] = (byte) (ssrc >> 16); - iv[10] = (byte) (ssrc >> 8); - iv[11] = (byte) ssrc; + //iv[8] = (byte) (ssrc >> 24); + //iv[9] = (byte) (ssrc >> 16); + //iv[10] = (byte) (ssrc >> 8); + //iv[11] = (byte) ssrc; // set the ROC in network order into IV + // 11 bytes of the RTP header are the 11 bytes of the iv + // the first byte of the RTP header is not used. + System.arraycopy(pkt.getBuffer(), pkt.getOffset(), iv, 0, 12); + iv[0] = 0; + iv[12] = (byte) (this.roc >> 24); iv[13] = (byte) (this.roc >> 16); iv[14] = (byte) (this.roc >> 8); iv[15] = (byte) this.roc; - SRTPCipher cipher = new SRTPCipherF8(this.encKey, this.saltKey); - final int payloadOffset = PacketManipulator.GetRTPHeaderLength(pkt); final int payloadLength = PacketManipulator.GetRTPPayloadLength(pkt); - cipher.process(pkt.getBuffer(), pkt.getOffset() + payloadOffset, - payloadLength, iv); + SRTPCipherF8.process(AEScipher, pkt.getBuffer(), pkt.getOffset() + payloadOffset, + payloadLength, iv, encKey, saltKey, AEScipherF8); } /** @@ -483,26 +524,15 @@ public void processPacketAESF8(RawPacket pkt) */ private byte[] authenticatePacketHMCSHA1(RawPacket pkt) { - byte[][] chunks = new byte[2][]; - int[] chunkLength = new int[2]; - - chunks[0] = pkt.getBuffer(); - chunkLength[0] = pkt.getLength(); - - chunks[1] = new byte[4]; - chunks[1][0] = (byte) (this.roc >> 24); - chunks[1][1] = (byte) (this.roc >> 16); - chunks[1][2] = (byte) (this.roc >> 8); - chunks[1][3] = (byte) this.roc; - - chunkLength[1] = 4; - - byte[] result = this.digest.authHMACSHA1(chunks, chunkLength); - byte[] tag = new byte[this.policy.getAuthTagLength()]; - - System.arraycopy(result, 0, tag, 0, this.policy.getAuthTagLength()); + hmacSha1.update(pkt.getBuffer(), 0, pkt.getLength()); + byte[] rb = new byte[4]; + rb[0] = (byte) (this.roc >> 24); + rb[1] = (byte) (this.roc >> 16); + rb[2] = (byte) (this.roc >> 8); + rb[3] = (byte) this.roc; + hmacSha1.update(rb); - return tag; + return hmacSha1.doFinal(); } /** @@ -613,25 +643,61 @@ public void deriveSrtpKeys(long index) // compute the session encryption key long label = 0; computeIv(iv, label, index, this.keyDerivationRate, this.masterSalt); - SRTPCipherCTR aes = new SRTPCipherCTR(this.masterKey); - aes.getCipherStream(this.encKey, this.policy.getEncKeyLength(), iv); + + SecretKey encryptionKey = new SecretKeySpec(masterKey, 0, policy.getEncKeyLength(), "AES"); + + try + { + AEScipher.init(Cipher.ENCRYPT_MODE, encryptionKey); + } + catch (InvalidKeyException e1) + { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + SRTPCipherCTR.getCipherStream(AEScipher, encKey, policy.getEncKeyLength(), iv); + // compute the session authentication key if (this.authKey != null) { label = 0x01; computeIv(iv, label, index, this.keyDerivationRate, this.masterSalt); - aes = new SRTPCipherCTR(this.masterKey); - aes.getCipherStream(this.authKey, this.policy.getAuthKeyLength(), iv); + + SRTPCipherCTR.getCipherStream(AEScipher, authKey, policy.getAuthKeyLength(), iv); + + SecretKey key = new SecretKeySpec(authKey, "HMAC"); + try + { + hmacSha1.init(key); + } + catch (InvalidKeyException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } - this.digest = new SRTPDigest(this.authKey); } // compute the session salt label = 0x02; computeIv(iv, label, index, this.keyDerivationRate, this.masterSalt); - aes = new SRTPCipherCTR(this.masterKey); - aes.getCipherStream(this.saltKey, this.policy.getSaltKeyLength(), iv); + + SRTPCipherCTR.getCipherStream(AEScipher, saltKey, policy.getSaltKeyLength(), iv); + + // As last step: initialize AES cipher with derived encryption key. + encryptionKey = new SecretKeySpec(encKey, 0, policy.getEncKeyLength(), "AES"); + try + { + AEScipher.init(Cipher.ENCRYPT_MODE, encryptionKey); + } + catch (InvalidKeyException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } /** @@ -717,9 +783,19 @@ private void update(int seqNum) */ public SRTPCryptoContext deriveContext(long ssrc, int roc, long deriveRate) { - SRTPCryptoContext pcc = - new SRTPCryptoContext(ssrc, roc, deriveRate, this.masterKey, - this.masterSalt, this.policy); + SRTPCryptoContext pcc = null; + try + { + pcc = new SRTPCryptoContext(ssrc, roc, deriveRate, + this.masterKey, this.masterSalt, this.policy, + this.cryptoProvider); + } + catch (GeneralSecurityException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } return pcc; + } } diff --git a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformEngine.java b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformEngine.java index 54a7f8480..6194c76c7 100755 --- a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformEngine.java +++ b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformEngine.java @@ -7,6 +7,7 @@ package net.java.sip.communicator.impl.media.transform.srtp; import net.java.sip.communicator.impl.media.transform.*; +import java.security.*; /** * SRTPTransformEngine class implements TransformEngine interface. @@ -54,9 +55,12 @@ public class SRTPTransformEngine * @param masterSalt the master salt key * @param srtpPolicy SRTP policy * @param srtcpPolicy SRTCP policy + * @param cryptoProvider cryptography services provider */ public SRTPTransformEngine(byte[] masterKey, byte[] masterSalt, - SRTPPolicy srtpPolicy, SRTPPolicy srtcpPolicy) + SRTPPolicy srtpPolicy, SRTPPolicy srtcpPolicy, + Provider cryptoProvider) + throws GeneralSecurityException { this.masterKey = new byte[masterKey.length]; System.arraycopy(masterKey, 0, this.masterKey, 0, masterKey.length); @@ -70,7 +74,8 @@ public SRTPTransformEngine(byte[] masterKey, byte[] masterSalt, this.defaultContext = new SRTPCryptoContext(0, 0, 0, this.masterKey, this.masterSalt, - this.srtpPolicy); + this.srtpPolicy, + cryptoProvider); } /* (non-Javadoc) diff --git a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformer.java b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformer.java index 0bc2c4a8c..0aecaa9bd 100755 --- a/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformer.java +++ b/src/net/java/sip/communicator/impl/media/transform/srtp/SRTPTransformer.java @@ -51,7 +51,7 @@ public class SRTPTransformer /** * All the known SSRC's corresponding SRTPCryptoContexts */ - private Hashtable contexts; + private Hashtable contexts; /** * Construct a SRTPTransformer @@ -61,7 +61,7 @@ public class SRTPTransformer public SRTPTransformer(SRTPTransformEngine engine) { this.engine = engine; - this.contexts = new Hashtable(); + this.contexts = new Hashtable(); } /* (non-Javadoc) @@ -78,11 +78,17 @@ public RawPacket transform(RawPacket pkt) if (context == null) { context = this.engine.getDefaultContext().deriveContext(ssrc, 0, 0); - context.deriveSrtpKeys(0); - this.contexts.put(new Long(ssrc), context); + if (context != null) + { + context.deriveSrtpKeys(0); + this.contexts.put(new Long(ssrc), context); + } } - context.transformPacket(pkt); + if (context != null) + { + context.transformPacket(pkt); + } return pkt; } @@ -101,16 +107,35 @@ public RawPacket reverseTransform(RawPacket pkt) if (context == null) { context = this.engine.getDefaultContext().deriveContext(ssrc, 0, 0); - context.deriveSrtpKeys(seqNum); - this.contexts.put(new Long(ssrc), context); + + if (context != null) + { + context.deriveSrtpKeys(seqNum); + this.contexts.put(new Long(ssrc), context); + } } - boolean validPacket = context.reverseTransformPacket(pkt); - if (!validPacket) + if (context != null) { - return null; + boolean validPacket = context.reverseTransformPacket(pkt); + + if (!validPacket) + { + return null; + } } return pkt; } + + /** + * Getter to use in derived classes. + * (Could modify the member variable to protected instead for direct access) + * + * @return the engine + */ + public SRTPTransformEngine getEngine() + { + return engine; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java index 158483db7..54acb4907 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java @@ -684,5 +684,23 @@ public void sessionMediaReceived(JingleSession jingleSession, { logger.info("session media received "); } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#setSecured(net.java.sip.communicator.service.protocol.CallParticipant, boolean, net.java.sip.communicator.service.media.CallSession.SecureStatusChangeSource) + */ + public void setSecured(CallParticipant participant, boolean secured, + OperationSetBasicTelephony.SecureStatusChangeSource source) + { + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#getSecured(net.java.sip.communicator.service.protocol.CallParticipant) + */ + public boolean getSecured(CallParticipant participant) + { + return false; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java index 96b37d308..3cd525903 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java @@ -231,4 +231,22 @@ public void callStateChanged(CallChangeEvent evt) fireCallEvent(CallEvent.CALL_ENDED, sourceCall); } } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#setSecured(net.java.sip.communicator.service.protocol.CallParticipant, boolean, net.java.sip.communicator.service.media.CallSession.SecureStatusChangeSource) + */ + public void setSecured(CallParticipant participant, boolean secured, + OperationSetBasicTelephony.SecureStatusChangeSource source) + { + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#getSecured(net.java.sip.communicator.service.protocol.CallParticipant) + */ + public boolean getSecured(CallParticipant participant) + { + return false; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index 8067e550f..9a85c7a3e 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -26,6 +26,7 @@ * @author Emil Ivov * @author Lubomir Marinov * @author Alan Kelly + * @author Emanuel Onica */ public class OperationSetBasicTelephonySipImpl extends AbstractOperationSetBasicTelephony @@ -2962,6 +2963,27 @@ public void setMute(CallParticipant participant, boolean mute) ((CallSipImpl) participant.getCall()).getMediaCallSession().setMute( mute); } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#setSecured(net.java.sip.communicator.service.protocol.CallParticipant, boolean, net.java.sip.communicator.service.media.CallSession.SecureStatusChangeSource) + */ + public void setSecured(CallParticipant participant, boolean secured, + SecureStatusChangeSource source) + { + ((CallSipImpl) participant.getCall()).getMediaCallSession(). + setSecureCommunicationStatus(secured, source); + } + + /* + * (non-Javadoc) + * @see net.java.sip.communicator.service.protocol.OperationSetBasicTelephony#getSecured(net.java.sip.communicator.service.protocol.CallParticipant) + */ + public boolean getSecured(CallParticipant participant) + { + return ((CallSipImpl) participant.getCall()).getMediaCallSession(). + getSecureCommunicationStatus(); + } /** * Transfers (in the sense of call transfer) a specific diff --git a/src/net/java/sip/communicator/service/media/CallSession.java b/src/net/java/sip/communicator/service/media/CallSession.java index e6ab6f2c0..163d55b1c 100644 --- a/src/net/java/sip/communicator/service/media/CallSession.java +++ b/src/net/java/sip/communicator/service/media/CallSession.java @@ -25,6 +25,7 @@ * * @author Emil Ivov * @author Lubomir Marinov + * @author Emanuel Onica */ public interface CallSession { @@ -190,4 +191,30 @@ public void processSdpAnswer(CallParticipant responder, String sdpAnswer) * session. */ public void stopStreaming(); + + /** + * Sets the default secure/unsecure communication status for the supported + * call sessions. + * + * @param activator value of default secure communication status + * @param source the initiator of the secure status change (can be local or remote) + */ + public void setSecureCommunicationStatus(boolean activator, + OperationSetBasicTelephony. + SecureStatusChangeSource source); + + /** + * Gets the default secure/unsecure communication status for the supported + * call sessions. + * + * @return default secure communication status for the supported call sessions + */ + public boolean getSecureCommunicationStatus(); + + /** + * Gets the call associated with this session + * + * @return the call associated with this session + */ + public Call getCall(); } diff --git a/src/net/java/sip/communicator/service/protocol/Call.java b/src/net/java/sip/communicator/service/protocol/Call.java index d5367aaf4..120afd49b 100644 --- a/src/net/java/sip/communicator/service/protocol/Call.java +++ b/src/net/java/sip/communicator/service/protocol/Call.java @@ -19,6 +19,7 @@ * or H323Call or AnyOtherTelephonyProtocolCall * * @author Emil Ivov + * @author Emanuel Onica */ public abstract class Call { @@ -39,6 +40,13 @@ public abstract class Call * A reference to the ProtocolProviderService instance that created us. */ private ProtocolProviderService protocolProvider = null; + + /** + * A collection of various GUI components used for a call management that might + * be needed inside specific layers of the call securing, depending on the + * securing algorithm used + */ + private Hashtable secureGUIComponents; /** * Creates a new Call instance. @@ -240,4 +248,40 @@ protected void fireCallChangeEvent( String type, * currently in. */ public abstract CallState getCallState(); + + /** + * This method is used to add references to various GUI components related to + * securing the call that might be used in different way in by various securing + * algorithms, and consequently might be needed for particular usage at the layers + * where the specified algorithms operate + * + * @param key a key used by a securing algorithm implementation + * to identify the GUI item needed + * @param value the GUI object + */ + public void addSecureGUIComponent(Object key, Object value) + { + if (secureGUIComponents == null) + secureGUIComponents = new Hashtable(); + + secureGUIComponents.put(key, value); + } + + /** + * This method is used to obtain the reference to various GUI components related to + * securing the call that might be used in different way in by various securing + * algorithms, and consequently might be needed for particular usage at the layers + * where the specified algorithms operate + * + * @param key a key used by a securing algorithm implementation + * to identify the GUI item needed + * @return the GUI object + */ + public Object getSecureGUIComponent(Object key) + { + if (secureGUIComponents == null) + return null; + else + return secureGUIComponents.get(key); + } } diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java index c8bba6b1d..2959837d2 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java @@ -18,6 +18,7 @@ * * @author Emil Ivov * @author Lubomir Marinov + * @author Emanuel Onica */ public interface OperationSetBasicTelephony extends OperationSet @@ -135,4 +136,35 @@ public void hangupCallParticipant(CallParticipant participant) * participant; otherwise, false */ public void setMute(CallParticipant participant, boolean mute); + + /** + * Use this to indicate the source of setting the secure status + * of the communication as being the local or remote peer or reverted by local + */ + public static enum SecureStatusChangeSource { + SECURE_STATUS_CHANGE_BY_LOCAL, + SECURE_STATUS_CHANGE_BY_REMOTE, + SECURE_STATUS_REVERTED; + } + + /** + * Sets the secured state of the call session in which a specific participant + * is involved + * + * @param participant the participant who toggled (or for whom is remotely toggled) + * the secure status change for the call + * @param secured the new secure status + * @param source the source who generated the call change + */ + public void setSecured(CallParticipant participant, boolean secured, + SecureStatusChangeSource source); + + /** + * Gets the secured state of the call session in which a specific participant + * is involved + * + * @param participant the participant for who the call state is required + * @return the call state + */ + public boolean getSecured(CallParticipant participant); } diff --git a/src/net/java/sip/communicator/service/protocol/SecureEvent.java b/src/net/java/sip/communicator/service/protocol/SecureEvent.java new file mode 100644 index 000000000..5ff419d7d --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/SecureEvent.java @@ -0,0 +1,79 @@ +/* + * SIP Communicator, 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; + +import java.util.*; +import net.java.sip.communicator.impl.media.*; + +/** + * SecureEvent class extends EventObject + * This is the event type sent to current call sessions running + * when the user changes the secure state of communication in the GUI, + * to inform about the go secure / go clear change in communication. + * The event is actual triggered by a modification of the usingSRTP + * static secure communication status in the CallSessionImpl class. + * + * @author Emanuel Onica (eonica@info.uaic.ro) + * + */ +public class SecureEvent + extends EventObject +{ + /** + * Constant value defining that the user triggered secure communication. + */ + public static final int SECURE_COMMUNICATION = 1; + + /** + * Constant value defining that the user triggered unsecure communication. + */ + public static final int UNSECURE_COMMUNICATION = 2; + + /** + * The actual event value - secure or unsecure, set at one of the above constants + */ + private int eventID; + + /** + * The source that triggered the event - local or remote peer + */ + private OperationSetBasicTelephony.SecureStatusChangeSource source; + + /** + * The event constructor + * + * @param callSession the event source - the call session for which this event applies + * @param eventID the change value - going secure or stopping secure communication + */ + public SecureEvent(CallSessionImpl callSession, + int eventID, + OperationSetBasicTelephony.SecureStatusChangeSource source) + { + super(callSession); + this.eventID = eventID; + this.source = source; + } + + /** + * Retrieves the value of change - secure or unsecure + * + * @return the actual event value + */ + public int getEventID() + { + return eventID; + } + + /** + * Retrieves the source that triggered the event + * (change by local peer or remote peer or reverting a previous change) + */ + public OperationSetBasicTelephony.SecureStatusChangeSource getSource() + { + return source; + } +} diff --git a/src/net/java/sip/communicator/service/protocol/SecureEventListener.java b/src/net/java/sip/communicator/service/protocol/SecureEventListener.java new file mode 100644 index 000000000..faa1504d1 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/SecureEventListener.java @@ -0,0 +1,57 @@ +/* + * SIP Communicator, 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; + +import java.util.*; + +/** + * SecureEventListener interface extends EventListener + * This is the listener interface used to handle an event + * related with a change in security status. It is required + * to be implemented by a CallSession for this purpose. + * The change in security status is triggered at the GUI level + * by the user, who can toggle on or off securing the communication. + * This modifies the current security status static indicator for + * the call sessions, action that is the one which actually triggers + * sending the changed secure event to the current call sessions in + * progress. + * There are two different cases related to when the user could + * toggle secure communication on and off at the GUI level. + * + * 1. No active call session is in progress and + * subsequently no listeners are registered. + * Result: Only the usingSRTP static general status of security + * is changed and any following call sessions will use + * that as default for going secure or not from the start. + * + * 2. Active call sessions (actually one according to the current + * media service implementation) are in progress and have + * registered their listeners. + * Result: The usingSRTP default start status is changed and + * triggers successfully the changed secure status event + * sending to the call sessions, which handle it modifying + * the session secure state (going secure or back to normal + * state during the actual session) according to the used key + * management system. + * + * @author Emanuel Onica (eonica@info.uaic.ro) + * + */ +public interface SecureEventListener + extends EventListener +{ + + /** + * The handler for the secure event received. + * The secure event represents an indication of change in the secure + * communication status of the implementor of this interface. + * The implementor should modify it's internal state if supported. + * + * @param secureEvent the secureEvent received + */ + public void secureStatusChanged(SecureEvent secureEvent); +}