Example #1
0
        /// <summary>
        ///  DtlsHandshake requires DtlsSrtpTransport to work.
        ///  DtlsSrtpTransport is similar to C++ DTLS class combined with Srtp class and can perform
        ///  Handshake as Server or Client in same call. The constructor of transport require a DtlsStrpClient
        ///  or DtlsSrtpServer to work.
        /// </summary>
        /// <param name="dtlsHandle">The DTLS transport handle to perform the handshake with.</param>
        /// <returns></returns>
        private bool DoDtlsHandshake(DtlsSrtpTransport dtlsHandle)
        {
            logger.LogDebug("RTCPeerConnection DoDtlsHandshake started.");

            var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio);

            dtlsHandle.OnDataReady += (buf) =>
            {
                //logger.LogDebug($"DTLS transport sending {buf.Length} bytes to {AudioDestinationEndPoint}.");
                rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, AudioDestinationEndPoint, buf);
            };

            var handshakeResult = dtlsHandle.DoHandshake();

            if (!handshakeResult)
            {
                logger.LogWarning($"RTCPeerConnection DTLS handshake failed.");
                return(false);
            }
            else
            {
                logger.LogDebug($"RTCPeerConnection DTLS handshake result {handshakeResult}, is handshake complete {dtlsHandle.IsHandshakeComplete()}.");

                var expectedFp        = RemotePeerDtlsFingerprint;
                var remoteFingerprint = DtlsUtils.Fingerprint(expectedFp.algorithm, dtlsHandle.GetRemoteCertificate().GetCertificateAt(0));

                if (remoteFingerprint.value != expectedFp.value)
                {
                    logger.LogWarning($"RTCPeerConnection remote certificate fingerprint mismatch, expected {expectedFp}, actual {remoteFingerprint}.");
                    return(false);
                }
                else
                {
                    logger.LogDebug($"RTCPeerConnection remote certificate fingerprint matched expected value of {remoteFingerprint.value} for {remoteFingerprint.algorithm}.");

                    base.SetSecurityContext(
                        dtlsHandle.ProtectRTP,
                        dtlsHandle.UnprotectRTP,
                        dtlsHandle.ProtectRTCP,
                        dtlsHandle.UnprotectRTCP);

                    return(true);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Constructor to create a new RTC peer connection instance.
        /// </summary>
        /// <param name="configuration">Optional.</param>
        public RTCPeerConnection(RTCConfiguration configuration) :
            base(true, true, true, configuration?.X_BindAddress)
        {
            if (_configuration != null &&
                _configuration.iceTransportPolicy == RTCIceTransportPolicy.relay &&
                _configuration.iceServers?.Count == 0)
            {
                throw new ApplicationException("RTCPeerConnection must have at least one ICE server specified for a relay only transport policy.");
            }

            if (configuration != null)
            {
                _configuration = configuration;
                if (_configuration.certificates?.Count > 0)
                {
                    // Find the first certificate that has a usable private key.
                    RTCCertificate usableCert = null;
                    foreach (var cert in _configuration.certificates)
                    {
                        // Attempting to check that a certificate has an exportable private key.
                        // TODO: Does not seem to be a particularly reliable way of checking private key exportability.
                        if (cert.Certificate.HasPrivateKey)
                        {
                            //if (cert.Certificate.PrivateKey is RSACryptoServiceProvider)
                            //{
                            //    var rsa = cert.Certificate.PrivateKey as RSACryptoServiceProvider;
                            //    if (!rsa.CspKeyContainerInfo.Exportable)
                            //    {
                            //        logger.LogWarning($"RTCPeerConnection was passed a certificate for {cert.Certificate.FriendlyName} with a non-exportable RSA private key.");
                            //    }
                            //    else
                            //    {
                            //        usableCert = cert;
                            //        break;
                            //    }
                            //}
                            //else
                            //{
                            usableCert = cert;
                            break;
                            //}
                        }
                    }

                    if (usableCert == null)
                    {
                        throw new ApplicationException("RTCPeerConnection was not able to find a certificate from the input configuration list with a usable private key.");
                    }
                    else
                    {
                        _currentCertificate = usableCert;
                    }
                }

                if (_configuration.X_UseRtpFeedbackProfile)
                {
                    RTP_MEDIA_PROFILE = RTP_MEDIA_FEEDBACK_PROFILE;
                }
            }
            else
            {
                _configuration = new RTCConfiguration();
            }

            // No certificate was provided so create a new self signed one.
            if (_configuration.certificates == null || _configuration.certificates.Count == 0)
            {
                _currentCertificate = new RTCCertificate {
                    Certificate = DtlsUtils.CreateSelfSignedCert()
                };
                _configuration.certificates = new List <RTCCertificate> {
                    _currentCertificate
                };
            }

            SessionID         = Guid.NewGuid().ToString();
            LocalSdpSessionID = Crypto.GetRandomInt(5).ToString();

            // Request the underlying RTP session to create a single RTP channel that will
            // be used to multiplex all required media streams.
            addSingleTrack();

            _rtpIceChannel = GetRtpChannel();

            _rtpIceChannel.OnIceCandidate             += (candidate) => onicecandidate?.Invoke(candidate);
            _rtpIceChannel.OnIceConnectionStateChange += (state) =>
            {
                if (state == RTCIceConnectionState.connected && _rtpIceChannel.NominatedEntry != null)
                {
                    var connectedEP = _rtpIceChannel.NominatedEntry.RemoteCandidate.DestinationEndPoint;
                    base.SetDestination(SDPMediaTypesEnum.audio, connectedEP, connectedEP);

                    logger.LogInformation($"ICE connected to remote end point {AudioDestinationEndPoint}.");

                    DtlsSrtpTransport dtlsHandle = new DtlsSrtpTransport(
                        IceRole == IceRolesEnum.active ?
                        (IDtlsSrtpPeer) new DtlsSrtpClient(_currentCertificate.Certificate) :
                        (IDtlsSrtpPeer) new DtlsSrtpServer(_currentCertificate.Certificate));

                    OnDtlsPacket += (buf) =>
                    {
                        logger.LogDebug($"DTLS transport received {buf.Length} bytes from {AudioDestinationEndPoint}.");
                        dtlsHandle.WriteToRecvStream(buf);
                    };

                    logger.LogDebug($"Starting DLS handshake with role {IceRole}.");
                    Task.Run <bool>(() => DoDtlsHandshake(dtlsHandle))
                    .ContinueWith(t =>
                    {
                        if (t.IsFaulted)
                        {
                            logger.LogWarning($"RTCPeerConnection DTLS handshake task completed in a faulted state. {t.Exception?.Flatten().Message}");

                            connectionState = RTCPeerConnectionState.failed;
                            onconnectionstatechange?.Invoke(connectionState);
                        }
                        else
                        {
                            connectionState = (t.Result) ? RTCPeerConnectionState.connected : connectionState = RTCPeerConnectionState.failed;
                            onconnectionstatechange?.Invoke(connectionState);
                        }
                    });
                }

                iceConnectionState = state;
                oniceconnectionstatechange?.Invoke(iceConnectionState);
            };
            _rtpIceChannel.OnIceGatheringStateChange += (state) => onicegatheringstatechange?.Invoke(state);
            _rtpIceChannel.OnIceCandidateError       += (candidate, error) => onicecandidateerror?.Invoke(candidate, error);

            OnRtpClosed += Close;
            OnRtcpBye   += Close;

            onnegotiationneeded?.Invoke();

            _rtpIceChannel.StartGathering();
        }