Initial commit.
[BoarSSL] / SSLTLS / OutputRecord.cs
1 /*
2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 using System;
26 using System.IO;
27
28 namespace SSLTLS {
29
30 internal class OutputRecord {
31
32 /*
33 * Splitting modes are for debugging and tests. Note that the
34 * automatic 1/n-1 split for CBC cipher suites in TLS 1.0 is
35 * handled independently in RecordEncryptCBC.
36 */
37
38 /* No split. */
39 internal const int MODE_NORMAL = 0;
40
41 /* Each record is split into two, of approximately the same size. */
42 internal const int MODE_SPLIT_HALF = 1;
43
44 /* Each record is preceded with an extra record of size 0. */
45 internal const int MODE_SPLIT_ZERO_BEFORE = 2;
46
47 /* Each record is split into two records (like SPLIT_HALF), and
48 an extra zero-length record is added between the two halves. */
49 internal const int MODE_SPLIT_ZERO_HALF = 3;
50
51 /* The first byte of each record is separated into its own record. */
52 internal const int MODE_SPLIT_ONE_START = 4;
53
54 /* The last byte of each record is separated into its own record. */
55 internal const int MODE_SPLIT_ONE_END = 5;
56
57 /* The record is split into records of length 1 byte each. */
58 internal const int MODE_SPLIT_MULTI_ONE = 6;
59
60 /*
61 * Spliting modes are only applied on the specified record types
62 * (these are bit flags that can be combined).
63 */
64 internal const int MODE_MT_CCS = 1 << SSL.CHANGE_CIPHER_SPEC;
65 internal const int MODE_MT_ALERT = 1 << SSL.ALERT;
66 internal const int MODE_MT_HANDSHAKE = 1 << SSL.HANDSHAKE;
67 internal const int MODE_MT_APPLICATION_DATA = 1 << SSL.APPLICATION_DATA;
68
69 const int MODE_MASK = 0xFFFF;
70
71 Stream sub;
72 byte[] buffer;
73 int ptr, basePtr, maxPtr;
74 int version;
75 int recordType;
76 RecordEncrypt renc;
77
78 int splitMode;
79 byte[] extra;
80
81 long countHandshake;
82 long countAppData;
83 long thresholdZeroHandshake;
84 long thresholdZeroAppData;
85 byte[] extra2;
86
87 internal OutputRecord(Stream sub)
88 {
89 this.sub = sub;
90 buffer = new byte[16384 + 500];
91 version = 0;
92 recordType = -1;
93 splitMode = MODE_NORMAL;
94 extra = null;
95 countHandshake = 0;
96 countAppData = 0;
97 thresholdZeroHandshake = 0;
98 thresholdZeroAppData = 0;
99 extra2 = new byte[500];
100 renc = new RecordEncryptPlain();
101 PrepNew();
102 }
103
104 /*
105 * If set, then all I/O errors while writing on the underlying
106 * stream will be converted to a generic SSLException with message
107 * "Unexpected transport closure". This helps test code that
108 * expects the peer to abort asynchronously, so the error may
109 * be detected during both reading or writing.
110 */
111 internal bool NormalizeIOError {
112 get; set;
113 }
114
115 internal void SetVersion(int version)
116 {
117 if (version != this.version) {
118 if (ptr != basePtr) {
119 FlushInner();
120 }
121 this.version = version;
122 PrepNew();
123 }
124 }
125
126 internal int RecordType {
127 get {
128 return recordType;
129 }
130 set {
131 if (value != recordType) {
132 if (ptr != basePtr) {
133 FlushInner();
134 }
135 recordType = value;
136 }
137 }
138 }
139
140 internal void SetEncryption(RecordEncrypt renc)
141 {
142 if (ptr != basePtr) {
143 FlushInner();
144 }
145 this.renc = renc;
146 PrepNew();
147 }
148
149 internal void SetSplitMode(int splitMode)
150 {
151 this.splitMode = splitMode;
152 if ((splitMode & MODE_MASK) != MODE_NORMAL && extra == null) {
153 extra = new byte[buffer.Length];
154 }
155 }
156
157 internal void SetThresholdZeroAppData(long t)
158 {
159 thresholdZeroAppData = t;
160 }
161
162 internal void SetThresholdZeroHandshake(long t)
163 {
164 thresholdZeroAppData = t;
165 }
166
167 void PrepNew()
168 {
169 int start = 0;
170 int end = buffer.Length;
171 renc.GetMaxPlaintext(ref start, ref end);
172 ptr = start;
173 basePtr = start;
174 maxPtr = end;
175 }
176
177 internal void Flush()
178 {
179 if (ptr == basePtr) {
180 return;
181 }
182 FlushInner();
183 sub.Flush();
184 }
185
186 internal void SendZeroLength(int type)
187 {
188 Flush();
189 int rt = RecordType;
190 RecordType = type;
191 FlushInner();
192 RecordType = rt;
193 sub.Flush();
194 }
195
196 void FlushInner()
197 {
198 int off = basePtr;
199 int len = ptr - basePtr;
200 if (version == 0) {
201 throw new Exception("Record version is not set");
202 }
203 int m = splitMode & MODE_MASK;
204 if (m == MODE_NORMAL || (splitMode & (1 << recordType)) == 0) {
205 EncryptAndWrite(off, len);
206 } else {
207 Array.Copy(buffer, off, extra, off, len);
208 switch (m) {
209 case MODE_SPLIT_HALF:
210 case MODE_SPLIT_ZERO_HALF:
211 int hlen = (len >> 1);
212 if (hlen > 0) {
213 EncryptAndWrite(off, hlen);
214 }
215 if (m == MODE_SPLIT_ZERO_HALF) {
216 EncryptAndWrite(off, 0);
217 }
218 Array.Copy(extra, off + hlen,
219 buffer, off, len - hlen);
220 hlen = len - hlen;
221 if (hlen > 0) {
222 EncryptAndWrite(off, hlen);
223 }
224 break;
225 case MODE_SPLIT_ZERO_BEFORE:
226 EncryptAndWrite(off, 0);
227 Array.Copy(extra, off, buffer, off, len);
228 if (len > 0) {
229 EncryptAndWrite(off, len);
230 }
231 break;
232 case MODE_SPLIT_ONE_START:
233 if (len > 0) {
234 EncryptAndWrite(off, 1);
235 }
236 if (len > 1) {
237 Array.Copy(extra, off + 1,
238 buffer, off, len - 1);
239 EncryptAndWrite(off, len - 1);
240 }
241 break;
242 case MODE_SPLIT_ONE_END:
243 if (len > 1) {
244 EncryptAndWrite(off, len - 1);
245 }
246 if (len > 0) {
247 buffer[off] = extra[off + len - 1];
248 EncryptAndWrite(off, 1);
249 }
250 break;
251 case MODE_SPLIT_MULTI_ONE:
252 for (int i = 0; i < len; i ++) {
253 buffer[off] = extra[off + i];
254 EncryptAndWrite(off, 1);
255 }
256 break;
257 default:
258 throw new SSLException(string.Format(
259 "Bad record splitting value: {0}", m));
260 }
261 }
262 PrepNew();
263 }
264
265 void EncryptAndWrite(int off, int len)
266 {
267 try {
268 EncryptAndWriteInner(off, len);
269 } catch {
270 if (NormalizeIOError) {
271 throw new SSLException(
272 "Unexpected transport closure");
273 } else {
274 throw;
275 }
276 }
277 }
278
279 void EncryptAndWriteInner(int off, int len)
280 {
281 if (recordType == SSL.HANDSHAKE) {
282 countHandshake ++;
283 if (countHandshake == thresholdZeroAppData) {
284 int start = 0;
285 int end = extra2.Length;
286 renc.GetMaxPlaintext(ref start, ref end);
287 int zoff = start;
288 int zlen = 0;
289 renc.Encrypt(SSL.APPLICATION_DATA, version,
290 extra2, ref zoff, ref zlen);
291 sub.Write(extra2, zoff, zlen);
292 }
293 } else if (recordType == SSL.APPLICATION_DATA) {
294 countAppData ++;
295 if (countAppData == thresholdZeroHandshake) {
296 int start = 0;
297 int end = extra2.Length;
298 renc.GetMaxPlaintext(ref start, ref end);
299 int zoff = start;
300 int zlen = 0;
301 renc.Encrypt(SSL.HANDSHAKE, version,
302 extra2, ref zoff, ref zlen);
303 sub.Write(extra2, zoff, zlen);
304 }
305 }
306
307 renc.Encrypt(recordType, version, buffer, ref off, ref len);
308 sub.Write(buffer, off, len);
309 }
310
311 internal void Write(byte x)
312 {
313 buffer[ptr ++] = x;
314 if (ptr == maxPtr) {
315 FlushInner();
316 }
317 }
318
319 internal void Write(byte[] data)
320 {
321 Write(data, 0, data.Length);
322 }
323
324 internal void Write(byte[] data, int off, int len)
325 {
326 while (len > 0) {
327 int clen = Math.Min(len, maxPtr - ptr);
328 Array.Copy(data, off, buffer, ptr, clen);
329 ptr += clen;
330 off += clen;
331 len -= clen;
332 if (ptr == maxPtr) {
333 FlushInner();
334 }
335 }
336 }
337 }
338
339 }