diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java index defaeb487538225d1f7f1fec6f93028f9a1f6934..0e2d714bb1a2d6b290df4dd26eecb9b238423c39 100755 --- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java @@ -306,6 +306,16 @@ public MediaStream createMediaStream( return createMediaStream(connector, device, null); } + /** + * {@inheritDoc} + */ + public MediaStream createMediaStream( + StreamConnector connector, + MediaType mediaType) + { + return createMediaStream(connector, mediaType, null); + } + /** * Creates a new <tt>MediaStream</tt> instance which will use the specified * <tt>MediaDevice</tt> for both capture and playback of media exchanged @@ -330,6 +340,17 @@ public MediaStream createMediaStream( return createMediaStream(null, connector, device, srtpControl); } + /** + * {@inheritDocs} + */ + public MediaStream createMediaStream( + StreamConnector connector, + MediaType mediaType, + SrtpControl srtpControl) + { + return createMediaStream(mediaType, connector, null, srtpControl); + } + /** * Initializes a new <tt>MediaStream</tt> instance. The method is the actual * implementation to which the public <tt>createMediaStream</tt> methods of diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java index 77b2220f0b0f44ec1d328aa23d77a9b6e36a7cb8..6e4e868cb47a6086a28371f735770a5c14dc1119 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java @@ -239,15 +239,23 @@ public int receive(byte[] buf, int off, int len, int waitMillis) } } - if (receiveQ.isEmpty() && (timeout >= 0)) + if (receiveQ.isEmpty()) { - try + if (timeout >= 0) { - receiveQ.wait(timeout); + try + { + receiveQ.wait(timeout); + } + catch (InterruptedException ie) + { + interrupted = true; + } } - catch (InterruptedException ie) + else { - interrupted = true; + // The specified waitMillis has been exceeded. + break; } } } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java index f9d7768fbdf45a575f024580d9e25b6949dd502c..e28209e88c2d1021e6102069fe4283116f988730 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java @@ -566,7 +566,7 @@ private static String toHex(byte[] fingerprint) * and validate against the fingerprints presented by the remote endpoint * via the signaling path */ - void verifyAndValidateCertificate( + private void verifyAndValidateCertificate( org.bouncycastle.asn1.x509.Certificate certificate) throws Exception { @@ -592,11 +592,32 @@ void verifyAndValidateCertificate( synchronized (this) { if (disposed) + { throw new IllegalStateException("disposed"); + } else - remoteFingerprint = remoteFingerprints.get(hashFunction); + { + Map<String,String> remoteFingerprints = this.remoteFingerprints; + + if (remoteFingerprints == null) + { + throw new IOException( + "No fingerprints declared over the signaling" + + " path!"); + } + else + { + remoteFingerprint = remoteFingerprints.get(hashFunction); + } + } } - if (!remoteFingerprint.equals(fingerprint)) + if (remoteFingerprint == null) + { + throw new IOException( + "No fingerprint declared over the signaling path with" + + " hash function: " + hashFunction + "!"); + } + else if (!remoteFingerprint.equals(fingerprint)) { throw new IOException( "Fingerprint " + remoteFingerprint @@ -611,24 +632,61 @@ void verifyAndValidateCertificate( * * @param certificate the certificate to be verified and validated against * the fingerprints presented by the remote endpoint via the signaling path + * @return <tt>true</tt> if the specified <tt>certificate</tt> was + * successfully verified and validated against the fingerprints presented by + * the remote endpoint over 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 + * over the signaling path */ - void verifyAndValidateCertificate( + boolean verifyAndValidateCertificate( org.bouncycastle.crypto.tls.Certificate certificate) throws Exception { - org.bouncycastle.asn1.x509.Certificate[] certificateList - = certificate.getCertificateList(); + boolean b = false; - if (certificateList.length == 0) - throw new IllegalArgumentException("certificate.certificateList"); + try + { + org.bouncycastle.asn1.x509.Certificate[] certificateList + = certificate.getCertificateList(); - for (org.bouncycastle.asn1.x509.Certificate x509Certificate - : certificateList) + if (certificateList.length == 0) + { + throw new IllegalArgumentException( + "certificate.certificateList"); + } + else + { + for (org.bouncycastle.asn1.x509.Certificate x509Certificate + : certificateList) + { + verifyAndValidateCertificate(x509Certificate); + } + b = true; + } + } + catch (Exception e) { - verifyAndValidateCertificate(x509Certificate); + /* + * XXX Contrary to RFC 5763 "Framework for Establishing a Secure + * Real-time Transport Protocol (SRTP) Security Context Using + * Datagram Transport Layer Security (DTLS)", we do NOT want to tear + * down the media session if the fingerprint does not match the + * hashed certificate. We want to notify the user via the + * SrtpListener. + */ + // TODO Auto-generated method stub + String message + = "Failed to verify and/or validate a certificate offered over" + + " the media path against fingerprints declared over the" + + " signaling path!"; + String throwableMessage = e.getMessage(); + + if ((throwableMessage == null) || (throwableMessage.length() == 0)) + logger.warn(message, e); + else + logger.warn(message + " " + throwableMessage); } + return b; } } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java index 2b66d7e6182f04cd91b4eaea9972b0ebaf4ef62c..47f5758805657e2c0b5c92bf0b81e817bba36a60 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java @@ -477,6 +477,10 @@ else if (delta < 0) if (delta > 0) pkt.shrink(delta); + /* + * In DTLS-SRTP no application data is transmitted + * over the DTLS channel. + */ pkt = null; } } @@ -491,7 +495,7 @@ else if (delta < 0) { /* * The specified pkt looks like a DTLS record but it is - * unexpected in the current state of the secure channels + * unexpected in the current state of the secure channel * represented by this PacketTransformer. */ pkt = null; @@ -499,11 +503,15 @@ else if (delta < 0) } else { + /* + * XXX If DTLS-SRTP has not been initialized yet or has failed to + * initialize, it is our explicit policy to let the received packet + * pass through and rely on the SrtpListener to notify the user that + * the session is not secured. + */ PacketTransformer srtpTransformer = this.srtpTransformer; - if (srtpTransformer == null) - pkt = null; - else + if (srtpTransformer != null) pkt = srtpTransformer.reverseTransform(pkt); } return pkt; @@ -522,8 +530,8 @@ private void runInConnectThread( DatagramTransport datagramTransport) { DTLSTransport dtlsTransport = null; - int srtpProtectionProfile; - TlsContext tlsContext; + int srtpProtectionProfile = 0; + TlsContext tlsContext = null; if (dtlsProtocol instanceof DTLSClientProtocol) { @@ -544,8 +552,11 @@ private void runInConnectThread( "Failed to connect this DTLS client to a DTLS server!", ioe); } - srtpProtectionProfile = tlsClient.getChosenProtectionProfile(); - tlsContext = tlsClient.getContext(); + if (dtlsTransport != null) + { + srtpProtectionProfile = tlsClient.getChosenProtectionProfile(); + tlsContext = tlsClient.getContext(); + } } else if (dtlsProtocol instanceof DTLSServerProtocol) { @@ -566,14 +577,19 @@ else if (dtlsProtocol instanceof DTLSServerProtocol) "Failed to accept a connection from a DTLS client!", ioe); } - srtpProtectionProfile = tlsServer.getChosenProtectionProfile(); - tlsContext = tlsServer.getContext(); + if (dtlsTransport != null) + { + srtpProtectionProfile = tlsServer.getChosenProtectionProfile(); + tlsContext = tlsServer.getContext(); + } } else throw new IllegalStateException("dtlsProtocol"); PacketTransformer srtpTransformer - = initializeSRTPTransformer(srtpProtectionProfile, tlsContext); + = (dtlsTransport == null) + ? null + : initializeSRTPTransformer(srtpProtectionProfile, tlsContext); boolean closeSRTPTransformer; synchronized (this) @@ -588,7 +604,7 @@ else if (dtlsProtocol instanceof DTLSServerProtocol) closeSRTPTransformer = (this.srtpTransformer != srtpTransformer); } - if (closeSRTPTransformer) + if (closeSRTPTransformer && (srtpTransformer != null)) srtpTransformer.close(); } @@ -799,11 +815,15 @@ public RawPacket transform(RawPacket pkt) */ if (!isDtlsRecord(buf, off, len)) { + /* + * XXX If DTLS-SRTP has not been initialized yet or has failed to + * initialize, it is our explicit policy to let the received packet + * pass through and rely on the SrtpListener to notify the user that + * the session is not secured. + */ PacketTransformer srtpTransformer = this.srtpTransformer; - if (srtpTransformer == null) - pkt = null; - else + if (srtpTransformer != null) pkt = srtpTransformer.transform(pkt); } return pkt; diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpRawPacket.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpRawPacket.java index 65cf9f4f3f494fd3610d1be30e6e238a34853a10..cc421119f2a8dc94ded4ab577475b6d436397df2 100644 --- a/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpRawPacket.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpRawPacket.java @@ -22,17 +22,10 @@ public class ZrtpRawPacket extends RawPacket { /** - * Each ZRTP packet contains this magic number. + * Each ZRTP packet contains this magic number/cookie. */ - public static byte[] zrtpMagic; - - static { - zrtpMagic = new byte[4]; - zrtpMagic[0]= 0x5a; - zrtpMagic[1]= 0x52; - zrtpMagic[2]= 0x54; - zrtpMagic[3]= 0x50; - } + public static final byte[] ZRTP_MAGIC + = new byte[] { 0x5a, 0x52, 0x54, 0x50 }; /** * Construct an input ZrtpRawPacket using a received RTP raw packet. @@ -41,7 +34,7 @@ public class ZrtpRawPacket extends RawPacket */ public ZrtpRawPacket(RawPacket pkt) { - super (pkt.getBuffer(), pkt.getOffset(), pkt.getLength()); + super(pkt.getBuffer(), pkt.getOffset(), pkt.getLength()); } /** @@ -61,10 +54,10 @@ public ZrtpRawPacket(byte[] buf, int off, int len) writeByte(1, (byte)0); int at = 4; - writeByte(at++, zrtpMagic[0]); - writeByte(at++, zrtpMagic[1]); - writeByte(at++, zrtpMagic[2]); - writeByte(at, zrtpMagic[3]); + writeByte(at++, ZRTP_MAGIC[0]); + writeByte(at++, ZRTP_MAGIC[1]); + writeByte(at++, ZRTP_MAGIC[2]); + writeByte(at, ZRTP_MAGIC[3]); } /** @@ -88,13 +81,8 @@ protected boolean isZrtpPacket() */ static boolean isZrtpData(RawPacket pkt) { - if(!pkt.getExtensionBit()) - return false; - - if(pkt.getHeaderExtensionType() == 0x505a) - return true; - - return false; + return + pkt.getExtensionBit() && (pkt.getHeaderExtensionType() == 0x505a); } /** @@ -108,10 +96,10 @@ static boolean isZrtpData(RawPacket pkt) protected boolean hasMagic() { return - (readByte(4) == zrtpMagic[0]) - && (readByte(5) == zrtpMagic[1]) - && (readByte(6) == zrtpMagic[2]) - && (readByte(7) == zrtpMagic[3]); + (readByte(4) == ZRTP_MAGIC[0]) + && (readByte(5) == ZRTP_MAGIC[1]) + && (readByte(6) == ZRTP_MAGIC[2]) + && (readByte(7) == ZRTP_MAGIC[3]); } /** diff --git a/src/org/jitsi/service/neomedia/MediaService.java b/src/org/jitsi/service/neomedia/MediaService.java index dcd441e5d4007748d2fdec836c7e97d10bc32632..e44b3071d6460bed6b5d4b2a305e1a8ec5a99f1f 100644 --- a/src/org/jitsi/service/neomedia/MediaService.java +++ b/src/org/jitsi/service/neomedia/MediaService.java @@ -102,8 +102,27 @@ public interface MediaService * * @return the newly created <tt>MediaStream</tt>. */ - public MediaStream createMediaStream(StreamConnector connector, - MediaDevice device); + public MediaStream createMediaStream( + StreamConnector connector, + MediaDevice device); + + /** + * Initializes a new <tt>MediaStream</tt> instance which is to exchange + * media of a specific <tt>MediaType</tt> via a specific + * <tt>StreamConnector</tt>. + * + * @param connector the <tt>StreamConnector</tt> the stream should use for + * sending and receiving media or <tt>null</tt> if the stream is to not have + * a <tt>StreamConnector</tt> configured at initialization time and a + * <tt>StreamConnector</tt> is to be specified later on + * @param mediaType the <tt>MediaType</tt> of the media to be exchanged by + * the new instance via the specified <tt>connector</tt> + * @return a new <tt>MediaStream</tt> instance which is to exchange media of + * the specified <tt>mediaType</tt> via the specified <tt>connector</tt> + */ + public MediaStream createMediaStream( + StreamConnector connector, + MediaType mediaType); /** * Creates a <tt>MediaStream</tt> that will be using the specified @@ -116,14 +135,37 @@ public MediaStream createMediaStream(StreamConnector connector, * <tt>StreamConnector</tt> is to be specified later on * @param device the device to be used for both capture and playback of * media exchanged via the specified <tt>StreamConnector</tt> - * @param zrtpControl a control which is already created, used to control + * @param srtpControl a control which is already created, used to control * the ZRTP operations. * * @return the newly created <tt>MediaStream</tt>. */ - public MediaStream createMediaStream(StreamConnector connector, - MediaDevice device, - SrtpControl zrtpControl); + public MediaStream createMediaStream( + StreamConnector connector, + MediaDevice device, + SrtpControl srtpControl); + + /** + * Initializes a new <tt>MediaStream</tt> instance which is to exchange + * media of a specific <tt>MediaType</tt> via a specific + * <tt>StreamConnector</tt>. The security of the media exchange is to be + * controlled by a specific <tt>SrtpControl</tt>. + * + * @param connector the <tt>StreamConnector</tt> the stream should use for + * sending and receiving media or <tt>null</tt> if the stream is to not have + * a <tt>StreamConnector</tt> configured at initialization time and a + * <tt>StreamConnector</tt> is to be specified later on + * @param mediaType the <tt>MediaType</tt> of the media to be exchanged by + * the new instance via the specified <tt>connector</tt> + * @param srtpControl the <tt>SrtpControl</tt> to control the security of + * the media exchange + * @return a new <tt>MediaStream</tt> instance which is to exchange media of + * the specified <tt>mediaType</tt> via the specified <tt>connector</tt> + */ + public MediaStream createMediaStream( + StreamConnector connector, + MediaType mediaType, + SrtpControl srtpControl); /** * Creates a new <tt>MediaDevice</tt> which uses a specific