def test_pull_certificate_verify(self): buf = Buffer(data=load("tls_certificate_verify.bin")) verify = pull_certificate_verify(buf) self.assertTrue(buf.eof()) self.assertEqual(verify.algorithm, tls.SignatureAlgorithm.RSA_PSS_RSAE_SHA256) self.assertEqual(verify.signature, CERTIFICATE_VERIFY_SIGNATURE)
def test_push_uint_var_too_big(self): buf = Buffer(capacity=8) with self.assertRaises(ValueError) as cm: buf.push_uint_var(4611686018427387904) self.assertEqual( str(cm.exception), "Integer is too big for a variable-length integer" )
def test_pull_certificate(self): buf = Buffer(data=load("tls_certificate.bin")) certificate = pull_certificate(buf) self.assertTrue(buf.eof()) self.assertEqual(certificate.request_context, b"") self.assertEqual(certificate.certificates, [(CERTIFICATE_DATA, b"")])
async def handle(self, event: Event) -> None: if isinstance(event, RawData): try: header = pull_quic_header(Buffer(data=event.data), host_cid_length=8) except ValueError: return if ( header.version is not None and header.version not in self.quic_config.supported_versions ): data = encode_quic_version_negotiation( source_cid=header.destination_cid, destination_cid=header.source_cid, supported_versions=self.quic_config.supported_versions, ) await self.send(RawData(data=data, address=event.address)) return connection = self.connections.get(header.destination_cid) if ( connection is None and len(event.data) >= 1200 and header.packet_type == PACKET_TYPE_INITIAL ): connection = QuicConnection( configuration=self.quic_config, original_connection_id=None ) self.connections[header.destination_cid] = connection self.connections[connection.host_cid] = connection if connection is not None: connection.receive_datagram(event.data, event.address, now=self.now()) await self._handle_events(connection, event.address) elif isinstance(event, Closed): pass
def encode_frame(frame_type: int, frame_data: bytes) -> bytes: frame_length = len(frame_data) buf = Buffer(capacity=frame_length + 16) buf.push_uint_var(frame_type) buf.push_uint_var(frame_length) buf.push_bytes(frame_data) return buf.data
def test_params_unknown(self): data = binascii.unhexlify("8000ff000100") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters(buf) self.assertEqual(params, QuicTransportParameters())
def test_application_close_not_utf8(self): data = binascii.unhexlify("0008676f6f6462798200") # parse buf = Buffer(data=data) frame = packet.pull_application_close_frame(buf) self.assertEqual(frame, (0, ""))
def test_params_unknown(self): # fb.mvfst.net sends a proprietary parameter 65280 data = binascii.unhexlify( "006400050004800104000006000480010400000700048001040000040004801" "0000000080008c0000000ffffffff00090008c0000000ffffffff0001000480" "00ea60000a00010300030002500000020010616161616262626263636363646" "46464ff00000100") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters(buf) self.assertEqual( params, QuicTransportParameters( idle_timeout=60000, stateless_reset_token=b"aaaabbbbccccdddd", max_packet_size=4096, initial_max_data=1048576, initial_max_stream_data_bidi_local=66560, initial_max_stream_data_bidi_remote=66560, initial_max_stream_data_uni=66560, initial_max_streams_bidi=4294967295, initial_max_streams_uni=4294967295, ack_delay_exponent=3, ), )
def test_pull_server_hello(self): buf = Buffer(data=load("tls_server_hello.bin")) hello = pull_server_hello(buf) self.assertTrue(buf.eof()) self.assertEqual( hello.random, binascii.unhexlify( "ada85271d19680c615ea7336519e3fdf6f1e26f3b1075ee1de96ffa8884e8280" ), ) self.assertEqual( hello.session_id, binascii.unhexlify( "9aee82a2d186c1cb32a329d9dcfe004a1a438ad0485a53c6bfcf55c132a23235" ), ) self.assertEqual(hello.cipher_suite, tls.CipherSuite.AES_256_GCM_SHA384) self.assertEqual(hello.compression_method, tls.CompressionMethod.NULL) self.assertEqual( hello.key_share, ( tls.Group.SECP256R1, binascii.unhexlify( "048b27d0282242d84b7fcc02a9c4f13eca0329e3c7029aa34a33794e6e7ba189" "5cca1c503bf0378ac6937c354912116ff3251026bca1958d7f387316c83ae6cf" "b2" ), ), ) self.assertEqual(hello.pre_shared_key, None) self.assertEqual(hello.supported_version, tls.TLS_VERSION_1_3)
def test_transport_close(self): data = binascii.unhexlify("0a0212696c6c6567616c2041434b206672616d6500") # parse buf = Buffer(data=data) frame = packet.pull_transport_close_frame(buf) self.assertEqual(frame, (10, 2, "illegal ACK frame\x00"))
def test_handle_new_token_frame(self): with client_and_server() as (client, server): # client receives NEW_TOKEN client._handle_new_token_frame( client_receive_context(client), QuicFrameType.NEW_TOKEN, Buffer(data=binascii.unhexlify("080102030405060708")), )
def test_handle_ack_frame_ecn(self): client = create_standalone_client(self) client._handle_ack_frame( client_receive_context(client), QuicFrameType.ACK_ECN, Buffer(data=b"\x00\x02\x00\x00\x00\x00\x00"), )
def test_handle_streams_blocked_uni_frame(self): with client_and_server() as (client, server): # client receives STREAMS_BLOCKED_UNI: 0 client._handle_streams_blocked_frame( client_receive_context(client), QuicFrameType.STREAMS_BLOCKED_UNI, Buffer(data=b"\x00"), )
def test_push_certificate(self): certificate = Certificate( request_context=b"", certificates=[(CERTIFICATE_DATA, b"")] ) buf = Buffer(1600) push_certificate(buf, certificate) self.assertEqual(buf.data, load("tls_certificate.bin"))
def test_pull_long_header_scid_too_long(self): buf = Buffer(data=binascii.unhexlify( "c2ff0000160015000000000000000000000000000000000000000000004" "01cfcee99ec4bbf1f7a30f9b0c9417b8c263cdd8cc972a4439d68a46320")) with self.assertRaises(ValueError) as cm: pull_quic_header(buf, host_cid_length=8) self.assertEqual(str(cm.exception), "Source CID is too long (21 bytes)")
def test_pull_long_header_dcid_too_long(self): buf = Buffer(data=binascii.unhexlify( "c6ff0000161500000000000000000000000000000000000000000000004" "01c514f99ec4bbf1f7a30f9b0c94fef717f1c1d07fec24c99a864da7ede")) with self.assertRaises(ValueError) as cm: pull_quic_header(buf, host_cid_length=8) self.assertEqual(str(cm.exception), "Destination CID is too long (21 bytes)")
def test_params_unknown(self): data = binascii.unhexlify("8000ff000100") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters( buf, protocol_version=QuicProtocolVersion.DRAFT_27) self.assertEqual(params, QuicTransportParameters())
def parse_settings(data: bytes) -> Dict[int, int]: buf = Buffer(data=data) settings = [] while not buf.eof(): setting = buf.pull_uint_var() value = buf.pull_uint_var() settings.append((setting, value)) return dict(settings)
def test_handle_data_blocked_frame(self): with client_and_server() as (client, server): # client receives DATA_BLOCKED: 12345 client._handle_data_blocked_frame( client_receive_context(client), QuicFrameType.DATA_BLOCKED, Buffer(data=encode_uint_var(12345)), )
def _receive_data_on_session_stream(self, data: bytes, fin: bool) -> None: self._capsule_decoder_for_session_stream.append(data) if fin: self._capsule_decoder_for_session_stream.final() for capsule in self._capsule_decoder_for_session_stream: if capsule.type in { CapsuleType.DATAGRAM, CapsuleType.REGISTER_DATAGRAM_CONTEXT, CapsuleType.CLOSE_DATAGRAM_CONTEXT }: raise ProtocolError( f"Unimplemented capsule type: {capsule.type}") if capsule.type in { CapsuleType.REGISTER_DATAGRAM_NO_CONTEXT, CapsuleType.CLOSE_WEBTRANSPORT_SESSION }: # We'll handle this case below. pass else: # We should ignore unknown capsules. continue if self._close_info is not None: raise ProtocolError( ("Receiving a capsule with type = {} after receiving " + "CLOSE_WEBTRANSPORT_SESSION").format(capsule.type)) if capsule.type == CapsuleType.REGISTER_DATAGRAM_NO_CONTEXT: buffer = Buffer(data=capsule.data) format_type = buffer.pull_uint_var() # https://ietf-wg-webtrans.github.io/draft-ietf-webtrans-http3/draft-ietf-webtrans-http3.html#name-datagram-format-type WEBTRANPORT_FORMAT_TYPE = 0xff7c00 if format_type != WEBTRANPORT_FORMAT_TYPE: raise ProtocolError( "Unexpected datagram format type: {}".format( format_type)) self._allow_datagrams = True elif capsule.type == CapsuleType.CLOSE_WEBTRANSPORT_SESSION: buffer = Buffer(data=capsule.data) code = buffer.pull_uint32() # 4 bytes for the uint32. reason = buffer.pull_bytes(len(capsule.data) - 4) # TODO(yutakahirano): Make sure `reason` is a UTF-8 text. self._close_info = (code, reason) if fin: self._call_session_closed(self._close_info, abruptly=False)
def test_push_finished(self): finished = Finished(verify_data=binascii.unhexlify( "f157923234ff9a4921aadb2e0ec7b1a30fce73fb9ec0c4276f9af268f408ec68") ) buf = Buffer(128) push_finished(buf, finished) self.assertEqual(buf.data, load("tls_finished.bin"))
def encode(self) -> bytes: """ Encodes this H3Capsule and return the bytes. """ buffer = Buffer(capacity=len(self.data) + 2 * UINT_VAR_MAX_SIZE) buffer.push_uint_var(self.type) buffer.push_uint_var(len(self.data)) buffer.push_bytes(self.data) return buffer.data
def test_push_certificate_verify(self): verify = CertificateVerify( algorithm=tls.SignatureAlgorithm.RSA_PSS_RSAE_SHA256, signature=CERTIFICATE_VERIFY_SIGNATURE, ) buf = Buffer(400) push_certificate_verify(buf, verify) self.assertEqual(buf.data, load("tls_certificate_verify.bin"))
def _make_connection(self, channel, data): ctx = channel.server.ctx buf = Buffer(data=data) header = pull_quic_header(buf, host_cid_length=ctx.connection_id_length) # version negotiation if header.version is not None and header.version not in ctx.supported_versions: self.channel.push( encode_quic_version_negotiation( source_cid=header.destination_cid, destination_cid=header.source_cid, supported_versions=ctx.supported_versions, )) return conn = self.conns.get(header.destination_cid) if conn: conn._linked_channel.close() conn._linked_channel = channel self.quic = conn._quic self.conn = conn return if header.packet_type != PACKET_TYPE_INITIAL or len(data) < 1200: return original_connection_id = None if self._retry is not None: if not header.token: # create a retry token channel.push( encode_quic_retry( version=header.version, source_cid=os.urandom(8), destination_cid=header.source_cid, original_destination_cid=header.destination_cid, retry_token=self._retry.create_token( channel.addr, header.destination_cid))) return else: try: original_connection_id = self._retry.validate_token( channel.addr, header.token) except ValueError: return self.quic = QuicConnection( configuration=ctx, logger_connection_id=original_connection_id or header.destination_cid, original_connection_id=original_connection_id, session_ticket_fetcher=channel.server.ticket_store.pop, session_ticket_handler=channel.server.ticket_store.add) self.conn = H3Connection(self.quic) self.conn._linked_channel = channel
def test_handle_max_streams_uni_frame(self): with client_and_server() as (client, server): self.assertEqual(client._remote_max_streams_uni, 128) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( client_receive_context(client), QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(129)), ) self.assertEqual(client._remote_max_streams_uni, 129) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( client_receive_context(client), QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(127)), ) self.assertEqual(client._remote_max_streams_uni, 129)
def _receive_datagram(self, data: bytes) -> List[H3Event]: """ Handle a datagram. """ buf = Buffer(data=data) try: flow_id = buf.pull_uint_var() except BufferReadError: raise ProtocolError("Could not parse flow ID") return [DatagramReceived(data=data[buf.tell():], flow_id=flow_id)]
def test_encrypted_extensions(self): data = load("tls_encrypted_extensions.bin") buf = Buffer(data=data) extensions = pull_encrypted_extensions(buf) self.assertIsNotNone(extensions) self.assertTrue(buf.eof()) self.assertEqual( extensions, EncryptedExtensions(other_extensions=[( tls.ExtensionType.QUIC_TRANSPORT_PARAMETERS, SERVER_QUIC_TRANSPORT_PARAMETERS, )]), ) # serialize buf = Buffer(capacity=100) push_encrypted_extensions(buf, extensions) self.assertEqual(buf.data, data)
def test_handle_stop_sending_frame(self): with client_and_server() as (client, server): # client creates bidirectional stream 0 client.send_stream_data(stream_id=0, data=b"hello") # client receives STOP_SENDING client._handle_stop_sending_frame( client_receive_context(client), QuicFrameType.STOP_SENDING, Buffer(data=b"\x00\x11\x22"), )
def test_handle_stream_data_blocked_frame(self): with client_and_server() as (client, server): # client creates bidirectional stream 0 client.send_stream_data(stream_id=0, data=b"hello") # client receives STREAM_DATA_BLOCKED client._handle_stream_data_blocked_frame( client_receive_context(client), QuicFrameType.STREAM_DATA_BLOCKED, Buffer(data=b"\x00\x01"), )
def test_pull_finished(self): buf = Buffer(data=load("tls_finished.bin")) finished = pull_finished(buf) self.assertTrue(buf.eof()) self.assertEqual( finished.verify_data, binascii.unhexlify( "f157923234ff9a4921aadb2e0ec7b1a30fce73fb9ec0c4276f9af268f408ec68" ), )