diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/CertificateInfo.java b/src/org/jitsi/impl/neomedia/transform/dtls/CertificateInfo.java index 335b2ed49..b7075c533 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/CertificateInfo.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/CertificateInfo.java @@ -16,6 +16,7 @@ package org.jitsi.impl.neomedia.transform.dtls; import org.bouncycastle.crypto.*; +import org.bouncycastle.crypto.tls.*; /** * Bundles information such as key pair, hash function, fingerprint, etc. about @@ -30,12 +31,12 @@ class CertificateInfo * The certificate with which the local endpoint represented by this * instance authenticates its ends of DTLS sessions. */ - public final org.bouncycastle.crypto.tls.Certificate certificate; + private final Certificate certificate; /** * The private and public keys of {@link #certificate}. */ - public final AsymmetricCipherKeyPair keyPair; + private final AsymmetricCipherKeyPair keyPair; /** * The fingerprint of {@link #certificate}. @@ -55,9 +56,20 @@ class CertificateInfo */ public final long timestamp; + /** + * Initializes a new {@code CertificateInfo} instance. + * + * @param keyPair the private and public keys of {@code certificate} + * @param certificate the certificate with which the local endpoint + * represented by the new instance is to authenticates its ends of DTLS + * sessions + * @param localFingerprintHashFunction + * @param localFingerprint + * @param timestamp + */ public CertificateInfo( AsymmetricCipherKeyPair keyPair, - org.bouncycastle.crypto.tls.Certificate certificate, + Certificate certificate, String localFingerprintHashFunction, String localFingerprint, long timestamp) @@ -68,4 +80,26 @@ public CertificateInfo( this.localFingerprint = localFingerprint; this.timestamp = timestamp; } + + /** + * 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. + */ + public Certificate getCertificate() + { + return certificate; + } + + /** + * Gets the private and public keys of {@link #certificate}. + * + * @return the private and public keys of {@link #certificate}. + */ + public AsymmetricCipherKeyPair getKeyPair() + { + return keyPair; + } } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java index c206a235b..c9e9430e8 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DatagramTransportImpl.java @@ -482,7 +482,15 @@ public void send(byte[] buf, int off, int len) switch (type) { case ContentType.handshake: - short msg_type = TlsUtils.readUint8(buf, off + 11); + short msg_type + = TlsUtils.readUint8( + buf, + off + + 1 /* type */ + + 2 /* version */ + + 2 /* epoch */ + + 6 /* sequence_number */ + + 2 /* length */); switch (msg_type) { @@ -501,8 +509,12 @@ public void send(byte[] buf, int off, int len) case HandshakeType.hello_request: case HandshakeType.hello_verify_request: case HandshakeType.server_hello_done: + endOfFlight = true; + break; default: endOfFlight = true; + logger.warn( + "Unknown DTLS handshake message type: " + msg_type); break; } // Do fall through! diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java index 725179146..5376ddc74 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsControlImpl.java @@ -35,7 +35,6 @@ import org.bouncycastle.operator.*; import org.bouncycastle.operator.bc.*; import org.jitsi.impl.neomedia.*; -import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; import org.jitsi.service.version.*; @@ -57,7 +56,7 @@ public class DtlsControlImpl * lower case. */ private static final Map HASH_FUNCTION_UPGRADES - = new HashMap(); + = new HashMap<>(); /** * The table which maps half-bytes to their hex characters. @@ -125,25 +124,11 @@ public class DtlsControlImpl static { // VERIFY_AND_VALIDATE_CERTIFICATE - ConfigurationService cfg = LibJitsi.getConfigurationService(); - boolean verifyAndValidateCertificate = true; - - if (cfg == null) - { - String s - = System.getProperty(VERIFY_AND_VALIDATE_CERTIFICATE_PNAME); - - if (s != null) - verifyAndValidateCertificate = Boolean.parseBoolean(s); - } - else - { - verifyAndValidateCertificate - = cfg.getBoolean( - VERIFY_AND_VALIDATE_CERTIFICATE_PNAME, - verifyAndValidateCertificate); - } - VERIFY_AND_VALIDATE_CERTIFICATE = verifyAndValidateCertificate; + VERIFY_AND_VALIDATE_CERTIFICATE + = ConfigUtils.getBoolean( + LibJitsi.getConfigurationService(), + VERIFY_AND_VALIDATE_CERTIFICATE_PNAME, + true); // HASH_FUNCTION_UPGRADES HASH_FUNCTION_UPGRADES.put( @@ -161,18 +146,14 @@ public class DtlsControlImpl */ 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]; + int[] ours = SRTP_PROTECTION_PROFILES; - for (int o = 0; o < ours.length; o++) + for (int their : theirs) + { + for (int our : ours) { - int our = ours[o]; - if (their == our) return their; } @@ -192,7 +173,7 @@ static int chooseSRTPProtectionProfile(int... theirs) * @return the fingerprint of the specified certificate computed * using the specified hashFunction */ - private static final String computeFingerprint( + private static String computeFingerprint( org.bouncycastle.asn1.x509.Certificate certificate, String hashFunction) { @@ -444,20 +425,18 @@ private static AsymmetricCipherKeyPair generateKeyPair() X500Name subject, AsymmetricCipherKeyPair keyPair) { - // get property for certificate creation and default to sha1 - String signatureAlgorithm = "SHA1withRSA"; - // get property override from the config service if it exists - ConfigurationService cfg = LibJitsi.getConfigurationService(); + // The signature algorithm of the generated certificate defaults to + // SHA1. However, allow the overriding of the default via the + // ConfigurationService. + String signatureAlgorithm + = ConfigUtils.getString( + LibJitsi.getConfigurationService(), + PROP_SIGNATURE_ALGORITHM, + "SHA1withRSA"); - if (cfg != null) - { - signatureAlgorithm - = cfg.getString(PROP_SIGNATURE_ALGORITHM, "SHA1withRSA"); - } if (logger.isDebugEnabled()) - { logger.debug("Signature algorithm: " + signatureAlgorithm); - } + try { long now = System.currentTimeMillis(); @@ -542,18 +521,6 @@ private static String toHex(byte[] fingerprint) */ private final CertificateInfo certificateInfo; - /** - * The RTPConnector which uses the TransformEngine of this - * SrtpControl. - */ - private AbstractRTPConnector connector; - - /** - * Indicates whether this DtlsControl will work in DTLS/SRTP or - * DTLS mode. - */ - private final boolean disableSRTP; - /** * The indicator which determines whether this instance has been disposed * i.e. prepared for garbage collection by {@link #doCleanup()}. @@ -566,23 +533,11 @@ private static String toHex(byte[] fingerprint) private Map remoteFingerprints; /** - * Whether rtcp-mux is in use. + * The properties of {@code DtlsControlImpl} and their values which this + * instance shares with {@link DtlsTransformEngine} and + * {@link DtlsPacketTransformer}. */ - private boolean rtcpmux = false; - - /** - * The value of the setup SDP attribute defined by RFC 4145 - * "TCP-Based Media Transport in the Session Description Protocol - * (SDP)" which determines whether this instance acts as a DTLS client - * or a DTLS server. - */ - private Setup setup; - - /** - * The instances currently registered as users of this SrtpControl - * (through {@link #registerUser(Object)}). - */ - private final Set users = new HashSet(); + private final Properties properties; /** * Initializes a new DtlsControlImpl instance. @@ -590,20 +545,19 @@ private static String toHex(byte[] fingerprint) public DtlsControlImpl() { // By default we work in DTLS/SRTP mode. - this(false); + this(/* srtpDisabled */ false); } /** * Initializes a new DtlsControlImpl instance. - * @param disableSRTP true if pure DTLS mode without SRTP - * extensions should be used. + * + * @param srtpDisabled true if pure DTLS mode without SRTP + * extensions is to be used; otherwise, false */ - public DtlsControlImpl(boolean disableSRTP) + public DtlsControlImpl(boolean srtpDisabled) { super(SrtpControlType.DTLS_SRTP); - this.disableSRTP = disableSRTP; - CertificateInfo certificateInfo; // The methods generateKeyPair(), generateX509Certificate(), @@ -626,24 +580,14 @@ public DtlsControlImpl(boolean disableSRTP) } } this.certificateInfo = certificateInfo; - } - /** - * {@inheritDoc} - */ - @Override - public void cleanup(Object user) - { - synchronized (users) - { - if (users.remove(user) && users.isEmpty()) - doCleanup(); - } + properties = new Properties(srtpDisabled); } /** * Initializes a new DtlsTransformEngine instance to be associated - * with and used by this DtlsControlImpl instance. + * with and used by this DtlsControlImpl instance. The method is + * implemented as a factory. * * @return a new DtlsTransformEngine instance to be associated with * and used by this DtlsControlImpl instance @@ -651,20 +595,16 @@ public void cleanup(Object user) @Override protected DtlsTransformEngine createTransformEngine() { - DtlsTransformEngine transformEngine = new DtlsTransformEngine(this); - - transformEngine.setConnector(connector); - transformEngine.setSetup(setup); - transformEngine.setRtcpmux(rtcpmux); - return transformEngine; + return new DtlsTransformEngine(this); } /** - * Prepares this DtlsControlImpl for garbage collection. + * {@inheritDoc} */ - private void doCleanup() + @Override + protected void doCleanup() { - super.cleanup(null); + super.doCleanup(); setConnector(null); @@ -676,44 +616,49 @@ private void doCleanup() } /** - * Gets the certificate with which the local endpoint represented by this - * instance authenticates its ends of DTLS sessions. + * Gets the certificate, hash function, fingerprint, etc. 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. + * @return the certificate, hash function, fingerprint, etc. with which the + * local endpoint represented by this instance authenticates its ends of + * DTLS sessions */ - org.bouncycastle.crypto.tls.Certificate getCertificate() + CertificateInfo getCertificateInfo() { - return certificateInfo.certificate; + return certificateInfo; } /** - * The private and public keys of the certificate of this instance. - * - * @return the private and public keys of the certificate of this - * instance + * {@inheritDoc} */ - AsymmetricCipherKeyPair getKeyPair() + @Override + public String getLocalFingerprint() { - return certificateInfo.keyPair; + return getCertificateInfo().localFingerprint; } /** * {@inheritDoc} */ @Override - public String getLocalFingerprint() + public String getLocalFingerprintHashFunction() { - return certificateInfo.localFingerprint; + return getCertificateInfo().localFingerprintHashFunction; } /** - * {@inheritDoc} + * Gets the properties of {@code DtlsControlImpl} and their values which + * this instance shares with {@link DtlsTransformEngine} and + * {@link DtlsPacketTransformer}. + * + * @return the properties of {@code DtlsControlImpl} and their values which + * this instance shares with {@code DtlsTransformEngine} and + * {@code DtlsPacketTransformer} */ - @Override - public String getLocalFingerprintHashFunction() + Properties getProperties() { - return certificateInfo.localFingerprintHashFunction; + return properties; } /** @@ -727,25 +672,19 @@ public boolean getSecureCommunicationStatus() } /** - * Indicates if SRTP extensions are disabled which means we're working in - * pure DTLS mode. - * @return true if SRTP extensions must be disabled. - */ - boolean isSrtpDisabled() - { - return disableSRTP; - } - - /** - * {@inheritDoc} + * Gets the value of the {@code setup} SDP attribute defined by RFC 4145 + * "TCP-Based Media Transport in the Session Description Protocol + * (SDP)" which determines whether this instance acts as a DTLS client + * or a DTLS server. + * + * @return the value of the {@code setup} SDP attribute defined by RFC 4145 + * "TCP-Based Media Transport in the Session Description Protocol + * (SDP)" which determines whether this instance acts as a DTLS client + * or a DTLS server */ - @Override - public void registerUser(Object user) + public DtlsControl.Setup getSetup() { - synchronized (users) - { - users.add(user); - } + return getProperties().getSetup(); } /** @@ -766,15 +705,7 @@ public boolean requiresSecureSignalingTransport() @Override public void setConnector(AbstractRTPConnector connector) { - if (this.connector != connector) - { - this.connector = connector; - - DtlsTransformEngine transformEngine = this.transformEngine; - - if (transformEngine != null) - transformEngine.setConnector(this.connector); - } + properties.put(Properties.CONNECTOR_PNAME, connector); } /** @@ -786,33 +717,27 @@ public void setRemoteFingerprints(Map remoteFingerprints) if (remoteFingerprints == null) throw new NullPointerException("remoteFingerprints"); - synchronized (this) + // Make sure that the hash functions (which are keys of the field + // remoteFingerprints) are written in lower case. + Map rfs = new HashMap<>(remoteFingerprints.size()); + + for (Map.Entry e : remoteFingerprints.entrySet()) { - // Make sure that the hash functions (which are keys of the field - // remoteFingerprints) are written in lower case. - Map rfs - = new HashMap(remoteFingerprints.size()); + String k = e.getKey(); - for (Map.Entry e : remoteFingerprints.entrySet()) + // It makes no sense to provide a fingerprint without a hash + // function. + if (k != null) { - String k = e.getKey(); - - // It makes no sense to provide a fingerprint without a hash - // function. - if (k != null) - { - String v = e.getValue(); + String v = e.getValue(); - // It makes no sense to provide a hash function without a - // fingerprint. - if (v != null) - rfs.put(k.toLowerCase(), v); - } + // It makes no sense to provide a hash function without a + // fingerprint. + if (v != null) + rfs.put(k.toLowerCase(), v); } - this.remoteFingerprints = rfs; - - notifyAll(); } + this.remoteFingerprints = rfs; } /** @@ -821,15 +746,7 @@ public void setRemoteFingerprints(Map remoteFingerprints) @Override public void setRtcpmux(boolean rtcpmux) { - if (this.rtcpmux != rtcpmux) - { - this.rtcpmux = rtcpmux; - - DtlsTransformEngine transformEngine = this.transformEngine; - - if (transformEngine != null) - transformEngine.setRtcpmux(rtcpmux); - } + properties.put(Properties.RTCPMUX_PNAME, rtcpmux); } /** @@ -838,15 +755,7 @@ public void setRtcpmux(boolean rtcpmux) @Override public void setSetup(Setup setup) { - if (this.setup != setup) - { - this.setup = setup; - - DtlsTransformEngine transformEngine = this.transformEngine; - - if (transformEngine != null) - transformEngine.setSetup(this.setup); - } + properties.put(Properties.SETUP_PNAME, setup); } /** @@ -855,10 +764,7 @@ public void setSetup(Setup setup) @Override public void start(MediaType mediaType) { - DtlsTransformEngine transformEngine = getTransformEngine(); - - if (transformEngine != null) - transformEngine.start(mediaType); + properties.put(Properties.MEDIA_TYPE_PNAME, mediaType); } /** @@ -895,6 +801,7 @@ private void verifyAndValidateCertificate( { throw new IllegalStateException("disposed"); } + Map remoteFingerprints = this.remoteFingerprints; if (remoteFingerprints == null) diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java index 3519a102b..0fdd60c2d 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsPacketTransformer.java @@ -15,6 +15,7 @@ */ package org.jitsi.impl.neomedia.transform.dtls; +import java.beans.*; import java.io.*; import java.security.*; import java.util.*; @@ -28,6 +29,7 @@ import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; +import org.jitsi.util.event.*; /** * Implements {@link PacketTransformer} for DTLS-SRTP. It's capable of working @@ -205,6 +207,17 @@ public static boolean isDtlsRecord(byte[] buf, int off, int len) */ private MediaType mediaType; + private final PropertyChangeListener propertyChangeListener + = new WeakReferencePropertyChangeListener( + new PropertyChangeListener() + { + @Override + public void propertyChange(PropertyChangeEvent ev) + { + DtlsPacketTransformer.this.propertyChange(ev); + } + }); + /** * The {@code Queue} of SRTP {@code RawPacket}s which were received from the * remote while {@link #_srtpTransformer} was unavailable i.e. {@code null}. @@ -219,15 +232,7 @@ public static boolean isDtlsRecord(byte[] buf, int off, int len) * a DTLS session on its own, but rather wait for the RTP transformer to * do so, and reuse it to initialize the SRTP transformer. */ - private boolean rtcpmux = false; - - /** - * The value of the setup SDP attribute defined by RFC 4145 - * "TCP-Based Media Transport in the Session Description Protocol - * (SDP)" which determines whether this instance acts as a DTLS client - * or a DTLS server. - */ - private DtlsControl.Setup setup; + private boolean rtcpmux; /** * The {@code SRTPTransformer} (to be) used by this instance. @@ -269,6 +274,11 @@ public DtlsPacketTransformer( { this.transformEngine = transformEngine; this.componentID = componentID; + + // Track the DTLS properties which control the conditional behaviors of + // DtlsPacketTransformer. + getProperties().addPropertyChangeListener(propertyChangeListener); + propertyChange(/* propertyName */ (String) null); } /** @@ -277,6 +287,8 @@ public DtlsPacketTransformer( @Override public synchronized void close() { + getProperties().removePropertyChangeListener(propertyChangeListener); + // SrtpControl.start(MediaType) starts its associated TransformEngine. // We will use that mediaType to signal the normal stop then as well // i.e. we will call setMediaType(null) first. @@ -369,6 +381,18 @@ DtlsControlImpl getDtlsControl() return getTransformEngine().getDtlsControl(); } + /** + * Gets the properties of {@link DtlsControlImpl} and their values which + * the associated {@code DtlsControlImpl} shares with this instance. + * + * @return the properties of {@code DtlsControlImpl} and their values which + * the associated {@code DtlsControlImpl} shares with this instance + */ + Properties getProperties() + { + return getTransformEngine().getProperties(); + } + /** * Gets the {@code SRTPTransformer} (to be) used by this instance. * @@ -708,6 +732,18 @@ else if (tlsContext instanceof TlsServerContext) return srtpTransformer; } + /** + * Determines whether this {@code DtlsPacketTransformer} is to operate in + * pure DTLS mode without SRTP extensions or in DTLS/SRTP mode. + * + * @return {@code true} for pure DTLS without SRTP extensions or + * {@code false} for DTLS/SRTP + */ + private boolean isSrtpDisabled() + { + return getProperties().isSrtpDisabled(); + } + private synchronized void maybeStart() { if (this.mediaType != null && this.connector != null && !started) @@ -743,6 +779,38 @@ void notifyAlertRaised( } } + private void propertyChange(PropertyChangeEvent ev) + { + propertyChange(ev.getPropertyName()); + } + + private void propertyChange(String propertyName) + { + // This DtlsPacketTransformer calls the method with null at construction + // time to initialize the respective states. + if (propertyName == null) + { + propertyChange(Properties.RTCPMUX_PNAME); + propertyChange(Properties.MEDIA_TYPE_PNAME); + propertyChange(Properties.CONNECTOR_PNAME); + } + else if (Properties.CONNECTOR_PNAME.equals(propertyName)) + { + setConnector( + (AbstractRTPConnector) getProperties().get(propertyName)); + } + else if (Properties.MEDIA_TYPE_PNAME.equals(propertyName)) + { + setMediaType((MediaType) getProperties().get(propertyName)); + } + else if (Properties.RTCPMUX_PNAME.equals(propertyName)) + { + Object newValue = getProperties().get(propertyName); + + setRtcpmux((newValue == null) ? false : (Boolean) newValue); + } + } + /** * Queues {@code RawPacket}s to be supplied to * {@link #transformSrtp(SinglePacketTransformer, Collection, boolean, List)} @@ -918,7 +986,7 @@ private void runInConnectThread( DatagramTransport datagramTransport) { DTLSTransport dtlsTransport = null; - final boolean srtp = !transformEngine.isSrtpDisabled(); + final boolean srtp = !isSrtpDisabled(); int srtpProtectionProfile = 0; TlsContext tlsContext = null; @@ -1072,11 +1140,12 @@ public void sendApplicationData(byte[] buf, int off, int len) * @param connector the RTPConnector which is to use or uses this * PacketTransformer */ - void setConnector(AbstractRTPConnector connector) + private void setConnector(AbstractRTPConnector connector) { if (this.connector != connector) { AbstractRTPConnector oldValue = this.connector; + this.connector = connector; DatagramTransportImpl datagramTransport = this.datagramTransport; @@ -1096,7 +1165,7 @@ void setConnector(AbstractRTPConnector connector) * @param mediaType the MediaType of the stream which this instance * is to work for/be associated with */ - synchronized void setMediaType(MediaType mediaType) + private synchronized void setMediaType(MediaType mediaType) { if (this.mediaType != mediaType) { @@ -1113,28 +1182,15 @@ synchronized void setMediaType(MediaType mediaType) /** * Enables/disables rtcp-mux. - * @param rtcpmux whether to enable or disable. + * + * @param rtcpmux {@code true} to enable rtcp-mux or {@code false} to + * disable it. */ void setRtcpmux(boolean rtcpmux) { this.rtcpmux = rtcpmux; } - /** - * Sets the DTLS protocol according to which this - * DtlsPacketTransformer is to act either as a DTLS server or a - * DTLS client. - * - * @param setup the value of the setup SDP attribute to set on this - * instance in order to determine whether this instance is to act as a DTLS - * client or a DTLS server - */ - void setSetup(DtlsControl.Setup setup) - { - if (this.setup != setup) - this.setup = setup; - } - /** * Starts this PacketTransformer. */ @@ -1167,7 +1223,7 @@ private synchronized void start() if (connector == null) throw new NullPointerException("connector"); - DtlsControl.Setup setup = this.setup; + DtlsControl.Setup setup = getProperties().getSetup(); SecureRandom secureRandom = DtlsControlImpl.createSecureRandom(); final DTLSProtocol dtlsProtocolObj; final TlsPeer tlsPeer; @@ -1402,7 +1458,7 @@ private List transformNonDtls( boolean transform, List outPkts) { - /* Pure/non-SRTP DTLS */ if (transformEngine.isSrtpDisabled()) + /* Pure/non-SRTP DTLS */ if (isSrtpDisabled()) { // (1) In the incoming/reverseTransform direction, only DTLS records // pass through. diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java index 9c91e0d35..ec30b6ae4 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/DtlsTransformEngine.java @@ -29,11 +29,6 @@ public class DtlsTransformEngine implements SrtpControl.TransformEngine { - /** - * The RTPConnector which uses this TransformEngine. - */ - private AbstractRTPConnector connector; - /** * The indicator which determines whether * {@link SrtpControl.TransformEngine#cleanup()} has been invoked on this @@ -46,12 +41,6 @@ public class DtlsTransformEngine */ private final DtlsControlImpl dtlsControl; - /** - * The MediaType of the stream which this instance works for/is - * associated with. - */ - private MediaType mediaType; - /** * The PacketTransformers of this TransformEngine for * data/RTP and control/RTCP packets. @@ -59,23 +48,6 @@ public class DtlsTransformEngine private final DtlsPacketTransformer[] packetTransformers = new DtlsPacketTransformer[2]; - /** - * Whether rtcp-mux is in use. - * - * When enabled, the DtlsPacketTransformer will, instead of - * establishing a DTLS session, wait for the transformer for RTP to - * establish one, and reuse it to initialize its SRTP transformer. - */ - private boolean rtcpmux = false; - - /** - * The value of the setup SDP attribute defined by RFC 4145 - * "TCP-Based Media Transport in the Session Description Protocol - * (SDP)" which determines whether this instance acts as a DTLS client - * or a DTLS server. - */ - private DtlsControl.Setup setup; - /** * Initializes a new DtlsTransformEngine instance. */ @@ -92,13 +64,6 @@ public void cleanup() { disposed = true; - /* - * SrtpControl.start(MediaType) starts its associated TransformEngine. - * We will use that mediaType to signal the normal stop then as well - * i.e. we will call setMediaType(null) first. - */ - setMediaType(null); - for (int i = 0; i < packetTransformers.length; i++) { DtlsPacketTransformer packetTransformer = packetTransformers[i]; @@ -109,29 +74,21 @@ public void cleanup() packetTransformers[i] = null; } } - - setConnector(null); } /** * Initializes a new DtlsPacketTransformer instance which is to - * work on control/RTCP or data/RTP packets. + * work on control/RTCP or data/RTP packets. The method is implemented as a + * factory. * * @param componentID the ID of the component for which the new instance is * to work * @return a new DtlsPacketTransformer instance which is to work on * control/RTCP or data/RTP packets (in accord with data) */ - private DtlsPacketTransformer createPacketTransformer(int componentID) + protected DtlsPacketTransformer createPacketTransformer(int componentID) { - DtlsPacketTransformer packetTransformer - = new DtlsPacketTransformer(this, componentID); - - packetTransformer.setConnector(connector); - packetTransformer.setSetup(setup); - packetTransformer.setRtcpmux(rtcpmux); - packetTransformer.setMediaType(mediaType); - return packetTransformer; + return new DtlsPacketTransformer(this, componentID); } /** @@ -168,122 +125,32 @@ private DtlsPacketTransformer getPacketTransformer(int componentID) } /** - * {@inheritDoc} - */ - public PacketTransformer getRTCPTransformer() - { - return getPacketTransformer(Component.RTCP); - } - - /** - * {@inheritDoc} - */ - public PacketTransformer getRTPTransformer() - { - return getPacketTransformer(Component.RTP); - } - - /** - * Indicates if SRTP extensions should be disabled which means we are - * currently working in pure DTLS mode. - * @return true if SRTP extensions should be disabled. - */ - boolean isSrtpDisabled() - { - return dtlsControl.isSrtpDisabled(); - } - - /** - * Sets the RTPConnector which is to use or uses this - * TransformEngine. + * Gets the properties of {@code DtlsControlImpl} and their values which + * {@link #dtlsControl} shares with this instance and + * {@link DtlsPacketTransformer}. * - * @param connector the RTPConnector which is to use or uses this - * TransformEngine + * @return the properties of {@code DtlsControlImpl} and their values which + * {@code dtlsControl} shares with this instance and + * {@code DtlsPacketTransformer} */ - void setConnector(AbstractRTPConnector connector) + Properties getProperties() { - if (this.connector != connector) - { - this.connector = connector; - - for (DtlsPacketTransformer packetTransformer : packetTransformers) - { - if (packetTransformer != null) - packetTransformer.setConnector(this.connector); - } - } - } - - /** - * Sets the MediaType of the stream which this instance is to work - * for/be associated with. - * - * @param mediaType the MediaType 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); - } - } - } - - /** - * Enables/disables rtcp-mux. - * @param rtcpmux whether to enable or disable. - */ - void setRtcpmux(boolean rtcpmux) - { - if (this.rtcpmux != rtcpmux) - { - this.rtcpmux = rtcpmux; - - for (DtlsPacketTransformer packetTransformer : packetTransformers) - { - if (packetTransformer != null) - packetTransformer.setRtcpmux(rtcpmux); - } - } + return getDtlsControl().getProperties(); } /** - * Sets the DTLS protocol according to which this - * DtlsTransformEngine is to act either as a DTLS server or a DTLS - * client. - * - * @param setup the value of the setup SDP attribute to set on this - * instance in order to determine whether this instance is to act as a DTLS - * client or a DTLS server + * {@inheritDoc} */ - void setSetup(DtlsControl.Setup setup) + public PacketTransformer getRTCPTransformer() { - if (this.setup != setup) - { - this.setup = setup; - - for (DtlsPacketTransformer packetTransformer : packetTransformers) - { - if (packetTransformer != null) - packetTransformer.setSetup(this.setup); - } - } + return getPacketTransformer(Component.RTCP); } /** - * Starts this instance in the sense that it becomes fully operational. - * - * @param mediaType the MediaType of the stream which this instance - * is to work for/be associated with + * {@inheritDoc} */ - void start(MediaType mediaType) + public PacketTransformer getRTPTransformer() { - setMediaType(mediaType); + return getPacketTransformer(Component.RTP); } } diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/Properties.java b/src/org/jitsi/impl/neomedia/transform/dtls/Properties.java new file mode 100644 index 000000000..31fc29006 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/transform/dtls/Properties.java @@ -0,0 +1,139 @@ +/* + * Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jitsi.impl.neomedia.transform.dtls; + +import java.util.*; +import java.util.concurrent.*; + +import org.jitsi.service.neomedia.*; +import org.jitsi.util.event.*; + +/** + * Gathers properties of {@link DtlsControlImpl} which it shares with + * {@link DtlsTransformEngine} and {@link DtlsPacketTransformer} i.e. assigning + * a value to a {@code DtlsControlImpl} property triggers assignments to the + * respective properties of {@code DtlsTransformEngine} and + * {@code DtlsPacketTransfomer}. + * + * @author Lyubomir Marinov + */ +class Properties + extends PropertyChangeNotifier +{ + /** + * The RTPConnector which uses the TransformEngine of this + * SrtpControl. + */ + public static final String CONNECTOR_PNAME + = Properties.class.getName() + ".connector"; + + public static final String MEDIA_TYPE_PNAME + = Properties.class.getName() + ".mediaType"; + + /** + * Whether rtcp-mux is in use. + */ + public static final String RTCPMUX_PNAME + = Properties.class.getName() + ".rtcpmux"; + + /** + * The value of the setup SDP attribute defined by RFC 4145 + * "TCP-Based Media Transport in the Session Description Protocol + * (SDP)" which determines whether this instance acts as a DTLS client + * or a DTLS server. + */ + public static final String SETUP_PNAME + = Properties.class.getName() + ".setup"; + + /** + * The actual {@code Map} of property names to property values represented + * by this instance. Stores only assignable properties i.e. {@code final}s + * are explicitly defined (e.g. {@link #srtpDisabled}). + */ + private final Map properties = new ConcurrentHashMap<>(); + + /** + * Indicates whether this DtlsControl will work in DTLS/SRTP or + * pure DTLS mode. + */ + private final boolean srtpDisabled; + + /** + * Initializes a new {@code Properties} instance. + * + * @param srtpDisabled {@code true} to specify pure DTLS without SRTP + * extensions or {@code false} to specify DTLS/SRTP. + */ + public Properties(boolean srtpDisabled) + { + this.srtpDisabled = srtpDisabled; + } + + /** + * Gets the value of the property with a specific name. + * + * @param name the name of the property to get the value of + * @return the value of the property with the specified {@code name} + */ + public Object get(String name) + { + return properties.get(name); + } + + /** + * Gets the value of the setup SDP attribute defined by RFC 4145 + * "TCP-Based Media Transport in the Session Description Protocol + * (SDP)" which determines whether this instance acts as a DTLS client + * or a DTLS server. + * + * @return the value of the setup SDP attribute defined by RFC 4145 + * "TCP-Based Media Transport in the Session Description Protocol + * (SDP)" which determines whether this instance acts as a DTLS client + * or a DTLS server. + */ + public DtlsControl.Setup getSetup() + { + return (DtlsControl.Setup) get(SETUP_PNAME); + } + + /** + * Indicates if SRTP extensions are disabled which means we're working in + * pure DTLS mode. + * + * @return true if SRTP extensions must be disabled. + */ + public boolean isSrtpDisabled() + { + return srtpDisabled; + } + + /** + * Sets the value of the property with a specific name. + * + * @param name the name of the property to set the value of + * @param value the value to set on the property with the specified + * {@code name} + */ + public void put(String name, Object value) + { + Object oldValue = properties.put(name, value); + boolean equals + = (oldValue == null) ? (value == null) : oldValue.equals(value); + + if (!equals) + firePropertyChange(name, oldValue, value); + } +} diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java index 8d6ba07b0..fdb26f716 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsClientImpl.java @@ -127,8 +127,8 @@ public Hashtable getClientExtensions() { Hashtable clientExtensions = super.getClientExtensions(); - if (!getDtlsControl().isSrtpDisabled() && - TlsSRTPUtils.getUseSRTPExtension(clientExtensions) == null) + if (!isSrtpDisabled() + && TlsSRTPUtils.getUseSRTPExtension(clientExtensions) == null) { if (clientExtensions == null) clientExtensions = new Hashtable(); @@ -188,6 +188,11 @@ public ProtocolVersion getMinimumVersion() return ProtocolVersion.DTLSv10; } + private Properties getProperties() + { + return packetTransformer.getProperties(); + } + /** * {@inheritDoc} * @@ -203,6 +208,18 @@ public void init(TlsClientContext context) super.init(context); } + /** + * Determines whether this {@code TlsClientImpl} is to operate in pure DTLS + * mode without SRTP extensions or in DTLS/SRTP mode. + * + * @return {@code true} for pure DTLS without SRTP extensions or + * {@code false} for DTLS/SRTP + */ + private boolean isSrtpDisabled() + { + return getProperties().isSrtpDisabled(); + } + /** * {@inheritDoc} * @@ -231,7 +248,7 @@ public void notifyAlertRaised( public void processServerExtensions(Hashtable serverExtensions) throws IOException { - if (getDtlsControl().isSrtpDisabled()) + if (isSrtpDisabled()) { super.processServerExtensions(serverExtensions); return; @@ -320,17 +337,16 @@ public TlsCredentials getClientCredentials( { if (clientCredentials == null) { - DtlsControlImpl dtlsControl = getDtlsControl(); + CertificateInfo certificateInfo + = getDtlsControl().getCertificateInfo(); - /* - * FIXME The signature and hash algorithms should be retrieved - * from the certificate. - */ + // FIXME The signature and hash algorithms should be retrieved + // from the certificate. clientCredentials = new DefaultTlsSignerCredentials( context, - dtlsControl.getCertificate(), - dtlsControl.getKeyPair().getPrivate(), + certificateInfo.getCertificate(), + certificateInfo.getKeyPair().getPrivate(), new SignatureAndHashAlgorithm( HashAlgorithm.sha1, SignatureAlgorithm.rsa)); diff --git a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java index 89f0078b7..5f10c1454 100644 --- a/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/dtls/TlsServerImpl.java @@ -183,6 +183,11 @@ protected ProtocolVersion getMinimumVersion() return ProtocolVersion.DTLSv10; } + private Properties getProperties() + { + return packetTransformer.getProperties(); + } + /** * {@inheritDoc} * @@ -197,13 +202,14 @@ protected TlsEncryptionCredentials getRSAEncryptionCredentials() { if (rsaEncryptionCredentials == null) { - DtlsControlImpl dtlsControl = getDtlsControl(); + CertificateInfo certificateInfo + = getDtlsControl().getCertificateInfo(); rsaEncryptionCredentials = new DefaultTlsEncryptionCredentials( context, - dtlsControl.getCertificate(), - dtlsControl.getKeyPair().getPrivate()); + certificateInfo.getCertificate(), + certificateInfo.getKeyPair().getPrivate()); } return rsaEncryptionCredentials; } @@ -222,17 +228,16 @@ protected TlsSignerCredentials getRSASignerCredentials() { if (rsaSignerCredentials == null) { - DtlsControlImpl dtlsControl = getDtlsControl(); + CertificateInfo certificateInfo + = getDtlsControl().getCertificateInfo(); - /* - * FIXME The signature and hash algorithms should be retrieved from - * the certificate. - */ + // FIXME The signature and hash algorithms should be retrieved from + // the certificate. rsaSignerCredentials = new DefaultTlsSignerCredentials( context, - dtlsControl.getCertificate(), - dtlsControl.getKeyPair().getPrivate(), + certificateInfo.getCertificate(), + certificateInfo.getKeyPair().getPrivate(), new SignatureAndHashAlgorithm( HashAlgorithm.sha1, SignatureAlgorithm.rsa)); @@ -253,7 +258,7 @@ public Hashtable getServerExtensions() { Hashtable serverExtensions = getServerExtensionsOverride(); - if (getDtlsControl().isSrtpDisabled()) + if (isSrtpDisabled()) { return serverExtensions; } @@ -284,12 +289,10 @@ public Hashtable getServerExtensions() } 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. - */ + // 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( @@ -314,35 +317,34 @@ public Hashtable getServerExtensions() private Hashtable getServerExtensionsOverride() throws IOException { - if (this.encryptThenMACOffered && allowEncryptThenMAC()) + if (encryptThenMACOffered && allowEncryptThenMAC()) { - /* - * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an - * encrypt-then-MAC request extension from a client and then selects - * a stream or AEAD cipher suite, it MUST NOT send an - * encrypt-then-MAC response extension back to the client. - */ - if (TlsUtils.isBlockCipherSuite(this.selectedCipherSuite)) + // draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an + // encrypt-then-MAC request extension from a client and then selects + // a stream or AEAD cipher suite, it MUST NOT send an + // encrypt-then-MAC response extension back to the client. + if (TlsUtils.isBlockCipherSuite(selectedCipherSuite)) { TlsExtensionsUtils.addEncryptThenMACExtension( - checkServerExtensions()); + checkServerExtensions()); } } - if (this.maxFragmentLengthOffered >= 0 - && MaxFragmentLength.isValid(maxFragmentLengthOffered)) + if (maxFragmentLengthOffered >= 0 + && MaxFragmentLength.isValid(maxFragmentLengthOffered)) { TlsExtensionsUtils.addMaxFragmentLengthExtension( - checkServerExtensions(), this.maxFragmentLengthOffered); + checkServerExtensions(), + maxFragmentLengthOffered); } - if (this.truncatedHMacOffered && allowTruncatedHMac()) + if (truncatedHMacOffered && allowTruncatedHMac()) { TlsExtensionsUtils.addTruncatedHMacExtension( - checkServerExtensions()); + checkServerExtensions()); } - if (TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) + if (TlsECCUtils.isECCCipherSuite(selectedCipherSuite)) { /* * RFC 4492 5.2. A server that selects an ECC cipher suite in @@ -351,13 +353,17 @@ private Hashtable getServerExtensionsOverride() * its ServerHello message, enumerating the point formats it can * parse. */ - this.serverECPointFormats = new short[]{ - ECPointFormat.uncompressed, - ECPointFormat.ansiX962_compressed_prime, - ECPointFormat.ansiX962_compressed_char2, }; + serverECPointFormats + = new short[] + { + ECPointFormat.uncompressed, + ECPointFormat.ansiX962_compressed_prime, + ECPointFormat.ansiX962_compressed_char2, + }; TlsECCUtils.addSupportedPointFormatsExtension( - checkServerExtensions(), serverECPointFormats); + checkServerExtensions(), + serverECPointFormats); } return serverExtensions; @@ -378,6 +384,18 @@ public void init(TlsServerContext context) super.init(context); } + /** + * Determines whether this {@code TlsServerImpl} is to operate in pure DTLS + * mode without SRTP extensions or in DTLS/SRTP mode. + * + * @return {@code true} for pure DTLS without SRTP extensions or + * {@code false} for DTLS/SRTP + */ + private boolean isSrtpDisabled() + { + return getProperties().isSrtpDisabled(); + } + /** * {@inheritDoc} * @@ -429,7 +447,7 @@ public void notifyClientCertificate(Certificate clientCertificate) public void processClientExtensions(Hashtable clientExtensions) throws IOException { - if (getDtlsControl().isSrtpDisabled()) + if (isSrtpDisabled()) { super.processClientExtensions(clientExtensions); return; @@ -454,10 +472,8 @@ public void processClientExtensions(Hashtable clientExtensions) = DtlsControlImpl.chooseSRTPProtectionProfile( useSRTPData.getProtectionProfiles()); - /* - * If there is no shared profile and that is not acceptable, the - * server SHOULD return an appropriate DTLS alert. - */ + // If there is no shared profile and that is not acceptable, the + // server SHOULD return an appropriate DTLS alert. if (chosenProtectionProfile == 0) { String msg = "No chosen SRTP protection profile!"; @@ -468,7 +484,9 @@ public void processClientExtensions(Hashtable clientExtensions) throw tfa; } else + { super.processClientExtensions(clientExtensions); + } } } } diff --git a/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java index 1df9d6465..d38bdd5f6 100644 --- a/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java +++ b/src/org/jitsi/impl/neomedia/transform/zrtp/ZrtpControlImpl.java @@ -65,12 +65,12 @@ public ZrtpControlImpl() } /** - * Cleans up the current zrtp control and its engine. + * {@inheritDoc} */ @Override - public void cleanup(Object user) + public void doCleanup() { - super.cleanup(user); + super.doCleanup(); zrtpConnector = null; } @@ -257,9 +257,9 @@ public boolean isSecurityVerified() } /** - * Returns false, ZRTP exchanges is keys over the media path. + * Returns {@code false}, ZRTP exchanges its keys over the media path. * - * @return false + * @return {@code false} */ public boolean requiresSecureSignalingTransport() { diff --git a/src/org/jitsi/service/neomedia/AbstractSrtpControl.java b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java index b62a50535..150df4580 100644 --- a/src/org/jitsi/service/neomedia/AbstractSrtpControl.java +++ b/src/org/jitsi/service/neomedia/AbstractSrtpControl.java @@ -17,6 +17,8 @@ import org.jitsi.service.neomedia.event.*; +import java.util.*; + /** * Provides an abstract, base implementation of {@link SrtpControl} to * facilitate implementers. @@ -36,6 +38,12 @@ public abstract class AbstractSrtpControl protected T transformEngine; + /** + * The {@code Object}s currently registered as users of this + * {@code SrtpControl} (through {@link #registerUser(Object)}). + */ + private final Set users = new HashSet<>(); + /** * Initializes a new AbstractSrtpControl instance with a specific * SrtpControlType. @@ -53,16 +61,16 @@ protected AbstractSrtpControl(SrtpControlType srtpControlType) /** * {@inheritDoc} * - * The implementation of AbstractSrtpControl cleans up its - * associated TransformEngine (if any). + * Invokes {@link #doCleanup()} if there are no more users (advertised via + * {@link #registerUser(Object)}) of this {@code SrtpControl}. */ @Override public void cleanup(Object user) { - if (transformEngine != null) + synchronized (users) { - transformEngine.cleanup(); - transformEngine = null; + if (users.remove(user) && users.isEmpty()) + doCleanup(); } } @@ -75,6 +83,18 @@ public void cleanup(Object user) */ protected abstract T createTransformEngine(); + /** + * Prepares this {@code SrtpControl} for garbage collection. + */ + protected void doCleanup() + { + if (transformEngine != null) + { + transformEngine.cleanup(); + transformEngine = null; + } + } + /** * {@inheritDoc} */ @@ -134,5 +154,9 @@ public void setSrtpListener(SrtpListener srtpListener) */ public void registerUser(Object user) { + synchronized (users) + { + users.add(user); + } } } diff --git a/src/org/jitsi/service/neomedia/SrtpControl.java b/src/org/jitsi/service/neomedia/SrtpControl.java index 7d0dbd16c..859517515 100644 --- a/src/org/jitsi/service/neomedia/SrtpControl.java +++ b/src/org/jitsi/service/neomedia/SrtpControl.java @@ -48,7 +48,10 @@ public interface TransformEngine /** * Cleans up this SrtpControl and its TransformEngine. - * @param user the instance which requests the clean up. + * + * @param user the {@Object} which requests the clean-up and is supposedly + * currently using this {@code SrtpControl} (i.e. has already used + * {@link #registerUser(Object)}). */ public void cleanup(Object user); @@ -133,7 +136,9 @@ public interface TransformEngine /** * Registers user as an instance which is currently using this * SrtpControl. - * @param user + * + * @param user the {@code Object} which is currently using this + * {@code SrtpControl} */ public void registerUser(Object user); }