1189251Ssam/*
2189251Ssam * TLSv1 Record Protocol
3189251Ssam * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
4189251Ssam *
5189251Ssam * This program is free software; you can redistribute it and/or modify
6189251Ssam * it under the terms of the GNU General Public License version 2 as
7189251Ssam * published by the Free Software Foundation.
8189251Ssam *
9189251Ssam * Alternatively, this software may be distributed under the terms of BSD
10189251Ssam * license.
11189251Ssam *
12189251Ssam * See README and COPYING for more details.
13189251Ssam */
14189251Ssam
15189251Ssam#include "includes.h"
16189251Ssam
17189251Ssam#include "common.h"
18214734Srpaulo#include "crypto/md5.h"
19214734Srpaulo#include "crypto/sha1.h"
20189251Ssam#include "tlsv1_common.h"
21189251Ssam#include "tlsv1_record.h"
22189251Ssam
23189251Ssam
24189251Ssam/**
25189251Ssam * tlsv1_record_set_cipher_suite - TLS record layer: Set cipher suite
26189251Ssam * @rl: Pointer to TLS record layer data
27189251Ssam * @cipher_suite: New cipher suite
28189251Ssam * Returns: 0 on success, -1 on failure
29189251Ssam *
30189251Ssam * This function is used to prepare TLS record layer for cipher suite change.
31189251Ssam * tlsv1_record_change_write_cipher() and
32189251Ssam * tlsv1_record_change_read_cipher() functions can then be used to change the
33189251Ssam * currently used ciphers.
34189251Ssam */
35189251Ssamint tlsv1_record_set_cipher_suite(struct tlsv1_record_layer *rl,
36189251Ssam				  u16 cipher_suite)
37189251Ssam{
38189251Ssam	const struct tls_cipher_suite *suite;
39189251Ssam	const struct tls_cipher_data *data;
40189251Ssam
41189251Ssam	wpa_printf(MSG_DEBUG, "TLSv1: Selected cipher suite: 0x%04x",
42189251Ssam		   cipher_suite);
43189251Ssam	rl->cipher_suite = cipher_suite;
44189251Ssam
45189251Ssam	suite = tls_get_cipher_suite(cipher_suite);
46189251Ssam	if (suite == NULL)
47189251Ssam		return -1;
48189251Ssam
49189251Ssam	if (suite->hash == TLS_HASH_MD5) {
50189251Ssam		rl->hash_alg = CRYPTO_HASH_ALG_HMAC_MD5;
51189251Ssam		rl->hash_size = MD5_MAC_LEN;
52189251Ssam	} else if (suite->hash == TLS_HASH_SHA) {
53189251Ssam		rl->hash_alg = CRYPTO_HASH_ALG_HMAC_SHA1;
54189251Ssam		rl->hash_size = SHA1_MAC_LEN;
55189251Ssam	}
56189251Ssam
57189251Ssam	data = tls_get_cipher_data(suite->cipher);
58189251Ssam	if (data == NULL)
59189251Ssam		return -1;
60189251Ssam
61189251Ssam	rl->key_material_len = data->key_material;
62189251Ssam	rl->iv_size = data->block_size;
63189251Ssam	rl->cipher_alg = data->alg;
64189251Ssam
65189251Ssam	return 0;
66189251Ssam}
67189251Ssam
68189251Ssam
69189251Ssam/**
70189251Ssam * tlsv1_record_change_write_cipher - TLS record layer: Change write cipher
71189251Ssam * @rl: Pointer to TLS record layer data
72189251Ssam * Returns: 0 on success (cipher changed), -1 on failure
73189251Ssam *
74189251Ssam * This function changes TLS record layer to use the new cipher suite
75189251Ssam * configured with tlsv1_record_set_cipher_suite() for writing.
76189251Ssam */
77189251Ssamint tlsv1_record_change_write_cipher(struct tlsv1_record_layer *rl)
78189251Ssam{
79189251Ssam	wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - New write cipher suite "
80189251Ssam		   "0x%04x", rl->cipher_suite);
81189251Ssam	rl->write_cipher_suite = rl->cipher_suite;
82189251Ssam	os_memset(rl->write_seq_num, 0, TLS_SEQ_NUM_LEN);
83189251Ssam
84189251Ssam	if (rl->write_cbc) {
85189251Ssam		crypto_cipher_deinit(rl->write_cbc);
86189251Ssam		rl->write_cbc = NULL;
87189251Ssam	}
88189251Ssam	if (rl->cipher_alg != CRYPTO_CIPHER_NULL) {
89189251Ssam		rl->write_cbc = crypto_cipher_init(rl->cipher_alg,
90189251Ssam						   rl->write_iv, rl->write_key,
91189251Ssam						   rl->key_material_len);
92189251Ssam		if (rl->write_cbc == NULL) {
93189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize "
94189251Ssam				   "cipher");
95189251Ssam			return -1;
96189251Ssam		}
97189251Ssam	}
98189251Ssam
99189251Ssam	return 0;
100189251Ssam}
101189251Ssam
102189251Ssam
103189251Ssam/**
104189251Ssam * tlsv1_record_change_read_cipher - TLS record layer: Change read cipher
105189251Ssam * @rl: Pointer to TLS record layer data
106189251Ssam * Returns: 0 on success (cipher changed), -1 on failure
107189251Ssam *
108189251Ssam * This function changes TLS record layer to use the new cipher suite
109189251Ssam * configured with tlsv1_record_set_cipher_suite() for reading.
110189251Ssam */
111189251Ssamint tlsv1_record_change_read_cipher(struct tlsv1_record_layer *rl)
112189251Ssam{
113189251Ssam	wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - New read cipher suite "
114189251Ssam		   "0x%04x", rl->cipher_suite);
115189251Ssam	rl->read_cipher_suite = rl->cipher_suite;
116189251Ssam	os_memset(rl->read_seq_num, 0, TLS_SEQ_NUM_LEN);
117189251Ssam
118189251Ssam	if (rl->read_cbc) {
119189251Ssam		crypto_cipher_deinit(rl->read_cbc);
120189251Ssam		rl->read_cbc = NULL;
121189251Ssam	}
122189251Ssam	if (rl->cipher_alg != CRYPTO_CIPHER_NULL) {
123189251Ssam		rl->read_cbc = crypto_cipher_init(rl->cipher_alg,
124189251Ssam						  rl->read_iv, rl->read_key,
125189251Ssam						  rl->key_material_len);
126189251Ssam		if (rl->read_cbc == NULL) {
127189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize "
128189251Ssam				   "cipher");
129189251Ssam			return -1;
130189251Ssam		}
131189251Ssam	}
132189251Ssam
133189251Ssam	return 0;
134189251Ssam}
135189251Ssam
136189251Ssam
137189251Ssam/**
138189251Ssam * tlsv1_record_send - TLS record layer: Send a message
139189251Ssam * @rl: Pointer to TLS record layer data
140189251Ssam * @content_type: Content type (TLS_CONTENT_TYPE_*)
141189251Ssam * @buf: Buffer to send (with TLS_RECORD_HEADER_LEN octets reserved in the
142189251Ssam * beginning for record layer to fill in; payload filled in after this and
143189251Ssam * extra space in the end for HMAC).
144189251Ssam * @buf_size: Maximum buf size
145189251Ssam * @payload_len: Length of the payload
146189251Ssam * @out_len: Buffer for returning the used buf length
147189251Ssam * Returns: 0 on success, -1 on failure
148189251Ssam *
149189251Ssam * This function fills in the TLS record layer header, adds HMAC, and encrypts
150189251Ssam * the data using the current write cipher.
151189251Ssam */
152189251Ssamint tlsv1_record_send(struct tlsv1_record_layer *rl, u8 content_type, u8 *buf,
153189251Ssam		      size_t buf_size, size_t payload_len, size_t *out_len)
154189251Ssam{
155189251Ssam	u8 *pos, *ct_start, *length, *payload;
156189251Ssam	struct crypto_hash *hmac;
157189251Ssam	size_t clen;
158189251Ssam
159189251Ssam	pos = buf;
160189251Ssam	/* ContentType type */
161189251Ssam	ct_start = pos;
162189251Ssam	*pos++ = content_type;
163189251Ssam	/* ProtocolVersion version */
164189251Ssam	WPA_PUT_BE16(pos, TLS_VERSION);
165189251Ssam	pos += 2;
166189251Ssam	/* uint16 length */
167189251Ssam	length = pos;
168189251Ssam	WPA_PUT_BE16(length, payload_len);
169189251Ssam	pos += 2;
170189251Ssam
171189251Ssam	/* opaque fragment[TLSPlaintext.length] */
172189251Ssam	payload = pos;
173189251Ssam	pos += payload_len;
174189251Ssam
175189251Ssam	if (rl->write_cipher_suite != TLS_NULL_WITH_NULL_NULL) {
176189251Ssam		hmac = crypto_hash_init(rl->hash_alg, rl->write_mac_secret,
177189251Ssam					rl->hash_size);
178189251Ssam		if (hmac == NULL) {
179189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
180189251Ssam				   "to initialize HMAC");
181189251Ssam			return -1;
182189251Ssam		}
183189251Ssam		crypto_hash_update(hmac, rl->write_seq_num, TLS_SEQ_NUM_LEN);
184189251Ssam		/* type + version + length + fragment */
185189251Ssam		crypto_hash_update(hmac, ct_start, pos - ct_start);
186189251Ssam		clen = buf + buf_size - pos;
187189251Ssam		if (clen < rl->hash_size) {
188189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Not "
189189251Ssam				   "enough room for MAC");
190189251Ssam			crypto_hash_finish(hmac, NULL, NULL);
191189251Ssam			return -1;
192189251Ssam		}
193189251Ssam
194189251Ssam		if (crypto_hash_finish(hmac, pos, &clen) < 0) {
195189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
196189251Ssam				   "to calculate HMAC");
197189251Ssam			return -1;
198189251Ssam		}
199189251Ssam		wpa_hexdump(MSG_MSGDUMP, "TLSv1: Record Layer - Write HMAC",
200189251Ssam			    pos, clen);
201189251Ssam		pos += clen;
202189251Ssam		if (rl->iv_size) {
203189251Ssam			size_t len = pos - payload;
204189251Ssam			size_t pad;
205189251Ssam			pad = (len + 1) % rl->iv_size;
206189251Ssam			if (pad)
207189251Ssam				pad = rl->iv_size - pad;
208189251Ssam			if (pos + pad + 1 > buf + buf_size) {
209189251Ssam				wpa_printf(MSG_DEBUG, "TLSv1: No room for "
210189251Ssam					   "block cipher padding");
211189251Ssam				return -1;
212189251Ssam			}
213189251Ssam			os_memset(pos, pad, pad + 1);
214189251Ssam			pos += pad + 1;
215189251Ssam		}
216189251Ssam
217189251Ssam		if (crypto_cipher_encrypt(rl->write_cbc, payload,
218189251Ssam					  payload, pos - payload) < 0)
219189251Ssam			return -1;
220189251Ssam	}
221189251Ssam
222189251Ssam	WPA_PUT_BE16(length, pos - length - 2);
223189251Ssam	inc_byte_array(rl->write_seq_num, TLS_SEQ_NUM_LEN);
224189251Ssam
225189251Ssam	*out_len = pos - buf;
226189251Ssam
227189251Ssam	return 0;
228189251Ssam}
229189251Ssam
230189251Ssam
231189251Ssam/**
232189251Ssam * tlsv1_record_receive - TLS record layer: Process a received message
233189251Ssam * @rl: Pointer to TLS record layer data
234189251Ssam * @in_data: Received data
235189251Ssam * @in_len: Length of the received data
236189251Ssam * @out_data: Buffer for output data (must be at least as long as in_data)
237189251Ssam * @out_len: Set to maximum out_data length by caller; used to return the
238189251Ssam * length of the used data
239189251Ssam * @alert: Buffer for returning an alert value on failure
240189251Ssam * Returns: 0 on success, -1 on failure
241189251Ssam *
242189251Ssam * This function decrypts the received message, verifies HMAC and TLS record
243189251Ssam * layer header.
244189251Ssam */
245189251Ssamint tlsv1_record_receive(struct tlsv1_record_layer *rl,
246189251Ssam			 const u8 *in_data, size_t in_len,
247189251Ssam			 u8 *out_data, size_t *out_len, u8 *alert)
248189251Ssam{
249189251Ssam	size_t i, rlen, hlen;
250189251Ssam	u8 padlen;
251189251Ssam	struct crypto_hash *hmac;
252189251Ssam	u8 len[2], hash[100];
253189251Ssam
254189251Ssam	wpa_hexdump(MSG_MSGDUMP, "TLSv1: Record Layer - Received",
255189251Ssam		    in_data, in_len);
256189251Ssam
257189251Ssam	if (in_len < TLS_RECORD_HEADER_LEN) {
258189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Too short record (in_len=%lu)",
259189251Ssam			   (unsigned long) in_len);
260189251Ssam		*alert = TLS_ALERT_DECODE_ERROR;
261189251Ssam		return -1;
262189251Ssam	}
263189251Ssam
264189251Ssam	wpa_printf(MSG_DEBUG, "TLSv1: Received content type %d version %d.%d "
265189251Ssam		   "length %d", in_data[0], in_data[1], in_data[2],
266189251Ssam		   WPA_GET_BE16(in_data + 3));
267189251Ssam
268189251Ssam	if (in_data[0] != TLS_CONTENT_TYPE_HANDSHAKE &&
269189251Ssam	    in_data[0] != TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC &&
270189251Ssam	    in_data[0] != TLS_CONTENT_TYPE_ALERT &&
271189251Ssam	    in_data[0] != TLS_CONTENT_TYPE_APPLICATION_DATA) {
272189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type 0x%x",
273189251Ssam			   in_data[0]);
274189251Ssam		*alert = TLS_ALERT_UNEXPECTED_MESSAGE;
275189251Ssam		return -1;
276189251Ssam	}
277189251Ssam
278189251Ssam	if (WPA_GET_BE16(in_data + 1) != TLS_VERSION) {
279189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected protocol version "
280189251Ssam			   "%d.%d", in_data[1], in_data[2]);
281189251Ssam		*alert = TLS_ALERT_PROTOCOL_VERSION;
282189251Ssam		return -1;
283189251Ssam	}
284189251Ssam
285189251Ssam	rlen = WPA_GET_BE16(in_data + 3);
286189251Ssam
287189251Ssam	/* TLSCiphertext must not be more than 2^14+2048 bytes */
288189251Ssam	if (TLS_RECORD_HEADER_LEN + rlen > 18432) {
289189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Record overflow (len=%lu)",
290189251Ssam			   (unsigned long) (TLS_RECORD_HEADER_LEN + rlen));
291189251Ssam		*alert = TLS_ALERT_RECORD_OVERFLOW;
292189251Ssam		return -1;
293189251Ssam	}
294189251Ssam
295189251Ssam	in_data += TLS_RECORD_HEADER_LEN;
296189251Ssam	in_len -= TLS_RECORD_HEADER_LEN;
297189251Ssam
298189251Ssam	if (rlen > in_len) {
299189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Not all record data included "
300189251Ssam			   "(rlen=%lu > in_len=%lu)",
301189251Ssam			   (unsigned long) rlen, (unsigned long) in_len);
302189251Ssam		*alert = TLS_ALERT_DECODE_ERROR;
303189251Ssam		return -1;
304189251Ssam	}
305189251Ssam
306189251Ssam	in_len = rlen;
307189251Ssam
308189251Ssam	if (*out_len < in_len) {
309189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Not enough output buffer for "
310189251Ssam			   "processing received record");
311189251Ssam		*alert = TLS_ALERT_INTERNAL_ERROR;
312189251Ssam		return -1;
313189251Ssam	}
314189251Ssam
315189251Ssam	os_memcpy(out_data, in_data, in_len);
316189251Ssam	*out_len = in_len;
317189251Ssam
318189251Ssam	if (rl->read_cipher_suite != TLS_NULL_WITH_NULL_NULL) {
319189251Ssam		if (crypto_cipher_decrypt(rl->read_cbc, out_data,
320189251Ssam					  out_data, in_len) < 0) {
321189251Ssam			*alert = TLS_ALERT_DECRYPTION_FAILED;
322189251Ssam			return -1;
323189251Ssam		}
324189251Ssam		if (rl->iv_size) {
325189251Ssam			if (in_len == 0) {
326189251Ssam				wpa_printf(MSG_DEBUG, "TLSv1: Too short record"
327189251Ssam					   " (no pad)");
328189251Ssam				*alert = TLS_ALERT_DECODE_ERROR;
329189251Ssam				return -1;
330189251Ssam			}
331189251Ssam			padlen = out_data[in_len - 1];
332189251Ssam			if (padlen >= in_len) {
333189251Ssam				wpa_printf(MSG_DEBUG, "TLSv1: Incorrect pad "
334189251Ssam					   "length (%u, in_len=%lu) in "
335189251Ssam					   "received record",
336189251Ssam					   padlen, (unsigned long) in_len);
337189251Ssam				*alert = TLS_ALERT_DECRYPTION_FAILED;
338189251Ssam				return -1;
339189251Ssam			}
340189251Ssam			for (i = in_len - padlen; i < in_len; i++) {
341189251Ssam				if (out_data[i] != padlen) {
342189251Ssam					wpa_hexdump(MSG_DEBUG,
343189251Ssam						    "TLSv1: Invalid pad in "
344189251Ssam						    "received record",
345189251Ssam						    out_data + in_len - padlen,
346189251Ssam						    padlen);
347189251Ssam					*alert = TLS_ALERT_DECRYPTION_FAILED;
348189251Ssam					return -1;
349189251Ssam				}
350189251Ssam			}
351189251Ssam
352189251Ssam			*out_len -= padlen + 1;
353189251Ssam		}
354189251Ssam
355189251Ssam		wpa_hexdump(MSG_MSGDUMP,
356189251Ssam			    "TLSv1: Record Layer - Decrypted data",
357189251Ssam			    out_data, in_len);
358189251Ssam
359189251Ssam		if (*out_len < rl->hash_size) {
360189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Too short record; no "
361189251Ssam				   "hash value");
362189251Ssam			*alert = TLS_ALERT_INTERNAL_ERROR;
363189251Ssam			return -1;
364189251Ssam		}
365189251Ssam
366189251Ssam		*out_len -= rl->hash_size;
367189251Ssam
368189251Ssam		hmac = crypto_hash_init(rl->hash_alg, rl->read_mac_secret,
369189251Ssam					rl->hash_size);
370189251Ssam		if (hmac == NULL) {
371189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
372189251Ssam				   "to initialize HMAC");
373189251Ssam			*alert = TLS_ALERT_INTERNAL_ERROR;
374189251Ssam			return -1;
375189251Ssam		}
376189251Ssam
377189251Ssam		crypto_hash_update(hmac, rl->read_seq_num, TLS_SEQ_NUM_LEN);
378189251Ssam		/* type + version + length + fragment */
379189251Ssam		crypto_hash_update(hmac, in_data - TLS_RECORD_HEADER_LEN, 3);
380189251Ssam		WPA_PUT_BE16(len, *out_len);
381189251Ssam		crypto_hash_update(hmac, len, 2);
382189251Ssam		crypto_hash_update(hmac, out_data, *out_len);
383189251Ssam		hlen = sizeof(hash);
384189251Ssam		if (crypto_hash_finish(hmac, hash, &hlen) < 0) {
385189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
386189251Ssam				   "to calculate HMAC");
387189251Ssam			return -1;
388189251Ssam		}
389189251Ssam		if (hlen != rl->hash_size ||
390189251Ssam		    os_memcmp(hash, out_data + *out_len, hlen) != 0) {
391189251Ssam			wpa_printf(MSG_DEBUG, "TLSv1: Invalid HMAC value in "
392189251Ssam				   "received message");
393189251Ssam			*alert = TLS_ALERT_BAD_RECORD_MAC;
394189251Ssam			return -1;
395189251Ssam		}
396189251Ssam	}
397189251Ssam
398189251Ssam	/* TLSCompressed must not be more than 2^14+1024 bytes */
399189251Ssam	if (TLS_RECORD_HEADER_LEN + *out_len > 17408) {
400189251Ssam		wpa_printf(MSG_DEBUG, "TLSv1: Record overflow (len=%lu)",
401189251Ssam			   (unsigned long) (TLS_RECORD_HEADER_LEN + *out_len));
402189251Ssam		*alert = TLS_ALERT_RECORD_OVERFLOW;
403189251Ssam		return -1;
404189251Ssam	}
405189251Ssam
406189251Ssam	inc_byte_array(rl->read_seq_num, TLS_SEQ_NUM_LEN);
407189251Ssam
408189251Ssam	return 0;
409189251Ssam}
410