diff --git a/.classpath b/.classpath index 2673ac531ee3985a0325cb58d77f48b9d4ed61a9..eeb6f8233a9044fff9c3f1c436c46c8e46d9d524 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,8 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="lib" path="lib/bccontrib-1.0-SNAPSHOT.jar"/> - <classpathentry kind="lib" path="lib/bcprov-jdk15on-148.jar"/> + <classpathentry kind="lib" path="lib/bcpkix-jdk15on-149.jar" sourcepath="C:/Users/lyubomir/Documents/bcpkix-jdk15on-149/src.zip"/> + <classpathentry kind="lib" path="lib/bcprov-jdk15on-149.jar" sourcepath="C:/Users/lyubomir/Documents/bcprov-jdk15on-149/src.zip"/> <classpathentry kind="lib" path="lib/fmj.jar"/> <classpathentry kind="lib" path="lib/ice4j.jar"/> <classpathentry kind="lib" path="lib/jain-sdp.jar"/> diff --git a/lib/bcpkix-jdk15on-149.jar b/lib/bcpkix-jdk15on-149.jar new file mode 100644 index 0000000000000000000000000000000000000000..96d1985565c9a0c7a470ba4ca0d9e4e3cdf7405c Binary files /dev/null and b/lib/bcpkix-jdk15on-149.jar differ diff --git a/lib/bcprov-jdk15on-148.jar b/lib/bcprov-jdk15on-149.jar similarity index 55% rename from lib/bcprov-jdk15on-148.jar rename to lib/bcprov-jdk15on-149.jar index 3fcb136da27867a203f26f607567b973062f6ca9..e1d4bb31ce19fd4608d2c8489891daddbbeebc90 100644 Binary files a/lib/bcprov-jdk15on-148.jar and b/lib/bcprov-jdk15on-149.jar differ diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java index ea73980ccc06d0033bc8c170e2cc8fc84623620e..20caa1ea3850e23cf246ef7781cb0df67bf5d400 100755 --- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java @@ -23,7 +23,9 @@ import org.jitsi.impl.neomedia.codec.video.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.format.*; +import org.jitsi.impl.neomedia.transform.dtls.*; import org.jitsi.impl.neomedia.transform.sdes.*; +import org.jitsi.impl.neomedia.transform.zrtp.*; import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; @@ -704,27 +706,21 @@ private MediaDevice getNonSendVideoDevice() } /** - * Initializes a new <tt>ZrtpControl</tt> instance which is to control all - * ZRTP options. - * - * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP - * options - */ - public ZrtpControl createZrtpControl() - { - return new ZrtpControlImpl(); - } - - /** - * Initializes a new <tt>SDesControl</tt> instance which is to control all - * SDes options. - * - * @return a new <tt>SDesControl</tt> instance which is to control all SDes - * options + * {@inheritDoc} */ - public SDesControl createSDesControl() + public SrtpControl createSrtpControl(SrtpControlType srtpControlType) { - return new SDesControlImpl(); + switch (srtpControlType) + { + case DTLS_SRTP: + return new DtlsControlImpl(); + case SDES: + return new SDesControlImpl(); + case ZRTP: + return new ZrtpControlImpl(); + default: + return null; + } } /** diff --git a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java index 0b74b213cdb83034528db8ddc958b9eb9eaf1cc4..9d9c18941e3ea18f0a549cfd3b96e1f42b664c83 100644 --- a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java @@ -291,7 +291,7 @@ public MediaStreamImpl( this.srtpControl = (srtpControl == null) ? NeomediaServiceUtils.getMediaServiceImpl() - .createZrtpControl() + .createSrtpControl(SrtpControlType.ZRTP) : srtpControl; if (connector != null) diff --git a/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java b/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java index 576a750d0340bcb27d940d8a5e6cb2e381d36405..b7089188028d9375b68f1c004736435c755afc26 100755 --- a/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java +++ b/src/org/jitsi/impl/neomedia/RTPConnectorInputStream.java @@ -35,7 +35,7 @@ public abstract class RTPConnectorInputStream * The length in bytes of the buffers of <tt>RTPConnectorInputStream</tt> * receiving packets from the network. */ - private static final int PACKET_RECEIVE_BUFFER_LENGTH = 4 * 1024; + public static final int PACKET_RECEIVE_BUFFER_LENGTH = 4 * 1024; /** * Packet receive buffer diff --git a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java index 63788bf7c7832d7087fd1b3edbbe60c1b7282aec..6afde13389e6bab94b09df36fdd83dd3f8a0978c 100644 --- a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java +++ b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java @@ -17,6 +17,8 @@ import javax.media.rtp.event.*; import javax.media.rtp.rtcp.*; +import net.sf.fmj.media.rtp.RTPHeader; + import org.jitsi.impl.neomedia.protocol.*; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; @@ -763,7 +765,8 @@ private synchronized int read( * RTP packet? */ if ((length >= 12) - && (/* v */ ((buffer[offset] & 0xc0) >>> 6) == 2)) + && (/* v */ ((buffer[offset] & 0xc0) >>> 6) + == RTPHeader.VERSION)) { long ssrc = readInt(buffer, offset + 8); diff --git a/src/org/jitsi/impl/neomedia/RawPacket.java b/src/org/jitsi/impl/neomedia/RawPacket.java index dc7ededd7f400532ca40c3d5e3cb056567109f5b..f2cea27faacff63525a4fee32aa84577e991b2ab 100644 --- a/src/org/jitsi/impl/neomedia/RawPacket.java +++ b/src/org/jitsi/impl/neomedia/RawPacket.java @@ -585,10 +585,10 @@ public byte[] readTimeStampIntoByteArray() /** * Returns <tt>true</tt> if the extension bit of this packet has been set - * and false otherwise. + * and <tt>false</tt> otherwise. * * @return <tt>true</tt> if the extension bit of this packet has been set - * and false otherwise. + * and <tt>false</tt> otherwise. */ public boolean getExtensionBit() { diff --git a/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java index 6d87bf798396c742161138c264930d49fbdb36f8..2e0de0506fd412f58db259818f5652bd976058e8 100755 --- a/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java +++ b/src/org/jitsi/impl/neomedia/transform/PacketTransformer.java @@ -18,12 +18,10 @@ public interface PacketTransformer { /** - * Transforms a specific packet. - * - * @param pkt the packet to be transformed - * @return the transformed packet + * Closes this <tt>PacketTransformer</tt> i.e. releases the resources + * allocated by it and prepares it for garbage collection. */ - public RawPacket transform(RawPacket pkt); + public void close(); /** * Reverse-transforms a specific packet (i.e. transforms a transformed @@ -35,8 +33,10 @@ public interface PacketTransformer public RawPacket reverseTransform(RawPacket pkt); /** - * Closes this <tt>PacketTransformer</tt> i.e. releases the resources - * allocated by it and prepares it for garbage collection. + * Transforms a specific packet. + * + * @param pkt the packet to be transformed + * @return the transformed packet */ - public void close(); + public RawPacket transform(RawPacket pkt); } diff --git a/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java b/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java index 29da1a44c20e84d9932d295316c5a848123870f3..9bde2392461a282cfbb76d0648df9fb8a22a844e 100644 --- a/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java +++ b/src/org/jitsi/impl/neomedia/transform/TransformEngineChain.java @@ -148,8 +148,12 @@ public RawPacket transform(RawPacket pkt) //the packet transformer may be null if for example the engine //only does RTP transformations and this is an RTCP transformer. - if( pTransformer != null) + if (pTransformer != null) + { pkt = pTransformer.transform(pkt); + if (pkt == null) + break; + } } return pkt; @@ -174,11 +178,11 @@ public RawPacket reverseTransform(RawPacket pkt) //the packet transformer may be null if for example the engine //only does RTP transformations and this is an RTCP transformer. - if( pTransformer != null) + if (pTransformer != null) { pkt = pTransformer.reverseTransform(pkt); if (pkt == null) - return null; + break; } } diff --git a/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java b/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java index e3f3df9559f32fe5964b3a6a527ba76a9dae14f7..f2c9f2c91ba2b514268dc9d9229c546ddccc9ec7 100644 --- a/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java +++ b/src/org/jitsi/impl/neomedia/transform/TransformUDPOutputStream.java @@ -67,12 +67,10 @@ protected RawPacket createRawPacket(byte[] buffer, int offset, int length) pkt = transformer.transform(pkt); /* - * This is for the case when the ZRTP engine stops the media stream - * allowing only ZRTP packets. + * XXX Allow transformer to abort the writing of buffer by not + * throwing a NullPointerException if pkt becomes null after + * transform. */ - // TODO Comment in order to use the GoClear feature. - if ((pkt == null) && (targets.size() > 0)) - throw new NullPointerException("pkt"); } return pkt; } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..77b2220f0b0f44ec1d328aa23d77a9b6e36a7cb8 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java @@ -0,0 +1,341 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.io.*; +import java.util.concurrent.*; + +import javax.media.rtp.*; + +import org.bouncycastle.crypto.tls.*; +import org.ice4j.ice.*; +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.codec.video.h264.*; + +/** + * Implements {@link DatagramTransport} in order to integrate the Bouncy Castle + * Crypto APIs in libjitsi for the purposes of implementing DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class DatagramTransportImpl + implements DatagramTransport +{ + /** + * The ID of the component which this instance works for/is associated with. + */ + private final int componentID; + + /** + * The <tt>RTPConnector</tt> which represents and implements the actual + * <tt>DatagramSocket</tt> adapted by this instance. + */ + private AbstractRTPConnector connector; + + /** + * The queue of <tt>RawPacket</tt>s which have been received from the + * network are awaiting to be received by the application through this + * <tt>DatagramTransport</tt>. + */ + private final ArrayBlockingQueue<MutableRawPacket> receiveQ; + + /** + * The capacity of {@link #receiveQ}. + */ + private final int receiveQCapacity; + + /** + * Initializes a new <tt>DatagramTransportImpl</tt>. + * + * @param data {@link Component#RTP} if the new instance is to work on + * data/RTP packets or {@link Component#RTCP} if the new instance is to work + * on control/RTCP packets + */ + public DatagramTransportImpl(int componentID) + { + switch (componentID) + { + case Component.RTCP: + case Component.RTP: + this.componentID = componentID; + break; + default: + throw new IllegalArgumentException("componentID"); + } + + receiveQCapacity + = RTPConnectorOutputStream + .MAX_PACKETS_PER_MILLIS_POLICY_PACKET_QUEUE_CAPACITY; + receiveQ = new ArrayBlockingQueue<MutableRawPacket>(receiveQCapacity); + } + + /** + * {@inheritDoc} + */ + public void close() + throws IOException + { + setConnector(null); + } + + /** + * {@inheritDoc} + */ + public int getReceiveLimit() + throws IOException + { + AbstractRTPConnector connector = this.connector; + int receiveLimit + = (connector == null) ? -1 : connector.getReceiveBufferSize(); + + if (receiveLimit <= 0) + receiveLimit = RTPConnectorInputStream.PACKET_RECEIVE_BUFFER_LENGTH; + return receiveLimit; + } + + /** + * {@inheritDoc} + */ + public int getSendLimit() + throws IOException + { + AbstractRTPConnector connector = this.connector; + int sendLimit + = (connector == null) ? -1 : connector.getSendBufferSize(); + + if (sendLimit <= 0) + { + /* + * XXX The estimation bellow is wildly inaccurate and hardly + * related... but we have to start somewhere. + */ + sendLimit + = DtlsPacketTransformer.DTLS_RECORD_HEADER_LENGTH + + Packetizer.MAX_PAYLOAD_SIZE; + } + return sendLimit; + } + + /** + * Queues a packet received from the network to be received by the + * application through this <tt>DatagramTransport</tt>. + * + * @param buf the array of <tt>byte</tt>s which contains the packet to be + * queued + * @param off the offset within <tt>buf</tt> at which the packet to be + * queued starts + * @param len the length within <tt>buf</tt> starting at <tt>off</tt> of the + * packet to be queued + */ + void queue(byte[] buf, int off, int len) + { + if (len > 0) + { + synchronized (receiveQ) + { + if (connector == null) + { + throw new IllegalStateException( + getClass().getName() + " is closed!"); + } + + byte[] pktBuf = new byte[len]; + int pktOff = 0; + + System.arraycopy(buf, off, pktBuf, pktOff, len); + + MutableRawPacket pkt + = new MutableRawPacket(pktBuf, pktOff, len); + + if (receiveQ.size() == receiveQCapacity) + receiveQ.remove(); + receiveQ.add(pkt); + receiveQ.notifyAll(); + } + } + } + + /** + * {@inheritDoc} + */ + public int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + long enterTime = System.currentTimeMillis(); + int received = 0; + boolean interrupted = false; + + while (received < len) + { + long timeout; + + if (waitMillis > 0) + { + timeout = waitMillis - System.currentTimeMillis() + enterTime; + if (timeout == 0 /* wait forever */) + timeout = -1 /* do not wait */; + } + else + { + timeout = waitMillis; + } + + synchronized (receiveQ) + { + if (connector == null) + throw new IOException(getClass().getName() + " is closed!"); + + MutableRawPacket pkt = receiveQ.peek(); + + if (pkt != null) + { + int toReceive = len - received; + boolean toReceiveIsPositive = (toReceive > 0); + + if (toReceiveIsPositive) + { + int pktLength = pkt.getLength(); + int pktOffset = pkt.getOffset(); + + if (toReceive > pktLength) + { + toReceive = pktLength; + toReceiveIsPositive = (toReceive > 0); + } + if (toReceiveIsPositive) + { + System.arraycopy( + pkt.getBuffer(), pktOffset, + buf, off + received, + toReceive); + received += toReceive; + } + if (toReceive == pktLength) + { + receiveQ.remove(); + } + else + { + pkt.setLength(pktLength - toReceive); + pkt.setOffset(pktOffset + toReceive); + } + if (toReceiveIsPositive) + { + /* + * The specified buf has received toReceive bytes + * and we do not concatenate RawPackets. + */ + break; + } + } + else + { + // The specified buf has received at least len bytes. + break; + } + } + + if (receiveQ.isEmpty() && (timeout >= 0)) + { + try + { + receiveQ.wait(timeout); + } + catch (InterruptedException ie) + { + interrupted = true; + } + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + return received; + } + + /** + * {@inheritDoc} + */ + public void send(byte[] buf, int off, int len) + throws IOException + { + OutputDataStream outputStream; + + switch (componentID) + { + case Component.RTCP: + outputStream = connector.getControlOutputStream(); + break; + case Component.RTP: + outputStream = connector.getDataOutputStream(); + break; + default: + throw new IllegalStateException("componentID"); + } + + outputStream.write(buf, off, len); + } + + /** + * Sets the <tt>RTPConnector</tt> which represents and implements the actual + * <tt>DatagramSocket</tt> to be adapted by this instance. + * + * @param connector the <tt>RTPConnector</tt> which represents and + * implements the actual <tt>DatagramSocket</tt> to be adapted by this + * instance + */ + void setConnector(AbstractRTPConnector connector) + { + synchronized (receiveQ) + { + this.connector = connector; + receiveQ.notifyAll(); + } + } + + /** + * Exposes the <tt>setLength</tt> and <tt>setOffset</tt> methods of + * <tt>RawPacket</tt> to <tt>DatagramTransportImpl</tt>. + */ + private static class MutableRawPacket + extends RawPacket + { + /** + * Initializes a new <tt>MutableRawPacket</tt> instance. + * + * @param buf the array of <tt>byte</tt>s to be represented by the new + * instance + * @param off the offset within <tt>buf</tt> at which the actual packet + * content to be represented by the new instance starts + * @param len the number of bytes within <tt>buf</tt> starting at + * <tt>offset</tt> which comprise the actual packet content to be + * represented by the new instance + */ + public MutableRawPacket(byte[] buf, int off, int len) + { + super(buf, off, len); + } + + /** + * {@inheritDoc} + */ + @Override + public void setLength(int length) + { + super.setLength(length); + } + + /** + * {@inheritDoc} + */ + @Override + public void setOffset(int offset) + { + super.setOffset(offset); + } + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..85967ae66be872e09689afda3de92c883fcefa32 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java @@ -0,0 +1,593 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.io.*; +import java.math.*; +import java.security.*; +import java.util.*; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.x500.*; +import org.bouncycastle.asn1.x500.style.*; +import org.bouncycastle.asn1.x509.*; +import org.bouncycastle.cert.*; +import org.bouncycastle.crypto.*; +import org.bouncycastle.crypto.generators.*; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.util.*; +import org.bouncycastle.operator.*; +import org.bouncycastle.operator.bc.*; +import org.jitsi.impl.neomedia.*; +import org.jitsi.service.neomedia.*; +import org.jitsi.service.version.*; +import org.jitsi.util.*; + +/** + * Implements {@link DtlsControl} i.e. {@link SrtpControl} for DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class DtlsControlImpl + extends AbstractSrtpControl<DtlsTransformEngine> + implements DtlsControl +{ + /** + * The table which maps half-<tt>byte</tt>s to their hex characters. + */ + private static final char[] HEX_ENCODE_TABLE + = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + /** + * The <tt>Logger</tt> used by the <tt>DtlsControlImpl</tt> class and its + * instances to print debug information. + */ + private static final Logger logger + = Logger.getLogger(DtlsControlImpl.class); + + /** + * The number of milliseconds within a day i.e. 24 hours. + */ + private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + + /** + * The certificate with which the local endpoint represented by this + * instance authenticates its ends of DTLS sessions. + */ + private final org.bouncycastle.crypto.tls.Certificate certificate; + + /** + * The <tt>RTPConnector</tt> which uses the <tt>TransformEngine</tt> of this + * <tt>SrtpControl</tt>. + */ + private AbstractRTPConnector connector; + + /** + * The indicator which determines whether this instance has been disposed + * i.e. prepared for garbage collection by {@link #cleanup()}. + */ + private boolean disposed = false; + + /** + * The DTLS protocol according to which this <tt>DtlsControl</tt> is to act + * either as a DTLS server or a DTLS client. + */ + private int dtlsProtocol; + + /** + * The private and public keys of {@link #certificate}. + */ + private final AsymmetricCipherKeyPair keyPair; + + /** + * The fingerprint of {@link #certificate}. + */ + private final String localFingerprint; + + /** + * The hash function of {@link #localFingerprint} (which is the same as the + * digest algorithm of the signature algorithm of {@link #certificate} in + * accord with RFC 4572). + */ + private final String localFingerprintHashFunction; + + /** + * The fingerprints presented by the remote endpoint via the signaling path. + */ + private Map<String,String> remoteFingerprints; + + /** + * Initializes a new <tt>DtlsControlImpl</tt> instance. + */ + public DtlsControlImpl() + { + super(SrtpControlType.DTLS_SRTP); + + keyPair = generateKeyPair(); + + org.bouncycastle.asn1.x509.Certificate x509Certificate + = generateX509Certificate(generateCN(), keyPair); + + certificate + = new org.bouncycastle.crypto.tls.Certificate( + new org.bouncycastle.asn1.x509.Certificate[] + { + x509Certificate + }); + localFingerprintHashFunction = findHashFunction(x509Certificate); + localFingerprint + = computeFingerprint( + x509Certificate, + localFingerprintHashFunction); + } + + /** + * {@inheritDoc} + */ + @Override + public void cleanup() + { + super.cleanup(); + + setConnector(null); + + synchronized (this) + { + disposed = true; + notifyAll(); + } + } + + /** + * Computes the fingerprint of a specific certificate using a specific + * hash function. + * + * @param certificate the certificate the fingerprint of which is to be + * computed + * @param hashFunction the hash function to be used in order to compute the + * fingerprint of the specified <tt>certificate</tt> + * @return the fingerprint of the specified <tt>certificate</tt> computed + * using the specified <tt>hashFunction</tt> + */ + private static final String computeFingerprint( + org.bouncycastle.asn1.x509.Certificate certificate, + String hashFunction) + { + try + { + AlgorithmIdentifier digAlgId + = new DefaultDigestAlgorithmIdentifierFinder().find( + hashFunction.toUpperCase()); + Digest digest = BcDefaultDigestProvider.INSTANCE.get(digAlgId); + byte[] in = certificate.getEncoded(ASN1Encoding.DER); + byte[] out = new byte[digest.getDigestSize()]; + + digest.update(in, 0, in.length); + digest.doFinal(out, 0); + + return toHex(out); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + { + throw (ThreadDeath) t; + } + else + { + logger.error("Failed to generate certificate fingerprint!", t); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + else + throw new RuntimeException(t); + } + } + } + + /** + * Initializes a new <tt>DtlsTransformEngine</tt> instance to be associated + * with and used by this <tt>DtlsControlImpl</tt> instance. + * + * @return a new <tt>DtlsTransformEngine</tt> instance to be associated with + * and used by this <tt>DtlsControlImpl</tt> instance + */ + protected DtlsTransformEngine createTransformEngine() + { + DtlsTransformEngine transformEngine = new DtlsTransformEngine(this); + + transformEngine.setConnector(connector); + transformEngine.setDtlsProtocol(dtlsProtocol); + return transformEngine; + } + + /** + * Generates a new subject for a self-signed certificate to be generated by + * <tt>DtlsControlImpl</tt>. + * + * @return an <tt>X500Name</tt> which is to be used as the subject of a + * self-signed certificate to be generated by <tt>DtlsControlImpl</tt> + */ + private static X500Name generateCN() + { + X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE); + String applicationName + = System.getProperty(Version.PNAME_APPLICATION_NAME); + String applicationVersion + = System.getProperty(Version.PNAME_APPLICATION_VERSION); + StringBuilder cn = new StringBuilder(); + + if (!StringUtils.isNullOrEmpty(applicationName, true)) + cn.append(applicationName); + if (!StringUtils.isNullOrEmpty(applicationVersion, true)) + { + if (cn.length() != 0) + cn.append(' '); + cn.append(applicationVersion); + } + if (cn.length() == 0) + cn.append(DtlsControlImpl.class.getName()); + builder.addRDN(BCStyle.CN, cn.toString()); + + return builder.build(); + } + + /** + * Generates a new pair of private and public keys. + * + * @return a new pair of private and public keys + */ + private static AsymmetricCipherKeyPair generateKeyPair() + { + RSAKeyPairGenerator generator = new RSAKeyPairGenerator(); + + generator.init( + new RSAKeyGenerationParameters( + new BigInteger("10001", 16), + new SecureRandom(), + 1024, + 80)); + return generator.generateKeyPair(); + } + + /** + * Generates a new self-signed certificate with a specific subject and a + * specific pair of private and public keys. + * + * @param subject the subject (and issuer) of the new certificate to be + * generated + * @param keyPair the pair of private and public keys of the certificate to + * be generated + * @return a new self-signed certificate with the specified <tt>subject</tt> + * and <tt>keyPair</tt> + */ + private static org.bouncycastle.asn1.x509.Certificate + generateX509Certificate( + X500Name subject, + AsymmetricCipherKeyPair keyPair) + { + try + { + long now = System.currentTimeMillis(); + Date notBefore = new Date(now - ONE_DAY); + Date notAfter = new Date(now + 6 * ONE_DAY); + X509v3CertificateBuilder builder + = new X509v3CertificateBuilder( + /* issuer */ subject, + /* serial */ BigInteger.valueOf(now), + notBefore, + notAfter, + subject, + /* publicKeyInfo */ + SubjectPublicKeyInfoFactory + .createSubjectPublicKeyInfo( + keyPair.getPublic())); + AlgorithmIdentifier sigAlgId + = new DefaultSignatureAlgorithmIdentifierFinder() + .find("SHA1withRSA"); + AlgorithmIdentifier digAlgId + = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); + ContentSigner signer + = new BcRSAContentSignerBuilder(sigAlgId, digAlgId) + .build(keyPair.getPrivate()); + + return builder.build(signer).toASN1Structure(); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "Failed to generate self-signed X.509 certificate", + t); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + else + throw new RuntimeException(t); + } + } + } + + /** + * The private and public keys of the <tt>certificate</tt> of this instance. + * + * @return the private and public keys of the <tt>certificate</tt> of this + * instance + */ + AsymmetricCipherKeyPair getKeyPair() + { + return keyPair; + } + + /** + * {@inheritDoc} + */ + public boolean getSecureCommunicationStatus() + { + // TODO Auto-generated method stub + return false; + } + + /** + * Determines the hash function i.e. the digest algorithm of the signature + * algorithm of a specific certificate. + * + * @param certificate the certificate the hash function of which is to be + * determined + * @return the hash function of the specified <tt>certificate</tt> + */ + private static String findHashFunction( + org.bouncycastle.asn1.x509.Certificate certificate) + { + try + { + AlgorithmIdentifier sigAlgId = certificate.getSignatureAlgorithm(); + AlgorithmIdentifier digAlgId + = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); + + return + BcDefaultDigestProvider.INSTANCE + .get(digAlgId) + .getAlgorithmName() + .toLowerCase(); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + { + throw (ThreadDeath) t; + } + else + { + logger.warn( + "Failed to find the hash function of the signature" + + " algorithm of a certificate!", + t); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + else + throw new RuntimeException(t); + } + } + } + + /** + * Gets the certificate with which the local endpoint represented by this + * instance authenticates its ends of DTLS sessions. + * + * @return the certificate with which the local endpoint represented by this + * instance authenticates its ends of DTLS sessions. + */ + org.bouncycastle.crypto.tls.Certificate getCertificate() + { + return certificate; + } + + /** + * {@inheritDoc} + */ + public String getLocalFingerprint() + { + return localFingerprint; + } + + /** + * {@inheritDoc} + */ + public String getLocalFingerprintHashFunction() + { + return localFingerprintHashFunction; + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>DtlsControlImpl</tt> always returns + * <tt>true</tt>. + */ + public boolean requiresSecureSignalingTransport() + { + return true; + } + + /** + * {@inheritDoc} + */ + public void setConnector(AbstractRTPConnector connector) + { + if (this.connector != connector) + { + this.connector = connector; + + DtlsTransformEngine transformEngine = this.transformEngine; + + if (transformEngine != null) + transformEngine.setConnector(this.connector); + } + } + + /** + * {@inheritDoc} + */ + public void setDtlsProtocol(int dtlsProtocol) + { + if (this.dtlsProtocol != dtlsProtocol) + { + /* + * TODO At the time of this writing, changing the DTLS protocol is + * not supported. + */ + if ((this.dtlsProtocol == DTLS_CLIENT_PROTOCOL) + || (this.dtlsProtocol == DTLS_SERVER_PROTOCOL)) + return; + + this.dtlsProtocol = dtlsProtocol; + + DtlsTransformEngine transformEngine = this.transformEngine; + + if (transformEngine != null) + transformEngine.setDtlsProtocol(this.dtlsProtocol); + } + } + + /** + * {@inheritDoc} + */ + public void setRemoteFingerprints(Map<String,String> remoteFingerprints) + { + if (remoteFingerprints == null) + throw new NullPointerException("remoteFingerprints"); + + synchronized (this) + { + this.remoteFingerprints = remoteFingerprints; + notifyAll(); + } + } + + /** + * {@inheritDoc} + */ + public void start(MediaType mediaType) + { + DtlsTransformEngine transformEngine = getTransformEngine(); + + if (transformEngine != null) + transformEngine.start(mediaType); + } + + /** + * Gets the <tt>String</tt> representation of a fingerprint specified in the + * form of an array of <tt>byte</tt>s in accord with RFC 4572. + * + * @param fingerprint an array of <tt>bytes</tt> which represents a + * fingerprint the <tt>String</tt> representation in accord with RFC 4572 + * of which is to be returned + * @return the <tt>String</tt> representation in accord with RFC 4572 of the + * specified <tt>fingerprint</tt> + */ + private static String toHex(byte[] fingerprint) + { + if (fingerprint.length == 0) + throw new IllegalArgumentException("fingerprint"); + + char[] chars = new char[3 * fingerprint.length - 1]; + + for (int f = 0, fLast = fingerprint.length - 1, c = 0; + f <= fLast; + f++) + { + int b = fingerprint[f] & 0xff; + + chars[c++] = HEX_ENCODE_TABLE[b >>> 4]; + chars[c++] = HEX_ENCODE_TABLE[b & 0x0f]; + if (f != fLast) + chars[c++] = ':'; + } + return new String(chars); + } + + /** + * Verifies and validates a specific certificate against the fingerprints + * presented by the remote endpoint via the signaling path. + * + * @param certificate the certificate to be verified and validated against + * the fingerprints presented by the remote endpoint via the signaling path + * @throws Exception if the specified <tt>certificate</tt> failed to verify + * and validate against the fingerprints presented by the remote endpoint + * via the signaling path + */ + void verifyAndValidateCertificate( + org.bouncycastle.asn1.x509.Certificate certificate) + throws Exception + { + /* + * RFC 4572 "Connection-Oriented Media Transport over the Transport + * Layer Security (TLS) Protocol in the Session Description Protocol + * (SDP)" defines that "[a] certificate fingerprint MUST be computed + * using the same one-way hash function as is used in the certificate's + * signature algorithm." + */ + String hashFunction = findHashFunction(certificate); + String fingerprint = computeFingerprint(certificate, hashFunction); + + /* + * As RFC 5763 "Framework for Establishing a Secure Real-time Transport + * Protocol (SRTP) Security Context Using Datagram Transport Layer + * Security (DTLS)" states, "the certificate presented during the DTLS + * handshake MUST match the fingerprint exchanged via the signaling path + * in the SDP." + */ + String remoteFingerprint; + + synchronized (this) + { + if (disposed) + throw new IllegalStateException("disposed"); + else + remoteFingerprint = remoteFingerprints.get(hashFunction); + } + if (!remoteFingerprint.equals(fingerprint)) + { + throw new IOException( + "Fingerprint " + remoteFingerprint + + " does not match the " + hashFunction + + "-hashed certificate " + fingerprint + "!"); + } + } + + /** + * Verifies and validates a specific certificate against the fingerprints + * presented by the remote endpoint via the signaling path. + * + * @param certificate the certificate to be verified and validated against + * the fingerprints presented by the remote endpoint via the signaling path + * @throws Exception if the specified <tt>certificate</tt> failed to verify + * and validate against the fingerprints presented by the remote endpoint + * via the signaling path + */ + void verifyAndValidateCertificate( + org.bouncycastle.crypto.tls.Certificate certificate) + throws Exception + { + org.bouncycastle.asn1.x509.Certificate[] certificateList + = certificate.getCertificateList(); + + if (certificateList.length == 0) + throw new IllegalArgumentException("certificate.certificateList"); + + for (org.bouncycastle.asn1.x509.Certificate x509Certificate + : certificateList) + { + verifyAndValidateCertificate(x509Certificate); + } + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..582905330a797b9d8535a53f284aed8333a1ed86 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java @@ -0,0 +1,626 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.io.*; +import java.security.*; + +import org.bouncycastle.crypto.tls.*; +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.transform.*; +import org.jitsi.service.neomedia.*; +import org.jitsi.util.*; + +/** + * Implements {@link PacketTransformer} for DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class DtlsPacketTransformer + implements PacketTransformer +{ + /** + * The length of the header of a DTLS record. + */ + static final int DTLS_RECORD_HEADER_LENGTH = 13; + + /** + * The number of milliseconds a <tt>DtlsPacketTransform</tt> is to wait on + * its {@link #dtlsProtocol} in order to receive a packet. + */ + private static final int DTLS_TRANSPORT_RECEIVE_WAITMILLIS = 1; + + /** + * The <tt>Logger</tt> used by the <tt>DtlsPacketTransformer</tt> class and + * its instances to print debug information. + */ + private static final Logger logger + = Logger.getLogger(DtlsPacketTransformer.class); + + /** + * Determines whether a specific array of <tt>byte</tt>s appears to contain + * a DTLS record. + * + * @param buf the array of <tt>byte</tt>s to be analyzed + * @param off the offset within <tt>buf</tt> at which the analysis is to + * start + * @param len the number of bytes within <tt>buf</tt> starting at + * <tt>off</tt> to be analyzed + * @return <tt>true</tt> if the specified <tt>buf</tt> appears to contain a + * DTLS record + */ + public static boolean isDtlsRecord(byte[] buf, int off, int len) + { + boolean b = false; + + if (len >= DTLS_RECORD_HEADER_LENGTH) + { + short type = TlsUtils.readUint8(buf, off); + + switch (type) + { + case ContentType.alert: + case ContentType.application_data: + case ContentType.change_cipher_spec: + case ContentType.handshake: + int major = buf[off + 1] & 0xff; + int minor = buf[off + 2] & 0xff; + ProtocolVersion version = null; + + if ((major == ProtocolVersion.DTLSv10.getMajorVersion()) + && (minor == ProtocolVersion.DTLSv10.getMinorVersion())) + { + version = ProtocolVersion.DTLSv10; + } + if ((version == null) + && (major == ProtocolVersion.DTLSv12.getMajorVersion()) + && (minor == ProtocolVersion.DTLSv12.getMinorVersion())) + { + version = ProtocolVersion.DTLSv12; + } + if (version != null) + { + int length = TlsUtils.readUint16(buf, off + 11); + + if (DTLS_RECORD_HEADER_LENGTH + length <= len) + b = true; + } + break; + default: + /* + * Unless a new ContentType has been defined by the Bouncy + * Castle Crypto APIs, the specified buf does not represent a + * DTLS record. + */ + break; + } + } + return b; + } + + /** + * The ID of the component which this instance works for/is associated with. + */ + private final int componentID; + + /** + * The background <tt>Thread</tt> which initializes {@link #dtlsTransport}. + */ + private Thread connectThread; + + /** + * The <tt>RTPConnector</tt> which uses this <tt>PacketTransformer</tt>. + */ + private AbstractRTPConnector connector; + + /** + * The <tt>DatagramTransport</tt> implementation which adapts + * {@link #connector} and this <tt>PacketTransformer</tt> to the terms of + * the Bouncy Castle Crypto APIs. + */ + private DatagramTransportImpl datagramTransport; + + /** + * The DTLS protocol according to which this <tt>DtlsPacketTransformer</tt> + * is to act either as a DTLS server or a DTLS client. + */ + private int dtlsProtocol; + + /** + * The <tt>DTLSTransport</tt> through which the actual packet + * transformations are being performed by this instance. + */ + private DTLSTransport dtlsTransport; + + /** + * The <tt>MediaType</tt> of the stream which this instance works for/is + * associated with. + */ + private MediaType mediaType; + + /** + * The <tt>TransformEngine</tt> which has initialized this instance. + */ + private final DtlsTransformEngine transformEngine; + + /** + * Initializes a new <tt>DtlsPacketTransformer</tt> instance. + * + * @param transformEngine the <tt>TransformEngine</tt> which is initializing + * the new instance + * @param componentID the ID of the component for which the new instance is + * to work + */ + public DtlsPacketTransformer( + DtlsTransformEngine transformEngine, + int componentID) + { + this.transformEngine = transformEngine; + this.componentID = componentID; + } + + /** + * {@inheritDoc} + */ + public synchronized void close() + { + setConnector(null); + setMediaType(null); + } + + /** + * Closes {@link #datagramTransport} if it is non-<tt>null</tt> and logs and + * swallows any <tt>IOException</tt>. + */ + private void closeDatagramTransport() + { + if (datagramTransport != null) + { + try + { + datagramTransport.close(); + } + catch (IOException ioe) + { + /* + * DatagramTransportImpl has no reason to fail because it is + * merely an adapter of #connector and this PacketTransformer to + * the terms of the Bouncy Castle Crypto API. + */ + logger.error( + "Failed to (properly) close " + + datagramTransport.getClass(), + ioe); + } + datagramTransport = null; + } + } + + /** + * Gets the <tt>DtlsControl</tt> implementation associated with this + * instance. + * + * @return the <tt>DtlsControl</tt> implementation associated with this + * instance + */ + DtlsControlImpl getDtlsControl() + { + return getTransformEngine().getDtlsControl(); + } + + /** + * Gets the <tt>TransformEngine</tt> which has initialized this instance. + * + * @return the <tt>TransformEngine</tt> which has initialized this instance + */ + DtlsTransformEngine getTransformEngine() + { + return transformEngine; + } + + /** + * {@inheritDoc} + */ + public RawPacket reverseTransform(RawPacket pkt) + { + byte[] buf = pkt.getBuffer(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + + if (isDtlsRecord(buf, off, len)) + { + boolean receive; + + synchronized (this) + { + if (datagramTransport == null) + { + receive = false; + } + else + { + datagramTransport.queue(buf, off, len); + receive = true; + } + } + if (receive) + { + DTLSTransport dtlsTransport = this.dtlsTransport; + + if (dtlsTransport == null) + { + /* + * The specified pkt looks like a DTLS record and it has + * been consumed for the purposes of the secure channel + * represented by this PacketTransformer. + */ + pkt = null; + } + else + { + try + { + int receiveLimit = dtlsTransport.getReceiveLimit(); + int delta = receiveLimit - len; + + if (delta > 0) + { + pkt.grow(delta); + buf = pkt.getBuffer(); + off = pkt.getOffset(); + len = pkt.getLength(); + } + else if (delta < 0) + { + pkt.shrink(-delta); + buf = pkt.getBuffer(); + off = pkt.getOffset(); + len = pkt.getLength(); + } + + int received + = dtlsTransport.receive( + buf, off, len, + DTLS_TRANSPORT_RECEIVE_WAITMILLIS); + + if (received <= 0) + { + // No application data was decoded. + pkt = null; + } + else + { + delta = len - received; + if (delta > 0) + pkt.shrink(delta); + } + } + catch (IOException ioe) + { + pkt = null; + logger.error("Failed to decode a DTLS record!", ioe); + } + } + } + else + { + /* + * The specified pkt looks like a DTLS record but it is + * unexpected in the current state of the secure channels + * represented by this PacketTransformer. + */ + pkt = null; + } + } + else + { + /* + * The specified pkt does not look like a DTLS record so it is not a + * valid packet on the secure channel represented by this + * PacketTransformer. + */ + pkt = null; + } + return pkt; + } + + /** + * Runs in {@link #connectThread} to initialize {@link #dtlsTransport}. + * + * @param dtlsProtocol + * @param tlsPeer + * @param datagramTransport + */ + private void runInConnectThread( + DTLSProtocol dtlsProtocol, + TlsPeer tlsPeer, + DatagramTransport datagramTransport) + { + DTLSTransport dtlsTransport = null; + + if (dtlsProtocol instanceof DTLSClientProtocol) + { + DTLSClientProtocol dtlsClientProtocol + = (DTLSClientProtocol) dtlsProtocol; + TlsClient tlsClient = (TlsClient) tlsPeer; + + try + { + dtlsTransport + = dtlsClientProtocol.connect( + tlsClient, + datagramTransport); + } + catch (IOException ioe) + { + logger.error( + "Failed to connect this DTLS client to a DTLS server!", + ioe); + } + } + else if (dtlsProtocol instanceof DTLSServerProtocol) + { + DTLSServerProtocol dtlsServerProtocol + = (DTLSServerProtocol) dtlsProtocol; + TlsServer tlsServer = (TlsServer) tlsPeer; + + try + { + dtlsTransport + = dtlsServerProtocol.accept( + tlsServer, + datagramTransport); + } + catch (IOException ioe) + { + logger.error( + "Failed to accept a connection from a DTLS client!", + ioe); + } + } + else + throw new IllegalStateException("dtlsProtocol"); + + synchronized (this) + { + if (Thread.currentThread().equals(this.connectThread) + && datagramTransport.equals(this.datagramTransport)) + { + this.dtlsTransport = dtlsTransport; + notifyAll(); + } + } + } + + /** + * Sets the <tt>RTPConnector</tt> which is to use or uses this + * <tt>PacketTransformer</tt>. + * + * @param connector the <tt>RTPConnector</tt> which is to use or uses this + * <tt>PacketTransformer</tt> + */ + void setConnector(AbstractRTPConnector connector) + { + if (this.connector != connector) + { + this.connector = connector; + + DatagramTransportImpl datagramTransport = this.datagramTransport; + + if (datagramTransport != null) + datagramTransport.setConnector(connector); + } + } + + /** + * Sets the DTLS protocol according to which this + * <tt>DtlsPacketTransformer</tt> is to act either as a DTLS server or a + * DTLS client. + * + * @param dtlsProtocol {@link DtlsControl#DTLS_CLIENT_PROTOCOL} to have this + * <tt>DtlsPacketTransformer</tt> act as a DTLS client or + * {@link DtlsControl#DTLS_SERVER_PROTOCOL} to have this + * <tt>DtlsPacketTransformer</tt> act as a DTLS server + */ + void setDtlsProtocol(int dtlsProtocol) + { + if (this.dtlsProtocol != dtlsProtocol) + { + this.dtlsProtocol = dtlsProtocol; + } + } + + /** + * Sets the <tt>MediaType</tt> of the stream which this instance is to work + * for/be associated with. + * + * @param mediaType the <tt>MediaType</tt> of the stream which this instance + * is to work for/be associated with + */ + synchronized void setMediaType(MediaType mediaType) + { + if (this.mediaType != mediaType) + { + if (this.mediaType != null) + stop(); + + this.mediaType = mediaType; + + if (this.mediaType != null) + start(); + } + } + + /** + * Starts this <tt>PacketTransformer</tt>. + */ + private synchronized void start() + { + if (this.datagramTransport != null) + { + if ((this.connectThread == null) && (dtlsTransport == null)) + { + logger.warn( + getClass().getName() + + " has been started but has failed to establish" + + " the DTLS connection!"); + } + return; + } + + int dtlsProtocol = this.dtlsProtocol; + + if ((dtlsProtocol != DtlsControl.DTLS_CLIENT_PROTOCOL) + && (dtlsProtocol != DtlsControl.DTLS_SERVER_PROTOCOL)) + throw new IllegalStateException("dtlsProtocol"); + + AbstractRTPConnector connector = this.connector; + + if (connector == null) + throw new NullPointerException("connector"); + + SecureRandom secureRandom = new SecureRandom(); + final DTLSProtocol dtlsProtocolObj; + final TlsPeer tlsPeer; + + if (dtlsProtocol == DtlsControl.DTLS_CLIENT_PROTOCOL) + { + dtlsProtocolObj = new DTLSClientProtocol(secureRandom); + tlsPeer = new TlsClientImpl(this); + } + else + { + dtlsProtocolObj = new DTLSServerProtocol(secureRandom); + tlsPeer = new TlsServerImpl(this); + } + + final DatagramTransportImpl datagramTransport + = new DatagramTransportImpl(componentID); + + datagramTransport.setConnector(connector); + + Thread connectThread + = new Thread() + { + @Override + public void run() + { + try + { + runInConnectThread( + dtlsProtocolObj, + tlsPeer, + datagramTransport); + } + finally + { + if (Thread.currentThread().equals( + DtlsPacketTransformer.this.connectThread)) + { + DtlsPacketTransformer.this.connectThread = null; + } + } + } + }; + + connectThread.setDaemon(true); + connectThread.setName( + DtlsPacketTransformer.class.getName() + ".connectThread"); + + this.connectThread = connectThread; + this.datagramTransport = datagramTransport; + + boolean started = false; + + try + { + connectThread.start(); + started = true; + } + finally + { + if (!started) + { + if (connectThread.equals(this.connectThread)) + this.connectThread = null; + if (datagramTransport.equals(this.datagramTransport)) + this.datagramTransport = null; + } + } + + notifyAll(); + } + + /** + * Stops this <tt>PacketTransformer</tt>. + */ + private synchronized void stop() + { + if (connectThread != null) + connectThread = null; + if (dtlsTransport != null) + { + try + { + dtlsTransport.close(); + } + catch (IOException ioe) + { + logger.error( + "Failed to (properly) close " + + dtlsTransport.getClass(), + ioe); + } + dtlsTransport = null; + } + closeDatagramTransport(); + + notifyAll(); + } + + /** + * {@inheritDoc} + */ + public RawPacket transform(RawPacket pkt) + { + byte[] buf = pkt.getBuffer(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + + /* + * If the specified pkt represents a DTLS record, then it should pass + * through this PacketTransformer (e.g. it has been sent through + * DatagramPacketImpl). + */ + if (!isDtlsRecord(buf, off, len)) + { + /* + * The specified pkt will pass through this PacketTransformer only + * if it gets transformed into a DTLS record. + */ + pkt = null; + + DTLSTransport dtlsTransport = this.dtlsTransport; + + if (dtlsTransport != null) + { + try + { + dtlsTransport.send(buf, off, len); + } + catch (IOException ioe) + { + logger.error( + "Failed to send application data over DTLS" + + " transport!", + ioe); + } + } + } + return pkt; + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..d5a765084ae6eb75e750efabaa53fc6d5cda876b --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java @@ -0,0 +1,225 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import org.ice4j.ice.*; +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.transform.*; +import org.jitsi.service.neomedia.*; + +/** + * Implements {@link SrtpControl.TransformEngine} (and, respectively, + * {@link org.jitsi.impl.neomedia.transform.TransformEngine}) for DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class DtlsTransformEngine + implements SrtpControl.TransformEngine +{ + /** + * The <tt>RTPConnector</tt> which uses this <tt>TransformEngine</tt>. + */ + private AbstractRTPConnector connector; + + /** + * The <tt>DtlsControl</tt> which has initialized this instance. + */ + private final DtlsControlImpl dtlsControl; + + /** + * The DTLS protocol according to which this <tt>DtlsTransformEngine</tt> is + * to act either as a DTLS server or a DTLS client. + */ + private int dtlsProtocol; + + /** + * The <tt>MediaType</tt> of the stream which this instance works for/is + * associated with. + */ + private MediaType mediaType; + + /** + * The <tt>PacketTransformer</tt>s of this <tt>TransformEngine</tt> for + * data/RTP and control/RTCP packets. + */ + private final DtlsPacketTransformer[] packetTransformers + = new DtlsPacketTransformer[2]; + + /** + * Initializes a new <tt>DtlsTransformEngine</tt> instance. + */ + public DtlsTransformEngine(DtlsControlImpl dtlsControl) + { + this.dtlsControl = dtlsControl; + } + + /** + * {@inheritDoc} + */ + public void cleanup() + { + for (int i = 0; i < packetTransformers.length; i++) + { + DtlsPacketTransformer packetTransformer = packetTransformers[i]; + + if (packetTransformer != null) + { + packetTransformer.close(); + packetTransformers[i] = null; + } + } + + setConnector(null); + setMediaType(null); + } + + /** + * Initializes a new <tt>DtlsPacketTransformer</tt> instance which is to + * work on control/RTCP or data/RTP packets. + * + * @param componentID the ID of the component for which the new instance is + * to work + * @return a new <tt>DtlsPacketTransformer</tt> instance which is to work on + * control/RTCP or data/RTP packets (in accord with <tt>data</tt>) + */ + private DtlsPacketTransformer createPacketTransformer(int componentID) + { + DtlsPacketTransformer packetTransformer + = new DtlsPacketTransformer(this, componentID); + + packetTransformer.setConnector(connector); + packetTransformer.setDtlsProtocol(dtlsProtocol); + packetTransformer.setMediaType(mediaType); + return packetTransformer; + } + + /** + * Gets the <tt>DtlsControl</tt> which has initialized this instance. + * + * @return the <tt>DtlsControl</tt> which has initialized this instance + */ + DtlsControlImpl getDtlsControl() + { + return dtlsControl; + } + + /** + * Gets the <tt>PacketTransformer</tt> of this <tt>TransformEngine</tt> + * which is to work or works for the component with a specific ID. + * + * @param componentID the ID of the component for which the returned + * <tt>PacketTransformer</tt> is to work or works + * @return the <tt>PacketTransformer</tt>, if any, which is to work or works + * for the component with the specified <tt>componentID</tt> + */ + private DtlsPacketTransformer getPacketTransformer(int componentID) + { + int index = componentID - 1; + DtlsPacketTransformer packetTransformer = packetTransformers[index]; + + if (packetTransformer == null) + { + packetTransformer = createPacketTransformer(componentID); + if (packetTransformer != null) + packetTransformers[index] = packetTransformer; + } + return packetTransformer; + } + + /** + * {@inheritDoc} + */ + public PacketTransformer getRTCPTransformer() + { + return getPacketTransformer(Component.RTCP); + } + + /** + * {@inheritDoc} + */ + public PacketTransformer getRTPTransformer() + { + return getPacketTransformer(Component.RTP); + } + + /** + * Sets the <tt>RTPConnector</tt> which is to use or uses this + * <tt>TransformEngine</tt>. + * + * @param connector the <tt>RTPConnector</tt> which is to use or uses this + * <tt>TransformEngine</tt> + */ + void setConnector(AbstractRTPConnector connector) + { + if (this.connector != connector) + { + this.connector = connector; + + for (DtlsPacketTransformer packetTransformer : packetTransformers) + { + if (packetTransformer != null) + packetTransformer.setConnector(this.connector); + } + } + } + + /** + * Sets the DTLS protocol according to which this + * <tt>DtlsTransformEngine</tt> is to act either as a DTLS server or a DTLS + * client. + * + * @param dtlsProtocol {@link DtlsControl#DTLS_CLIENT_PROTOCOL} to have this + * <tt>DtlsTransformEngine</tt> act as a DTLS client or + * {@link DtlsControl#DTLS_SERVER_PROTOCOL} to have this + * <tt>DtlsTransformEngine</tt> act as a DTLS server + */ + void setDtlsProtocol(int dtlsProtocol) + { + if (this.dtlsProtocol != dtlsProtocol) + { + this.dtlsProtocol = dtlsProtocol; + + for (DtlsPacketTransformer packetTransformer : packetTransformers) + { + if (packetTransformer != null) + packetTransformer.setDtlsProtocol(this.dtlsProtocol); + } + } + } + + /** + * Sets the <tt>MediaType</tt> of the stream which this instance is to work + * for/be associated with. + * + * @param mediaType the <tt>MediaType</tt> of the stream which this instance + * is to work for/be associated with + */ + private void setMediaType(MediaType mediaType) + { + if (this.mediaType != mediaType) + { + this.mediaType = mediaType; + + for (DtlsPacketTransformer packetTransformer : packetTransformers) + { + if (packetTransformer != null) + packetTransformer.setMediaType(this.mediaType); + } + } + } + + /** + * Starts this instance in the sense that it becomes fully operational. + * + * @param mediaType the <tt>MediaType</tt> of the stream which this instance + * is to work for/be associated with + */ + void start(MediaType mediaType) + { + setMediaType(mediaType); + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..aa75730b1c137941c80db6bc70919af4193bdd46 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java @@ -0,0 +1,145 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.io.*; + +import org.bouncycastle.crypto.tls.*; +import org.jitsi.util.*; + +/** + * Implements {@link TlsClient} for the purposes of supporting DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class TlsClientImpl + extends DefaultTlsClient +{ + /** + * The <tt>Logger</tt> used by the <tt>TlsClientImpl</tt> class and its + * instances to print debug information. + */ + private static final Logger logger = Logger.getLogger(TlsClientImpl.class); + + private final TlsAuthentication authentication + = new TlsAuthenticationImpl(); + + /** + * The <tt>PacketTransformer</tt> which has initialized this instance. + */ + private final DtlsPacketTransformer packetTransformer; + + /** + * Initializes a new <tt>TlsClientImpl</tt> instance. + * + * @param packetTransformer the <tt>PacketTransformer</tt> which is + * initializing the new instance + */ + public TlsClientImpl(DtlsPacketTransformer packetTransformer) + { + this.packetTransformer = packetTransformer; + } + + /** + * {@inheritDoc} + */ + public synchronized TlsAuthentication getAuthentication() + throws IOException + { + return authentication; + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>TlsClientImpl</tt> always returns + * <tt>ProtocolVersion.DTLSv10</tt> because <tt>ProtocolVersion.DTLSv12</tt> + * does not work with the Bouncy Castle Crypto APIs at the time of this + * writing. + */ + @Override + public ProtocolVersion getClientVersion() + { + return ProtocolVersion.DTLSv10; + } + + /** + * Gets the <tt>DtlsControl</tt> implementation associated with this + * instance. + * + * @return the <tt>DtlsControl</tt> implementation associated with this + * instance + */ + private DtlsControlImpl getDtlsControl() + { + return packetTransformer.getDtlsControl(); + } + + /** + * {@inheritDoc} + */ + @Override + public ProtocolVersion getMinimumVersion() + { + return ProtocolVersion.DTLSv10; + } + + /** + * Implements {@link TlsAuthentication} for the purposes of supporting + * DTLS-SRTP. + * + * @author Lyubomir Marinov + */ + private class TlsAuthenticationImpl + implements TlsAuthentication + { + private TlsCredentials clientCredentials; + + /** + * {@inheritDoc} + */ + public TlsCredentials getClientCredentials( + CertificateRequest certificateRequest) + throws IOException + { + if (clientCredentials == null) + { + DtlsControlImpl dtlsControl = getDtlsControl(); + + clientCredentials + = new DefaultTlsSignerCredentials( + context, + dtlsControl.getCertificate(), + dtlsControl.getKeyPair().getPrivate()); + } + return clientCredentials; + } + + /** + * {@inheritDoc} + */ + public void notifyServerCertificate(Certificate serverCertificate) + throws IOException + { + try + { + getDtlsControl().verifyAndValidateCertificate( + serverCertificate); + } + catch (Exception e) + { + logger.error( + "Failed to verify and/or validate server certificate!", + e); + if (e instanceof IOException) + throw (IOException) e; + else + throw new IOException(e); + } + } + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a12a41b9dd163b88a46f12c16bc314129f0b6737 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java @@ -0,0 +1,137 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.io.*; + +import org.bouncycastle.crypto.tls.*; +import org.jitsi.util.*; + +/** + * Implements {@link TlsServer} for the purposes of supporting DTLS-SRTP. + * + * @author Lyubomir Marinov + */ +public class TlsServerImpl + extends DefaultTlsServer +{ + /** + * The <tt>Logger</tt> used by the <tt>TlsServerImpl</tt> class and its + * instances to print debug information. + */ + private static final Logger logger = Logger.getLogger(TlsServerImpl.class); + + private final CertificateRequest certificateRequest + = new CertificateRequest( + new short[] { ClientCertificateType.rsa_sign }, + /* certificateAuthorities */ null); + + /** + * The <tt>PacketTransformer</tt> which has initialized this instance. + */ + private final DtlsPacketTransformer packetTransformer; + + private TlsSignerCredentials rsaSignerCredentials; + + /** + * Initializes a new <tt>TlsServerImpl</tt> instance. + * + * @param packetTransformer the <tt>PacketTransformer</tt> which is + * initializing the new instance + */ + public TlsServerImpl(DtlsPacketTransformer packetTransformer) + { + this.packetTransformer = packetTransformer; + } + + /** + * {@inheritDoc} + */ + @Override + public CertificateRequest getCertificateRequest() + { + return certificateRequest; + } + + /** + * Gets the <tt>DtlsControl</tt> implementation associated with this + * instance. + * + * @return the <tt>DtlsControl</tt> implementation associated with this + * instance + */ + private DtlsControlImpl getDtlsControl() + { + return packetTransformer.getDtlsControl(); + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>TlsServerImpl</tt> always returns + * <tt>ProtocolVersion.DTLSv10</tt> because <tt>ProtocolVersion.DTLSv12</tt> + * does not work with the Bouncy Castle Crypto APIs at the time of this + * writing. + */ + @Override + protected ProtocolVersion getMaximumVersion() + { + return ProtocolVersion.DTLSv10; + } + + /** + * {@inheritDoc} + */ + @Override + protected ProtocolVersion getMinimumVersion() + { + return ProtocolVersion.DTLSv10; + } + + /** + * {@inheritDoc} + */ + @Override + protected TlsSignerCredentials getRSASignerCredentials() + throws IOException + { + if (rsaSignerCredentials == null) + { + DtlsControlImpl dtlsControl = getDtlsControl(); + + rsaSignerCredentials + = new DefaultTlsSignerCredentials( + context, + dtlsControl.getCertificate(), + dtlsControl.getKeyPair().getPrivate()); + } + return rsaSignerCredentials; + } + + /** + * {@inheritDoc} + */ + @Override + public void notifyClientCertificate(Certificate clientCertificate) + throws IOException + { + try + { + getDtlsControl().verifyAndValidateCertificate(clientCertificate); + } + catch (Exception e) + { + logger.error( + "Failed to verify and/or validate client certificate!", + e); + if (e instanceof IOException) + throw (IOException) e; + else + throw new IOException(e); + } + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java b/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java index 12aa6763686f30ba568d3516a3642656ee660811..6c20f5167d18966b102bce90b8e23ea88c506dcb 100644 --- a/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java +++ b/src/org/jitsi/impl/neomedia/transform/rtcp/StatisticsEngine.java @@ -40,6 +40,36 @@ public class StatisticsEngine */ public static final String RTP_STAT_PREFIX = "rtpstat:"; + /** + * Determines whether a specific <tt>RawPacket</tt> appears to represent an + * RTCP packet. + * + * @param pkt the <tt>RawPacket</tt> to be examined + * @return <tt>true</tt> if the specified <tt>pkt</tt> appears to represent + * an RTCP packet + */ + private static boolean isRTCP(RawPacket pkt) + { + int len = pkt.getLength(); + boolean b = false; + + if (len >= 4) + { + byte[] buf = pkt.getBuffer(); + int off = pkt.getOffset(); + int v = (buf[off] & 0xc0) >>> 6; + + if (v == RTCPHeader.VERSION) + { + int length = (buf[off + 2] << 8) + (buf[off + 3] << 0); + + if (length <= len) + b = true; + } + } + return b; + } + /** * Number of lost packets reported. */ @@ -202,6 +232,10 @@ public RawPacket transform(RawPacket pkt) { try { + // SRTP may send non-RTCP packets. + if (!isRTCP(pkt)) + return pkt; + numberOfSenderReports++; byte[] data = pkt.getBuffer(); diff --git a/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java b/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java index 6bea28d1457013ecc8927c68cab14fa7f51086c8..80b217a110930f1ff0cdcf6af9309e67229c7ad4 100644 --- a/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/sdes/SDesControlImpl.java @@ -11,7 +11,6 @@ import java.util.*; import org.jitsi.impl.neomedia.*; -import org.jitsi.impl.neomedia.transform.*; import org.jitsi.impl.neomedia.transform.zrtp.*; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.event.*; @@ -25,6 +24,7 @@ * @author Ingo Bauersachs */ public class SDesControlImpl + extends AbstractSrtpControl<SDesTransformEngine> implements SDesControl { /** @@ -38,17 +38,18 @@ public class SDesControlImpl private final List<String> supportedCryptoSuites = new ArrayList<String>(3); private SrtpCryptoAttribute[] attributes; - private SDesTransformEngine engine; + private SrtpSDesFactory sdesFactory; private SrtpCryptoAttribute selectedInAttribute; private SrtpCryptoAttribute selectedOutAttribute; - private SrtpListener srtpListener; /** * SDESControl */ public SDesControlImpl() { + super(SrtpControlType.SDES); + { enabledCryptoSuites.add(SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80); enabledCryptoSuites.add(SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32); @@ -74,15 +75,6 @@ public void nextBytes(byte[] bytes) }); } - public void cleanup() - { - if (engine != null) - { - engine.close(); - engine = null; - } - } - public SrtpCryptoAttribute getInAttribute() { return selectedInAttribute; @@ -107,12 +99,7 @@ public SrtpCryptoAttribute getOutAttribute() public boolean getSecureCommunicationStatus() { - return engine != null; - } - - public SrtpListener getSrtpListener() - { - return srtpListener; + return transformEngine != null; } public Iterable<String> getSupportedCryptoSuites() @@ -120,16 +107,18 @@ public Iterable<String> getSupportedCryptoSuites() return Collections.unmodifiableList(supportedCryptoSuites); } - public TransformEngine getTransformEngine() + /** + * Initializes a new <tt>SDesTransformEngine</tt> instance to be associated + * with and used by this <tt>SDesControlImpl</tt> instance. + * + * @return a new <tt>SDesTransformEngine</tt> instance to be associated with + * and used by this <tt>SDesControlImpl</tt> instance + * @see AbstractSrtpControl#createTransformEngine() + */ + protected SDesTransformEngine createTransformEngine() { - if(engine == null) - { - engine - = new SDesTransformEngine( - selectedInAttribute, - selectedOutAttribute); - } - return engine; + return + new SDesTransformEngine(selectedInAttribute, selectedOutAttribute); } /** @@ -217,7 +206,13 @@ public SrtpCryptoAttribute responderSelectAttribute( return null; } - public void setConnector(AbstractRTPConnector newValue) + /** + * {@inheritDoc} + * + * The implementation of <tt>SDesControlImpl</tt> does nothing because + * <tt>SDesControlImpl</tt> does not utilize the <tt>RTPConnector</tt>. + */ + public void setConnector(AbstractRTPConnector connector) { } @@ -228,26 +223,9 @@ public void setEnabledCiphers(Iterable<String> ciphers) enabledCryptoSuites.add(c); } - /** - * Not used. - * - * @param masterSession not used. - */ - public void setMasterSession(boolean masterSession) - { - } - - public void setMultistream(SrtpControl master) - { - } - - public void setSrtpListener(SrtpListener srtpListener) - { - this.srtpListener = srtpListener; - } - public void start(MediaType type) { + SrtpListener srtpListener = getSrtpListener(); // in srtp the started and security event is one after another in some // other security mechanisms (e.g. zrtp) there can be started and no // security one or security timeout event diff --git a/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java index 017e7e26bec55b8dc3a3c0f7569a53f8c81570d1..6339a566d35a4ff460fde8686b919f16cdbd6ccc 100644 --- a/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java +++ b/src/org/jitsi/impl/neomedia/transform/sdes/SDesTransformEngine.java @@ -8,6 +8,7 @@ import org.jitsi.impl.neomedia.transform.*; import org.jitsi.impl.neomedia.transform.srtp.*; +import org.jitsi.service.neomedia.*; import ch.imvs.sdes4j.srtp.*; @@ -17,7 +18,7 @@ * @author Ingo Bauersachs */ public class SDesTransformEngine - implements TransformEngine + implements SrtpControl.TransformEngine { private SRTPTransformer srtpTransformer; private SRTCPTransformer srtcpTransformer; @@ -36,7 +37,10 @@ public SDesTransformEngine(SrtpCryptoAttribute inAttribute, srtcpTransformer = new SRTCPTransformer(forwardCtx, reverseCtx); } - public void close() + /** + * {@inheritDoc} + */ + public void cleanup() { if (srtpTransformer != null) srtpTransformer.close(); diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java b/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java index 2d0d824b709d1d20127b9ca66986a7f79116bfca..6e30e27dc37d8345bf145787ba54d9acddff380a 100644 --- a/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/SecurityEventManager.java @@ -10,7 +10,6 @@ import java.util.*; -import org.jitsi.impl.neomedia.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.event.*; import org.jitsi.service.protocol.event.*; diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java index ece8f2d1e3e29066dd7901e72b5b780f46257a78..290a78b64c50e06cfe166ef4f58c89eabb24d04a 100644 --- a/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZRTPTransformEngine.java @@ -7,6 +7,7 @@ package org.jitsi.impl.neomedia.transform.zrtp; import gnu.java.zrtp.*; +import gnu.java.zrtp.utils.*; import gnu.java.zrtp.zidfile.*; import java.io.*; @@ -17,10 +18,9 @@ import org.jitsi.impl.neomedia.transform.srtp.*; import org.jitsi.service.fileaccess.*; import org.jitsi.service.libjitsi.*; +import org.jitsi.service.neomedia.*; import org.jitsi.util.*; -import gnu.java.zrtp.utils.*; - /** * JMF extension/connector to support GNU ZRTP4J. * @@ -121,7 +121,7 @@ * zrtpEngine = transConnector.getEngine(); * zrtpEngine.setUserCallback(new MyCallback()); * if (!zrtpEngine.initialize("test_t.zid")) - * System.out.println("iniatlize failed"); + * System.out.println("initialize failed"); * * // initialize the RTPManager using the ZRTP connector * @@ -144,12 +144,11 @@ * describes overloaded methods and a possible different behaviour. * * @author Werner Dittmann <Werner.Dittmann@t-online.de> - * */ public class ZRTPTransformEngine - implements TransformEngine, - PacketTransformer, - ZrtpCallback + implements SrtpControl.TransformEngine, + PacketTransformer, + ZrtpCallback { /** * Very simple Timeout provider class. @@ -647,7 +646,7 @@ public void stopZrtp() zrtpEngine = null; started = false; } - // The SRTP transformer are usually already closed durin security-off + // The SRTP transformer are usually already closed during security-off // processing. Check here again just in case ... if (srtpOutTransformer != null) { @@ -671,6 +670,8 @@ public void stopZrtp() */ public void cleanup() { + stopZrtp(); + if (timeoutProvider != null) { timeoutProvider.stopRun(); @@ -803,7 +804,7 @@ else if (zPkt.hasMagic()) /** * The callback method required by the ZRTP implementation. * First allocate space to hold the complete ZRTP packet, copy - * the message part in its place, the initalize the header, counter, + * the message part in its place, the initialize the header, counter, * SSRC and crc. * * @param data The ZRTP packet data diff --git a/src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java similarity index 97% rename from src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java index 795fa0d16947383a3d8ac0596c4f2d05dc48188a..d606a1d894b8b58ffc8eae4e56048c06dacf3e1f 100644 --- a/src/org/jitsi/impl/neomedia/ZrtpConfigureUtils.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpConfigureUtils.java @@ -3,7 +3,7 @@ * * Distributable under LGPL license. See terms of license at gnu.org. */ -package org.jitsi.impl.neomedia; +package org.jitsi.impl.neomedia.transform.zrtp; import gnu.java.zrtp.*; diff --git a/src/org/jitsi/impl/neomedia/ZrtpControlImpl.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java similarity index 80% rename from src/org/jitsi/impl/neomedia/ZrtpControlImpl.java rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java index 4f8961a29a7246fe31ce7ba500097a00764da5e3..feb0119d685232436ac8eca3a0fe1fb0498d5834 100644 --- a/src/org/jitsi/impl/neomedia/ZrtpControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java @@ -4,16 +4,15 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package org.jitsi.impl.neomedia; +package org.jitsi.impl.neomedia.transform.zrtp; import gnu.java.zrtp.*; import gnu.java.zrtp.utils.*; import java.util.*; -import org.jitsi.impl.neomedia.transform.zrtp.*; +import org.jitsi.impl.neomedia.*; import org.jitsi.service.neomedia.*; -import org.jitsi.service.neomedia.event.*; /** * Controls zrtp in the MediaStream. @@ -21,6 +20,7 @@ * @author Damian Minkov */ public class ZrtpControlImpl + extends AbstractSrtpControl<ZRTPTransformEngine> implements ZrtpControl { /** @@ -47,21 +47,12 @@ public static enum ZRTPCustomInfoCodes */ private AbstractRTPConnector zrtpConnector = null; - /** - * The zrtp engine control by this ZrtpControl. - */ - private ZRTPTransformEngine zrtpEngine = null; - - /** - * The listener interested in security events about zrtp. - */ - private SrtpListener zrtpListener = null; - /** * Creates the control. */ - ZrtpControlImpl() + public ZrtpControlImpl() { + super(SrtpControlType.ZRTP); } /** @@ -69,13 +60,8 @@ public static enum ZRTPCustomInfoCodes */ public void cleanup() { - if(zrtpEngine != null) - { - zrtpEngine.stopZrtp(); - zrtpEngine.cleanup(); - } + super.cleanup(); - zrtpEngine = null; zrtpConnector = null; } @@ -97,7 +83,10 @@ public String getCipherString() */ public int getCurrentProtocolVersion() { - return ((zrtpEngine != null) ? zrtpEngine.getCurrentProtocolVersion() : 0); + ZRTPTransformEngine zrtpEngine = this.transformEngine; + + return + (zrtpEngine != null) ? zrtpEngine.getCurrentProtocolVersion() : 0; } /** @@ -136,7 +125,10 @@ public String[] getHelloHashSep(int index) */ public int getNumberSupportedVersions() { - return ((zrtpEngine != null) ? zrtpEngine.getNumberSupportedVersions(): 0); + ZRTPTransformEngine zrtpEngine = this.transformEngine; + + return + (zrtpEngine != null) ? zrtpEngine.getNumberSupportedVersions() : 0; } /** @@ -150,6 +142,8 @@ public int getNumberSupportedVersions() * from our peer. If peer's hello hash is not available return null. */ public String getPeerHelloHash() { + ZRTPTransformEngine zrtpEngine = this.transformEngine; + if (zrtpEngine != null) return zrtpEngine.getPeerHelloHash(); else @@ -189,6 +183,8 @@ public String getPeerZidString() */ public boolean getSecureCommunicationStatus() { + ZRTPTransformEngine zrtpEngine = this.transformEngine; + return (zrtpEngine != null) && zrtpEngine.getSecureCommunicationStatus(); } @@ -205,16 +201,6 @@ public String getSecurityString() return getTransformEngine().getUserCallback().getSecurityString(); } - /** - * Returns the <tt>ZrtpListener</tt> which listens for security events. - * - * @return the <tt>ZrtpListener</tt> which listens for security events - */ - public SrtpListener getSrtpListener() - { - return this.zrtpListener; - } - /** * Returns the timeout value that will we will wait * and fire timeout secure event if call is not secured. @@ -230,24 +216,25 @@ public long getTimeoutValue() } /** - * Returns the zrtp engine currently used by this stream. - * @return the zrtp engine + * Initializes a new <tt>ZRTPTransformEngine</tt> instance to be associated + * with and used by this <tt>ZrtpControlImpl</tt> instance. + * + * @return a new <tt>ZRTPTransformEngine</tt> instance to be associated with + * and used by this <tt>ZrtpControlImpl</tt> instance */ - public ZRTPTransformEngine getTransformEngine() + protected ZRTPTransformEngine createTransformEngine() { - if(zrtpEngine == null) - { - zrtpEngine = new ZRTPTransformEngine(); - - // NOTE: set paranoid mode before initializing - // zrtpEngine.setParanoidMode(paranoidMode); - zrtpEngine.initialize( - "GNUZRTP4J.zid", - false, - ZrtpConfigureUtils.getZrtpConfiguration()); - zrtpEngine.setUserCallback(new SecurityEventManager(this)); - } - return zrtpEngine; + ZRTPTransformEngine transformEngine = new ZRTPTransformEngine(); + + // NOTE: set paranoid mode before initializing + // zrtpEngine.setParanoidMode(paranoidMode); + transformEngine.initialize( + "GNUZRTP4J.zid", + false, + ZrtpConfigureUtils.getZrtpConfiguration()); + transformEngine.setUserCallback(new SecurityEventManager(this)); + + return transformEngine; } /* @@ -285,8 +272,10 @@ public void setConnector(AbstractRTPConnector connector) /** * When in multistream mode, enables the master session. - * @param masterSession whether current control, controls the master session. + * + * @param masterSession whether current control, controls the master session */ + @Override public void setMasterSession(boolean masterSession) { // by default its not master, change only if set to be master @@ -297,14 +286,14 @@ public void setMasterSession(boolean masterSession) } /** - * Start multi-stream ZRTP sessions. - * - * After the ZRTP Master (DH) session reached secure state the SCCallback - * calls this method to start the multi-stream ZRTP sessions. + * Start multi-stream ZRTP sessions. After the ZRTP Master (DH) session + * reached secure state the SCCallback calls this method to start the + * multi-stream ZRTP sessions. Enable auto-start mode (auto-sensing) to the + * engine. * - * enable auto-start mode (auto-sensing) to the engine. * @param master master SRTP data */ + @Override public void setMultistream(SrtpControl master) { if(master == null || master == this) @@ -335,16 +324,6 @@ public void setSASVerification(boolean verified) engine.resetSASVerified(); } - /** - * Sets a <tt>ZrtpListener</tt> that will listen for zrtp security events. - * - * @param zrtpListener the <tt>ZrtpListener</tt> to set - */ - public void setSrtpListener(SrtpListener zrtpListener) - { - this.zrtpListener = zrtpListener; - } - /** * Starts and enables zrtp in the stream holding this control. * @param mediaType the media type of the stream this control controls. @@ -387,12 +366,12 @@ public void start(MediaType mediaType) // it may happen when using multistreams, audio has inited // and started video // initially engine has value enableZrtp = false - zrtpAutoStart = zrtpEngine.isEnableZrtp(); + zrtpAutoStart = transformEngine.isEnableZrtp(); securityEventManager.setSessionType(sessionType); } engine.setConnector(zrtpConnector); - securityEventManager.setSrtpListener(zrtpListener); + securityEventManager.setSrtpListener(getSrtpListener()); // tells the engine whether to autostart(enable) // zrtp communication, if false it just passes packets without diff --git a/src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java similarity index 99% rename from src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java rename to src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java index 9e91e6d2795b6d47013e1b25d079481dbef9cf70..4ca3192b630b0accca0bb9db04708575f3d10981 100644 --- a/src/org/jitsi/impl/neomedia/ZrtpFortunaEntropyGatherer.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpFortunaEntropyGatherer.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package org.jitsi.impl.neomedia; +package org.jitsi.impl.neomedia.transform.zrtp; import gnu.java.zrtp.utils.*; diff --git a/src/org/jitsi/service/neomedia/AbstractSrtpControl.java b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java new file mode 100644 index 0000000000000000000000000000000000000000..4f248c7281178732742a80ce205b001fe3bf2715 --- /dev/null +++ b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java @@ -0,0 +1,121 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia; + +import org.jitsi.service.neomedia.event.*; + +/** + * Provides an abstract, base implementation of {@link SrtpControl} to + * facilitate implementers. + * + * @author Lyubomir Marinov + */ +public abstract class AbstractSrtpControl<T extends SrtpControl.TransformEngine> + implements SrtpControl +{ + private final SrtpControlType srtpControlType; + + /** + * The <tt>SrtpListener</tt> listening to security events (to be) fired by + * this <tt>SrtpControl</tt> instance. + */ + private SrtpListener srtpListener; + + protected T transformEngine; + + /** + * Initializes a new <tt>AbstractSrtpControl</tt> instance with a specific + * <tt>SrtpControlType</tt>. + * + * @param srtpControlType the <tt>SrtpControlType</tt> of the new instance + */ + protected AbstractSrtpControl(SrtpControlType srtpControlType) + { + if (srtpControlType == null) + throw new NullPointerException("srtpControlType"); + + this.srtpControlType = srtpControlType; + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>AbstractSrtpControl</tt> cleans up its + * associated <tt>TransformEngine</tt> (if any). + */ + public void cleanup() + { + if (transformEngine != null) + { + transformEngine.cleanup(); + transformEngine = null; + } + } + + /** + * Initializes a new <tt>TransformEngine</tt> instance to be associated with + * and used by this <tt>SrtpControl</tt> instance. + * + * @return a new <tt>TransformEngine</tt> instance to be associated with and + * used by this <tt>SrtpControl</tt> instance + */ + protected abstract T createTransformEngine(); + + /** + * {@inheritDoc} + */ + public SrtpControlType getSrtpControlType() + { + return srtpControlType; + } + + /** + * {@inheritDoc} + */ + public SrtpListener getSrtpListener() + { + return srtpListener; + } + + /** + * {@inheritDoc} + */ + public T getTransformEngine() + { + if (transformEngine == null) + transformEngine = createTransformEngine(); + return transformEngine; + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>AbstractSrtpControl</tt> does nothing because + * support for multistream mode is the exception rather than the norm. + */ + public void setMasterSession(boolean masterSession) + { + } + + /** + * {@inheritDoc} + * + * The implementation of <tt>AbstractSrtpControl</tt> does nothing because + * support for multistream mode is the exception rather than the norm. + */ + public void setMultistream(SrtpControl master) + { + } + + /** + * {@inheritDoc} + */ + public void setSrtpListener(SrtpListener srtpListener) + { + this.srtpListener = srtpListener; + } +} diff --git a/src/org/jitsi/service/neomedia/DtlsControl.java b/src/org/jitsi/service/neomedia/DtlsControl.java new file mode 100644 index 0000000000000000000000000000000000000000..fbc6eb92a500b5a8753ad85ee619bb15f2a59f95 --- /dev/null +++ b/src/org/jitsi/service/neomedia/DtlsControl.java @@ -0,0 +1,93 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia; + +import java.util.*; + + +/** + * Implements {@link SrtpControl} for DTSL-SRTP. + * + * @author Lyubomir Marinov + */ +public interface DtlsControl + extends SrtpControl +{ + /** + * The DTLS protocol to be set on a <tt>DtlsControl</tt> instance via + * {@link #setDtlsProtocol(int)} to indicate that the <tt>DtlsControl</tt> + * is to act as a DTLS client. + */ + public static final int DTLS_CLIENT_PROTOCOL = 1; + + /** + * The DTLS protocol to be set on a <tt>DtlsControl</tt> instance via + * {@link #setDtlsProtocol(int)} to indicate that the <tt>DtlsControl</tt> + * is to act as a DTLS server. + */ + public static final int DTLS_SERVER_PROTOCOL = 2; + + /** + * The human-readable non-localized name of the (S)RTP transport protocol + * represented by <tt>DtlsControl</tt>. + */ + public static final String PROTO_NAME + = SrtpControlType.DTLS_SRTP.toString(); + + /** + * The transport protocol (i.e. <tt><proto></tt>) to be specified in + * a SDP media description (i.e. <tt>m=</tt> line) in order to denote a + * RTP/SAVP stream transported over DTLS with UDP. + */ + public static final String UDP_TLS_RTP_SAVP = "UDP/TLS/RTP/SAVP"; + + /** + * The transport protocol (i.e. <tt><proto></tt>) to be specified in + * a SDP media description (i.e. <tt>m=</tt> line) in order to denote a + * RTP/SAVPF stream transported over DTLS with UDP. + */ + public static final String UDP_TLS_RTP_SAVPF = "UDP/TLS/RTP/SAVPF"; + + /** + * Gets the fingerprint of the local certificate that this instance uses to + * authenticate its ends of DTLS sessions. + * + * @return the fingerprint of the local certificate that this instance uses + * to authenticate its ends of DTLS sessions + */ + public String getLocalFingerprint(); + + /** + * Gets the hash function with which the fingerprint of the local + * certificate is computed i.e. the digest algorithm of the signature + * algorithm of the local certificate. + * + * @return the hash function with which the fingerprint of the local + * certificate is computed + */ + public String getLocalFingerprintHashFunction(); + + /** + * Sets the DTLS protocol according to which this <tt>DtlsControl</tt> is to + * act. + * + * @param dtlsProtocol {@link #DTLS_CLIENT_PROTOCOL} to have this instance + * act as a DTLS client or {@link #DTLS_SERVER_PROTOCOL} to have this + * instance act as a DTLS server + */ + public void setDtlsProtocol(int dtlsProtocol); + + /** + * Sets the certificate fingerprints presented by the remote endpoint via + * the signaling path. + * + * @param remoteFingerprints a <tt>Map</tt> of hash functions to certificate + * fingerprints that have been presented by the remote endpoint via the + * signaling path + */ + public void setRemoteFingerprints(Map<String,String> remoteFingerprints); +} diff --git a/src/org/jitsi/service/neomedia/MediaService.java b/src/org/jitsi/service/neomedia/MediaService.java index 3c2cd651171775d9218c0e4d170fdb9b0cbb14d7..dcd441e5d4007748d2fdec836c7e97d10bc32632 100644 --- a/src/org/jitsi/service/neomedia/MediaService.java +++ b/src/org/jitsi/service/neomedia/MediaService.java @@ -166,22 +166,14 @@ public MediaStream createMediaStream(StreamConnector connector, public RTPTranslator createRTPTranslator(); /** - * Initializes a new <tt>SDesControl</tt> instance which is to control all - * SDes options. + * Initializes a new <tt>SrtpControl</tt> instance with a specific + * <tt>SrtpControlType</tt>. * - * @return a new <tt>SDesControl</tt> instance which is to control all SDes - * options + * @param srtpControlType the <tt>SrtpControlType</tt> of the new instance + * @return a new <tt>SrtpControl</tt> instance with the specified + * <tt>srtpControlType</tt> */ - public SDesControl createSDesControl(); - - /** - * Initializes a new <tt>ZrtpControl</tt> instance which is to control all - * ZRTP options. - * - * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP - * options - */ - public ZrtpControl createZrtpControl(); + public SrtpControl createSrtpControl(SrtpControlType srtpControlType); /** * Get available <tt>ScreenDevice</tt>s. diff --git a/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java b/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java deleted file mode 100644 index 74647a5da95b8a931ea3f2d14025c3cd4a8a3981..0000000000000000000000000000000000000000 --- a/src/org/jitsi/service/neomedia/MediaTypeSrtpControl.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package org.jitsi.service.neomedia; - -/** - * Utility class to combine <tt>MediaType</tt> and <tt>SrtpControlType</tt> as a - * map key. - * - * @author Ingo Bauersachs - */ -public class MediaTypeSrtpControl implements Comparable<MediaTypeSrtpControl> -{ - /** - * The media type. - */ - public final MediaType mediaType; - - /** - * The SRTP control type. - */ - public final SrtpControlType srtpControlType; - - /** - * Creates a new instance of this class. - * @param mt The <tt>MediaType</tt> for this key. - * @param sct The <tt>SrtpControlType</tt> for this key. - */ - public MediaTypeSrtpControl(MediaType mt, SrtpControlType sct) - { - mediaType = mt; - srtpControlType = sct; - } - - @Override - public boolean equals(Object obj) - { - if(obj == null || obj.getClass() != MediaTypeSrtpControl.class) - return false; - - MediaTypeSrtpControl other = (MediaTypeSrtpControl)obj; - return mediaType == other.mediaType - && srtpControlType == other.srtpControlType; - } - - @Override - public int hashCode() - { - return mediaType.hashCode() ^ srtpControlType.hashCode(); - } - - public int compareTo(MediaTypeSrtpControl o) - { - return getWeight() == o.getWeight() ? - 0 : - getWeight() < o.getWeight() ? - -1 : 1; - } - - private int getWeight() - { - int mtWeight = 0; - switch(mediaType) - { - case AUDIO: - mtWeight = 1; - break; - case VIDEO: - mtWeight = 2; - break; - } - int stWeight = 0; - switch(srtpControlType) - { - case ZRTP: - stWeight = 1; - break; - case MIKEY: - stWeight = 2; - break; - case SDES: - stWeight = 3; - break; - } - return mtWeight * 10 + stWeight; - } -} diff --git a/src/org/jitsi/service/neomedia/SDesControl.java b/src/org/jitsi/service/neomedia/SDesControl.java index c1bd9d7264e200d24be1bfa663d7b36aad95c40c..e56f575f2e447f4fff8531a50ebcf2cfd5c70022 100644 --- a/src/org/jitsi/service/neomedia/SDesControl.java +++ b/src/org/jitsi/service/neomedia/SDesControl.java @@ -16,6 +16,12 @@ public interface SDesControl extends SrtpControl { + /** + * The human-readable non-localized name of the (S)RTP transport protocol + * represented by <tt>DtlsControl</tt>. + */ + public static final String PROTO_NAME = SrtpControlType.SDES.toString(); + /** * Name of the config setting that supplies the default enabled cipher * suites. Cipher suites are comma-separated. diff --git a/src/org/jitsi/service/neomedia/SrtpControl.java b/src/org/jitsi/service/neomedia/SrtpControl.java index 778ac12c1f9f26a73dfb9bd14e1b90aaa9f6a502..86e34d1aaf5d9f0ae3bd0f9bee4d0e7bc8cbb926 100644 --- a/src/org/jitsi/service/neomedia/SrtpControl.java +++ b/src/org/jitsi/service/neomedia/SrtpControl.java @@ -7,7 +7,6 @@ package org.jitsi.service.neomedia; import org.jitsi.impl.neomedia.*; -import org.jitsi.impl.neomedia.transform.*; import org.jitsi.service.neomedia.event.*; /** @@ -17,8 +16,29 @@ */ public interface SrtpControl { + public static final String RTP_SAVP = "RTP/SAVP"; + + public static final String RTP_SAVPF = "RTP/SAVPF"; + + /** + * Adds a <tt>cleanup()</tt> method to + * <tt>org.jitsi.impl.neomedia.transform.TransformEngine</tt> which is to go + * in hand with the <tt>cleanup()</tt> method of <tt>SrtpControl</tt>. + * + * @author Lyubomir Marinov + */ + public interface TransformEngine + extends org.jitsi.impl.neomedia.transform.TransformEngine + { + /** + * Cleans up this <tt>TransformEngine</tt> and prepares it for garbage + * collection. + */ + public void cleanup(); + } + /** - * Cleans up the current SRTP control and its engine. + * Cleans up this <tt>SrtpControl</tt> and its <tt>TransformEngine</tt>. */ public void cleanup(); @@ -31,6 +51,13 @@ public interface SrtpControl */ public boolean getSecureCommunicationStatus(); + /** + * Gets the <tt>SrtpControlType</tt> of this instance. + * + * @return the <tt>SrtpControlType</tt> of this instance + */ + public SrtpControlType getSrtpControlType(); + /** * Returns the <tt>SrtpListener</tt> which listens for security events. * diff --git a/src/org/jitsi/service/neomedia/SrtpControlType.java b/src/org/jitsi/service/neomedia/SrtpControlType.java index 453e1bf8ad99ba9cead47ee75e207b0e71cc9e8c..051f4c4b52c93964bb5f3da436b0217a8c117659 100644 --- a/src/org/jitsi/service/neomedia/SrtpControlType.java +++ b/src/org/jitsi/service/neomedia/SrtpControlType.java @@ -11,22 +11,55 @@ * <tt>SrtpControl</tt> implementations. * * @author Ingo Bauersachs + * @author Lyubomir Marinov */ public enum SrtpControlType { + /** + * Datagram Transport Layer Security (DTLS) Extension to Establish Keys for + * the Secure Real-time Transport Protocol (SRTP) + */ + DTLS_SRTP("DTLS-SRTP"), + + /** + * Multimedia Internet KEYing (RFC 3830) + */ + MIKEY("MIKEY"), + /** * Session Description Protocol (SDP) Security Descriptions for Media * Streams (RFC 4568) */ - SDES, + SDES("SDES"), /** * ZRTP: Media Path Key Agreement for Unicast Secure RTP (RFC 6189) */ - ZRTP, + ZRTP("ZRTP"); /** - * Multimedia Internet KEYing (RFC 3830) + * The human-readable non-localized name of the (S)RTP transport protocol + * represented by this <tt>SrtpControlType</tt> and its respective + * <tt>SrtpControl</tt> class. + */ + private final String protoName; + + /** + * Initializes a new <tt>SrtpControlType</tt> instance with a specific + * human-readable non-localized (S)RTP transport protocol name. + * + * @param protoName the human-readable non-localized name of the (S)RTP + * transport protocol represented by the new instance and its respective + * <tt>SrtpControl</tt> class */ - MIKEY + private SrtpControlType(String protoName) + { + this.protoName = protoName; + } + + @Override + public String toString() + { + return protoName; + } } diff --git a/src/org/jitsi/service/neomedia/ZrtpControl.java b/src/org/jitsi/service/neomedia/ZrtpControl.java index d9173d896e8a97e53719a32509afccb0d9b71ce7..37a7f3f1bc77762a42f5378014b3251f60e64a89 100644 --- a/src/org/jitsi/service/neomedia/ZrtpControl.java +++ b/src/org/jitsi/service/neomedia/ZrtpControl.java @@ -14,6 +14,12 @@ public interface ZrtpControl extends SrtpControl { + /** + * The human-readable non-localized name of the (S)RTP transport protocol + * represented by <tt>ZrtpControl</tt>. + */ + public static final String PROTO_NAME = SrtpControlType.ZRTP.toString(); + /** * Gets the cipher information for the current media stream. *