From ecd98efa9d5cb1c6f3e14ee5c8a97d405d3a1ef9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 31 Aug 2010 08:57:26 +0200 Subject: [PATCH] Support output fragmentation of TLS records --- src/libcharon/plugins/eap_tls/eap_tls.c | 5 +- src/libcharon/plugins/eap_ttls/eap_ttls.c | 5 +- src/libtls/tls.c | 111 ++++++++++++++++------ src/libtls/tls.h | 24 +++-- src/libtls/tls_socket.c | 30 +++--- 5 files changed, 123 insertions(+), 52 deletions(-) diff --git a/src/libcharon/plugins/eap_tls/eap_tls.c b/src/libcharon/plugins/eap_tls/eap_tls.c index 8b5d4cb19..155038056 100644 --- a/src/libcharon/plugins/eap_tls/eap_tls.c +++ b/src/libcharon/plugins/eap_tls/eap_tls.c @@ -251,7 +251,7 @@ static status_t process_buf(private_eap_tls_t *this) { status_t status; - status = this->tls->process(this->tls, this->input); + status = this->tls->process(this->tls, this->input.ptr, this->input.len); if (status != NEED_MORE) { return status; @@ -260,7 +260,8 @@ static status_t process_buf(private_eap_tls_t *this) this->inpos = 0; chunk_free(&this->output); - return this->tls->build(this->tls, &this->output); + this->output = chunk_alloc(EAP_TLS_FRAGMENT_LEN); + return this->tls->build(this->tls, this->output.ptr, &this->output.len, NULL); } METHOD(eap_method_t, process, status_t, diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls.c b/src/libcharon/plugins/eap_ttls/eap_ttls.c index a3cfcd1f7..e2f09a70c 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls.c +++ b/src/libcharon/plugins/eap_ttls/eap_ttls.c @@ -254,7 +254,7 @@ static status_t process_buf(private_eap_ttls_t *this) { status_t status; - status = this->tls->process(this->tls, this->input); + status = this->tls->process(this->tls, this->input.ptr, this->input.len); if (status != NEED_MORE) { return status; @@ -263,7 +263,8 @@ static status_t process_buf(private_eap_ttls_t *this) this->inpos = 0; chunk_free(&this->output); - return this->tls->build(this->tls, &this->output); + this->output = chunk_alloc(EAP_TTLS_FRAGMENT_LEN); + return this->tls->build(this->tls, this->output.ptr, &this->output.len, NULL); } METHOD(eap_method_t, process, status_t, diff --git a/src/libtls/tls.c b/src/libtls/tls.c index 91d89db8f..61787e366 100644 --- a/src/libtls/tls.c +++ b/src/libtls/tls.c @@ -137,6 +137,16 @@ struct private_tls_t { * Number of bytes read in input buffer */ size_t inpos; + + /** + * Allocated output buffer + */ + chunk_t output; + + /** + * Number of bytes processed from output buffer + */ + size_t outpos; }; /** @@ -150,23 +160,27 @@ typedef struct __attribute__((packed)) { } tls_record_t; METHOD(tls_t, process, status_t, - private_tls_t *this, chunk_t data) + private_tls_t *this, void *buf, size_t buflen) { tls_record_t *record; status_t status; u_int len; - while (data.len > sizeof(tls_record_t)) + while (buflen) { if (this->input.len == 0) { + if (buflen < sizeof(tls_record_t)) + { + break; + } while (TRUE) { /* try to process records inline */ - record = (tls_record_t*)data.ptr; + record = buf; len = untoh16(&record->length); - if (len + sizeof(tls_record_t) > data.len) + if (len + sizeof(tls_record_t) > buflen) { /* not a full record, read to buffer */ this->input = chunk_alloc(len + sizeof(tls_record_t)); this->inpos = 0; @@ -180,16 +194,18 @@ METHOD(tls_t, process, status_t, { return status; } - data = chunk_skip(data, len + sizeof(tls_record_t)); - if (data.len == 0) + buf += len + sizeof(tls_record_t); + buflen -= len + sizeof(tls_record_t); + if (buflen == 0) { return NEED_MORE; } } } - len = min(data.len, this->input.len - this->inpos); - memcpy(this->input.ptr + this->inpos, data.ptr, len); - data = chunk_skip(data, len); + len = min(buflen, this->input.len - this->inpos); + memcpy(this->input.ptr + this->inpos, buf, len); + buf += len; + buflen -= len; this->inpos += len; DBG2(DBG_TLS, "buffering %d bytes, %d bytes of %d byte record received", len, this->inpos, this->input.len); @@ -210,7 +226,7 @@ METHOD(tls_t, process, status_t, } } } - if (data.len != 0) + if (buflen != 0) { DBG1(DBG_TLS, "received incomplete TLS record header"); return FAILED; @@ -219,34 +235,68 @@ METHOD(tls_t, process, status_t, } METHOD(tls_t, build, status_t, - private_tls_t *this, chunk_t *data) + private_tls_t *this, void *buf, size_t *buflen, size_t *msglen) { + tls_content_type_t type; tls_record_t record; status_t status; + chunk_t data; + size_t len; - *data = chunk_empty; - while (TRUE) + len = *buflen; + if (this->output.len == 0) { - tls_content_type_t type; - chunk_t body; - - status = this->protection->build(this->protection, &type, &body); - switch (status) + /* query upper layers for new records, as many as we can get */ + while (TRUE) { - case INVALID_STATE: - return NEED_MORE; - case NEED_MORE: - break; - default: - return status; + status = this->protection->build(this->protection, &type, &data); + switch (status) + { + case NEED_MORE: + record.type = type; + htoun16(&record.version, this->version); + htoun16(&record.length, data.len); + this->output = chunk_cat("mcm", this->output, + chunk_from_thing(record), data); + DBG2(DBG_TLS, "sending TLS %N record (%d bytes)", + tls_content_type_names, type, data.len); + continue; + case INVALID_STATE: + if (this->output.len == 0) + { + return INVALID_STATE; + } + break; + default: + return status; + } + break; + } + if (msglen) + { + *msglen = this->output.len; } - record.type = type; - htoun16(&record.version, this->version); - htoun16(&record.length, body.len); - *data = chunk_cat("mcm", *data, chunk_from_thing(record), body); - DBG2(DBG_TLS, "sending TLS %N record (%u bytes)", - tls_content_type_names, type, sizeof(tls_record_t) + body.len); } + else + { + DBG2(DBG_TLS, "sending %d bytes buffered fragment", + min(*buflen, this->output.len - this->outpos)); + if (msglen) + { + *msglen = 0; + } + } + len = min(len, this->output.len - this->outpos); + memcpy(buf, this->output.ptr + this->outpos, len); + this->outpos += len; + *buflen = len; + if (this->outpos == this->output.len) + { + chunk_free(&this->output); + this->outpos = 0; + return ALREADY_DONE; + } + return NEED_MORE; } METHOD(tls_t, is_server, bool, @@ -323,6 +373,7 @@ METHOD(tls_t, destroy, void, this->alert->destroy(this->alert); free(this->input.ptr); + free(this->output.ptr); free(this); } diff --git a/src/libtls/tls.h b/src/libtls/tls.h index ec8d04eee..ba9ede99b 100644 --- a/src/libtls/tls.h +++ b/src/libtls/tls.h @@ -108,24 +108,36 @@ struct tls_t { /** * Process one or more TLS records, pass it to upper layers. * - * @param data TLS record data, including headers + * @param buf TLS record data, including headers + * @param buflen number of bytes in buf to process * @return * - SUCCESS if TLS negotiation complete * - FAILED if TLS handshake failed * - NEED_MORE if more invocations to process/build needed */ - status_t (*process)(tls_t *this, chunk_t data); + status_t (*process)(tls_t *this, void *buf, size_t buflen); /** - * Query upper layer for TLS record, build protected record. + * Query upper layer for one or more TLS records, build fragments. * - * @param data allocated data of the built TLS record + * The TLS stack automatically fragments the records to the given buffer + * size. Fragmentation is indicated by the reclen ouput parameter and + * the return value. For the first fragment of a TLS record, a non-zero + * record length is returned in reclen. If more fragments follow, NEED_MORE + * is returned. A return value of ALREADY_DONE indicates that the final + * fragment has been returned. + * + * @param buf buffer to write TLS record fragments to + * @param buflen size of buffer, receives bytes written + * @param msglen receives size of all TLS fragments * @return * - SUCCESS if TLS negotiation complete * - FAILED if TLS handshake failed - * - NEED_MORE if more input records required + * - INVALID_STATE if more input data required + * - NEED_MORE if more fragments available + * - ALREADY_DONE if the last available fragment returned */ - status_t (*build)(tls_t *this, chunk_t *data); + status_t (*build)(tls_t *this, void *buf, size_t *buflen, size_t *msglen); /** * Check if TLS stack is acting as a server. diff --git a/src/libtls/tls_socket.c b/src/libtls/tls_socket.c index 6aa776879..e0c440a4c 100644 --- a/src/libtls/tls_socket.c +++ b/src/libtls/tls_socket.c @@ -96,25 +96,31 @@ METHOD(tls_application_t, build, status_t, */ static bool exchange(private_tls_socket_t *this, bool wr) { - chunk_t data; - char buf[2048]; + char buf[1024]; ssize_t len; int round = 0; for (round = 0; TRUE; round++) { - if (this->tls->build(this->tls, &data) != NEED_MORE) + while (TRUE) { - return FALSE; - } - if (data.len) - { - len = write(this->fd, data.ptr, data.len); - free(data.ptr); - if (len != data.len) + len = sizeof(buf); + switch (this->tls->build(this->tls, buf, &len, NULL)) { - return FALSE; + case NEED_MORE: + case ALREADY_DONE: + len = write(this->fd, buf, len); + if (len == -1) + { + return FALSE; + } + continue; + case INVALID_STATE: + break; + default: + return FALSE; } + break; } if (wr) { @@ -139,7 +145,7 @@ static bool exchange(private_tls_socket_t *this, bool wr) { return FALSE; } - if (this->tls->process(this->tls, chunk_create(buf, len)) != NEED_MORE) + if (this->tls->process(this->tls, buf, len) != NEED_MORE) { return FALSE; }