def test_rtp_any_ssrc(self): # protect RTP tx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_ANY_OUTBOUND)) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect(4567) self.assertEqual(str(cm.exception), 'packet must be bytes') # bad length with self.assertRaises(ValueError) as cm: tx_session.protect(b'0' * 1500) self.assertEqual(str(cm.exception), 'packet is too long') # unprotect RTP rx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_ANY_INBOUND)) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP)
def test_add_remove_stream(self): # protect RTP tx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # add stream and unprotect RTP rx_session = Session() rx_session.add_stream(Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP) # remove stream rx_session.remove_stream(12345) # try removing stream again with self.assertRaises(Error) as cm: rx_session.remove_stream(12345) self.assertEqual(str(cm.exception), 'no appropriate context found')
def test_rtp_specific_ssrc(self): # protect RTP tx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # unprotect RTP rx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP)
async def connect(self): assert self.state == self.State.CLOSED self._set_state(self.State.CONNECTING) while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: raise DtlsError('DTLS handshake failed (error %d)' % error) # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) if remote_fingerprint != self.remote_fingerprint.upper(): raise DtlsError('DTLS fingerprint does not match') # generate keying material buf = ffi.new('unsigned char[]', 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self.is_server: srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) self._tx_srtp = Session(tx_policy) # start data pump logger.debug('%s - DTLS handshake complete', self.role) self._set_state(self.State.CONNECTED) asyncio.ensure_future(self.__run())
async def connect(self): while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) await self._write_ssl() if error == lib.SSL_ERROR_WANT_READ: data = await self.transport.recv() lib.BIO_write(self.read_bio, data, len(data)) else: raise Exception('DTLS handshake failed (error %d)' % error) await self._write_ssl() # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) if remote_fingerprint != self.remote_fingerprint.upper(): raise Exception('DTLS fingerprint does not match') # generate keying material buf = ffi.new("char[]", 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' if not lib.SSL_export_keying_material(self.ssl, buf, len(buf), extractor, len(extractor), ffi.NULL, 0, 0): raise Exception('DTLS could not extract SRTP keying material') view = ffi.buffer(buf) if self.is_server: srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) logger.info('DTLS handshake complete') rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) self._tx_srtp = Session(tx_policy)
def test_rtcp_any_ssrc(self): # protect RCTP tx_session = Session( policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_OUTBOUND)) protected = tx_session.protect_rtcp(RTCP) self.assertEqual(len(protected), 42) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect_rtcp(4567) self.assertEqual(str(cm.exception), "packet must be bytes") # bad length with self.assertRaises(ValueError) as cm: tx_session.protect_rtcp(b"0" * 1500) self.assertEqual(str(cm.exception), "packet is too long") # unprotect RTCP rx_session = Session( policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_INBOUND)) unprotected = rx_session.unprotect_rtcp(protected) self.assertEqual(len(unprotected), 28) self.assertEqual(unprotected, RTCP)
def test_key(self): policy = Policy() self.assertEqual(policy.key, None) policy.key = KEY self.assertEqual(policy.key, KEY) policy.key = None self.assertEqual(policy.key, None) with self.assertRaises(TypeError) as cm: policy.key = 1234 self.assertEqual(policy.key, None) self.assertEqual(str(cm.exception), 'key must be bytes')
def test_allow_repeat_tx(self): policy = Policy() self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = True self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = False self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = 1 self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = 0 self.assertEqual(policy.allow_repeat_tx, False)
def record_rtp_packet(self, packet, is_last=False): if not packet: print("No packet") return False if self.verbose: print("Dumping packet ", packet) rtp_raw_full = None try: rtp_raw_full = bytes.fromhex(''.join(packet[self.rtp_offset:])) except: print("Failed to get hex", packet) return False if len(rtp_raw_full) < RTP_FIXED_HEADER: print("udp payload is too small") return False if self.verbose: print(self.rtp_offset, packet[self.rtp_offset - 1], packet[self.rtp_offset], packet[self.rtp_offset + 1]) # decode RTP Header rtp_raw_header = rtp_raw_full[:RTP_FIXED_HEADER] if self.verbose: print(rtp_raw_header.hex()) rtp = RTP_HEADER._make(struct.unpack(RTP_HEADER_FMT, rtp_raw_header)) if self.verbose: print(rtp) if rtp.PAYLOAD_TYPE & 0b10000000: # strip marker bit from payload rtp = RTP_HEADER(FIRST=rtp.FIRST, PAYLOAD_TYPE=rtp.PAYLOAD_TYPE & 0b01111111, SEQUENCE_NUMBER=rtp.SEQUENCE_NUMBER, TIMESTAMP=rtp.TIMESTAMP, SSRC=rtp.SSRC) print('stripped marker bit', rtp) # Filter the RTP with v=2 if (rtp.FIRST & 0b11000000) != 0b10000000: print("Not an RTP") return False rtp_exten = rtp.FIRST & 0b10000 rtp_exten_length = 0 rtp_csrc = rtp.FIRST & 0b1111 # calculate rtp header length calc_rtp_header_len = RTP_FIXED_HEADER + rtp_csrc * 4 if rtp_exten: exten_start = RTP_FIXED_HEADER + rtp_csrc * 4 exten_raw = rtp_raw_full[exten_start:exten_start + 4] if len(exten_raw) != 4: print("Skipping malformed RTP") return False rtp_exten_profile, rtp_exten_length = struct.unpack( '>HH', exten_raw) calc_rtp_header_len += 4 + rtp_exten_length * 4 if self.verbose: print("calc_rtp_header_len", calc_rtp_header_len) if self.override_payload_offset: calc_rtp_header_len = self.override_payload_offset - self.rtp_offset # Filter opus if self.payload_type and rtp.PAYLOAD_TYPE != self.payload_type: print( "Skipping payload {rtp_payload_type} while {opus_payload_type} expected." .format(rtp_payload_type=rtp.PAYLOAD_TYPE, opus_payload_type=self.payload_type)) return False if self.filter_ssrc and self.filter_ssrc != rtp.SSRC: print( "Skipping ssrc={rtp_ssrc} while {filter_ssrc} expected".format( rtp_ssrc=rtp.SSRC, filter_ssrc=self.filter_ssrc)) return False if len(rtp_raw_full) < (calc_rtp_header_len + 1): print("Empty payload") return False if self.srtpkey: if self.ssrc != rtp.SSRC: #if not self.srtp_session: # self.srtp_session = Session() self.srtp_session = Session() print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy(key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_INBOUND) print(plc) self.srtp_session.add_stream(plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.unprotect(rtp_raw_full) except: print("decrypt fail seq={sequence}, ssrc={ssrc}".format( sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) ''' if self.resrtpkey: srtpkey = base64.b64decode(self.resrtpkey) plc = Policy(key=srtpkey,ssrc_value=rtp.SSRC,ssrc_type=Policy.SSRC_ANY_INBOUND) self.srtp_session.add_stream(plc) print("Using restrpkey here from next packet") ''' return False self.write_udp( bytes.fromhex(''.join(packet[:self.rtp_offset])) + rtp_raw_full) return True
def playback_as_srtp_stream(self, infile, outfile, starting_sequence=0): ''' Read an ogg file and create rtp/srtp in hex form ''' self.ogg.reset(open(infile, 'rb')) with open(outfile, 'w') as hex_fd: page_counter = 0 sequence_counter = starting_sequence for header, content in self.ogg: print(header) if 0 == page_counter: opus_identity_head = OPUS_IDENTITY_HEADER._make( struct.unpack(OPUS_IDENTITY_HEADER_FMT, content)) print(opus_identity_head) #elif 1 == page_counter: # opus_comment_head = OPUS_COMMENT_HEADER._make(struct.unpack(OPUS_COMMENT_HEADER_FMT, content)) # print(opus_comment_head) else: print(' '.join([hex(x) for x in content])) if page_counter > 1: # show the TOC byte #toc = content[0] #config = (toc >> 3) #s = toc & 0b100 #s = s >> 2 #num_frames = toc & 0b11 #print("config %d, s %d, c/frames %d" % (config, s, num_frames)) # make an RTP packet rtp = RTP_HEADER(0x80, self.payload_type, sequence_counter, header.GRANULE_POS, header.BITSTREAM) rtp_raw_full = struct.pack(RTP_HEADER_FMT, * rtp) + content if self.srtpkey: if self.ssrc != rtp.SSRC: print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy( key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_OUTBOUND) print(plc) self.srtp_session = Session(policy=plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.protect( rtp_raw_full) except: print( "encrypt fail seq={sequence}, ssrc={ssrc}". format(sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) hex_fd.write(self.hexdump(rtp_raw_full)) if sequence_counter == MAX_RTP_SEQUENCE_NUM: sequence_counter = 0 else: sequence_counter += 1 page_counter += 1 print("Read %d pages" % page_counter)
def record_rtp_packet(self, packet, is_last=False): assert self.ogg is not None if not packet: print("No packet") return False if self.verbose: print("Dumping packet ", packet) rtp_raw_full = None try: rtp_raw_full = bytes.fromhex(''.join(packet[self.rtp_offset:])) except: print("Failed to get hex", packet) return False if len(rtp_raw_full) < RTP_FIXED_HEADER: print("udp payload is too small") return False if self.verbose: print(self.rtp_offset, packet[self.rtp_offset - 1], packet[self.rtp_offset], packet[self.rtp_offset + 1]) # decode RTP Header rtp_raw_header = rtp_raw_full[:RTP_FIXED_HEADER] if self.verbose: print(rtp_raw_header.hex()) rtp = RTP_HEADER._make(struct.unpack(RTP_HEADER_FMT, rtp_raw_header)) if self.verbose: print(rtp) # Filter the RTP with v=2 if (rtp.FIRST & 0b11000000) != 0b10000000: print("Not an RTP") return False rtp_exten = rtp.FIRST & 0b10000 rtp_exten_length = 0 rtp_csrc = rtp.FIRST & 0b1111 # calculate rtp header length calc_rtp_header_len = RTP_FIXED_HEADER + rtp_csrc * 4 if rtp_exten: exten_start = RTP_FIXED_HEADER + rtp_csrc * 4 exten_raw = rtp_raw_full[exten_start:exten_start + 4] if len(exten_raw) != 4: print("Skipping malformed RTP") return False rtp_exten_profile, rtp_exten_length = struct.unpack( '>HH', exten_raw) calc_rtp_header_len += 4 + rtp_exten_length * 4 if self.verbose: print("calc_rtp_header_len", calc_rtp_header_len) if self.override_payload_offset: calc_rtp_header_len = self.override_payload_offset - self.rtp_offset # Filter opus if self.payload_type and rtp.PAYLOAD_TYPE != self.payload_type: print( "Skipping payload {rtp_payload_type} while {opus_payload_type} expected." .format(rtp_payload_type=rtp.PAYLOAD_TYPE, opus_payload_type=self.payload_type)) return False if self.filter_ssrc and self.filter_ssrc != rtp.SSRC: print( "Skipping ssrc={rtp_ssrc} while {filter_ssrc} expected".format( rtp_ssrc=rtp.SSRC, filter_ssrc=self.filter_ssrc)) return False if len(rtp_raw_full) < (calc_rtp_header_len + 1): print("Empty payload") return False if self.srtpkey: if self.ssrc != rtp.SSRC: if not self.srtp_session: self.srtp_session = Session() print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy(key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_INBOUND) print(plc) self.srtp_session.add_stream(plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.unprotect(rtp_raw_full) except: print("decrypt fail seq={sequence}, ssrc={ssrc}".format( sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) ''' if self.resrtpkey: srtpkey = base64.b64decode(self.resrtpkey) plc = Policy(key=srtpkey,ssrc_value=rtp.SSRC,ssrc_type=Policy.SSRC_ANY_INBOUND) self.srtp_session.add_stream(plc) print("Using restrpkey here from next packet") ''' return False # Add bitstream header if self.ogg.get_curr_bitstream() != rtp.SSRC: self.write_stream_header(rtp.SSRC) self.write_stream_comment('hex_to_opus', [str(rtp)]) # rtp_payload = rtp_raw_full[RTP_FIXED_HEADER:] rtp_payload = rtp_raw_full[calc_rtp_header_len:] self.ogg.write_page( rtp_payload, is_data=True, is_last=is_last, ptime=20, pageno=rtp.SEQUENCE_NUMBER) # By default the ptime=20 return True
def test_no_key(self): policy = Policy(ssrc_type=Policy.SSRC_ANY_OUTBOUND) with self.assertRaises(Error) as cm: Session(policy=policy) self.assertEqual(str(cm.exception), 'unsupported parameter')
def test_ssrc_type(self): policy = Policy() self.assertEqual(policy.ssrc_type, Policy.SSRC_UNDEFINED) policy.ssrc_type = Policy.SSRC_ANY_INBOUND self.assertEqual(policy.ssrc_type, Policy.SSRC_ANY_INBOUND)
def test_window_size(self): policy = Policy() self.assertEqual(policy.window_size, 0) policy.window_size = 1024 self.assertEqual(policy.window_size, 1024)
async def start(self, remoteParameters: RTCDtlsParameters) -> None: """ Start DTLS transport negotiation with the parameters of the remote DTLS transport. :param remoteParameters: An :class:`RTCDtlsParameters`. """ assert self._state == State.NEW assert len(remoteParameters.fingerprints) if self.transport.role == "controlling": self._role = "server" lib.SSL_set_accept_state(self.ssl) else: self._role = "client" lib.SSL_set_connect_state(self.ssl) self._set_state(State.CONNECTING) try: while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: self.__log_debug("x DTLS handshake failed (error %d)", error) for info in get_error_queue(): self.__log_debug("x %s", ":".join(info)) self._set_state(State.FAILED) return except ConnectionError: self.__log_debug("x DTLS handshake failed (connection error)") self._set_state(State.FAILED) return # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) fingerprint_is_valid = False for f in remoteParameters.fingerprints: if (f.algorithm.lower() == "sha-256" and f.value.lower() == remote_fingerprint.lower()): fingerprint_is_valid = True break if not fingerprint_is_valid: self.__log_debug("x DTLS handshake failed (fingerprint mismatch)") self._set_state(State.FAILED) return # generate keying material buf = ffi.new("unsigned char[]", 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b"EXTRACTOR-dtls_srtp" _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self._role == "server": srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_policy.allow_repeat_tx = True rx_policy.window_size = 1024 self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_policy.allow_repeat_tx = True tx_policy.window_size = 1024 self._tx_srtp = Session(tx_policy) # start data pump self.__log_debug("- DTLS handshake complete") self._set_state(State.CONNECTED) self._task = asyncio.ensure_future(self.__run())
async def start(self, remoteParameters): """ Start DTLS transport negotiation with the parameters of the remote DTLS transport. :param: remoteParameters: An :class:`RTCDtlsParameters`. """ assert self._state not in [State.CLOSED, State.FAILED] assert len(remoteParameters.fingerprints) # handle the case where start is already in progress if self._start is not None: return await self._start.wait() self._start = asyncio.Event() if self.transport.role == 'controlling': self._role = 'server' lib.SSL_set_accept_state(self.ssl) else: self._role = 'client' lib.SSL_set_connect_state(self.ssl) self._set_state(State.CONNECTING) while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: self._set_state(State.FAILED) raise DtlsError('DTLS handshake failed (error %d)' % error) # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) fingerprint_is_valid = False for f in remoteParameters.fingerprints: if f.algorithm == 'sha-256' and f.value.lower( ) == remote_fingerprint.lower(): fingerprint_is_valid = True break if not fingerprint_is_valid: self._set_state(State.FAILED) raise DtlsError('DTLS fingerprint does not match') # generate keying material buf = ffi.new('unsigned char[]', 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self._role == 'server': srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_policy.allow_repeat_tx = True rx_policy.window_size = 1024 self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_policy.allow_repeat_tx = True tx_policy.window_size = 1024 self._tx_srtp = Session(tx_policy) # start data pump self.__log_debug('- DTLS handshake complete') self._set_state(State.CONNECTED) asyncio.ensure_future(self.__run()) self._start.set()
def test_ssrc_value(self): policy = Policy() self.assertEqual(policy.ssrc_value, 0) policy.ssrc_value = 12345 self.assertEqual(policy.ssrc_value, 12345)