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