// Copyright (c) 2019, Google Inc. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package runner import ( "bytes" "encoding/binary" "fmt" "io" "net" ) type encryptionLevel byte const ( encryptionInitial encryptionLevel = 0 encryptionEarlyData encryptionLevel = 1 encryptionHandshake encryptionLevel = 2 encryptionApplication encryptionLevel = 3 ) // mockQUICTransport provides a record layer for sending/receiving messages // when testing TLS over QUIC. It is only intended for testing, as it runs over // an in-order reliable transport, looks nothing like the QUIC wire image, and // provides no confidentiality guarantees. (In fact, it leaks keys in the // clear.) // // Messages from TLS that are sent over a mockQUICTransport are a series of // records in the following format: // // enum { // initial(0), early_data(1), handshake(2), application(3), (255) // } EncryptionLevel; // // struct { // ContentType record_type; // EncryptionLevel level; // CipherSuite cipher_suite; // opaque encrypted_record<0..2^32-1>; // } MockQUICRecord; // // The "encrypted" record is the concatenation of the encryption key and // plaintext. It and the cipher suite exist only to check both sides agree on // encryption parameters. The key is included in the length prefix so records // may be skipped without knowing the key length. type mockQUICTransport struct { net.Conn readLevel, writeLevel encryptionLevel readSecret, writeSecret []byte readCipherSuite, writeCipherSuite uint16 skipEarlyData bool } func newMockQUICTransport(conn net.Conn) *mockQUICTransport { return &mockQUICTransport{Conn: conn} } func (m *mockQUICTransport) read() (recordType, []byte, error) { for { header := make([]byte, 8) if _, err := io.ReadFull(m.Conn, header); err != nil { return 0, nil, err } typ := recordType(header[0]) level := header[1] cipherSuite := binary.BigEndian.Uint16(header[2:4]) length := binary.BigEndian.Uint32(header[4:]) value := make([]byte, length) if _, err := io.ReadFull(m.Conn, value); err != nil { return 0, nil, fmt.Errorf("error reading record") } if level != byte(m.readLevel) { if m.skipEarlyData && level == byte(encryptionEarlyData) { continue } return 0, nil, fmt.Errorf("received level %d does not match expected %d", level, m.readLevel) } if cipherSuite != m.readCipherSuite { return 0, nil, fmt.Errorf("received cipher suite %d does not match expected %d", cipherSuite, m.readCipherSuite) } if len(m.readSecret) > len(value) { return 0, nil, fmt.Errorf("input length too short") } secret := value[:len(m.readSecret)] out := value[len(m.readSecret):] if !bytes.Equal(secret, m.readSecret) { return 0, nil, fmt.Errorf("secrets don't match: got %x but expected %x", secret, m.readSecret) } // Although not true for QUIC in general, our transport is ordered, so // we expect to stop skipping early data after a valid record. m.skipEarlyData = false return typ, out, nil } } func (m *mockQUICTransport) readRecord(want recordType) (recordType, *block, error) { typ, contents, err := m.read() if err != nil { return 0, nil, err } return typ, &block{contents, 0, nil}, nil } func (m *mockQUICTransport) writeRecord(typ recordType, data []byte) (int, error) { if typ != recordTypeApplicationData && typ != recordTypeHandshake { return 0, fmt.Errorf("unsupported record type %d\n", typ) } length := len(m.writeSecret) + len(data) payload := make([]byte, 1+1+2+4+length) payload[0] = byte(typ) payload[1] = byte(m.writeLevel) binary.BigEndian.PutUint16(payload[2:4], m.writeCipherSuite) binary.BigEndian.PutUint32(payload[4:8], uint32(length)) copy(payload[8:], m.writeSecret) copy(payload[8+len(m.writeSecret):], data) if _, err := m.Conn.Write(payload); err != nil { return 0, err } return len(data), nil } func (m *mockQUICTransport) Write(b []byte) (int, error) { panic("unexpected call to Write") } func (m *mockQUICTransport) Read(b []byte) (int, error) { panic("unexpected call to Read") }