/// <summary> /// Hands the socket handle to the DTLS context and waits for the handshake to complete. /// </summary> /// <param name="webRtcSession">The WebRTC session to perform the DTLS handshake on.</param> private static bool DoDtlsHandshake(RTCPeerConnection pc, SIPSorceryMedia.DtlsHandshake dtls, bool isClient, byte[] sdpFingerprint) { logger.LogDebug("DoDtlsHandshake started."); if (!File.Exists(DTLS_CERTIFICATE_PATH)) { throw new ApplicationException($"The DTLS certificate file could not be found at {DTLS_CERTIFICATE_PATH}."); } else if (!File.Exists(DTLS_KEY_PATH)) { throw new ApplicationException($"The DTLS key file could not be found at {DTLS_KEY_PATH}."); } int res = 0; bool fingerprintMatch = false; if (isClient) { logger.LogDebug($"DTLS client handshake starting to {pc.AudioDestinationEndPoint}."); // For the DTLS handshake to work connect must be called on the socket so openssl knows where to send. var rtpSocket = pc.GetRtpChannel(SDPMediaTypesEnum.audio).RtpSocket; rtpSocket.Connect(pc.AudioDestinationEndPoint); byte[] fingerprint = null; var peerEP = pc.AudioDestinationEndPoint; res = dtls.DoHandshakeAsClient((ulong)rtpSocket.Handle, (short)peerEP.AddressFamily.GetHashCode(), peerEP.Address.GetAddressBytes(), (ushort)peerEP.Port, ref fingerprint); if (fingerprint != null) { logger.LogDebug($"DTLS server fingerprint {ByteBufferInfo.HexStr(fingerprint)}."); fingerprintMatch = sdpFingerprint.SequenceEqual(fingerprint); } } else { byte[] fingerprint = null; res = dtls.DoHandshakeAsServer((ulong)pc.GetRtpChannel(SDPMediaTypesEnum.audio).RtpSocket.Handle, ref fingerprint); if (fingerprint != null) { logger.LogDebug($"DTLS client fingerprint {ByteBufferInfo.HexStr(fingerprint)}."); fingerprintMatch = sdpFingerprint.SequenceEqual(fingerprint); } } logger.LogDebug("DtlsContext initialisation result=" + res); if (dtls.IsHandshakeComplete()) { logger.LogDebug("DTLS negotiation complete."); if (!fingerprintMatch) { logger.LogWarning("DTLS fingerprint mismatch."); return(false); } else { var srtpSendContext = new SIPSorceryMedia.Srtp(dtls, isClient); var srtpReceiveContext = new SIPSorceryMedia.Srtp(dtls, !isClient); pc.SetSecurityContext( srtpSendContext.ProtectRTP, srtpReceiveContext.UnprotectRTP, srtpSendContext.ProtectRTCP, srtpReceiveContext.UnprotectRTCP); return(true); } } else { return(false); } }
private static RTCPeerConnection Createpc(WebSocketContext context) { RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = new List <RTCCertificate> { new RTCCertificate { X_CertificatePath = DTLS_CERTIFICATE_PATH, X_KeyPath = DTLS_KEY_PATH, X_Fingerprint = DTLS_CERTIFICATE_FINGERPRINT } }, X_RemoteSignallingAddress = context.UserEndPoint.Address, iceServers = new List <RTCIceServer> { new RTCIceServer { urls = SIPSORCERY_STUN_SERVER, username = SIPSORCERY_STUN_SERVER_USERNAME, credential = SIPSORCERY_STUN_SERVER_PASSWORD, credentialType = RTCIceCredentialType.password } }, iceTransportPolicy = RTCIceTransportPolicy.relay }; var pc = new RTCPeerConnection(pcConfiguration); #if DTLS_IS_ENABLED SIPSorceryMedia.DtlsHandshake dtls = new SIPSorceryMedia.DtlsHandshake(DTLS_CERTIFICATE_PATH, DTLS_KEY_PATH); dtls.Debug = true; #endif // Add inactive audio and video tracks. MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.RecvOnly); pc.addTrack(audioTrack); MediaStreamTrack videoTrack = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }, MediaStreamStatusEnum.Inactive); pc.addTrack(videoTrack); pc.onicecandidateerror += (candidate, error) => logger.LogWarning($"Error adding remote ICE candidate. {error} {candidate}"); pc.onconnectionstatechange += (state) => logger.LogDebug($"Peer connection state change to {state}."); pc.OnReceiveReport += (type, rtcp) => logger.LogDebug($"RTCP {type} report received."); pc.OnRtcpBye += (reason) => logger.LogDebug($"RTCP BYE receive, reason: {(string.IsNullOrWhiteSpace(reason) ? "<none>" : reason)}."); pc.onicecandidate += (candidate) => { if (pc.signalingState == RTCSignalingState.have_local_offer || pc.signalingState == RTCSignalingState.have_remote_offer) { context.WebSocket.Send($"candidate:{candidate}"); } }; // Peer ICE connection state changes are for ICE events such as the STUN checks completing. pc.oniceconnectionstatechange += (state) => { logger.LogDebug($"ICE connection state change to {state}."); if (state == RTCIceConnectionState.connected) { logger.LogInformation($"ICE connected to remote end point {pc.AudioDestinationEndPoint}."); if (pc.RemotePeerDtlsFingerprint == null) { logger.LogWarning("DTLS handshake cannot proceed, no fingerprint was available for the remote peer."); pc.Close("No DTLS fingerprint."); } else { #if DTLS_IS_ENABLED if (pc.IceRole == IceRolesEnum.active) { logger.LogDebug("Starting DLS handshake as client task."); _ = Task.Run(() => { bool handshakedResult = DoDtlsHandshake(pc, dtls, true, pc.RemotePeerDtlsFingerprint); logger.LogDebug($"DTLS handshake result {handshakedResult}."); }); } else { logger.LogDebug("Starting DLS handshake as server task."); _ = Task.Run(() => { bool handshakedResult = DoDtlsHandshake(pc, dtls, false, pc.RemotePeerDtlsFingerprint); logger.LogDebug($"DTLS handshake result {handshakedResult}."); }); } #endif } } }; return(pc); }