• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2019, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15package runner
16
17import (
18	"bytes"
19	"encoding/binary"
20	"fmt"
21	"io"
22	"net"
23)
24
25type encryptionLevel byte
26
27const (
28	encryptionInitial     encryptionLevel = 0
29	encryptionEarlyData   encryptionLevel = 1
30	encryptionHandshake   encryptionLevel = 2
31	encryptionApplication encryptionLevel = 3
32)
33
34// mockQUICTransport provides a record layer for sending/receiving messages
35// when testing TLS over QUIC. It is only intended for testing, as it runs over
36// an in-order reliable transport, looks nothing like the QUIC wire image, and
37// provides no confidentiality guarantees. (In fact, it leaks keys in the
38// clear.)
39//
40// Messages from TLS that are sent over a mockQUICTransport are a series of
41// records in the following format:
42//
43//   enum {
44//       initial(0), early_data(1), handshake(2), application(3), (255)
45//   } EncryptionLevel;
46//
47//   struct {
48//       ContentType record_type;
49//       EncryptionLevel level;
50//       CipherSuite cipher_suite;
51//       opaque encrypted_record<0..2^32-1>;
52//   } MockQUICRecord;
53//
54// The "encrypted" record is the concatenation of the encryption key and
55// plaintext. It and the cipher suite exist only to check both sides agree on
56// encryption parameters. The key is included in the length prefix so records
57// may be skipped without knowing the key length.
58type mockQUICTransport struct {
59	net.Conn
60	readLevel, writeLevel             encryptionLevel
61	readSecret, writeSecret           []byte
62	readCipherSuite, writeCipherSuite uint16
63	skipEarlyData                     bool
64}
65
66func newMockQUICTransport(conn net.Conn) *mockQUICTransport {
67	return &mockQUICTransport{Conn: conn}
68}
69
70func (m *mockQUICTransport) read() (recordType, []byte, error) {
71	for {
72		header := make([]byte, 8)
73		if _, err := io.ReadFull(m.Conn, header); err != nil {
74			return 0, nil, err
75		}
76		typ := recordType(header[0])
77		level := header[1]
78		cipherSuite := binary.BigEndian.Uint16(header[2:4])
79		length := binary.BigEndian.Uint32(header[4:])
80		value := make([]byte, length)
81		if _, err := io.ReadFull(m.Conn, value); err != nil {
82			return 0, nil, fmt.Errorf("error reading record")
83		}
84		if level != byte(m.readLevel) {
85			if m.skipEarlyData && level == byte(encryptionEarlyData) {
86				continue
87			}
88			return 0, nil, fmt.Errorf("received level %d does not match expected %d", level, m.readLevel)
89		}
90		if cipherSuite != m.readCipherSuite {
91			return 0, nil, fmt.Errorf("received cipher suite %d does not match expected %d", cipherSuite, m.readCipherSuite)
92		}
93		if len(m.readSecret) > len(value) {
94			return 0, nil, fmt.Errorf("input length too short")
95		}
96		secret := value[:len(m.readSecret)]
97		out := value[len(m.readSecret):]
98		if !bytes.Equal(secret, m.readSecret) {
99			return 0, nil, fmt.Errorf("secrets don't match: got %x but expected %x", secret, m.readSecret)
100		}
101		// Although not true for QUIC in general, our transport is ordered, so
102		// we expect to stop skipping early data after a valid record.
103		m.skipEarlyData = false
104		return typ, out, nil
105	}
106}
107
108func (m *mockQUICTransport) readRecord(want recordType) (recordType, *block, error) {
109	typ, contents, err := m.read()
110	if err != nil {
111		return 0, nil, err
112	}
113	return typ, &block{contents, 0, nil}, nil
114}
115
116func (m *mockQUICTransport) writeRecord(typ recordType, data []byte) (int, error) {
117	if typ != recordTypeApplicationData && typ != recordTypeHandshake {
118		return 0, fmt.Errorf("unsupported record type %d\n", typ)
119	}
120	length := len(m.writeSecret) + len(data)
121	payload := make([]byte, 1+1+2+4+length)
122	payload[0] = byte(typ)
123	payload[1] = byte(m.writeLevel)
124	binary.BigEndian.PutUint16(payload[2:4], m.writeCipherSuite)
125	binary.BigEndian.PutUint32(payload[4:8], uint32(length))
126	copy(payload[8:], m.writeSecret)
127	copy(payload[8+len(m.writeSecret):], data)
128	if _, err := m.Conn.Write(payload); err != nil {
129		return 0, err
130	}
131	return len(data), nil
132}
133
134func (m *mockQUICTransport) Write(b []byte) (int, error) {
135	panic("unexpected call to Write")
136}
137
138func (m *mockQUICTransport) Read(b []byte) (int, error) {
139	panic("unexpected call to Read")
140}
141