1// Copyright 2019 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
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
34func (e encryptionLevel) String() string {
35	switch e {
36	case encryptionInitial:
37		return "initial"
38	case encryptionEarlyData:
39		return "early data"
40	case encryptionHandshake:
41		return "handshake"
42	case encryptionApplication:
43		return "application"
44	}
45	return fmt.Sprintf("unknown level (%d)", e)
46}
47
48// mockQUICTransport provides a record layer for sending/receiving messages
49// when testing TLS over QUIC. It is only intended for testing, as it runs over
50// an in-order reliable transport, looks nothing like the QUIC wire image, and
51// provides no confidentiality guarantees. (In fact, it leaks keys in the
52// clear.)
53//
54// Messages from TLS that are sent over a mockQUICTransport are a series of
55// records in the following format:
56//
57//	enum {
58//	    initial(0), early_data(1), handshake(2), application(3), (255)
59//	} EncryptionLevel;
60//
61//	struct {
62//	    ContentType record_type;
63//	    EncryptionLevel level;
64//	    CipherSuite cipher_suite;
65//	    opaque encrypted_record<0..2^32-1>;
66//	} MockQUICRecord;
67//
68// The "encrypted" record is the concatenation of the encryption key and
69// plaintext. It and the cipher suite exist only to check both sides agree on
70// encryption parameters. The key is included in the length prefix so records
71// may be skipped without knowing the key length.
72type mockQUICTransport struct {
73	net.Conn
74	readLevel, writeLevel             encryptionLevel
75	readSecret, writeSecret           []byte
76	readCipherSuite, writeCipherSuite uint16
77	skipEarlyData                     bool
78}
79
80func newMockQUICTransport(conn net.Conn) *mockQUICTransport {
81	return &mockQUICTransport{Conn: conn}
82}
83
84func (m *mockQUICTransport) read() (recordType, []byte, error) {
85	for {
86		header := make([]byte, 8)
87		if _, err := io.ReadFull(m.Conn, header); err != nil {
88			return 0, nil, err
89		}
90		typ := recordType(header[0])
91		level := encryptionLevel(header[1])
92		cipherSuite := binary.BigEndian.Uint16(header[2:4])
93		length := binary.BigEndian.Uint32(header[4:])
94		value := make([]byte, length)
95		if _, err := io.ReadFull(m.Conn, value); err != nil {
96			return 0, nil, fmt.Errorf("error reading record")
97		}
98		if level != m.readLevel {
99			if m.skipEarlyData && level == encryptionEarlyData {
100				continue
101			}
102			return 0, nil, fmt.Errorf("received record at %s encryption level, but expected %s", level, m.readLevel)
103		}
104		if cipherSuite != m.readCipherSuite {
105			return 0, nil, fmt.Errorf("received cipher suite %d does not match expected %d", cipherSuite, m.readCipherSuite)
106		}
107		if len(m.readSecret) > len(value) {
108			return 0, nil, fmt.Errorf("input length too short")
109		}
110		secret := value[:len(m.readSecret)]
111		out := value[len(m.readSecret):]
112		if !bytes.Equal(secret, m.readSecret) {
113			return 0, nil, fmt.Errorf("secrets don't match: got %x but expected %x", secret, m.readSecret)
114		}
115		// Although not true for QUIC in general, our transport is ordered, so
116		// we expect to stop skipping early data after a valid record.
117		m.skipEarlyData = false
118		return typ, out, nil
119	}
120}
121
122func (m *mockQUICTransport) readRecord(want recordType) (recordType, []byte, error) {
123	return m.read()
124}
125
126func (m *mockQUICTransport) writeRecord(typ recordType, data []byte) (int, error) {
127	if typ != recordTypeApplicationData && typ != recordTypeHandshake {
128		return 0, fmt.Errorf("unsupported record type %d\n", typ)
129	}
130	length := len(m.writeSecret) + len(data)
131	payload := make([]byte, 1+1+2+4+length)
132	payload[0] = byte(typ)
133	payload[1] = byte(m.writeLevel)
134	binary.BigEndian.PutUint16(payload[2:4], m.writeCipherSuite)
135	binary.BigEndian.PutUint32(payload[4:8], uint32(length))
136	copy(payload[8:], m.writeSecret)
137	copy(payload[8+len(m.writeSecret):], data)
138	if _, err := m.Conn.Write(payload); err != nil {
139		return 0, err
140	}
141	return len(data), nil
142}
143
144func (m *mockQUICTransport) Write(b []byte) (int, error) {
145	panic("unexpected call to Write")
146}
147
148func (m *mockQUICTransport) Read(b []byte) (int, error) {
149	panic("unexpected call to Read")
150}
151