diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java index 85967ae66be872e09689afda3de92c883fcefa32..f9d7768fbdf45a575f024580d9e25b6949dd502c 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java @@ -19,6 +19,7 @@ import org.bouncycastle.crypto.*; import org.bouncycastle.crypto.generators.*; import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.tls.*; import org.bouncycastle.crypto.util.*; import org.bouncycastle.operator.*; import org.bouncycastle.operator.bc.*; @@ -57,6 +58,16 @@ public class DtlsControlImpl */ private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + /** + * The <tt>SRTPProtectionProfile</tt>s supported by + * <tt>DtlsControlImpl</tt>. + */ + static final int[] SRTP_PROTECTION_PROFILES + = { + SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80, + SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_32 + }; + /** * The certificate with which the local endpoint represented by this * instance authenticates its ends of DTLS sessions. @@ -128,6 +139,36 @@ public DtlsControlImpl() localFingerprintHashFunction); } + /** + * Chooses the first from a list of <tt>SRTPProtectionProfile</tt>s that is + * supported by <tt>DtlsControlImpl</tt>. + * + * @param theirs the list of <tt>SRTPProtectionProfile</tt>s to choose from + * @return the first from the specified <tt>theirs</tt> that is supported + * by <tt>DtlsControlImpl</tt> + */ + static int chooseSRTPProtectionProfile(int... theirs) + { + int[] ours = SRTP_PROTECTION_PROFILES; + + if (theirs != null) + { + for (int t = 0; t < theirs.length; t++) + { + int their = theirs[t]; + + for (int o = 0; o < ours.length; o++) + { + int our = ours[o]; + + if (their == our) + return their; + } + } + } + return 0; + } + /** * {@inheritDoc} */ diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java index 582905330a797b9d8535a53f284aed8333a1ed86..2b66d7e6182f04cd91b4eaea9972b0ebaf4ef62c 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java @@ -10,8 +10,10 @@ import java.security.*; import org.bouncycastle.crypto.tls.*; +import org.ice4j.ice.*; import org.jitsi.impl.neomedia.*; import org.jitsi.impl.neomedia.transform.*; +import org.jitsi.impl.neomedia.transform.srtp.*; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; @@ -32,7 +34,7 @@ public class DtlsPacketTransformer * 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; + private static final int DTLS_TRANSPORT_RECEIVE_WAITMILLIS = -1; /** * The <tt>Logger</tt> used by the <tt>DtlsPacketTransformer</tt> class and @@ -142,6 +144,11 @@ public static boolean isDtlsRecord(byte[] buf, int off, int len) */ private MediaType mediaType; + /** + * The <tt>SRTPTransformer</tt> to be used by this instance. + */ + private PacketTransformer srtpTransformer; + /** * The <tt>TransformEngine</tt> which has initialized this instance. */ @@ -222,6 +229,178 @@ DtlsTransformEngine getTransformEngine() return transformEngine; } + /** + * Initializes a new <tt>SRTPTransformer</tt> instance with a specific + * (negotiated) <tt>SRTPProtectionProfile</tt> and the keying material + * specified by a specific <tt>TlsContext</tt>. + * + * @param srtpProtectionProfile the (negotiated) + * <tt>SRTPProtectionProfile</tt> to initialize the new instance with + * @param tlsContext the <tt>TlsContext</tt> which represents the keying + * material + * @return a new <tt>SRTPTransformer</tt> instance initialized with + * <tt>srtpProtectionProfile</tt> and <tt>tlsContext</tt> + */ + private PacketTransformer initializeSRTPTransformer( + int srtpProtectionProfile, + TlsContext tlsContext) + { + boolean rtcp; + + switch (componentID) + { + case Component.RTCP: + rtcp = true; + break; + case Component.RTP: + rtcp = false; + break; + default: + throw new IllegalStateException("componentID"); + } + + int cipher_key_length; + int cipher_salt_length; + int cipher; + int auth_function; + int auth_key_length; + int RTCP_auth_tag_length, RTP_auth_tag_length; + + switch (srtpProtectionProfile) + { + case SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_32: + cipher_key_length = 128 / 8; + cipher_salt_length = 112 / 8; + cipher = SRTPPolicy.AESCM_ENCRYPTION; + auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION; + auth_key_length = 160 / 8; + RTCP_auth_tag_length = 80 / 8; + RTP_auth_tag_length = 32 / 8; + break; + case SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80: + cipher_key_length = 128 / 8; + cipher_salt_length = 112 / 8; + cipher = SRTPPolicy.AESCM_ENCRYPTION; + auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION; + auth_key_length = 160 / 8; + RTCP_auth_tag_length = RTP_auth_tag_length = 80 / 8; + break; + case SRTPProtectionProfile.SRTP_NULL_HMAC_SHA1_32: + cipher_key_length = 0; + cipher_salt_length = 0; + cipher = SRTPPolicy.NULL_ENCRYPTION; + auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION; + auth_key_length = 160 / 8; + RTCP_auth_tag_length = 80 / 8; + RTP_auth_tag_length = 32 / 8; + break; + case SRTPProtectionProfile.SRTP_NULL_HMAC_SHA1_80: + cipher_key_length = 0; + cipher_salt_length = 0; + cipher = SRTPPolicy.NULL_ENCRYPTION; + auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION; + auth_key_length = 160 / 8; + RTCP_auth_tag_length = RTP_auth_tag_length = 80 / 8; + break; + default: + throw new IllegalArgumentException("srtpProtectionProfile"); + } + + byte[] keyingMaterial + = tlsContext.exportKeyingMaterial( + ExporterLabel.dtls_srtp, + null, + 2 * (cipher_key_length + cipher_salt_length)); + byte[] client_write_SRTP_master_key = new byte[cipher_key_length]; + byte[] server_write_SRTP_master_key = new byte[cipher_key_length]; + byte[] client_write_SRTP_master_salt = new byte[cipher_salt_length]; + byte[] server_write_SRTP_master_salt = new byte[cipher_salt_length]; + byte[][] keyingMaterialValues + = { + client_write_SRTP_master_key, + server_write_SRTP_master_key, + client_write_SRTP_master_salt, + server_write_SRTP_master_salt + }; + + for (int i = 0, keyingMaterialOffset = 0; + i < keyingMaterialValues.length; + i++) + { + byte[] keyingMaterialValue = keyingMaterialValues[i]; + + System.arraycopy( + keyingMaterial, keyingMaterialOffset, + keyingMaterialValue, 0, + keyingMaterialValue.length); + keyingMaterialOffset += keyingMaterialValue.length; + } + + SRTPPolicy srtcpPolicy + = new SRTPPolicy( + cipher, + cipher_key_length, + auth_function, + auth_key_length, + RTCP_auth_tag_length, + cipher_salt_length); + SRTPPolicy srtpPolicy + = new SRTPPolicy( + cipher, + cipher_key_length, + auth_function, + auth_key_length, + RTP_auth_tag_length, + cipher_salt_length); + SRTPContextFactory clientSRTPContextFactory + = new SRTPContextFactory( + client_write_SRTP_master_key, + client_write_SRTP_master_salt, + srtpPolicy, + srtcpPolicy); + SRTPContextFactory serverSRTPContextFactory + = new SRTPContextFactory( + server_write_SRTP_master_key, + server_write_SRTP_master_salt, + srtpPolicy, + srtcpPolicy); + SRTPContextFactory forwardSRTPContextFactory; + SRTPContextFactory reverseSRTPContextFactory; + + if (tlsContext instanceof TlsClientContext) + { + forwardSRTPContextFactory = clientSRTPContextFactory; + reverseSRTPContextFactory = serverSRTPContextFactory; + } + else if (tlsContext instanceof TlsServerContext) + { + forwardSRTPContextFactory = serverSRTPContextFactory; + reverseSRTPContextFactory = clientSRTPContextFactory; + } + else + { + throw new IllegalArgumentException("tlsContext"); + } + + PacketTransformer srtpTransformer; + + if (rtcp) + { + srtpTransformer + = new SRTCPTransformer( + forwardSRTPContextFactory, + reverseSRTPContextFactory); + } + else + { + srtpTransformer + = new SRTPTransformer( + forwardSRTPContextFactory, + reverseSRTPContextFactory); + } + return srtpTransformer; + } + /** * {@inheritDoc} */ @@ -297,6 +476,8 @@ else if (delta < 0) delta = len - received; if (delta > 0) pkt.shrink(delta); + + pkt = null; } } catch (IOException ioe) @@ -318,12 +499,12 @@ else if (delta < 0) } 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; + PacketTransformer srtpTransformer = this.srtpTransformer; + + if (srtpTransformer == null) + pkt = null; + else + pkt = srtpTransformer.reverseTransform(pkt); } return pkt; } @@ -341,12 +522,14 @@ private void runInConnectThread( DatagramTransport datagramTransport) { DTLSTransport dtlsTransport = null; + int srtpProtectionProfile; + TlsContext tlsContext; if (dtlsProtocol instanceof DTLSClientProtocol) { DTLSClientProtocol dtlsClientProtocol = (DTLSClientProtocol) dtlsProtocol; - TlsClient tlsClient = (TlsClient) tlsPeer; + TlsClientImpl tlsClient = (TlsClientImpl) tlsPeer; try { @@ -361,12 +544,14 @@ private void runInConnectThread( "Failed to connect this DTLS client to a DTLS server!", ioe); } + srtpProtectionProfile = tlsClient.getChosenProtectionProfile(); + tlsContext = tlsClient.getContext(); } else if (dtlsProtocol instanceof DTLSServerProtocol) { DTLSServerProtocol dtlsServerProtocol = (DTLSServerProtocol) dtlsProtocol; - TlsServer tlsServer = (TlsServer) tlsPeer; + TlsServerImpl tlsServer = (TlsServerImpl) tlsPeer; try { @@ -381,19 +566,30 @@ else if (dtlsProtocol instanceof DTLSServerProtocol) "Failed to accept a connection from a DTLS client!", ioe); } + srtpProtectionProfile = tlsServer.getChosenProtectionProfile(); + tlsContext = tlsServer.getContext(); } else throw new IllegalStateException("dtlsProtocol"); + PacketTransformer srtpTransformer + = initializeSRTPTransformer(srtpProtectionProfile, tlsContext); + boolean closeSRTPTransformer; + synchronized (this) { if (Thread.currentThread().equals(this.connectThread) && datagramTransport.equals(this.datagramTransport)) { this.dtlsTransport = dtlsTransport; + this.srtpTransformer = srtpTransformer; notifyAll(); } + closeSRTPTransformer + = (this.srtpTransformer != srtpTransformer); } + if (closeSRTPTransformer) + srtpTransformer.close(); } /** @@ -577,6 +773,11 @@ private synchronized void stop() } dtlsTransport = null; } + if (srtpTransformer != null) + { + srtpTransformer.close(); + srtpTransformer = null; + } closeDatagramTransport(); notifyAll(); @@ -598,28 +799,12 @@ public RawPacket transform(RawPacket pkt) */ if (!isDtlsRecord(buf, off, len)) { - /* - * The specified pkt will pass through this PacketTransformer only - * if it gets transformed into a DTLS record. - */ - pkt = null; + PacketTransformer srtpTransformer = this.srtpTransformer; - 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); - } - } + if (srtpTransformer == null) + pkt = null; + else + pkt = srtpTransformer.transform(pkt); } return pkt; } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java index aa75730b1c137941c80db6bc70919af4193bdd46..98c83e64a5c0a995cfcb9c1956b74d5c363ef6da 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java @@ -7,6 +7,7 @@ package org.jitsi.impl.neomedia.transform.dtls; import java.io.*; +import java.util.*; import org.bouncycastle.crypto.tls.*; import org.jitsi.util.*; @@ -28,6 +29,20 @@ public class TlsClientImpl private final TlsAuthentication authentication = new TlsAuthenticationImpl(); + /** + * The <tt>SRTPProtectionProfile</tt> negotiated between this DTLS-SRTP + * client and its server. + */ + private int chosenProtectionProfile; + + /** + * The SRTP Master Key Identifier (MKI) used by the + * <tt>SRTPCryptoContext</tt> associated with this instance. Since the + * <tt>SRTPCryptoContext</tt> class does not utilize it, the value is + * {@link TlsUtils#EMPTY_BYTES}. + */ + private final byte[] mki = TlsUtils.EMPTY_BYTES; + /** * The <tt>PacketTransformer</tt> which has initialized this instance. */ @@ -53,6 +68,44 @@ public synchronized TlsAuthentication getAuthentication() return authentication; } + /** + * Gets the <tt>SRTPProtectionProfile</tt> negotiated between this DTLS-SRTP + * client and its server. + * + * @return the <tt>SRTPProtectionProfile</tt> negotiated between this + * DTLS-SRTP client and its server + */ + int getChosenProtectionProfile() + { + return chosenProtectionProfile; + } + + /** + * {@inheritDoc} + * + * Includes the <tt>use_srtp</tt> extension in the DTLS extended client + * hello. + */ + @Override + @SuppressWarnings("rawtypes") + public Hashtable getClientExtensions() + throws IOException + { + Hashtable clientExtensions = super.getClientExtensions(); + + if (TlsSRTPUtils.getUseSRTPExtension(clientExtensions) == null) + { + if (clientExtensions == null) + clientExtensions = new Hashtable(); + TlsSRTPUtils.addUseSRTPExtension( + clientExtensions, + new UseSRTPData( + DtlsControlImpl.SRTP_PROTECTION_PROFILES, + mki)); + } + return clientExtensions; + } + /** * {@inheritDoc} * @@ -67,6 +120,18 @@ public ProtocolVersion getClientVersion() return ProtocolVersion.DTLSv10; } + /** + * Gets the <tt>TlsContext</tt> with which this <tt>TlsClient</tt> has been + * initialized. + * + * @return the <tt>TlsContext</tt> with which this <tt>TlsClient</tt> has + * been initialized + */ + TlsContext getContext() + { + return context; + } + /** * Gets the <tt>DtlsControl</tt> implementation associated with this * instance. @@ -88,6 +153,78 @@ public ProtocolVersion getMinimumVersion() return ProtocolVersion.DTLSv10; } + /** + * {@inheritDoc} + * + * Overrides the super implementation as a simple means of detecting that + * the security-related negotiations between the local and the remote + * enpoints are starting. The detection carried out for the purposes of + * <tt>SrtpListener</tt>. + */ + @Override + public void init(TlsClientContext context) + { + // TODO Auto-generated method stub + super.init(context); + } + + /** + * {@inheritDoc} + * + * Makes sure that the DTLS extended server hello contains the + * <tt>use_srtp</tt> extension. + */ + @Override + @SuppressWarnings("rawtypes") + public void processServerExtensions(Hashtable serverExtensions) + throws IOException + { + UseSRTPData useSRTPData + = TlsSRTPUtils.getUseSRTPExtension(serverExtensions); + + if (useSRTPData == null) + { + throw new IOException( + "DTLS extended server hello does not include the use_srtp" + + " extension!"); + } + else + { + int[] protectionProfiles = useSRTPData.getProtectionProfiles(); + int chosenProtectionProfile + = (protectionProfiles.length == 1) + ? DtlsControlImpl.chooseSRTPProtectionProfile( + protectionProfiles[0]) + : 0; + + if (chosenProtectionProfile == 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + else + { + /* + * If the client detects a nonzero-length MKI in the server's + * response that is different than the one the client offered, + * then the client MUST abort the handshake and SHOULD send an + * invalid_parameter alert. + */ + byte[] mki = useSRTPData.getMki(); + + if (Arrays.equals(mki, this.mki)) + { + super.processServerExtensions(serverExtensions); + + this.chosenProtectionProfile = chosenProtectionProfile; + } + else + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + } + /** * Implements {@link TlsAuthentication} for the purposes of supporting * DTLS-SRTP. diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java index a12a41b9dd163b88a46f12c16bc314129f0b6737..e7d50ff92b90c53c52f6138618029b04d635baf0 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java @@ -7,6 +7,7 @@ package org.jitsi.impl.neomedia.transform.dtls; import java.io.*; +import java.util.*; import org.bouncycastle.crypto.tls.*; import org.jitsi.util.*; @@ -30,6 +31,12 @@ public class TlsServerImpl new short[] { ClientCertificateType.rsa_sign }, /* certificateAuthorities */ null); + /** + * The <tt>SRTPProtectionProfile</tt> negotiated between this DTLS-SRTP + * server and its client. + */ + private int chosenProtectionProfile; + /** * The <tt>PacketTransformer</tt> which has initialized this instance. */ @@ -57,6 +64,30 @@ public CertificateRequest getCertificateRequest() return certificateRequest; } + /** + * Gets the <tt>SRTPProtectionProfile</tt> negotiated between this DTLS-SRTP + * server and its client. + * + * @return the <tt>SRTPProtectionProfile</tt> negotiated between this + * DTLS-SRTP server and its client + */ + int getChosenProtectionProfile() + { + return chosenProtectionProfile; + } + + /** + * Gets the <tt>TlsContext</tt> with which this <tt>TlsServer</tt> has been + * initialized. + * + * @return the <tt>TlsContext</tt> with which this <tt>TlsServer</tt> has + * been initialized + */ + TlsContext getContext() + { + return context; + } + /** * Gets the <tt>DtlsControl</tt> implementation associated with this * instance. @@ -112,6 +143,72 @@ protected TlsSignerCredentials getRSASignerCredentials() return rsaSignerCredentials; } + /** + * {@inheritDoc} + * + * Includes the <tt>use_srtp</tt> extension in the DTLS extended server + * hello. + */ + @Override + @SuppressWarnings("rawtypes") + public Hashtable getServerExtensions() + throws IOException + { + Hashtable serverExtensions = super.getServerExtensions(); + + if (TlsSRTPUtils.getUseSRTPExtension(serverExtensions) == null) + { + if (serverExtensions == null) + serverExtensions = new Hashtable(); + + UseSRTPData useSRTPData + = TlsSRTPUtils.getUseSRTPExtension(clientExtensions); + int chosenProtectionProfile + = DtlsControlImpl.chooseSRTPProtectionProfile( + useSRTPData.getProtectionProfiles()); + + /* + * If there is no shared profile and that is not acceptable, the + * server SHOULD return an appropriate DTLS alert. + */ + if (chosenProtectionProfile == 0) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + else + { + /* + * Upon receipt of a "use_srtp" extension containing a + * "srtp_mki" field, the server MUST include a matching + * "srtp_mki" value in its "use_srtp" extension to indicate that + * it will make use of the MKI. + */ + TlsSRTPUtils.addUseSRTPExtension( + serverExtensions, + new UseSRTPData( + new int[] { chosenProtectionProfile }, + useSRTPData.getMki())); + this.chosenProtectionProfile = chosenProtectionProfile; + } + } + return serverExtensions; + } + + /** + * {@inheritDoc} + * + * Overrides the super implementation as a simple means of detecting that + * the security-related negotiations between the local and the remote + * enpoints are starting. The detection carried out for the purposes of + * <tt>SrtpListener</tt>. + */ + @Override + public void init(TlsServerContext context) + { + // TODO Auto-generated method stub + super.init(context); + } + /** * {@inheritDoc} */ @@ -134,4 +231,41 @@ public void notifyClientCertificate(Certificate clientCertificate) throw new IOException(e); } } + + /** + * {@inheritDoc} + * + * Makes sure that the DTLS extended client hello contains the + * <tt>use_srtp</tt> extension. + */ + @Override + @SuppressWarnings("rawtypes") + public void processClientExtensions(Hashtable clientExtensions) + throws IOException + { + UseSRTPData useSRTPData + = TlsSRTPUtils.getUseSRTPExtension(clientExtensions); + + if (useSRTPData == null) + { + throw new IOException( + "DTLS extended client hello does not include the use_srtp" + + " extension!"); + } + else + { + int chosenProtectionProfile + = DtlsControlImpl.chooseSRTPProtectionProfile( + useSRTPData.getProtectionProfiles()); + + /* + * If there is no shared profile and that is not acceptable, the + * server SHOULD return an appropriate DTLS alert. + */ + if (chosenProtectionProfile == 0) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + else + super.processClientExtensions(clientExtensions); + } + } } diff --git a/src/org/jitsi/impl/neomedia/transform/srtp/SRTPCryptoContext.java b/src/org/jitsi/impl/neomedia/transform/srtp/SRTPCryptoContext.java index c1f459ae2c5ebffc0b4f7ecbd8fa75266a2fad2d..72f15b647e6537f59f11f401df56d12d469eb9d2 100644 --- a/src/org/jitsi/impl/neomedia/transform/srtp/SRTPCryptoContext.java +++ b/src/org/jitsi/impl/neomedia/transform/srtp/SRTPCryptoContext.java @@ -337,14 +337,7 @@ public int getAuthTagLength() */ public int getMKILength() { - if (mki != null) - { - return mki.length; - } - else - { - return 0; - } + return (mki == null) ? 0 : mki.length; } /**