TLS: Add preliminary support for partial message processing

Reassemble partial TLS records to make the internal TLS client
implementation more convenient for stream sockets.

Signed-hostap: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2011-11-13 10:47:04 +02:00
parent 613522a40a
commit dbdcfa3979
5 changed files with 151 additions and 61 deletions

View file

@ -347,6 +347,12 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
const struct wpabuf *in_data, const struct wpabuf *in_data,
struct wpabuf **appl_data); struct wpabuf **appl_data);
struct wpabuf * tls_connection_handshake2(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
struct wpabuf **appl_data,
int *more_data_needed);
/** /**
* tls_connection_server_handshake - Process TLS handshake (server side) * tls_connection_server_handshake - Process TLS handshake (server side)
* @tls_ctx: TLS context data from tls_init() * @tls_ctx: TLS context data from tls_init()
@ -392,6 +398,11 @@ struct wpabuf * tls_connection_decrypt(void *tls_ctx,
struct tls_connection *conn, struct tls_connection *conn,
const struct wpabuf *in_data); const struct wpabuf *in_data);
struct wpabuf * tls_connection_decrypt2(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
int *more_data_needed);
/** /**
* tls_connection_resumed - Was session resumption used * tls_connection_resumed - Was session resumption used
* @tls_ctx: TLS context data from tls_init() * @tls_ctx: TLS context data from tls_init()

View file

@ -331,6 +331,17 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
struct tls_connection *conn, struct tls_connection *conn,
const struct wpabuf *in_data, const struct wpabuf *in_data,
struct wpabuf **appl_data) struct wpabuf **appl_data)
{
return tls_connection_handshake2(tls_ctx, conn, in_data, appl_data,
NULL);
}
struct wpabuf * tls_connection_handshake2(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
struct wpabuf **appl_data,
int *need_more_data)
{ {
#ifdef CONFIG_TLS_INTERNAL_CLIENT #ifdef CONFIG_TLS_INTERNAL_CLIENT
u8 *res, *ad; u8 *res, *ad;
@ -344,7 +355,7 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
res = tlsv1_client_handshake(conn->client, res = tlsv1_client_handshake(conn->client,
in_data ? wpabuf_head(in_data) : NULL, in_data ? wpabuf_head(in_data) : NULL,
in_data ? wpabuf_len(in_data) : 0, in_data ? wpabuf_len(in_data) : 0,
&res_len, &ad, &ad_len); &res_len, &ad, &ad_len, need_more_data);
if (res == NULL) if (res == NULL)
return NULL; return NULL;
out = wpabuf_alloc_ext_data(res, res_len); out = wpabuf_alloc_ext_data(res, res_len);
@ -455,23 +466,23 @@ struct wpabuf * tls_connection_decrypt(void *tls_ctx,
struct tls_connection *conn, struct tls_connection *conn,
const struct wpabuf *in_data) const struct wpabuf *in_data)
{ {
return tls_connection_decrypt2(tls_ctx, conn, in_data, NULL);
}
struct wpabuf * tls_connection_decrypt2(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
int *need_more_data)
{
if (need_more_data)
*need_more_data = 0;
#ifdef CONFIG_TLS_INTERNAL_CLIENT #ifdef CONFIG_TLS_INTERNAL_CLIENT
if (conn->client) { if (conn->client) {
struct wpabuf *buf; return tlsv1_client_decrypt(conn->client, wpabuf_head(in_data),
int res;
buf = wpabuf_alloc((wpabuf_len(in_data) + 500) * 3);
if (buf == NULL)
return NULL;
res = tlsv1_client_decrypt(conn->client, wpabuf_head(in_data),
wpabuf_len(in_data), wpabuf_len(in_data),
wpabuf_mhead(buf), need_more_data);
wpabuf_size(buf));
if (res < 0) {
wpabuf_free(buf);
return NULL;
}
wpabuf_put(buf, res);
return buf;
} }
#endif /* CONFIG_TLS_INTERNAL_CLIENT */ #endif /* CONFIG_TLS_INTERNAL_CLIENT */
#ifdef CONFIG_TLS_INTERNAL_SERVER #ifdef CONFIG_TLS_INTERNAL_SERVER

View file

@ -136,25 +136,43 @@ int tls_derive_keys(struct tlsv1_client *conn,
* @out_len: Length of the output buffer. * @out_len: Length of the output buffer.
* @appl_data: Pointer to application data pointer, or %NULL if dropped * @appl_data: Pointer to application data pointer, or %NULL if dropped
* @appl_data_len: Pointer to variable that is set to appl_data length * @appl_data_len: Pointer to variable that is set to appl_data length
* @need_more_data: Set to 1 if more data would be needed to complete
* processing
* Returns: Pointer to output data, %NULL on failure * Returns: Pointer to output data, %NULL on failure
*/ */
u8 * tlsv1_client_handshake(struct tlsv1_client *conn, u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
const u8 *in_data, size_t in_len, const u8 *in_data, size_t in_len,
size_t *out_len, u8 **appl_data, size_t *out_len, u8 **appl_data,
size_t *appl_data_len) size_t *appl_data_len, int *need_more_data)
{ {
const u8 *pos, *end; const u8 *pos, *end;
u8 *msg = NULL, *in_msg, *in_pos, *in_end, alert, ct; u8 *msg = NULL, *in_msg = NULL, *in_pos, *in_end, alert, ct;
size_t in_msg_len; size_t in_msg_len;
int no_appl_data; int no_appl_data;
int used; int used;
if (need_more_data)
*need_more_data = 0;
if (conn->state == CLIENT_HELLO) { if (conn->state == CLIENT_HELLO) {
if (in_len) if (in_len)
return NULL; return NULL;
return tls_send_client_hello(conn, out_len); return tls_send_client_hello(conn, out_len);
} }
if (conn->partial_input) {
if (wpabuf_resize(&conn->partial_input, in_len) < 0) {
wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate "
"memory for pending record");
tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
TLS_ALERT_INTERNAL_ERROR);
goto failed;
}
wpabuf_put_data(conn->partial_input, in_data, in_len);
in_data = wpabuf_head(conn->partial_input);
in_len = wpabuf_len(conn->partial_input);
}
if (in_data == NULL || in_len == 0) if (in_data == NULL || in_len == 0)
return NULL; return NULL;
@ -176,12 +194,24 @@ u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
goto failed; goto failed;
} }
if (used == 0) { if (used == 0) {
/* need more data */ struct wpabuf *partial;
wpa_printf(MSG_DEBUG, "TLSv1: Partial processing not " wpa_printf(MSG_DEBUG, "TLSv1: Need more data");
"yet supported"); os_free(in_msg);
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert); partial = wpabuf_alloc_copy(pos, end - pos);
wpabuf_free(conn->partial_input);
conn->partial_input = partial;
if (conn->partial_input == NULL) {
wpa_printf(MSG_DEBUG, "TLSv1: Failed to "
"allocate memory for pending "
"record");
tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
TLS_ALERT_INTERNAL_ERROR);
goto failed; goto failed;
} }
if (need_more_data)
*need_more_data = 1;
return 0;
}
ct = pos[0]; ct = pos[0];
in_pos = in_msg; in_pos = in_msg;
@ -211,6 +241,8 @@ u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
failed: failed:
os_free(in_msg); os_free(in_msg);
if (conn->alert_level) { if (conn->alert_level) {
wpabuf_free(conn->partial_input);
conn->partial_input = NULL;
conn->state = FAILED; conn->state = FAILED;
os_free(msg); os_free(msg);
msg = tlsv1_client_send_alert(conn, conn->alert_level, msg = tlsv1_client_send_alert(conn, conn->alert_level,
@ -221,6 +253,11 @@ failed:
*out_len = 0; *out_len = 0;
} }
if (need_more_data == NULL || !(*need_more_data)) {
wpabuf_free(conn->partial_input);
conn->partial_input = NULL;
}
return msg; return msg;
} }
@ -263,53 +300,80 @@ int tlsv1_client_encrypt(struct tlsv1_client *conn,
* @conn: TLSv1 client connection data from tlsv1_client_init() * @conn: TLSv1 client connection data from tlsv1_client_init()
* @in_data: Pointer to input buffer (encrypted TLS data) * @in_data: Pointer to input buffer (encrypted TLS data)
* @in_len: Input buffer length * @in_len: Input buffer length
* @out_data: Pointer to output buffer (decrypted data from TLS tunnel) * @need_more_data: Set to 1 if more data would be needed to complete
* @out_len: Maximum out_data length * processing
* Returns: Number of bytes written to out_data, -1 on failure * Returns: Decrypted data or %NULL on failure
* *
* This function is used after TLS handshake has been completed successfully to * This function is used after TLS handshake has been completed successfully to
* receive data from the encrypted tunnel. * receive data from the encrypted tunnel.
*/ */
int tlsv1_client_decrypt(struct tlsv1_client *conn, struct wpabuf * tlsv1_client_decrypt(struct tlsv1_client *conn,
const u8 *in_data, size_t in_len, const u8 *in_data, size_t in_len,
u8 *out_data, size_t out_len) int *need_more_data)
{ {
const u8 *in_end, *pos; const u8 *in_end, *pos;
int used; int used;
u8 alert, *out_end, *out_pos, ct; u8 alert, *out_pos, ct;
size_t olen; size_t olen;
struct wpabuf *buf = NULL;
if (need_more_data)
*need_more_data = 0;
if (conn->partial_input) {
if (wpabuf_resize(&conn->partial_input, in_len) < 0) {
wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate "
"memory for pending record");
alert = TLS_ALERT_INTERNAL_ERROR;
goto fail;
}
wpabuf_put_data(conn->partial_input, in_data, in_len);
in_data = wpabuf_head(conn->partial_input);
in_len = wpabuf_len(conn->partial_input);
}
pos = in_data; pos = in_data;
in_end = in_data + in_len; in_end = in_data + in_len;
out_pos = out_data;
out_end = out_data + out_len;
while (pos < in_end) { while (pos < in_end) {
ct = pos[0]; ct = pos[0];
olen = out_end - out_pos; if (wpabuf_resize(&buf, in_end - pos) < 0) {
alert = TLS_ALERT_INTERNAL_ERROR;
goto fail;
}
out_pos = wpabuf_put(buf, 0);
olen = wpabuf_tailroom(buf);
used = tlsv1_record_receive(&conn->rl, pos, in_end - pos, used = tlsv1_record_receive(&conn->rl, pos, in_end - pos,
out_pos, &olen, &alert); out_pos, &olen, &alert);
if (used < 0) { if (used < 0) {
wpa_printf(MSG_DEBUG, "TLSv1: Record layer processing " wpa_printf(MSG_DEBUG, "TLSv1: Record layer processing "
"failed"); "failed");
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert); goto fail;
return -1;
} }
if (used == 0) { if (used == 0) {
/* need more data */ struct wpabuf *partial;
wpa_printf(MSG_DEBUG, "TLSv1: Partial processing not " wpa_printf(MSG_DEBUG, "TLSv1: Need more data");
"yet supported"); partial = wpabuf_alloc_copy(pos, in_end - pos);
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert); wpabuf_free(conn->partial_input);
return -1; conn->partial_input = partial;
if (conn->partial_input == NULL) {
wpa_printf(MSG_DEBUG, "TLSv1: Failed to "
"allocate memory for pending "
"record");
alert = TLS_ALERT_INTERNAL_ERROR;
goto fail;
}
if (need_more_data)
*need_more_data = 1;
return buf;
} }
if (ct == TLS_CONTENT_TYPE_ALERT) { if (ct == TLS_CONTENT_TYPE_ALERT) {
if (olen < 2) { if (olen < 2) {
wpa_printf(MSG_DEBUG, "TLSv1: Alert " wpa_printf(MSG_DEBUG, "TLSv1: Alert "
"underflow"); "underflow");
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert = TLS_ALERT_DECODE_ERROR;
TLS_ALERT_DECODE_ERROR); goto fail;
return -1;
} }
wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d", wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d",
out_pos[0], out_pos[1]); out_pos[0], out_pos[1]);
@ -319,32 +383,33 @@ int tlsv1_client_decrypt(struct tlsv1_client *conn,
continue; continue;
} }
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, out_pos[1]); alert = out_pos[1];
return -1; goto fail;
} }
if (ct != TLS_CONTENT_TYPE_APPLICATION_DATA) { if (ct != TLS_CONTENT_TYPE_APPLICATION_DATA) {
wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type " wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type "
"0x%x when decrypting application data", "0x%x when decrypting application data",
pos[0]); pos[0]);
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert = TLS_ALERT_UNEXPECTED_MESSAGE;
TLS_ALERT_UNEXPECTED_MESSAGE); goto fail;
return -1;
} }
out_pos += olen; wpabuf_put(buf, olen);
if (out_pos > out_end) {
wpa_printf(MSG_DEBUG, "TLSv1: Buffer not large enough "
"for processing the received record");
tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
TLS_ALERT_INTERNAL_ERROR);
return -1;
}
pos += used; pos += used;
} }
return out_pos - out_data; wpabuf_free(conn->partial_input);
conn->partial_input = NULL;
return buf;
fail:
wpabuf_free(buf);
wpabuf_free(conn->partial_input);
conn->partial_input = NULL;
tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert);
return NULL;
} }
@ -427,6 +492,7 @@ void tlsv1_client_deinit(struct tlsv1_client *conn)
os_free(conn->client_hello_ext); os_free(conn->client_hello_ext);
tlsv1_client_free_dh(conn); tlsv1_client_free_dh(conn);
tlsv1_cred_free(conn->cred); tlsv1_cred_free(conn->cred);
wpabuf_free(conn->partial_input);
os_free(conn); os_free(conn);
} }

View file

@ -29,13 +29,13 @@ int tlsv1_client_prf(struct tlsv1_client *conn, const char *label,
u8 * tlsv1_client_handshake(struct tlsv1_client *conn, u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
const u8 *in_data, size_t in_len, const u8 *in_data, size_t in_len,
size_t *out_len, u8 **appl_data, size_t *out_len, u8 **appl_data,
size_t *appl_data_len); size_t *appl_data_len, int *need_more_data);
int tlsv1_client_encrypt(struct tlsv1_client *conn, int tlsv1_client_encrypt(struct tlsv1_client *conn,
const u8 *in_data, size_t in_len, const u8 *in_data, size_t in_len,
u8 *out_data, size_t out_len); u8 *out_data, size_t out_len);
int tlsv1_client_decrypt(struct tlsv1_client *conn, struct wpabuf * tlsv1_client_decrypt(struct tlsv1_client *conn,
const u8 *in_data, size_t in_len, const u8 *in_data, size_t in_len,
u8 *out_data, size_t out_len); int *need_more_data);
int tlsv1_client_get_cipher(struct tlsv1_client *conn, char *buf, int tlsv1_client_get_cipher(struct tlsv1_client *conn, char *buf,
size_t buflen); size_t buflen);
int tlsv1_client_shutdown(struct tlsv1_client *conn); int tlsv1_client_shutdown(struct tlsv1_client *conn);

View file

@ -68,6 +68,8 @@ struct tlsv1_client {
tlsv1_client_session_ticket_cb session_ticket_cb; tlsv1_client_session_ticket_cb session_ticket_cb;
void *session_ticket_cb_ctx; void *session_ticket_cb_ctx;
struct wpabuf *partial_input;
}; };